Самодельные беспроводные оконные датчики: STM32L0 + RFM69 + Android
Добрый день, уважаемые хабровчане! Несколько лет назад я купился на красочную рекламу zWave и установил себе оконные датчики, базирующиеся на этом протоколе. К домашнему серверу был подключен USB zWave-Stick, который играл роль контроллера, написан небольшой модуль на Java, который получал данные с этого контроллера, а также написано небольшое приложение для Андроида, которое красиво отображало состояние всех датчиков. Батарейки вставлены, датчики зарегистрированы на контроллере, все заработало. Но через пару месяцев наступило жесточайшее разочарование. Во первых, данные zWave датчики работают по принципу «послать сообщение и, не ожидая подтверждения, заснуть». В моем случае это привело к тому, что сигнал от наиболее дальних от контроллера датчиков просто не доходил до контроллера. Не помогла даже установки дополнительного zWave-повторителя. Во-вторых, они настолько быстро садили батарейку, что примерно через шесть месяцев работать переставали все датчики. Причина в том, что они каждый час просыпались, чтобы сообщить контроллеру свое состояние. Отключить или изменить этот параметр не получилось, так как штатное программное обеспечение это сделать категорически не позволяло. Помучавшись два года с этой сырой, ненадежной и недружественной технологией, я решил что с меня хватит. Но вместо того, чтобы все убрать и выкинуть, мне пришла идея оставить корпуса, но поменять в них электронику. Выбор пал на достаточно простой приемопередатчик RFM69 (433 MHz), на базе которого удалось сделать как плату для датчика, так и контроллер, подключаемый через USB к серверу. Новая система в эксплуатации уже 5 месяцев, надежность близка к 100% (но некоторые сбои таки были), батарейки садится пока не думают. То есть уже сейчас видно, что все недостатки старой системы на базе zWave устранены, и я хочу поделиться техническими подробностями этой моей поделки, см. картинку.
Кому интересно, прошу под кат.
Как мне кажется, zWave в моем случае оказался неработоспособным по двум причинам: в доме два этажа с железобетонным межэтажным перекрытием и большая площадь (то есть значительное удаление некоторых датчиков от контроллера). Ну и недоработки в фирменном программном обеспечении, которые не позволяли отключить периодическое пробуждение датчиков, что привело к быстрому разряду батареи. Когда стало понятно, что мне с zWave не по пути, я стал искать другие варианты, которые можно было бы впихнуть в те же корпуса датчиков.
Мой первый прототип датчиков базировался на ESP8266. Контроллер — на базе моей платы, о которой я уже писал на Хабре. В качестве прототипа система даже заработала, но пришлось от нее отказаться по двум причинам. Первая причина та же самая — в доме оказались закутки с очень низким уровнем сигнала WiFi, что привело к очень большому времени активации ESP8266 при пробуждении и, как следствие, к сильному разряду батарейки. Хотя я допускаю, что просто не умею правильно готовить эту самую ESP8266 (статьи вроде этой подтверждают такой тезис). Но вторая причина оказалась более серьезной. Так как я решил оставить не только корпус, но и батарейный отсек, то был ограничен батарейкой CR123A, которая на 3.0 вольта. То есть цена и сложность датчика возрастала за счет повышающего DC/DC преобразователя. Хотя и по этой теме на Хабре есть замечательная статья. Но, так или иначе, эти две причины перевесили и ESP8266 я отбросил.
Второй прототип был проводной. Сделал прототип как датчика, так и USB-контроллера, дописал серверный модуль, чтобы он этот контроллер понимал дополнительно к zWave-Stick. Была идея постепенно тянуть провода и датчик за датчиком менять zWave-плату на проводную. Но ковырять стены в конечном итоге не стал и этот прототип также отбросил.
Потом решил посмотреть в сторону радиомодулей на 433 и 868 MHz и заказал несколько модулей для экспериментов: RFM69HCW, A110LR09A и MRF89XAM8A. По размерам модуля, цене, а также из-за наличия как библиотек, так и хороших примеров остановился на RFM69HCW, который и лег в основу системы.
В системе получилось всего четыре компонента:
- беспроводные датчики (433 MHz, батарейка CR123A 3.0V),
- сетевой контроллер (433 MHz, питание по USB от сервера)
- сервер (о котором я уже писал на Хабре в этой статье) и серверный программный модуль
- мобильный клиент (Андроид-приложение)
Ниже я подробно опишу каждый модуль, а в завершение дам статистику работы системы за последние месяцы работы.
Датчики
В датчиках используется радиомодуль RFM69HCW. Он имеет широкий диапазон рабочего напряжения (1.8V-2.4V 17dBm, 2.4V-3.6V 20dBm) и управляется через SPI. То есть нужен микроконтроллер. Некоторое время назад я познакомился с серией STM23L0 и заказал для экспериментов STM32L051. В ней меня подкупил как небольшой ток в режиме сна (0.27 μA), так и рабочее напряжение, практически идентичное напряжению радиомодуля (1.65V-3.6V). Плюс низкая цена.
Получилась такая схема:
Напряжение питания как микроконтроллера, так и радиомодуля таковы, что их можно напрямую запитать от элемента CR123A. STM23L0 имеет внутренний источник опорного напряжения, подключенный к каналу 17 АЦП, а также калибровочное значение этого источника, что позволяет измерять текущее значение напряжения питания VDD. Радиомодуль подключен к питанию через полевик Q1, что позволяет управлять его питанием с микроконтроллера.
Режим сна реализован переводом микроконтроллера в «Standby». В этом режиме у STM23L0 отключено ядро, практически вся периферия и внутренний регулятор напряжения, что обеспечивает потребление на уровне 0.29 μA (при отключенных часах реального времени). Но в этом режиме есть особенность — микроконтроллер пробуждается только по восходящему фронту сигнала на пине WKUP (А0). Так как используется магнитный переключатель, который двухпозиционный (замкнут или разомкнут), то нужен небольшой блок, который генерирует импульс небольшой длительности как при замыкании, так и при размыкании магнитного контакта. Этот импульс подается на вход А0 микроконтроллера и пробуждает его. Такой преобразователь реализован на микросхеме IC3 «Исключающее ИЛИ» энергоэффективной серии 74AUP (74AUP1G86), которая работает в диапазоне напряжений 0.8V-3.6V и потребляет 0.2 μA. Таким образом, общее потребление в режиме сна должно быть в районе 0.5 μA, что подтверждается измерениями на полностью собранном датчике.
Когда микроконтроллер просыпается, он сначала измеряет напряжение питания и, если оно меньше 1.8 вольта, то не судьба — передатчик запустить не получится и микроконтроллер засыпает обратно.
Если же напряжение батарейки больше 1.8 вольта, то микроконтроллер включает и инициализирует радиомодуль, передает состояние на сетевой контроллер и ожидает подтверждения. В пакет данных входят уникальный 32х-битный номер пакета (события), напряжение батарейки, температура, состояние магнитного контакта, контрольный байт (CRC7). В случае успешного подтверждения микроконтроллер засыпает обратно. Если нет, то высылает статус снова, но максимум 10 раз. Такую границу я установил, чтобы датчик не пытался до бесконечности ждать ответа в ситуации, когда сетевой контроллер просто выключен. Последний переданный уникальный номер события хранится во внутренней EEPROM памяти микроконтроллера.
Во время передачи данных микроконтроллер мигает светодиодом (куда же без этого). Светодиод включается при помощи ШИМ, где скважность зависит от текущего значения напряжения, что позволяет иметь практически одинаковую яркость практически во всем диапазоне рабочего напряжения.
Платы спроектированы в EagleCAD, проект плат доступен здесь. Платы двухсторонние: на верхней стороне, обращенной к раме окна, размещен микроконтроллер и его обвязка, а на нижней, обращенной в комнату — радиомодуль и светодиод. Заказывал в Китае, паял сам в обычной кухонной духовке (верхний слой) и феном (нижний слой).
Радиомодулю нужна антенна. Это просто кусочек провода длинной 164 мм.
После монтажа каждый датчик нужно программировать, для чего предусмотрен разъем SWD. Остальные контакты на плате я оставил ради возможных экспериментов в будущем.
Прошивка написана на С++, часть кода вынесена в достаточно универсальные базовые классы, которые инкапсулируют вызовы библиотеки ST HAL. Исходный код лежит здесь. Для разработки использовалась System Workbench for STM32. Размер итогового двоичного файла прошивки 22 kB.
А вот так выглядит датчик в корпусе:
В зависимости от положения датчика, для некоторых датчиков я оставил антенну снаружи (на фоне белой рамы ее, впрочем, не видно), а для некоторых датчиков изогнул провод в рамку и полностью запихал внутрь корпуса.
Ради интереса подсчитал стоимость компонент для одного датчика (по ценам каталога Mouser, цена и итоговая стоимость в евро)
Элемент | Описание | Значение | Номер MOUSER | Кол-во | Стоимость |
IC3 | Исключающее ИЛИ | 1G86 | 771–74AUP1G86GW-G | 1 | 0.254 |
IC1 | Микроконтроллер | STM32L051 | 511-STM32L051K6T6 | 1 | 2.14 |
SV1 | Разъем | SWD | 68602–406HLF | 1 | 0.157 |
LED1 | LED 3 мм | 3mm | 630-HLMP-K155 | 1 | 0.351 |
Q1 | P-MOSFET | BSH205 | 771-BSH205G2R | 1 | 0.276 |
S1 | Магнитный контакт | ORD211–0810 | 876-ORD211–0810 | 1 | 0.625 |
IC2 | RFM69HCW радиомодуль | RFM69HCW | 474-COM-13910 | 1 | 5.36 |
C1, C2, C3, C6 | SMD конденсатор | 0.1uF | 80-C0805C104J5RAC | 4 | 0.1 |
C5 | SMD конденсатор | 0.4nF | C0805C471K8HACTU | 1 | 0.021 |
C4 | SMD конденсатор (тантал) | 47uF | 581-TAJR225K016RNJ | 1 | 0.334 |
R1 | SMD резистор | 10K | 660-RK73H2ATTDD1002F | 1 | 0.01 |
R10 | SMD резистор | 330 | 660-RK73H2ATTDD3300F | 1 | 0.01 |
R3, R4, R6, R7, R8, R11 | SMD резистор | 47K | 660-RK73H2ATTDD4702F | 6 | 0.06 |
R5 | SMD резистор | 56 | 660-RK73H2ATTD56R0F | 1 | 0.013 |
R9 | SMD резистор | 56M | 603-RC0805JR-0756ML | 1 | 0.037 |
9.748 |
Получилось, кстати, ровно в три раза дешевле, чем стоил оригинальный датчик zWave. Хотя это только стоимость деталей без учета корпуса. Но, на мой взгляд, в рознице датчики zWave продаются до неприличия дорого.
Сетевой контроллер
Для контроллера использовался тот же самый радиомодуль и такой же микроконтроллер, как и для датчиков. Это позволило переиспользовать большую часть кода прошивки. Для контроллера я выбрал такой форм-фактор платы, чтобы использовать готовый корпус от Raspberry Pi. По сравнению с датчиком, добавился разъем для внешней антенны и контур USB на базе FT232RL и изолятора SI-8621. Конечно, вместо этого можно было бы взять какой-нибудь STM32 с USB на борту. Но тогда, во первых, пришлось бы разделять код прошивки и, во-вторых, самому заниматься программной реализацией USB. Вариант со внешней FT232RL хоть и дороже, но надежней и быстрее в реализации. В результате получилась такая схема:
А в собранном виде получилось вот так:
Микроконтроллер здесь в сон не уходит, радиомодуль работает на прием, но после успешного приема пакета с любого из датчиков модуль переходит в режим передачи и отправляет подтверждение приема. Кроме этого, любой успешно принятый пакет передается по USB на сервер. Формат пакета — это строка значений, разделенных символом »;»:
GW;3;12;-57;0;146;30;18
где:
- первая позиция — метка пакета (всегда «GW»)
- вторая позиция — тип данных (статус датчика или код ошибки)
- третья позиция — номер датчика
- четвертая позиция — качество сигнала на стороне сетевого контроллера (RFM69HCW сохраняет качество сигнала во время приема и позволяет опросить его, когда прием закончен)
- пятая позиция — состояние окна (0 открыто, 1 закрыто)
- шестая позиция — уникальный номер пакета (события) с датчика. Этот номер позволяет на стороне сервера контролировать, все ли события были получены сервером или одно или несколько событий были утеряны.
- седьмая позиция — актуальное напряжение батарейки (30 соответствует 3.0V)
- последняя, восьмая позиция — температура с внутреннего датчика микроконтроллера. Пока еще не придумал, как это можно использовать
Исходный код прошивки находится там же, что и для датчика. Они имеют общий файл main и общую библиотеку базовых классов. Вариант прошивки (датчик или контроллер) выбирается при помощи директивы define в файле main.cpp.
Стоимость сетевого контроллера получилась выше за счет USB-контура, антенны и дополнительных разъемов. Но этой добавкой можно пренебречь, так как контроллер один. Но даже с учетом этой добавки он также получился в три раза дешевле, чем zWave-Stick, который был перед этим на его месте.
Серверный модуль
Сетевой контроллер подключен к серверу, который работает под управлением CentOS 7. На сервере запущена программа, написанная на Java. И по своей структуре, и по реализации, и по задачам программа достаточно простая:
- При помощи очень древней и, вроде бы, больше не поддерживаемой библиотеки RxTx мониторится указанный в конфигурации USB порт (в моем случае /dev/ttyUSB0). На данный момент это самый плохо оптимизированный участок всей системы, так как библиотека сильно грузит процессор сервера.
- В случае поступления данных с USB-порта, они записываются в лог-файл и сохраняются в классе состояния датчиков. При перезагрузке сервера состояние теряется. Чтобы его восстановить, нужно пройтись по всему дому и вручную открыть и закрыть все окна. Наверное, это самый большой недостаток моей системы, но я вполне сознательно отказался от периодического опроса датчиков ради экономии их батарейки.
- Приложение также является небольшим TCP/IP сервером и слушает TCP/IP порт указанный в конфигурации. По этому порту оно может принимать соединения от мобильного клиента.
- В случае, если мобильный клиент подсоединяется к серверу, он высылает текущее состояние всех датчиков. Также при помощи периодических heartbit-сообщений сервер отслеживает активность соединения.
- Сервер и мобильный клиент обмениваются сообщениями в формате XML. Эти сообщения реализованы в виде небольшой Java-библиотеки, которая совместно используется как на стороне сервера, так и на стороне мобильного Андроид-приложения.
- Хоть большого смысла это пока не имеет, но ради интереса я добавил функцию авторизации мобильного клиента по IMEI, а также AES-шифрование сообщений между сервером и клиентом по ключу, зашитому в исходный код (Java-пакет javax.crypto). Но это так, чисто для эксперимента, так как TCP/IP порт этого серверного модуля доступен только из внутренней сети и снаружи не виден. Хотя, если я захочу открыть этот порт в большой мир, то теперь делать это будет не так страшно.
Кому интересно, исходный код серверного модуля здесь.
Мобильный клиент (Андроид-приложение)
Тут много не напишешь, несмотря на то, что это приложение и есть конечный результат всего проекта. Это стандартное и очень простое Java-приложение с тремя вкладками: состояние окон первого этажа, состояние второго этажа, и небольшая телеметрия сервера (см. исходный код здесь):
В основе графики лежит набор SVG-файлов, которые в виде стека рисуются друг над другом в зависимости от состояния датчиков. Поддерживается долгое нажатие в области каждого окна, по которому отображается подсказка со временем, во сколько это окно было открыто:
В принципе, ничего не мешает добавить в эту подсказку пиктограмму заряда батарейки, но руки пока не дошли.
Опыт эксплуатации
«Каждый чих» записывается в лог-файл на стороне сервера. Анализ этих файлов за последние 5 месяцев позволяет немного подробнее понять, как же эта система себя чувствует.
Анализ очень простой — просто grep в папке лог-файлов на сервере.
Сколько было ошибок передачи данных по радио-каналу? За 5 месяцев зарегистрировано 8 ошибок, когда получено сообщение неправильного размера и 25 ошибок неверного CRC. В обеих случаях нельзя напрямую сказать, с каким датчиком были проблемы, так как пакет данных в этом случае полностью поврежден. Так как сетевой контроллер во всех этих случаях прием данных не подтверждает, то датчик должен высылать данные по новой, что в большинстве случаев эти ошибки исправляет.
А вот сводная таблица по работе датчиков за 5 месяцев.
Этаж | Датчик | Количество Событий |
Потерянных Событий |
Напряжение Батареи |
Медиана Мощности Сигнала |
1 | 10 | 105 | 0 | 3.1 | -66 |
1 | 11 | 52 | 0 | 3.3 | -70 |
1 | 12 | 122 | 0 | 3.3 | -61 |
1 | 13 | 89 | 0 | 3.3 | -74 |
1 | 14 | 2573 | 0 | 3.3 | -68 |
1 | 15 | 261 | 0 | 3.3 | -60 |
1 | 16 | 543 | 2 | 3.3 | -70 |
1 | 17 | 156 | 2 | 3.3 | -74 |
1 | 18 | 177 | 2 | 3.2 | -68 |
1 | 19 | 384 | 3 | 3.3 | -56 |
2 | 3 | 368 | 2 | 3.3 | -62 |
2 | 4 | 86 | 0 | 3.3 | -71 |
2 | 5 | 521 | 2 | 3.3 | -59 |
2 | 6 | 115 | 0 | 3.3 | -62 |
2 | 7 | 316 | 2 | 3.3 | -63 |
2 | 8 | 419 | 1 | 3.3 | -60 |
2 | 9 | 89 | 0 | 3.3 | -68 |
Датчик №10 один раз завис. Это, видимо, привело к значительному уменьшению напряжения батарейки. Причина зависания пока не ясна. Зависнет еще раз, придется разбираться.
Датчик №14 установлен на входной двери, именно поэтому у него такое большое количество срабатываний. Но на напряжении батарейки такое большое число срабатываний пока не отразилось.
Потерянное событие — это когда датчик пытался отправить состояние, но сервер его так и не подтвердил. Все потерянные события связаны с тем, что я отключал сервер примерно на полдня для чистки.
В колонке «Медиана мощности сигнала» показано медианное значение RSSI (в dBm), где каждое отдельное значение получено после завершения приема каждого пакета. Датчик с самым лучшим сигналом (№19, -56 dBm) находится на расстоянии 2 метров в прямой видимости от сетевого контроллера, но вот антенна в этом датчике свернута рамкой внутри корпуса. Сам сетевой контроллер находится на первом этаже. Однако сигнал с датчиков второго этажа очень даже неплох. Это связано с тем, что на всех датчиках второго этажа антенна вынесена из корпуса датчика.
Вместо послесловия
С одной стороны я рад, что самостоятельно, в качестве хобби получилось с нуля спроектировать, собрать и запустить систему такого уровня. И еще больше рад, что она работает лучше «фирменной» системы, базирующейся на хайповой технологии zWave. Также есть куда развивать и улучшать эту систему. Меня только гложут небольшие сомнения. А именно, что человек без профильного электротехнического образования собирает на коленке нечто, что работает надежнее фирменных продуктов известных в узких кругах IOT-брендов. Наверное, прогресс свернул куда-то не туда.