我的温湿度计是秒秒蓝牙温湿度计(MHO-C401),和小米是一样的。
首先,确保您的树莓派的操作系统(如Raspberry Pi OS)是最新的,并且蓝牙功能是启用的。安装必要的蓝牙相关工具和库:
sudo apt-get update
sudo apt-get upgrade
sudo apt-get install bluetooth bluez libbluetooth-dev libudev-dev
使用Python来编写脚本。首先,安装Python及必要的库,如bluepy
,这是一个Python模块,用于与蓝牙LE设备通信。
sudo apt-get install python3-pip
pip3 install bluepy
编写一个简单的Python脚本来扫描周围的蓝牙设备,以找到秒秒蓝牙温湿度计的MAC地址。下面是一个基本的示例:
from bluepy.btle import Scanner
scanner = Scanner()
devices = scanner.scan(10.0) # 扫描10秒
for dev in devices:
print("Device %s (%s), RSSI=%d dB" % (dev.addr, dev.addrType, dev.rssi))
for (adtype, desc, value) in dev.getScanData():
print(" %s = %s" % (desc, value))
结果:
sudo python3 blue.py
Device 61:ad:7b:8c:03:bd (random), RSSI=-70 dB
Flags = 1a
Manufacturer = 4c000c0e083c5c0762c09c6fc77e15aa223010064b1d62288c18
Device 6d:e0:8f:a6:ac:59 (random), RSSI=-69 dB
Flags = 1a
Manufacturer = 4c0009081302c0a800051b58
Device 63:c6:21:60:56:2c (random), RSSI=-77 dB
Flags = 1a
Manufacturer = 4c000c0e083c5c0762c09c6fc77e15aa223010064b1d62288c18
Device 7a:93:04:3d:5e:cf (random), RSSI=-75 dB
Flags = 1a
Tx Power = 11
Manufacturer = 4c001006471d1cc1f568
Device 79:c9:33:67:86:46 (random), RSSI=-76 dB
Flags = 1a
Manufacturer = 4c0010050318b52fab
Device a4:c1:38:6c:9c:96 (public), RSSI=-85 dB
Flags = 06
16b Service Data = 95fe30588703a7969c6c38c1a408
Complete Local Name = MHO-C401
Device e1:3b:d1:16:0e:e2 (random), RSSI=-68 dB
Manufacturer = 4c0012020002
Device c6:a0:73:ab:c1:ae (random), RSSI=-76 dB
Manufacturer = 4c0012020001
Device 04:e2:29:b3:4a:e7 (public), RSSI=-98 dB
Flags = 06
0x1c = 00
Manufacturer = 290910000004e229b34ae60100e903
这里“a4:c1:38:6c:9c:96”就是我的“秒秒测蓝牙温湿度计”
找到秒秒蓝牙温湿度计的MAC地址后,您需要编写代码来连接设备并读取数据。这可能需要一些尝试和错误,因为您需要知道正确的服务和特性UUID来读取数据。这些信息可能在设备的文档中或者需要通过一些实验来确定。
这里提供一个基本的框架,但请注意,您可能需要调整服务和特性的UUID:
from bluepy.btle import Peripheral
try:
p = Peripheral("a4:c1:38:6c:9c:96", "public")
# 您可能需要根据实际情况更改服务和特性的UUID
services = p.getServices()
for service in services:
print(service)
# 假设您找到了正确的服务和特性
# characteristic = p.getCharacteristics(uuid='特性的UUID')[0]
# if characteristic.supportsRead():
# while True:
# data = characteristic.read()
# print(data)
# time.sleep(1)
finally:
p.disconnect()
结果:
Service <uuid=Generic Access handleStart=1 handleEnd=7>
Service <uuid=Generic Attribute handleStart=8 handleEnd=11>
Service <uuid=Device Information handleStart=12 handleEnd=24>
Service <uuid=Battery Service handleStart=25 handleEnd=28>
Service <uuid=00010203-0405-0607-0809-0a0b0c0d1912 handleStart=29 handleEnd=32>
Service <uuid=ebe0ccb0-7a0a-4b0c-8a1a-6ff2997da3a6 handleStart=33 handleEnd=78>
Service <uuid=fe95 handleStart=79 handleEnd=98>
Service <uuid=00000100-0065-6c62-2e74-6f696d2e696d handleStart=99 handleEnd=107>
这些输出显示了您连接的蓝牙设备提供的服务列表。每个服务由一个UUID(Universally Unique Identifier)标识,而且每个服务都有一个起始和结束句柄。这些服务可能包括设备信息、电池服务和其他特定于设备的服务。
以下是您列出的一些服务的可能含义:
Generic Access (UUID: Generic Access): 这是标准的蓝牙服务,用于提供设备的基本信息,例如名称和可见性。
Generic Attribute (UUID: Generic Attribute): 这个服务负责管理设备上的服务和特性的发现。
Device Information (UUID: Device Information): 这个服务通常包含设备的制造商名称、型号、序列号等信息。
Battery Service (UUID: Battery Service): 提供有关设备电池状态的信息,如电量百分比。
其他服务 (UUID: 特定值): 这些服务可能是特定于设备的,例如用于传输特定数据(例如温湿度数据)的自定义服务。特别是UUID为ebe0ccb0-7a0a-4b0c-8a1a-6ff2997da3a6的服务可能是与您的秒秒蓝牙温湿度计相关的关键服务。
为了获取温湿度数据,您需要进一步探索与UUID ebe0ccb0-7a0a-4b0c-8a1a-6ff2997da3a6
对应的服务。这个服务下可能有特定的特性(Characteristics),这些特性存储了温度和湿度的实际数据。
接下来,您可以使用 getCharacteristics()
方法来列出此服务下的所有特性,并尝试读取这些特性来查找包含温湿度数据的特性。代码示例可能如下:
service_uuid = "ebe0ccb0-7a0a-4b0c-8a1a-6ff2997da3a6"
svc = p.getServiceByUUID(service_uuid)
characteristics = svc.getCharacteristics()
for char in characteristics:
print("Characteristic %s" % char)
print(" - UUID: %s" % char.uuid)
print(" - Handle: %s" % char.getHandle())
print(" - Properties: %s" % char.propertiesToString())
# 尝试读取特性
if char.supportsRead():
try:
value = char.read()
print(" - Value: %s" % value)
except Exception as e:
print("Error reading characteristic: %s" % str(e))
这个脚本将遍历指定服务下的所有特性,并尝试读取它们的值。您需要根据实际情况解析这些值来获取温度和湿度数据。请注意,这可能需要一些试验和错误,因为需要确定哪个特性包含了您需要的数据。
Characteristic Characteristic <ebe0ccb7-7a0a-4b0c-8a1a-6ff2997da3a6>
- UUID: ebe0ccb7-7a0a-4b0c-8a1a-6ff2997da3a6
- Handle: 35
- Properties: READ WRITE
Characteristic Characteristic <ebe0ccb9-7a0a-4b0c-8a1a-6ff2997da3a6>
- UUID: ebe0ccb9-7a0a-4b0c-8a1a-6ff2997da3a6
- Handle: 38
- Properties: READ
Characteristic Characteristic <ebe0ccba-7a0a-4b0c-8a1a-6ff2997da3a6>
- UUID: ebe0ccba-7a0a-4b0c-8a1a-6ff2997da3a6
- Handle: 41
- Properties: READ WRITE
Characteristic Characteristic <ebe0ccbb-7a0a-4b0c-8a1a-6ff2997da3a6>
- UUID: ebe0ccbb-7a0a-4b0c-8a1a-6ff2997da3a6
- Handle: 44
- Properties: READ
Characteristic Characteristic <ebe0ccbc-7a0a-4b0c-8a1a-6ff2997da3a6>
- UUID: ebe0ccbc-7a0a-4b0c-8a1a-6ff2997da3a6
- Handle: 47
- Properties: NOTIFY
Characteristic Characteristic <ebe0ccbe-7a0a-4b0c-8a1a-6ff2997da3a6>
- UUID: ebe0ccbe-7a0a-4b0c-8a1a-6ff2997da3a6
- Handle: 51
- Properties: READ WRITE
Characteristic Characteristic <ebe0ccc1-7a0a-4b0c-8a1a-6ff2997da3a6>
- UUID: ebe0ccc1-7a0a-4b0c-8a1a-6ff2997da3a6
- Handle: 54
- Properties: READ NOTIFY
Characteristic Characteristic <ebe0ccc4-7a0a-4b0c-8a1a-6ff2997da3a6>
- UUID: ebe0ccc4-7a0a-4b0c-8a1a-6ff2997da3a6
- Handle: 58
- Properties: READ
Characteristic Characteristic <ebe0ccc8-7a0a-4b0c-8a1a-6ff2997da3a6>
- UUID: ebe0ccc8-7a0a-4b0c-8a1a-6ff2997da3a6
- Handle: 61
- Properties: WRITE
Characteristic Characteristic <ebe0ccd1-7a0a-4b0c-8a1a-6ff2997da3a6>
- UUID: ebe0ccd1-7a0a-4b0c-8a1a-6ff2997da3a6
- Handle: 64
- Properties: WRITE
Characteristic Characteristic <ebe0ccd4-7a0a-4b0c-8a1a-6ff2997da3a6>
- UUID: ebe0ccd4-7a0a-4b0c-8a1a-6ff2997da3a6
- Handle: 67
- Properties: WRITE
Characteristic Characteristic <ebe0ccd7-7a0a-4b0c-8a1a-6ff2997da3a6>
- UUID: ebe0ccd7-7a0a-4b0c-8a1a-6ff2997da3a6
- Handle: 70
- Properties: READ WRITE
Characteristic Characteristic <ebe0ccd8-7a0a-4b0c-8a1a-6ff2997da3a6>
- UUID: ebe0ccd8-7a0a-4b0c-8a1a-6ff2997da3a6
- Handle: 73
- Properties: WRITE
Characteristic Characteristic <ebe0ccd9-7a0a-4b0c-8a1a-6ff2997da3a6>
- UUID: ebe0ccd9-7a0a-4b0c-8a1a-6ff2997da3a6
- Handle: 76
- Properties: WRITE NOTIFY
每个特性都有一个唯一的UUID、句柄(Handle)以及一组属性(如 READ, WRITE, NOTIFY 等)。
现在,需要确定哪个特性包含温度和湿度数据。基于这些特性的属性,我们可以进行一些推断:
READ 属性的特性可用于读取数据。对于温湿度计来说,温度和湿度数据很可能是可以读取的。
NOTIFY 属性的特性可能用于实时更新数据。当温度或湿度发生变化时,设备可能会通过这些特性发送通知。
WRITE 属性的特性可能用于更改设备的设置或配置。
要找到包含温湿度数据的特性,您可以尝试读取那些具有 READ 属性的特性。例如:
characteristics = svc.getCharacteristics()
for char in characteristics:
if "READ NOTIFY" in char.propertiesToString():
try:
value = char.read()
print(f"Characteristic {char.uuid} Value: {value}")
except Exception as e:
print(f"Error reading characteristic {char.uuid}: {e}")
利用BLE助手进行分析,尝试有“NOTIFY,READ”不同的通知,分析结果。
接收到的数据 52 09 1F 89 0A 注意大小端 数据转为10进制 09 52->2386->即23.86摄氏度 1f->31->湿度31% 0A 89->26.97->电池电压26.97 mv
----------- 数据来源
UUID:ebe0ccc1-7a0a-4b0c-8a1a-6ff-2997da3a6 Properties:READ NOTIFY
import time
from bluepy import btle
from dataclasses import dataclass
mac = "a4:c1:38:6c:9c:96"
@dataclass
class Result:
temperature: float
humidity: int
voltage: float
battery: int = 0
class Measure(btle.DefaultDelegate):
def __init__(self, params):
btle.DefaultDelegate.__init__(self)
self.temperature = None
def handleNotification(self, cHandle, data):
try:
result = Result(0, 0, 0, 0)
temp = int.from_bytes(data[0:2], byteorder='little', signed=True) / 100
humidity = int.from_bytes(data[2:3], byteorder='little')
voltage = int.from_bytes(data[3:5], byteorder='little') / 1000
battery = round((voltage - 2) / (3.261 - 2) * 100, 2)
result.temperature = temp
result.humidity = humidity
result.voltage = voltage
result.battery = battery
print(result)
self.temperature = result.temperature
except Exception as e:
print(e)
class Connect:
def __init__(self):
self.measure = Measure("mijia")
def connect(self):
self.measure.temperature = None
p = btle.Peripheral(mac)
p.writeCharacteristic(0x0038, b'\x01\x00', True)
p.writeCharacteristic(0x0046, b'\xf4\x01\x00', True)
self.measure = Measure("mijia")
p.withDelegate(self.measure)
return p
def getTemperature(self):
return self.measure.temperature
if __name__ == '__main__':
while True:
p = Connect().connect()
time.sleep(1)
if p.waitForNotifications(3000):
p.disconnect()