From 693de4da421370ccd0d4b9adc8e76c57219ea546 Mon Sep 17 00:00:00 2001 From: "anklimov@gmail.com" Date: Sat, 3 Oct 2026 00:41:20 +0300 Subject: [PATCH] docs: add CAN (lightspot) and rotary encoder docs from author drafts Ported the two design docs (originally docx) to markdown with code-verified corrections (CAN runs at 50 kbit/s); sections still in design (T:99 rotary profiles, external SPI CAN controller) are marked 'not implemented' and tracked in BACKLOG.md. --- documentation/core/rotary_encoder.md | 119 ++++++++++ documentation/integrations/can_lightspot.md | 239 ++++++++++++++++++++ 2 files changed, 358 insertions(+) create mode 100644 documentation/core/rotary_encoder.md create mode 100644 documentation/integrations/can_lightspot.md diff --git a/documentation/core/rotary_encoder.md b/documentation/core/rotary_encoder.md new file mode 100644 index 0000000..8da6b0e --- /dev/null +++ b/documentation/core/rotary_encoder.md @@ -0,0 +1,119 @@ +# Rotary Encoder + +> Источник: авторский документ «Rotary Encoder» (docx, 2026-10-02), сверен с кодом `lighthub/inputs.cpp`. + +Появилась возможность подключить к контроллеру орган управления, который называется Rotary encoder (далее — энкодер). + +Энкодер упрощает управление такими устройствами, как диммируемое и светодиодное освещение, позволяя выбор яркости/цвета/насыщенности/цветовой температуры делать тактильно, вращением рукоятки регулятора. Одним энкодером можно управлять несколькими, как локальными, так и удаленными устройствами. Освещение здесь приведено в качестве самого применимого примера. Если хотите — данным элементом управления можно регулировать все, что поддерживает команды на увеличение и уменьшение. Например, через автоматизации Home Assistant можно управлять громкостью ресивера или колонки Алиса. + +Многие энкодеры также имеют контакт нажатия, и данная возможность тоже может быть использована. Все варианты анализа одинарных-двойных-тройных-длительных нажатий могут быть использованы аналогично с настройкой дискретных входов контроллера и использоваться для любых нужд, в том числе для включения, выключения устройств, переключения профилей управления энкодера. + +Все возможности доступны при настройке энкодера через JSON-конфигурацию. + +Подключить энкодер можно к прошивке LightHub на всех платформах (включенных с флагом `ROTARYENCODER`, см. «Доступность» ниже). Особенно хочется подчеркнуть возможность подключить такой элемент (наряду с дополнительными кнопками и датчиками) к контроллеру LightSpot на базе STM32 или ESP32 — минималистической системе, которая может быть смонтирована в каждом выключателе и которая может общаться с основным контроллером LightHub по двухпроводной шине CAN (см. [CAN и lightspot](../integrations/can_lightspot.md)). Также можно собрать универсальный выключатель с кнопками и рукояткой энкодера на базе ESP32 или ESP8266, взаимодействующий с остальными системами дома через WiFi (как с контроллерами LightHub, так и с другими устройствами, управляемыми через MQTT), хотя вариант проводного подключения, конечно, предпочтителен. + +## Настройка + +Рассмотрим настройку энкодера на примерах. + +В данном примере энкодер подключен к следующим PIN-ам контроллера: + +- 14 — контакт нажатия; +- 4 и 5 — контакты A и B энкодера, соответственно. + +Четвертый (опциональный) параметр массива `#` может задавать режим подключения энкодера. Имеет смысл пробовать менять значение в интервале 1-3, если значение по умолчанию (2) не работает корректно для какого-то отдельно взятого энкодера. + +Параметр `T` соответствует значению **32** — это тип входа «rotary encoder». + +Параметр `"+-"` определяет обработчик, вызываемый при вращении рукоятки энкодера по или против часовой стрелки. Обработчик аналогичен описанному в разделе «диммирование с использованием кнопок» ([button_presses.md](button_presses.md)). Но там пары параметров `icmd`/`irev` и `ecmd`/`erev` используются поочередно, а в случае энкодера первый параметр используется в случае вращения по часовой стрелке, а второй — против часовой. + +Настройка в примере ниже переключает локальное устройство `dimmer` между состояниями ON и OFF (команда `toggle`) по однократному клику на энкодер (параметр `click`), а при вращении рукоятки по и против часовой стрелки увеличивает или уменьшает яркость локального устройства `dimmer` соответственно (команды `%+1` и `%-1`): + +```json +{ + "T": 32, "#": [14, 4, 5, 2], + "+-": { "item": "dimmer/set", "icmd": "%+1", "irev": "%-1" }, + "click": { "item": "dimmer/cmd", "icmd": "toggle" } +} +``` + +Поворот по часовой и против часовой стрелки можно разнести на два отдельных обработчика, если вдруг это будет необходимо, чтобы управлять разными устройствами при вращении в разных направлениях. Следующее описание эквивалентно предыдущему: + +```json +{ + "T": 32, "#": [14, 4, 5], + "+": { "item": "dimmer/set", "icmd": "%+1" }, + "-": { "item": "dimmer/set", "icmd": "%-1" }, + "click": { "item": "dimmer/cmd", "icmd": "toggle" } +} +``` + +Теперь попробуем управлять удаленным устройством `dimmer2`, подключенным к контроллеру с именем `light` через MQTT: + +```json +{ + "T": 32, "#": [14, 4, 5], + "+-": { "emit": "myhome/light/dimmer2/set", "ecmd": "%+1", "erev": "%-1" }, + "click": { "emit": "myhome/light/dimmer2/cmd", "ecmd": "toggle" } +} +``` + +И пример по управлению удаленным устройством с itemid = 31, настроенным на контроллере с адресом 13 по CAN-шине (см. [CAN и lightspot](../integrations/can_lightspot.md)): + +```json +{ + "T": 32, "#": [14, 4, 5], + "+-": { "can": [13, 31, "set"], "icmd": "%+1", "irev": "%-1" }, + "click": { "can": [13, 31, "cmd"], "icmd": "toggle" } +} +``` + +### Профили (act) + +Усложним задачу. Теперь попробуем управлять одним энкодером яркостью, цветом и насыщенностью светодиодной ленты. Для этого понадобится создать несколько профилей энкодера. + +Профили можно задать в виде JSON-массива или JSON-объекта в параметре `act`. Массив и объект аналогичны: в первом случае для активации нужного профиля будет использоваться целое число, определяющее порядковый номер элемента в массиве, содержащего нужный профиль (начиная с 1, так как значение 0 зарезервировано для активации «корневого профиля», который может быть описан непосредственно в объекте входа, на одном уровне с параметрами `#`, `T` — аналогично предыдущим примерам). Во втором случае для активации можно пользоваться именем объекта (пустая строка активирует корневой профиль). Это, конечно, делает конфиг более читаемым, но чуть более ресурсоемко. + +В примере ниже изначально активен корневой профиль, в котором осуществляется управление яркостью светодиодной ленты. Однократный клик (команда `activate` в обработчике события `click`) активирует профиль номер 1 — в котором управляется насыщенность цвета. Однократный клик из профиля 1 переводит энкодер в профиль 2, в котором можно настроить оттенок цвета. Ну и, соответственно, клик из профиля 2 активирует корневой профиль (0). В каждом профиле длительное нажатие включает или выключает `led`. + +```json +{ + "T": 32, "#": [14, 4, 5], + "+-": { "item": "led/set", "icmd": "%+1", "irev": "%-1" }, + "lcmd": { "item": "led/cmd", "icmd": "toggle" }, + "click": { "activate": 1 }, + "act": [ + { + "+-": { "item": "led/hue", "icmd": "%+1", "irev": "%-1" }, + "lcmd": { "item": "led/cmd", "icmd": "toggle" }, + "click": { "activate": 2 } + }, + { + "+-": { "item": "led/sat", "icmd": "%+1", "irev": "%-1" }, + "lcmd": { "item": "led/cmd", "icmd": "toggle" }, + "click": { "activate": 0 } + } + ] +} +``` + +NB: `activate` можно использовать в описании других входов контроллера — таким образом, например, нажатием других кнопок можно выбирать режим действия энкодера. + +## Доступность (сверка с кодом, 2026-10-02) + +- Тип входа `T:32` (`IN_RE`) работает при компиляции с флагом `ROTARYENCODER` (`inputs.cpp`). +- Флаг включен по умолчанию: **due, esp32-wifi, esp32c3-wifi, esp8266-wifi, lighthub21, nrf52840, stm32, stm32-noip** (`build-flags/*`). +- Массив `#`: `[контакт_нажатия, A, B, режим]`; режим по умолчанию — 2 (`inputs.cpp`, `setupRotaryEncoder`). +- `activate` поддерживает число (индекс профиля, 0 — корневой), строку (имя профиля) и массив `["имя_входа", <значение>]` — активация профиля во внешнем входе (`inputs.cpp`, `checkInstructions`). + +## Не реализовано + +Черновик из исходного документа, **не реализованный** в текущем коде: + +- вход `T:99` с профилями и обработчиками `goto`/`onenter` (переходы между профилями по событиям, «сценарный» энкодер). + +## Связанные документы + +- [Нажатия кнопок](button_presses.md) +- [CAN и lightspot](../integrations/can_lightspot.md) +- [Справочник суффиксов](suffixes_reference.md) diff --git a/documentation/integrations/can_lightspot.md b/documentation/integrations/can_lightspot.md new file mode 100644 index 0000000..1be1994 --- /dev/null +++ b/documentation/integrations/can_lightspot.md @@ -0,0 +1,239 @@ +# LightHub и шина CAN + +> Источник: авторский документ «Lighthub и шина CAN» (docx, 2026-10-02), сверен с кодом `lighthub/candriver.cpp/h`. +> CAN-поддержка в прошивке появилась в **v4.4.0**. + +## Что такое CAN шина + +CAN-шины очень широко используются в промышленной и автомобильной автоматике как дешевый, но надежный транспорт для взаимодействия модулей и блоков автоматики между собой. Такие шины есть, фактически, в каждом современном автомобиле. + +В прошивке LightHub используется фиксированная скорость **50 kbit/s** (`candriver.cpp`: `STMCan.setBaudRate(50000)` / `CAN.begin(50000)` / `Can0.begin(CAN_BPS_50K)`), что обеспечивает длину шины до нескольких сотен метров — достаточно для большинства домовладений. + +Поддержка CAN встроена во многие современные (и не очень) микроконтроллеры. Из тех микроконтроллеров, на которых работает прошивка LightHub, встроенная поддержка используется в следующих чипах: + +- ARM SAM3E (Arduino DUE) — библиотека `due_can` +- ESP32 — библиотека `arduino-CAN` (пины: GPIO 35 — RX, GPIO 5 — TX) +- STM32 — библиотека `STM32_CAN` + +> Подключение внешнего CAN-контроллера по SPI для чипов без встроенной поддержки **не реализовано** в текущем коде (`candriver.cpp` использует только встроенные контроллеры). + +## Какие возможности появляются у LightHub + +Теперь нет необходимости организовывать подключение локальной сети (или WiFi) к каждому устройству. Необходим минимум один контроллер (лучше пара), который подключен одновременно и к локальной сети, и к шине CAN. Он будет выполнять роль шлюза и держателя настроек периферийных контроллеров. + +Периферийные устройства могут подключаться только к электропитанию и двухпроводной шине CAN. А это значит, что устройств может стать кратно больше, можно использовать очень простые, небольшие и дешевые устройства и, в пределе, размещать их в любом удобном месте. Например, в подрозетниках, выключателях, кондиционерах, датчиках движения. + +При всем прочем, даже такие упрощенные устройства умеют почти то же самое, что их «старшие братья»: + +- тот же формат JSON-файла настройки позволяет организовывать как локальное, так и удаленное управление устройством; +- малое устройство может иметь на борту термостаты, возможность подключения термодатчиков и прочих сенсоров, PID-регуляторы, UART для управления кондиционером или для Modbus; +- к таким устройствам можно подключать элементы управления: кнопки, переключатели, энкодеры (элемент с вращающейся ручкой), герконы дверей и датчики движения/присутствия; +- они также поддерживают все возможности по определению одинарных-двойных-тройных-длительных нажатий кнопок, как и все LightHub. + +Такая архитектура позволяет еще более децентрализовать управление умным домом: каждый маленький автономный контроллер (расположенный даже в выключателе) способен решить задачи простой автоматизации и локального управления даже в том случае, если часть системы вышла из строя. + +Кроме того, CAN-шина позволяет таким простым устройствам напрямую общаться между собой, например: + +- для реализации сценариев переключения сцен освещения, проходных выключателей; +- для передачи измерений сенсоров на удаленные контроллеры (например, измерения влажности помещений на контроллер форсуночного увлажнения, значения CO2 в помещениях на систему вентиляции). + +Причем такое взаимодействие теперь может происходить без посредничества локальной сети и MQTT-брокера, что еще более увеличивает надежность и живучесть системы в целом. + +### Примеры применения контроллеров, подключенных только по шине CAN (далее — lightspot) + +- Автономный управляемый термостат теплого пола +- Автономное, управляемое локальным выключателем реле или диммер света (AC или RGB) +- Датчик движения, локально и удаленно управляющий светом, с сенсорами температуры, влажности, CO2 на борту +- Многоклавишный многофункциональный выключатель с сенсорами температуры-влажности, энкодером +- Удаленные счетчики воды-газа-электричества +- Управляемые клапаны вентиляции +- Удаленные ультразвуковые датчики приближения для управления подсветкой ступеней + +## Как организована передача данных по шине CAN + +Таким образом, каждое устройство в произвольный момент времени может инициировать передачу кадра данных размером до 8 байт, и этот кадр будет принят сразу всеми подключенными к шине устройствами. + +### Формат идентификатора кадра + +Идентификатор кадра (до 32 бит, `canid_t` в `candriver.h`): + +| Поле | Биты | Назначение | +|--|--|--| +| reserve | 3 | Должно быть 0 | +| status | 1 | 1 — статус, 0 — запрос | +| payloadType | 4 | Тип передаваемых данных | +| deviceId | 8 | devID устройства (1..255) | +| itemId | 10 | 0..1023 (или sequence # для длинных кадров) | +| subItemId | 6 | 0..62 (63 — субайтем не задан) | + +### Типы полезной нагрузки (payloadType) + +| Значение | Имя | Назначение | +|--|--|--| +| 1 | `itemCommand` | Команда или статус для/от того или иного item | +| 2 | `lookupMAC` | Запрос devID по MAC-адресу устройства (в ответе передается devID) | +| 3 | `configFrame` | Кадр передачи конфигурации (JSON, частями) | +| 4 | `OTAFrame` | Кадр передачи прошивки по CAN | +| 5 | `auth` | Аутентификация | +| 6 | `metric` | Метрики (см. metricType) | +| 7 | `sysCmd` | Системные команды (см. commandType) | +| 8 | `rawPinCtrl` | Прямое управление пинами | + +### Формат данных (datagram_t) + +```c +enum metricType { MAC=1, IP=2, NetMask=3, GW=4, DNS=5, UpTime=6, Salt=7 }; + +typedef union { + uint8_t data[8]; + char payload[8]; + struct { + itemCmdStore cmd; // команда + itemArgStore param; // аргумент команды + }; + struct { + macAddress mac; // MAC-адрес + uint16_t currentConfCRC; // CRC текущей конфигурации + }; + struct { + uint8_t sysCmd; + uint8_t sysCmdData[7]; + }; + struct { + uint32_t metric1; + uint32_t metric2; + }; +} datagram_t; +``` + +Системные команды (sysCmd, `commandType`): + +| Значение | Команда | +|--|--| +| 1 | `reboot` — перезагрузка контроллера | +| 2 | `get` — получить информацию | +| 3 | `save` — сохранить конфигурацию | +| 4 | `load` — загрузить конфигурацию | + +## Процедура инициализации устройства и загрузки конфигурации + +1. **Вычисление MAC-адреса.** Этот адрес является уникальным идентификатором устройства, даже если у него отсутствует сетевой интерфейс и имеется в наличии только CAN. MAC-адрес вычисляется из уникального серийного номера чипа для ARM-базированных устройств, для AVR считывается из EEPROM, для ESP используется MAC-адрес интерфейса WiFi. + +2. Устройство отправляет запрос в CAN-шину, установив `payloadType = lookupMAC (2)` в пакете и передав 6 байт MAC-адреса. + +3. Мастер-контроллер, содержащий конфигурацию устройства внутри своего файла настроек, формирует ответный пакет нулевой длины с установленным битом `status` в id и заполненным полем `deviceId`. С этого момента устройство знает свой devID. + +4. Устройство отправляет запрос в CAN-шину, установив `payloadType = configFrame (3)` и полученный на предыдущем этапе devID. В качестве sequence # для первого пакета используется значение 0xFFFF (-1), запрос каждого последующего пакета осуществляется с увеличением sequence # на единицу. В ответ на каждый запрос устройство получает кадр данных; последовательность данных содержит JSON-конфигурацию. + +### Пример конфигурации CAN внутри конфига мастер-устройства + +```json +"can": { + "addr": 14, + "conf": { + "DE:54:71:48:17:2E": { + "can": { "addr": 21, "name": "olmexino" }, + "items": { + "relay1": [[6, 1], 1], + "relay2": [[6, 2], 5], + "relays": [["GROUP", 3], ["relay1", "relay2"]] + }, + "in": { + "41": { "T": 2, "item": "relays", "can": [10, 2], "scmd": "TOGGLE", "rcmd": "" } + } + }, + "DE:48:4E:03:B3:DF": { + "can": { "addr": 22, "name": "sonar" }, + "items": { + "relay1": [[6, 1], 1] + }, + "in": [ + { + "T": 7, "#": [14, 15], + "act": [ + { "map": [0, 100], "scmd": { "can": [13, 32], "icmd": "XON 10000" } }, + { "map": [0, 200], "scmd": { "can": [13, 31], "icmd": "XON 15000" } } + ] + } + ] + } + } +} +``` + +`can.addr` — devID этого контроллера. `can.conf` — объект «MAC-адрес → конфигурация периферийного контроллера»; каждая периферия содержит собственный `can` (devID и имя) и обычные секции `items`/`in`. + +## Способ настройки взаимодействия контроллеров с использованием шины CAN + +В процессе работы контроллеры имеют возможность отправки на один или несколько других контроллеров, подключенных по шине CAN, следующего набора пакетов: + +| Тип управляющего воздействия | Числовое значение суффикса | Суффикс (символьное значение) | +|--|--:|--| +| Команды для управления удаленными устройствами | 1 | `cmd` | +| Уставки для удаленного устройства | 2 | `set` | +| Текущие значения сенсоров | 3 | `val` | +| Отложенная команда | 4 | `del` | +| Цвет в нотации HSV | 5 | `hsv` | +| Цвет в нотации RGB | 6 | `rgb` | +| Интенсивность вентиляции | 7 | `fan` | +| Режим работы климатической установки | 8 | `mode` | +| Вспомогательная команда (FREEZE / UNFREEZE / ENABLE / DISABLE) | 9 | `ctrl` | +| Цвет (hue) | 10 | `hue` | +| Насыщенность цвета | 11 | `sat` | +| Цветовая температура | 12 | `temp` | + +### Адресация объекта назначения (item) + +Для адресации объекта назначения (item), который обработает соответствующую команду в сети CAN, используется следующая нотация: + +``` +[ , , ] +``` + +- **DevID** — адрес устройства, определяется на этапе конфигурирования (см. выше). DevID = 0 адресует сообщение всем устройствам сети. +- **ItemID** — числовой идентификатор объекта (item). Задается при настройке удаленного объекта в JSON-конфигурации того контроллера, к которому объект подключен: первый элемент массива настройки item должен быть массивом, в котором первый объект определяет тип объекта, а второй — требуемый ItemID. Пример: + +```json +"items": { + "relay1": [[6, 1], 1], + "relay2": [[6, 2], 5], + "relays": [["GROUP", 3], ["relay1", "relay2"]] +} +``` + +В примере выше для объекта `relay1` определяется itemID = 1, для объекта `relay2` — itemID = 2, а для группы `relays` — itemID = 3. + +В том случае, если ItemID пересекаются между контроллерами и пакет отправлен на широковещательный адрес 0, пакет будет выполнен сразу всеми контроллерами, в которых определен объект с данным ID. + +- **Suffix** — числовое или символьное значение, определяющее тип управляющего воздействия согласно таблице выше. Допустимо определять дополнительные числовые суффиксы, например, для управления теми регистрами устройства Modbus, которые не укладываются в таблицу выше. В этом случае при описании устройства Modbus можно задать параметр `"id": <значение>`, где значение — число в интервале 13-31, и этот параметр можно будет адресовать, задавая данное число в массиве `can` на третьей позиции. +- **Subitem** — опциональный параметр, который может использоваться двояко: + - если указана **цифра** — адресация суб-объекта, входящего в удаленный управляемый объект. Примеры объектов, которые имеют внутри перечень суб-объектов: MULTIVENT (многоканальная вентиляция/кондиционирование), HUM (многоканальный форсуночный увлажнитель), адресная светодиодная лента. Для адресации соответствующего суб-объекта используется порядковый номер, согласно которому данный суб-объект описан в JSON-конфигурации контроллера назначения; + - если указана **строка** — делается попытка преобразовать данную строку в команду, и это значение передается удаленному устройству. Удаленное устройство исполнит пакет только в том случае, если текущее состояние устройства эквивалентно данной команде. Данная функция особо полезна для удаленных групп устройств: например, можно адресовать команды управления яркостью группе светильников, а исполнят их только светильники, находящиеся в состоянии «ON». + +## Функция шлюза + +Мастер-контроллер, который хранит конфигурацию периферийного, также выполняет функцию шлюза между шиной CAN и интерфейсами MQTT и HTTP. + +Так как мастер-контроллер знает имя периферийного, при подключении к MQTT-брокеру он дополнительно подписывается на командный топик периферийного. Это позволяет управлять устройствами, подключенными через шину CAN, абсолютно прозрачно, с использованием стандартной нотации командных топиков `root/name/item/[subitem/]suffix`, например, через `myhome/sonar/relay1/cmd` для примера конфига выше. + +Когда удаленный периферийный контроллер выполнит команду (либо вследствие изменения внутреннего состояния устройства) — изменение его статуса транслируется в шину CAN. Мастер-контроллер, который владеет конфигом периферийного, транслирует такое изменение статуса в свой стандартный статусный топик, например, в `myhome/s_out/relay1/cmd`. + +## Доступность в сборках (сверка с кодом, 2026-10-02) + +- Флаг `CANDRV` включен по умолчанию: **esp32-wifi, lighthub21, stm32-noip**; в **esp32c3-wifi** закомментирован (`build-flags/*`). +- Скорость: 50 kbit/s, фиксированная (`candriver.cpp`). +- ESP32: CAN на GPIO 35 (RX) / GPIO 5 (TX) (`candriver.cpp`). +- Поддержка SPI-кан-контроллеров для чипов без встроенной CAN — **не реализована** (см. выше). + +## Не реализовано + +Черновики из исходного документа, допущенные до релиза, но **не реализованные** в текущем коде: + +- вход `T:99` с профилями, обработчиками `goto`/`onenter` и сценарным переходом между профилями; +- подключение CAN через внешний SPI-контроллер для чипов без встроенной поддержки. + +## Связанные документы + +- [Rotary Encoder](../core/rotary_encoder.md) — энкодер как орган управления (в т.ч. на lightspot) +- [Быстрая справка по MQTT](../core/mqtt_quick_reference.md) +- [Справочник типов каналов](../core/channel_types_reference.md)