我如何从小米的BLE温度计中获取数据

背景:作为我的爱好之一,我有一个“智能家居”。 我想要漂亮的设备,但我也想要自由和隐私。 因此,我正在与刺猬家庭助理进行小米uzhik的杂交。

为了保持舒适的环境,我们需要知道家里正在发生什么。 简而言之,需要传感器。 小米有很多不同的东西,但最重要的是我最喜欢电子墨水上的方形温度计。 但是从某种意义上说,他根本不聪明,除了图形界面之外,他根本不提供任何界面-WiFi,BLE,ZigBee都不提供。 但是CR2032电池可以使用几年。 还有一个带有蓝牙的版本,但是有点不优雅-一种厚煎饼。

在春季开始时,宣布了一种新的温度/湿度传感器,该传感器在电子墨水上使用BLE甚至在时钟上也是如此。 我真的不需要手表,但是其他所有东西立即抑制了所有理性的争论,温度计是在一家受欢迎的在线商店中订购的。 它骑着,骑着,终于到了。



传感器已成功添加到MiHome应用程序中(我到处都有英文界面,他们说俄语版本的MiHome存在翻译困难)。 显示当前值和读数变化的历史记录。

但是随着与家庭助理的集成,出现了困难。 温度传感器的现有组件绝不希望从设备中获取数据,并且抱怨数据格式不正确。 好吧,没事可做,我们掏出铁锹开始挖掘。

首先想到的是熟悉BLE协议的设备,但是在评估了文档的大小之后,决定改用流行的poke方法。

壳的第一种方法


首先,打开ubuntu上的终端并运行bluetoothctl。 我们看到以下内容:

[NEW] Controller 00:1A:7D:DA:71:13 fett [default] [NEW] Device 3F:59:C8:80:70:BE LYWSD02 [NEW] Device 4C:65:A8:DC:0D:AF MJ_HT_V1 

MJ_HT_V1是旧的温度传感器,LYWSD02是新的温度传感器。 模型命名格式的差异有些令人震惊。

然后,您需要以某种方式阅读,通常可以从我们那里获得什么样的数据。 他打开了mitemp库的源,该库在Home Assistant中用于从旧传感器接收数据。 在那里,我发现使用了blewrap库,而该库又是两个Python库的包装器,用于处理BLE。 我不需要那么多图层,我们将使用bluepy 。 有文档,内容不胜枚举,我们读写脚本来遍历设备上的所有数据字段。

 from bluepy import btle mac = '3F:59:C8:80:70:BE' p = btle.Peripheral(mac) for s in p.getServices(): print('Service:', s.uuid) for c in s.getCharacteristics(): print('\tCharacteristic:', c.uuid) print('\t\t', c.propertiesToString()) if c.supportsRead(): print('\t\t', c.read()) 

通常,一切都很简单-BLE设备提供了一组服务,每个服务都包含一组特征。 每个特征可以是8种类型之一,对于一种特征,您可以同时指定几种类型。 服务和功能以两种方式标识-十六进制值和UUID形式的地址。 我对使用UUID更为熟悉。

因此,我考虑了这两种传感器的所有规格,仔细研究了一下,然后意识到完全不同制造商的设备还是以小米品牌出售的。 在旧传感器的值中,发现了“ Cleargrass Inc”,而在新传感器中,发现了“ miaomiaoce.com”。 这两个传感器的服务结构和特征也完全不同,并且新传感器的特征列表的长度是其两倍。 然后很明显,您需要编写自己的库以与传感器集成(当然,我首先用谷歌搜索,也许应要求LYWSD02有一些有用的东西,但是我没有提供任何明智的谷歌工具)。

那么如何获得数据呢?


在可用的特征类型中,除了“读取”之外,还有“写入”和“通知”。 写-将数据发送到设备,并通知-接收数据。 同时还有WRITE NOTIFY-设备仅在通过WRITE命令发送所需的字节来订阅数据后才发送数据。

尝试用手做任何事情都没有带来任何结果,第一线的绝望已经达到,但是那一刻,我读了有关基于Nordic Semiconductors芯片的工艺的文章,并将nRF Connect程序放在了我的智能手机上。 在它的帮助下,我能够订购该设备提供的所有服务,保存了答案日志,并开始尝试了解其中的内容。



这些三箭头激活订阅。

旧传感器的一个特点是温度和湿度数据以UTF字符串形式出现,而新传感器则以二进制形式返回所有内容。

订阅通知


要从传感器接收数据,您需要发送订阅请求。 在mitemp库中,为此特性发送了两个字节,但不清楚从何处获取。在这里,我查看了nRF Connect中旧传感器的数据结构,并注意到为特性指定了所需的地址,该特性带有数据,例如描述符。 然后,我再次开始阅读bluepy的文档,并意识到可以轻松地从特征对象获得描述符地址。 剩下的只是使用回调方法编写一个类,该方法将从通知中接收数据。

 class MyDelegate(btle.DefaultDelegate): def handleNotification(self, cHandle, data): print(data) mac_addr = '3F:59:C8:80:70:BE' p = btle.Peripheral(mac_addr) 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) while True: p.waitForNotifications(5.0) 

我们从谷壳中分离出谷物


幸运的是,只有三个特征被标记为WRITE NOTIFY,而数据来自不同的频率以及视觉特征。

第一个请求立即发送了大量数据,然后卡住了。 在这种情况下,第一个字节是一个单调递增的数字。 这似乎是平均值的累积历史。

第二个和第三个是定期发送的,但是仔细观察,我发现其中一个没有改变,在第二个的数据中只有一个字节被更改。 好吧,这就是当前时间(我提醒您,该温度计有一个时钟。任何用于智能家居的自重式设备都应该有一个时钟)。

假设第三个特性是关于温度和湿度的有用数据。 为了证实这一假设,进行了一项物理实验-他走到传感器上,粗略地呼吸了一下。 显示屏上的数据值急剧增加,终端中的字节改变。 Hooray,数据在附近的某处。

资料解析


我通常使用文本数据(以JSON / xml的形式获取HTTP数据,将其放在文件或数据库中),因此我并不真正了解如何处理该任务。 因此,我开始尝试以可以从python生成的不同方式来转换数据。 我在这里编写了这样的转换函数,并开始观察它与传感器屏幕上的数据之间的关系。

 def parse(v): print([x for x in v]) print('{0:#x}'.format(int.from_bytes(data, byteorder='big'))) print('{0:#x}'.format(int.from_bytes(data, byteorder='little'))) 

不同程度的晦涩的线条涌入控制台,但是第三个字节始终是一个数字,该数字与湿度值一致。 为了保真起见,我再次对传感器进行了呼吸-屏幕和第三个字节中的湿度值都改变了!

然后我建议将温度存储在前两个字节中。 为了更改数据,我将传感器转移到浴室的加热毛巾架上。 但是,无论我如何尝试转换结果,所需的数字都无法解决。

在通往成功的道路上


那时,我再次查看了传感器描述,发现里面有一个来自瑞士Sensirion的传感器。 也许值得从此开始,但这不是我们的方法。 在瑞士Sensirion网站上找到了一堆传感器,并找到了相应的数据表。 在数据手册中,除其他外,找到了一个公式,可将通过I2C总线传输的字节转换为数字。

T[°C]=45+175 cdot fracST2161



但是……结果却很奇怪。 -34.66之类的东西,但是我显然更温暖。 从悲伤和悲伤中,我什至打开了传感器,并检查了Swiss Sensirion的传感器是否正确。 事实证明确实如此,但是使用SHTC3索引,它需要一个稍微不同的公式。

T[°C]=45+175 cdot fracST216



但是,完全一样,转换后的数据甚至与真实数据几乎不相似。 在这里,我感到更加难过,打开了Adfruit SHTC3库的源代码,并开始尝试将转换代码从C ++转换为python。 我将所有东西都带到了平板电脑上-原始数据,转换后的结构和结果。

 def handleNotification(self, cHandle, data): temp = data[:2] humid = data[2] unpacked = struct.unpack('H', temp)[0] print(data, unpacked, -45 + 175 * unpacked / 2 ** 19, sep='\t') 

有这样的事情:

 b',\n2' 2604 -44.130821228027344 b'-\n2' 2605 -44.1304874420166 b'+\n2' 2603 -44.131155014038086 b',\n2' 2604 -44.130821228027344 

是的...有点冷...但是,等等,等等,什么是2604? 就是这样,屏幕上26.0度! 为了证实这一假设,他再次将传感器带到电池中,检查值是否一致。

结果,我们得到以下数据转换代码:

 def handleNotification(self, cHandle, data): humid_bytes = data[2] temp_bytes = data[:2] humidity = humid_bytes temperature = struct.unpack('H', temp_bytes)[0] / 100 print(temperature, humidity) 

结语


连接到传感器并搜索正确的转换算法的操作花费了两个晚上。 我几次想放弃所有东西,但与此同时,新的想法出现了,我继续尝试。

现在将数据传输到Home Assistant,然后您需要完成集成代码,并可能将其从bluepy重写为bleak,因为bleak使用async / await,更适合于aiohttp编写的Home Assistant。



参考文献:


Source: https://habr.com/ru/post/zh-CN452558/


All Articles