Как я прикручивал часы LYWSD02MMC от Xiaomi к «умному дому» Domoticz на raspberry pi 3

Новый датчик температуры/влажности с часами на электронных чернилах с BLE от Xiaomi появился в продаже относительно недавно. На вид это скорее часы, нежели датчик температуры и влажности – большую часть экрана занимают показания часов, а показания температуры и влажности пристроились скромно внизу вместе с индикатором комфорта, стилизованного под рожицу. Обзоров этих часов в инете хватает, здесь, например.
Часы довольно симпатичные, мне понравились. К Mihome подключились без проблем. Стало интересно, можно ли передавать показания датчиков в систему «умного дома» Domoticz.
Поиск вывел на сайт, где автор подробно расписал, как получить данные с этих часов.
У меня Domoticz установлен на raspberry pi 3 и первое, что попробовал — связаться с часами по блютузу, для этого наколхозил скрипт на python3:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from bluepy import btle
import struct

mac_addr_LYWSD02 = '3F:59:C8:61:10:CF'

class MyDelegate(btle.DefaultDelegate):
    def handleNotification(self, cHandle, data):
        global HUM
        global TEMP
        HUM = data[2]
        TEMP = struct.unpack('h', data[:2])[0] / 100

p = btle.Peripheral(mac_addr_LYWSD02)
p.setDelegate(MyDelegate())
uuid = 'EBE0CCC1-7A0A-4B0C-8A1A-6FF2997DA3A6'
ch = p.getCharacteristics(uuid=uuid)[0]
desc = ch.getDescriptors(forUUID=0x2902)[0]
desc.write(0x01.to_bytes(2, byteorder="little"), withResponse=True)

print ("LYWSD02")

i = 0
while True:
     HUM = 0
     TEMP = 0
     p.waitForNotifications(1)
     i += 1
     print(i,"T =",TEMP,"*C,   H =", HUM,"%")
     if i >= 20 :
       break

p.disconnect()

В строке mac_addr_LYWSD02 = 'xx:xx:xx:xx:xx:xx' необходимо прописать MAC адрес своих часов, его можно посмотреть в приложении Mihome:

В результате (не сразу, конечно :) увидел следующие:
root@raspberrypi:/home/pi/domoticz/devices/lywsd02mmc# python3 test.py
LYWSD02
1 T = 0 *C,   H = 0 %
2 T = 0 *C,   H = 0 %
3 T = 26.11 *C,   H = 44 %
4 T = 0 *C,   H = 0 %
5 T = 0 *C,   H = 0 %
6 T = 0 *C,   H = 0 %
7 T = 0 *C,   H = 0 %
8 T = 0 *C,   H = 0 %
9 T = 26.08 *C,   H = 44 %
10 T = 0 *C,   H = 0 %
11 T = 0 *C,   H = 0 %
12 T = 0 *C,   H = 0 %
13 T = 0 *C,   H = 0 %
14 T = 0 *C,   H = 0 %
15 T = 26.08 *C,   H = 44 %
16 T = 0 *C,   H = 0 %
17 T = 0 *C,   H = 0 %
18 T = 0 *C,   H = 0 %
19 T = 0 *C,   H = 0 %
20 T = 0 *C,   H = 0 %
root@raspberrypi:/home/pi/domoticz/devices/lywsd02mmc#

Нулевые значения получились, потому что 1 сек мало для времени ожидания ответа в waitForNotifications(timeout). Подробнее можно посмотреть здесь.Теперь можно было подумать о связке с Domoticz. В оборудовании добавил Dummy (Does nothing, use for virtual switches only):

Затем создал виртуальный датчик температуры/влажности:

В устройствах надо посмотреть Idx датчика, в моем случае 27:

Теперь попробуем новому датчику передать данные. В адресной строке браузера пишем:

 http://192.168.8.10:8080/json.htm?type=command&param=udevice&idx=27&nvalue=0&svalue=25;38;0
192.168.8.10:8080 – адрес Domoticz; idx=27 – индекс датчика. Подробнее можно почитать здесь. В результате должны получить:

А в датчике появятся переданные значения температуры и влажности:

Все функционирует, осталось собрать воедино. Опять же наколхозил скрипт:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

domoticzserver   = '127.0.0.1:8080'
domoticzusername = 'Admin'
domoticzpassword = 'admin'
IDX = '27'
mac_addr_LYWSD02 = '3F:59:C8:61:10:CF'

from bluepy import btle
import struct
import urllib.request
import base64

class MyDelegate(btle.DefaultDelegate):
    def handleNotification(self, cHandle, data):
        global HUM
        global TEMP
        HUM = data[2]
        TEMP = struct.unpack('h', data[:2])[0] / 100
        
def _encodeBase64(consumer_key, consumer_secret):
    dummy_param_name = 'bla'
    key_url_encoded = urllib.parse.urlencode({dummy_param_name: consumer_key})[len(dummy_param_name) + 1:]
    secret_url_encoded = urllib.parse.urlencode({dummy_param_name: consumer_secret})[len(dummy_param_name) + 1:]
    credentials = '{}:{}'.format(key_url_encoded, secret_url_encoded)
    bytes_base64_encoded_credentials = base64.encodebytes(credentials.encode('utf-8'))
    return bytes_base64_encoded_credentials.decode('utf-8').replace('\n', '')

def domoticzrequest (url):
  request = urllib.request.Request(url)
  request.add_header("Authorization", "Basic %s" % base64string)
  response = urllib.request.urlopen(request)
  respond = response.read()
  response.close()
  return respond
  
p = btle.Peripheral(mac_addr_LYWSD02)
p.setDelegate(MyDelegate())
uuid = 'EBE0CCC1-7A0A-4B0C-8A1A-6FF2997DA3A6'
ch = p.getCharacteristics(uuid=uuid)[0]
desc = ch.getDescriptors(forUUID=0x2902)[0]
desc.write(0x01.to_bytes(2, byteorder="little"), withResponse=True)

print ("LYWSD02")

i = 0
HUM = 0
TEMP = 0

while not p.waitForNotifications(5):
     i += 1
     if i >= 20 :
       break
       
p.disconnect()

print(i,"T =",TEMP,"*C,   H =", HUM,"%")

base64string = _encodeBase64(domoticzusername, domoticzpassword)

HUM_STAT = "0" # Норма
if HUM > 85:
  HUM_STAT = "3" # Влажно
elif HUM < 20:
  HUM_STAT = "2" # Сухо
elif HUM <= 60 and HUM >= 40:
  HUM_STAT = "1" # Комфортно
  
print(domoticzrequest("http://" + domoticzserver + "/json.htm?type=command&param=udevice&idx=" + IDX + "&nvalue=0&svalue=" + str(TEMP)[:4] + ";" + str(HUM) + ";" + HUM_STAT))

В начале скрипта необходимо прописать свои данные:

Скрипт я назвал lywsd02.py и поместил в :/home/pi/domoticz/devices/lywsd02mmc. При запуске, если все хорошо, в датчике отобразятся данные с часов, а в консоли увидим:
root@raspberrypi:/home/pi/domoticz/devices/lywsd02mmc# python3 lywsd02.py
LYWSD02
0 T = 26.12 *C,   H = 44 %
b'{\n   "status" : "OK",\n   "title" : "Update Device"\n}\n'
root@raspberrypi:/home/pi/domoticz/devices/lywsd02mmc#
# Здесь во второй строчке данные полученные от часов, в третьей строчке ответ от Domoticz

Ну и осталась самая малость — прописать запуск скрипта каждый час в cron:
# Даем права на исполение:
pi@raspberrypi:~/domoticz/devices/lywsd02mmc $ sudo chmod 755 lywsd02.py
# Теперь пропишем  запуск скрипта в cron:
pi@raspberrypi:~/domoticz/devices/lywsd02mmc $ sudo crontab -e
# В nano правим: в начале вставляем строчку 
MAILTO=""
# в конце дописывам:
0 * * * *  /home/pi/domoticz/devices/lywsd02mmc/lywsd02.py > /dev/null 2>&1
# Ctrl X сохраняем, получаем:
crontab: installing new crontab
# Перегружаемся:
pi@raspberrypi:~/domoticz/devices/lywsd02mmc $ sudo reboot

Ну вот, как-то так.
И да, это не готовое решение, а скорее размышление и эксперимент.

В дополнение добавлю еще несколько строк:
Значение уровня батареи можно узнать по handle: 0x0052. При чтении получим 1 byte от 0 до 0x64 (0..100%)

С учетом этого скрипт для Domoticz Будет выглядеть:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from bluepy import btle
import struct
import urllib.request
import base64

domoticzserver   = '127.0.0.1:8080'
domoticzusername = 'Admin'
domoticzpassword = 'admin'
IDX = '27'
mac_addr_LYWSD02 = '3F:59:C8:61:10:CF'


class MyDelegate(btle.DefaultDelegate):
    def handleNotification(self, cHandle, data):
        global HUM
        global TEMP
        HUM = data[2]
        TEMP = struct.unpack('h', data[:2])[0] / 100

def _encodeBase64(consumer_key, consumer_secret):
    dummy_param_name = 'bla'
    key_url_encoded = urllib.parse.urlencode({dummy_param_name: consumer_key})[len(dummy_param_name) + 1:]
    secret_url_encoded = urllib.parse.urlencode({dummy_param_name: consumer_secret})[len(dummy_param_name) + 1:]
    credentials = '{}:{}'.format(key_url_encoded, secret_url_encoded)
    bytes_base64_encoded_credentials = base64.encodebytes(credentials.encode('utf-8'))
    return bytes_base64_encoded_credentials.decode('utf-8').replace('\n', '')

def domoticzrequest (url):
  request = urllib.request.Request(url)
  request.add_header("Authorization", "Basic %s" % base64string)
  response = urllib.request.urlopen(request)
  respond = response.read()
  response.close()
  return respond


p = btle.Peripheral(mac_addr_LYWSD02)
p.setDelegate(MyDelegate())
uuid = 'EBE0CCC1-7A0A-4B0C-8A1A-6FF2997DA3A6'
ch = p.getCharacteristics(uuid=uuid)[0]
desc = ch.getDescriptors(forUUID=0x2902)[0]
desc.write(0x01.to_bytes(2, byteorder="little"), withResponse=True)

print ("LYWSD02")

i = 0
HUM = 0
TEMP = 0

while not p.waitForNotifications(5):
     i += 1
     if i >= 20 :
       break

batt  = p.readCharacteristic(0x0052)

battlev = struct.unpack('B', batt)[0]


p.disconnect()
print(i,"T =",TEMP,"*C,   H =", HUM,"%   BATT = ", battlev, "%" )

base64string = _encodeBase64(domoticzusername, domoticzpassword)

HUM_STAT = "0" # Норма
if HUM > 85:
  HUM_STAT = "3" # Влажно
elif HUM < 20:
  HUM_STAT = "2" # Сухо
elif HUM <= 60 and HUM >= 40:
  HUM_STAT = "1" # Комфортно

print(domoticzrequest("http://" + domoticzserver + "/json.htm?type=command&param=udevice&idx=" + IDX + "&nvalue=0&svalue=" + str(TEMP)[:4] + ";" + str(HUM) + ";" + HUM_STAT + "&battery=" + str(battlev)))


Отобразить значение температуры на экране часов в градусах Фарингейта можно записав по handle: 0x0049 значение 0x01 (0xFF — отображение в градусах Цельсия).
Ну и собственно, значение текущего времени можно прочитать по handle: 0x003e. Получим 5 bytes, первые четыре — время в секундах от 00:00:00 01.01.1970, последний байт — временная зона.
Вот пример скрипта для синхронизации часов с от RTC малинки
#!/usr/bin/env python3

import time
from bluepy import btle
import struct

tzlocal = 3

print("LYWSD02 time update")

mac = '3F:59:C8:61:10:CF'
p = btle.Peripheral(mac)

data_r  = p.readCharacteristic(0x003e)

print("Read(0x003e) = ", data_r)

print ("LYWSD02 time: ", time.ctime(struct.unpack('I', data_r[:4])[0]))

print("Sistem   time:", time.ctime())

sis_time = int(time.time())

data_w = struct.pack("<I", sis_time) + bytes([tzlocal])

print ("Write(0x003e) = ", data_w)

p.writeCharacteristic(0x003e,data_w,False)

time.sleep(5)

data_r  = p.readCharacteristic(0x003e)

print("Read(0x003e) = ", data_r)

print ("LYWSD02 time: ", time.ctime(struct.unpack('I', data_r[:4])[0]))

p.disconnect()
Добавить в избранное +67 +110
+
avatar
-1
Где <cut???
+
avatar
+1
Неожиданный предпросмотр.
+
avatar
+2
Для полного «комплекта» — нужно собрать генту для малины.
+
avatar
  • Zynq
  • 03 ноября 2019, 22:38
+7
пропатчить kde2 под freebsd забыли
+
avatar
-1
пропатчить kde2 под freebsd забыли
На самом деле, плазма ставится в бсд на щелк пальцев. А вот собрать генту под малину — это нужно быть «ценителем».
+
avatar
+1
чтобы впринципе собирать генту нужно быть«ценителем» своего времени… уже давно доказано что особо разницы нет в сборке под свое железо.
+
avatar
0
чтобы впринципе собирать генту нужно быть«ценителем» своего времени…
Что не так с генту? Я порт «ейный» через день юзаю.
уже давно доказано что особо разницы нет в сборке под свое железо.
Когда, где и кем?
+
avatar
  • Zynq
  • 04 ноября 2019, 10:40
+1
Windows'ом :)
+
avatar
+2
>>Что не так с генту? Я порт «ейный» через день юзаю.
с ней все впорядке, смысла в ней особого нет.
>>Когда, где и кем?
С момента появления 2го ядра… прирост 1-2%, что в общей работе системы вообще никак не сказывается.
Хочется копатся с ней, пожалуйста, я сказал, что потраченое на генту время не стоит того…
+
avatar
  • IH8
  • 03 ноября 2019, 23:41
+1
Спасибо, улыбаюсь. Pi from scratch )) Двое суток и у вас сбор статистики
+
avatar
  • -wall-
  • 03 ноября 2019, 22:44
+1
А возможно ли прикрутить к Home Assistant?
upd: А, по вашей ссылке как раз о прикручивании к нему и речь.
+
avatar
  • renat85
  • 04 ноября 2019, 20:07
0
Последнее обновление eaphome добавило очень много устройств xiaomi. Посмотрите, в том числе и эти часики.
+
avatar
0
может встречали статью как круглий Ксиаоми прикрутить к Домотич?
+
avatar
  • mr_om
  • 03 ноября 2019, 23:19
0
А точно так же нельзя? Вроде бы протоколы и поток данных тот же. Пробовали?
+
avatar
0
Круглый сильно проще прикручивается, у него температура и влажность открытым текстом передаются.
+
avatar
0
Как приятно, что в OpenHAB весь Xiaomi уже прикрутили. Правда, Bluetooth устройства работают не из коробки, надо jar подложить. А дальше никаких танцев с бубном.
+
avatar
  • standov
  • 04 ноября 2019, 00:54
+1
А расскажите плиз как ble вообще с покрытием и стабильностью, какой свисток лучше. Тоже активно живу с опенхабом давно но ble как-то обходил стороной
+
avatar
0
Тут надо понимать, что надёжнее всего провод. Всё что не связано проводом работает нестабильно. Блютус в этом ряду отличается крайней нестабильностью. У меня была нормальная связь с неподвижными предметами. Подвижные постоянно отваливались и чудили. Так что если вы хотите детектировать людей с браслетами, то готовьтесь к жестким глюкам.
+
avatar
  • standov
  • 04 ноября 2019, 10:10
0
Ну а стационарные? дальность там, с каким свистком?
+
avatar
  • motral
  • 04 ноября 2019, 12:08
0
BLE имеет ряд особенностей, со стабильностью у него все в порядке
+
avatar
  • CyJLTaH
  • 03 ноября 2019, 23:31
0
Неожиданно хорошие часы.
Маловаты по мне, увеличить бы их раза в полтора. Но и эти видно хорошо. в пару комнат уже поставил.
Как данные с часов предаются, я понял. Но там еще и синхронизация часов по времени интернета происходит. Извините старого связиста, но мне то, где это прописано в скриптах, не очевидно.
+
avatar
+9
О, снова моя статейка всплыла. Я, сейчас, кстати, озаботился прикручиванием новых «ClearGrass Temp & RH». И, пожалуй, напишу общую библиотеку под все три версии градусников от Сяоми.
+
avatar
+2
Не знаю можно ли постить ссылки на сторонние ресурсы, но вот мой вариант получения данных для «ClearGrass Temp & RH» в Domoticz:
Ссылка на мой пост на 4PDA
+
avatar
0
Спасибо, то что нужно!
+
avatar
  • Boing
  • 04 ноября 2019, 00:12
+5
Умный дом интересен, но как не хочется ломать голову с этими всеми системами, гуглением, скриптами, в которых я ноль, компиляциями, зависимостей от интернета… Ужас просто… так что такие статьи смотрю и облизываюсь.
Рад, что у других всё повеселее, чем у меня) Спасибо за статью)
+
avatar
  • Bangkok
  • 04 ноября 2019, 02:04
0
Из всех сяомишных мерятелей температуры, какой самый толковый?
В смысле прикрутить к кондею через шлюз, чтобы по температуре включал и отключал кондей?
Был какой то круглый, но работал так себе.
+
avatar
  • Z2K
  • 04 ноября 2019, 07:15
+4
«Из всех сяомишных мерятелей температуры, какой самый толковый? — на сяомишных температура лучше всех. Смотрю все +18 показывают, а сяомишный +24, сразу тепло стало, свитер снял. Все другие раздарил, только им пользуюсь, и экономия газа на отоплении. :))
+
avatar
  • SilentF
  • 04 ноября 2019, 14:55
0
Эм, а разве сам кондей для этого не обладает соответствующими настройками?
+
avatar
  • Bangkok
  • 04 ноября 2019, 15:08
0
Не, старый конд в сьемной хате. Чинить/менять никто не будет
Молотит все время. Некомфортно.
Вопрос не в этом, а в том какой из всех датчиков сяоми наиболее точен/адекватен и не тормозит в системе умный дом со шлюзом? Был круглый, какой то через чур задумчивый оказался.
+
avatar
  • Kheamu
  • 04 ноября 2019, 05:51
+1
Скажите, почему Домотикз, а не Home Assistant?

И да, всё уже придумано ---> github.com/h4/lywsd02
+
avatar
+2
да потому же что и не OpenHab )) личные предпочтения каждого
+
avatar
0
не важно на чем строить дом, все системы с примерно одинаковыми возможностями. у меня тоже Domoticz, но используется только для настройки, управлять намного удобнее планшетами на стенах с Dashticz и пультом в своем телефоне (homekit у apple)
+
avatar
  • PAV
  • 04 ноября 2019, 11:01
0
А можно наоборот? Передать данные на часы?
Было бы классно использовать эти часы, как дополнительный терминал умного дома.
Я уже думал в этом ключе. Пока только по взломанному протоколу передаю на Oregon значения дополнительных датчиков.
+
avatar
  • Zynq
  • 04 ноября 2019, 11:06
0
Орегон на 433?
+
avatar
  • PAV
  • 04 ноября 2019, 18:50
0
да
+
avatar
  • motral
  • 04 ноября 2019, 12:13
0
Хотите сказать распарсенному) ох уж эти хакеры…
Тут очевидно часы = датчик, вряд ли может он принимать
+
avatar
  • PAV
  • 04 ноября 2019, 18:52
0
Ох уж эти умники. Блютус есть, какая ему разница — принимать, отправлять. Экран есть, какой-то контроллер есть, какая разница данные с датчика отображать или с блютуса.
+
avatar
0
Кто-нить видел похожие часы на 433МГц? у меня по дому стоят штук 7, работают без проблем, перекрывают всю площадь и 2 этажа, но вот дизайн:(
ebay.com/itm/113839074614
+
avatar
  • frg
  • 04 ноября 2019, 12:25
+1
А откуда вы взяли вот эту строчку?
uuid = 'EBE0CCC1-7A0A-4B0C-8A1A-6FF2997DA3A6'
И вот эту?
desc = ch.getDescriptors(forUUID=0x2902)[0]

Реверс инженеринг или есть открытое API?
+
avatar
  • 0poK
  • 04 ноября 2019, 14:27
0
Реверс инженеринг, собственно всю работу mkhlbrnv сделал, за что ему большое уважение, ссылка на его статью вначале приведена, там все подробно расписано.
+
avatar
  • kex_
  • 04 ноября 2019, 13:28
0
Небольшой вопрос — этот девайс ( LYWSD02) — его видно в miio (npm install -g miio)?
+
avatar
  • Bosk
  • 05 ноября 2019, 15:37
0
А обратную задачу решить? Чтобы часы показывали время из «умного дома»?
+
avatar
  • 0poK
  • 05 ноября 2019, 23:35
+1
Чтобы не зависеть от китайского сервера, можно синхронизироваться от малинки. Вот пример скрипта:
#!/usr/bin/env python3

import time
from bluepy import btle
import struct

# временная зона:
tzlocal = 3

print("LYWSD02 time update")

mac = '3F:59:C8:61:10:CF'
p = btle.Peripheral(mac)

data_r  = p.readCharacteristic(0x003e)

print("Read(0x003e) = ", data_r)

print ("LYWSD02 time: ", time.ctime(struct.unpack('I', data_r[:4])[0]))

print("Sistem   time:", time.ctime())

sis_time = int(time.time())

data_w = struct.pack("<I", sis_time) + bytes([tzlocal])

print ("Write(0x003e) = ", data_w)

p.writeCharacteristic(0x003e,data_w,False)

time.sleep(5)

data_r  = p.readCharacteristic(0x003e)

print("Read(0x003e) = ", data_r)

print ("LYWSD02 time: ", time.ctime(struct.unpack('I', data_r[:4])[0]))


p.disconnect()

В начале считывается время с часов, затем записывается время с системных часов и через 5с считывается уже обновленное время:
root@raspberrypi:/home/pi/domoticz/devices/lywsd02mmc# python3 lywsd02time.py
LYWSD02 time update
Read(0x003e) =  b'\xb0\xdd\xc1]\x03'
LYWSD02 time:  Tue Nov  5 23:38:08 2019
Sistem   time: Tue Nov  5 23:38:09 2019
Write(0x003e) =  b'\xb1\xdd\xc1]\x03'
Read(0x003e) =  b'\xb6\xdd\xc1]\x03'
LYWSD02 time:  Tue Nov  5 23:38:14 2019
root@raspberrypi:/home/pi/domoticz/devices/lywsd02mmc#

Можно добавить в cron и запускать раз месяц, например.
+
avatar
  • batal
  • 05 ноября 2019, 22:39
0
Спасибо за исходники :)
+
avatar
0
Подожду когда добавят барометр и буду брать. $)
+
avatar
  • Homer
  • 22 декабря 2019, 06:37
0
Вопрос автору статьи: у вас отрицательные температуры нормально отображаются? У меня как 650 градусов:
1576878932000000000 45 0.01
1576878998000000000 45 0.01
1576879052000000000 45 655.34
1576879118000000000 45 655.3
1576879172000000000 45 655.35
температура — правая колонка. это с помощью этой библиотеки получено github.com/h4/lywsd02
С вашим скриптом тоже такие цифры
3 T = 654.38 *C, H = 41 %
+
avatar
  • 0poK
  • 27 декабря 2019, 21:48
+1
Не тестировал при отрицательной температуре. Поэтому ошибочка и не вылезла. В строке: TEMP = struct.unpack('H', data[:2])[0] / 100 нужно unsigned short поменять на short: TEMP = struct.unpack('h', data[:2])[0] / 100