Привет!
На прошлой неделе компания Ноотехника выпустила два первых датчика — движения и температуры и влажности для своей линейки дистанционного управления светом Noolite.
К сожалению, работа с датчиком температуры и влажности с помощью родного USB-приёмника от Ноотехники не поддерживается, только через их Ethernet-шлюз.
В нашем контроллере для домашней автоматизации Wiren Board Smart Home есть встроенный универсальный приёмопередатчик на частоту 433MHz, с помощью которого можно работать с многими устройствами. Это значит, что WB Smart Home может работать с устройствами Noolite напрямую, без использования USB-приёмников и передатчиков Ноотехники.
Впрочем, чтобы работать со сторонними устройствами, для начала обычно требуется реверс-инжиниринг (т.е. взлом) протокола радио-обмена.
Протокол Noolite, используемый в блоках управления освещением, мы вскрыли и разобрали в одной из предыдущих статей.
В этой статье мы расскажем про реверс-инжиниринг протокола датчика PT111, обновлённую информацию об устройстве протокола Noolite вообще, а также покажем, как работа с датчиками выглядит в WB Smart Home.
Датчики
(как всегда, спасибо dima117 и thinking-home.ru за очень оперативно предоставленные устройства для экспериментов.)
Как уже было сказано, Ноотехника выпустила два датчика: движения PM111 и температуры/влажности PT111. По слухам, к выходу готовится ещё датчик температуры.
PM111
Датчик движения PM111 имеет PIR-сенсор и датчик освещённости. Датчик работает аналогично пультам Noolite.
После детектирования движения датчик отправляет команду на включение, через определённое время после отсутствия движения — команду на выключение.
Чувствительность датчика, длительность включения и порог освещённости регулируются.
Датчик движения со снятой задней крышкой и защитным колпачком (картинки кликабельны):
Кстати, интересная трассировка платы.
Датчик температуры и влажности PT111
Внутри используется датчик Sensirion SHT10.
Датчик SHT10 довольно дорогой: оптовая цена в России — больше 400 рублей, китайцы продают их по $6. Видимо этим частично и определяется довольно высокая цена итогового продукта.
PT111 умеет работать в трёх режимах: собственно в режиме передачи показаний, а также в режимах термостата и гигростата.
В двух последних режимах PT111 эмулирует обычный пульт Noolite, отправляя команды включения и выключения при переходе температуры или влажности через заданный порог.
В режимах термостата и гигростатата функциональность отправки показаний зачем-то отключается.
В отличие от распространённых погодных датчиков Oregon Scientific, PT111 отправляет пакет с данными только при изменении показаний температуры или влажности, но не чаще, чем примерно раз в минуту.
На задней панели датчика расположена кнопка, при нажатии на которую PT111 отправляет команды привязки или отвязки. При нажатии на кнопку в режиме датчика, PT111 также немедленно отправляет пакет с данными, если они изменились с прошлого раза.
Протокол PT111
Реверс-инжиниринг протокола как всегда начинается со сбора дампов пакетов.
Как сказано выше, у PT111 есть недокументированная особенность, очень полезная для отладки — датчик отправляет показания при нажатии на кнопку привязки.
Задача несколько усложнялась тем, что официального приёмника Noolite (ethernet-шлюза) под рукой у нас не было. Это значит, что не было правильных точных значений температуры и влажности для каждого пакета с данными.
В процесса сбора данных мы отправляли датчик в морозилку, грели его и т.д., чтобы собрать как можно больше данных.
Вот так выглядят приходящие с датчика данные, в том виде, как их принимает демон радио-модуля Wiren Board Smart Home:
$ mosquitto_sub -v -t /events/wb-homa-rcd/protocols/+
/events/wb-homa-rcd/protocols/raw raw=aaaaaaaaaaaaaaa85559aaa5569a66a6aaa6a960aab3554aad34cd4d554d52c055555555555555555550aaaaad34cd55554cb4a155555a699aaaaa99
/events/wb-homa-rcd/protocols/noo raw=1111110100000011111001001010001000000010000110 fmt=1 cmd=15 flip=1 addr=149f
Представленный здесь пакет — это пакет привязки пульта, отправленный при нажатии на кнопку на датчике.
Пакет с данными выглядит так (здесь уже приведена расшифровка):
/events/wb-homa-rcd/protocols/raw raw=aaaaaaaaaaaaaaa1999aaa9a9aa6a6569555555a699a95aa9a6a4333355535354d4cad2aaaaab4d3352b5534d480000000000000000000000101ffff
/events/wb-homa-rcd/protocols/noo addr=149f temp=27.2 lowbat=0 fmt=7 cmd=21 flip=0 humidity=58 raw=10101010000000100010000100010111001111111111111001001010001110000010010001
Исходный пакет
aaaaaaaaaaaaaaa1999aaa9a9aa6a6569555555a699a95aa9a6a4333355535354d4cad2aaaaab4d3352b5534d480000000000000000000000101ffff
после отрезания хвоста и преабмулы и преобразования из манчестерского кода выглядит так:
10101010000000100010000100010111001111111111111001001010001110000010010001
«Расширенный» формат пакета
Разбиваем сообщение на октеты, начиная с конца — в пакетах Noolite формат и размер пакета кодируются в конце пакета.
10 10101000 00001000 10000100 01011100 11111111
( адрес, 16 bit ) 11111001 00101000
(формат) 11100000
(чексумма) 10010001
Значения последних 4 байт мы знаем: это 2 байта адреса блока, байт кода формата и байт контрольной суммы.
В отличие от ранее известных пакетов, этот имеет код формата fmt=7.
От пакетов с командами обычных пультов есть и другое отличие: количество первых бит.
Вот так выглядит пакет с обычной командой выключения:
#ch:2 off_ch 110000 10011111 10100100 00000000 10000100 # fmt=0
Здесь перед байтами идёт блок из 6 бит. Первый бит всегда единица, следующий бит (flip) инвертируется каждую передачу (чтобы отличить дубликаты команды от новой команды), затем идут 4 бита, задающие код команды.
В «расширенном» формате первый блок имеет 10 бит. Значение первого бита непонятно, для датчика PT111 он всегда равен единице. Следующий бит — это flip-бит. Под команду в этом формате отведено уже 8 бит, а не 4. Формат пакета (расширенный или обычный) определяется кодом формата из предпоследнего байта. «Расширенный» формат, кроме собственно датчика PT111 (fmt=7), замечен также в командах «switch mode» и «switch color» USB-передатчика Noolite с кодом формата fmt=4.
В «расширенном» формате не используются коды команды из обычного формата, т.е. 0-15 — влезающие в 4 бита; коды команд начинаются с 16.
В зависимости от формата пакета по-разному считается и контрольная сумма (последний байт). Функция подсчёта — crc8_maxim — остаётся одинаковой в обоих случаях, однако по-разному обрабатывается первый блок, не кратный восьми битам:
if cmd < 16:
data = chr(((cmd << 1) | flip_bit) << 3)
else:
data = chr(flip_bit << 7) + chr(cmd)
Итоговый парсер пакета выглядит так (весь код есть в репозитарии):
парсер пакета
def parsePacket(self, packet):
if len(packet) < 38:
return
remainder = (len(packet) - 6 ) % 4
if remainder != 0:
packet += '0'*(4-remainder)
crc = int(packet[-8:][::-1], 2)
fmt = int(packet[-16:-8][::-1], 2)
addr_lo = int(packet[-32:-24][::-1], 2)
addr_hi = int(packet[-24:-16][::-1], 2)
addr = (addr_hi << 8) + addr_lo
if fmt < 4:
sextet_1 = packet[:6]
flip_bit = int(sextet_1[1], 2)
cmd = int(sextet_1[2:][::-1], 2)
args_data = packet[6:-32]
else:
dectet_1 = packet[:10]
flip_bit = int(dectet_1[1], 2)
cmd = int(dectet_1[2:][::-1], 2)
args_data = packet[10:-32]
#~ print "fmt=", fmt, len(args_data)
#~ print args_data
if fmt == 0:
if len(args_data) != 0:
return
elif fmt == 1:
if len(args_data) != 8:
return
elif fmt == 3:
if len(args_data) != 32:
return
elif fmt == 4:
if len(args_data) != 0:
return
elif fmt == 7:
if len(args_data) != 32:
return
else:
return
if args_data:
args = [int(x[::-1], 2) for x in utils.batch_gen(args_data, 8, align_right=True)]
else:
args = []
return flip_bit, cmd, addr, fmt, crc, args
Извлекаем данные
(флип-бит, код команда) 10 10101000
(данные) 00001000 10000100 01011100 11111111
( адрес, 16 bit ) 11111001 00101000
(формат) 11100000
(чексумма) 10010001
Как видно выше, под данные в пакете PT111 отведено 4 байта.
Посмотрим, как меняются эти данные от посылки к посылке (4 байта данных выделены):
10 10101000 { 00101000 10000100 01010100 11111111 } 11111001 00101000 11100000 10000000
10 10101000 { 00001000 10000100 01001100 11111111 } 11111001 00101000 11100000 11101101
11 10101000 { 11000100 10000100 10000010 11111111 } 11111001 00101000 11100000 01010110
10 10101000 { 01011000 10000100 10111100 11111111 } 11111001 00101000 11100000 11001010
11 10101000 { 00011000 10000100 01011100 11111111 } 11111001 00101000 11100000 10101011
11 10101000 { 01101000 10000100 11110100 11111111 } 11111001 00101000 11100000 00100000
Видим, что четвёртый байт данных во всех посылках равен 0xFF (0b11111111). Видимо он является зарезервированным.
Что такое третий байт данных? С большой вероятностью это целое 8-битное значение влажности: раскодированные таким образом значения получаются адекватными. Более того, значение этого байта никогда не превышает 100 (0x64), что можно проверить выставив на датчике максимальную влажность (например подышать в сенсор пару минут).
При сильном локальном нагреве воздуха паяльником, значение байта падает до 5-10, что похоже на правду.
Первый байт постоянно меняется при различных манипуляциях с датчиком — видимо он каким-то образом отвечает за температуру.
Второй байт кажется не меняется. Проверим, не кодируется ли здесь статус разряженной батарейки (судя по описанию датчик передаёт предупреждение об этом по радио).
Замкнём контакты одной батарейки тонким проводом, чтобы понизить напряжение:
( было ) 10 10101000 11000000 { 10000100 } 00111100 11111111 11111001 00101000 11100000 10010101
( стало ) 10 10101000 11100000 { 10000101 } 00100110 11111111 11111001 00101000 11100000 00111000
Видим, что изменился младший бит этого байта. Он кодирует статус заряда батарейки.
Что может быть в оставшихся битах второго байта? Например там может отдельно храниться знак температуры, как в датчиках Oregon Scientific.
Охлаждаем сенсор:
11 10101000 00110000 { 10000100 } 00001100 11111111 11111001 00101000 11100000 1110011 # 12, 33
(первый бит поменялся)
10 10101000 10011000 { 00000100 } 01101100 11111111 11111001 00101000 11100000 1100101 (25)
10 10101000 00111000 { 00000100 } 11011100 11111111 11111001 00101000 11100000 0110111 (28)
11 10101000 10111000 { 00000100 } 11110100 11111111 11111001 00101000 11100000 10100001
(долго лежал при -18C, первые четыре бита стали 1111)
11 10101000 01111010 { 11110100 } 11000010 11111111 11111001 00101000 11100000 1000100 (3934 = -16.2C)
(достали из холодильника, датчик нагревается)
10 10101000 00101010 { 11110100 } 11011100 11111111 11111001 00101000 11100000 00100110 (-17.2C)
10 10101000 10110001 { 11110100 } 01111100 11111111 11111001 00101000 11100000 10001011 (-11.5C)
10 10101000 01000011 { 11110100 } 11100010 11111111 11111001 00101000 11100000 01010011 (-6.2C)
10 10101000 11000111 { 11110100 } 00101010 11111111 11111001 00101000 11100000 01010100 (-2.9C)
11 10101000 00001111 { 11110100 } 11011010 11111111 11111001 00101000 11100000 00101010 (-1.6C)
(переход через ноль? 1111 => 0000)
11 10101000 11100100 { 00000100 } 01111010 11111111 11111001 00101000 11100000 01011101 (3.9C)
10 10101000 11000010 { 00000100 } 10000110 11111111 11111001 00101000 11100000 00000100
Дополнительно греем сенсор:
10 10101000 11000101 { 01000100 } 11000000 11111111 11111001 00101000 11100000 1100011 # 67.5
10 10101000 00100110 { 01000100 } 01000000 11111111 11111001 00101000 11100000 1011001 #
Видим что первые 4 бита второго байта ведут себя следующим образом:
- При уменьшении температуры значение меняется сначала с 0b0001 на 0b0000 (порядок бит — обратный)
- После заморозки до -18, значение становится 0b1111
- При нагреве в какой-то момент значение меняется с 0b1111 на 0b0000
- При сильном нагреве выше комнатной температуры значение меняется с 0b0001 на 0b0002
Итого, самое простое объяснение, которое приходит в голову — это то, что в первых четырёх битах хранится кусок signed-значения температуры.
Склеиваем эти четыре бита с 8 битами из первого байта и получаем 12-бит signed значение. Результаты декодирования позволяют понять, что это 12-бит знаковое значение температуры в десяты долях градусов Цельсия.
Пример декодирования:
1)
bytes 1-2 = 11100100 00000100
0000 00100111 (bin) = 39 (dec) = 3.9C
2)
bytes 1-2 = 00101010 11110100
1111 01010100 (bin) = 3924 (dec).
3924 - 0x1000 = -172 = -17.2C
Итоговый код декодирования температуры:
temp = ((args[1] & 0x0F) << 8) + args[0]
if temp > 0x7ff:
temp = temp - 0x1000
temp = temp * 0.1
Итого, формат данных PT111
temp/hum:
flip, 2 bit --> 10 10101000 11100000 10000101 00100110 11111111 11111001 00101000 11100000 00111000
cmd 8 bit ---^ | | ^ ^ ^ ^ addr_lo addr_hi fmt crc
temperature, signed, 0.1C --> |-- 12 bit -| | | | |
unknown, 3bit, 0b010 ------->---------------- | | |
bat low, 1bit ------------->------------------ | |
humidity, 8 bit ------------->----------------------- |
unknown, 8 bit, always 0xFF so far -------->------------------
WB Smart Home
В нативном веб-интерфейсе WB Smart Home датчики выглядят как-то так:
Итоговый код работы с Noolite в нашем репозитории.
Реклама
Контроллер WB Smart Home доступен в продаже у нас на сайте.
А ещё мы ищем программистов C/C++/Python (Linux)
(лучше сразу всё из списка), разрабатывать ПО для наших железок для домашней и промышленной автоматизации.
Работа включает в том числе добавление поддержки в WB Smart Home новых внешних проводных и беспроводных устройств, вроде описанных в этой статье.
География: Москва — Долгопрудный, удалённая работа и неполная занятость тоже рассматриваются.
Писать можно на info+hr3@contactless.ru.
Работа включает в том числе добавление поддержки в WB Smart Home новых внешних проводных и беспроводных устройств, вроде описанных в этой статье.
География: Москва — Долгопрудный, удалённая работа и неполная занятость тоже рассматриваются.
Писать можно на info+hr3@contactless.ru.