пятница, 15 декабря 2017 г.

Программа DALI Controller

Для расширения функционала управления освещением квартиры было решено сделать DALI<->GPIO-адаптер и написать собственный сервер.

Почему не использовать уже готовые решения, что должен уметь сервер, подробнее про адаптер и протокол MQTT описано в статье Управление освещением в квартире и другая электрика: техническое устройство (раздел "Устройство сервера").

Здесь описывается устройство и функционал программы (сервера) DALI Controller.

Окружение программы

Сервер должен работать на низкопроизводительном компьютере Raspberry, прослушивать шину DALI и посылать в нее команды через GPIO. В качестве операционной системы выступает Raspbian (один из вариантов Linux для Raspberry). Для удаленного управления сервером поддерживается сетевое подключение.

Выбор инструментов и технологий

Для работы с GPIO в Raspbian существует несколько различных third-party библиотек. В качестве надежной (работа с GPIO по DMA, что резко снижает зависимость от загрузки процессора и прерываний) и продуманной, будет использоваться C-библиотека pigpio (подробнее про нее см. Raspberry Pi 3, pigpio, шина DALI и программа для работы с ней). Причем, в режиме работы - демон (вместо линковки).
Сервер должен поддерживать управление из консоли, командной строки и через MQTT, а также публиковать статус устройств по MQTT.

Сначала ядро программы было написано C, но в какой-то момент я понял, что развитие программы тормозится неудобной организацией межпоточного взаимодействия. Вместо концентрации на функционале программы приходится прилагать много сил, чтобы реализовать это на C. Я решил посмотреть другие варианты и, наконец, попробовать Golang. Это был мой первый подход к языку, и после небольшого исследования возможностей я понял:
  • Go имеет встроенные решения для эффективной многопоточности 
  • на Go можно писать под Raspbian
  • из Go можно вызывать C-код и линковать C-библиотеки
  • Go компилируется в эффективный код
  • для Go существует множество сторонних библиотек

Устройство программы

Сервер состоит из нескольких модулей, которые работают параллельно:
  • прослушивание шины DALI для перехвата команд, отправляемых другими устройствами (выключателями) и ответов на команды. Используется для логирования, отслеживания изменения статусов устройств, запуска настраиваемых на событие JavaScript-скриптов
  • JavaScript-интерпретатор для настраиваемых скриптов
  • MQTT-клиент для получения команд управления и публикации статусов
  • псевдографическая цветная консоль, показывающая лог, список и статусы устройств, прогресс выполнения долгих команд. Имеет поле для ручного ввода команды
  • встроенная помощь по всем командам DALI и другим командам сервера
  • отправка команд в шину DALI (полученных как параметры запуска приложения, через MQTT, ручным вводом в консоли или из JavaScript)
  • дополнительно: модуль опроса устройств DHT-22 (датчик температуры и влажности) с публикацией в MQTT

На снимке экрана видно:
  • лог команд (в скобках указан источник команды: шина DALI, MQTT или JavaScript)
  • иногда возникающие ошибки опроса уличного датчика DHT-22 (слишком длинные провода до него)
  • список устройств с DALI-адресом и статусом. Видно, что часть устройств на шине не имеют имени (они пока не используются)

Это снимок самодельного мобильного приложения, работающего по MQTT. Оно общается с сервером DALI Controller для показа статуса устройств и управления ими.

Работа программы

При запуске программа опрашивает устройства, чтобы выяснить их начальный статус. Затем статус отслеживается путем анализа команд, которые посылаются этому устройству (в том числе путем нажатия настенных выключателей). Любое изменение статуса публикуется по MQTT и все заинтересованные MQTT-клиенты (в т.ч. мобильные приложения) сразу же отражают это изменение у себя.
При управлении (светом) из мобильного приложения (или другого MQTT-клиента), сервер получает команду по MQTT и отправляет ее в шину DALI, отражая, если нужно, изменение статуса.
Консоль программы позволяет, при необходимости, вводить команды вручную. Обычно это требуется при подключении новых устройств для их конфигурирования.


В работе всей этой кухни есть интересная особенность. В арсенале DALI нет команды "переключить светильник". Есть различные варианты "включить" и "выключить", а "переключить" - нет. Это значит, что настенный выключатель должен помнить последнюю команду, которую он посылал, чтобы при следующем нажатии посылать обратную. Да, возможен вариант, когда выключатель отслеживает или опрашивает реальное состояние устройства перед решением, что же ему посылать. Но дешевые выключатели этого точно не делают (про дорогие я не осведомлен).
Представьте картину:
  • включаем свет выключателем (он отправляет команду "вкл" и запоминает факт, что свет включился)
  • выключаем из мобильного приложения
  • хотим снова включить его выключателем, но ничего не происходит. Поскольку выключатель помнит, что свет включен, он пошлет команду "выкл". И чтобы свет все-таки включить, нужно нажать выключатель еще раз (он снова пошлет обратную команду - теперь уже "вкл")
Для устранения этой неприятной ситуации в сервере используется возможность написать JavaScript на событие.
Что делает скрипт:
  • при получении команды по шине DALI запускается скрипт
  • если команда послана настенным выключателем (а в данный момент все команды, пришедшие по DALI, от выключателей) и она включает или выключает свет, то команда сверяется с текущим статусом светильника (его правильный статус помнит сервер)
  • если команда от выключателя пытается "повторить" уже существующее состояние света, но скрипт тут же самостоятельно посылает этому устройству обратную команду
Работа этого скрипта устраняет проблему с нажатие выключателя, при котором ничего не происходит. Эта ситуация реально видна на снимке экрана выше:
10:52:43 on спальня (dali)
10:52:43 off спальня (scr)
первая строка - по шине DALI получена команда "включить свет в спальне". Однако скрипт видит, что свет уже включен, а значит нажатием выключателя требовалось его выключить, и он сам посылает новую команду "выключить свет в спальне". Это вторая команда с источником "(scr)" (что означает JavaScript).

Выглядит как "костыль". Но на самом деле, это "костыль" не к серверу, а к недостатку протокола DALI: с одной стороны, он имеет множество команд управления светом, а с другой, сделать простейший выключатель без памяти состояния (или необходимости аппаратной и программной имплементации приемной части интерфейса) не позволяет.

Несколько месяцев использования освещения в такой конфигурации, с активным использованием как настенных выключателей, так и мобильного приложения, показывают 100% отсутствие ошибок типа "нажал - ничего не происходит".

3 комментария:

  1. Добрый день. Очень интересный проект! А не могли бы поделиться кодом и схемой как вы GPIO подключили?

    ОтветитьУдалить
  2. Кодом могу, схемой - не уверен. Она простая, если надо, попробую поискать. Пишите свой email, пришлю исходники (на Go).

    ОтветитьУдалить