You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

198 lines
6.6 KiB
Python

2 months ago
import asyncio
from bleak import BleakClient, BleakScanner
from bleak.backends.characteristic import BleakGATTCharacteristic
from binascii import unhexlify
from crcmod import crcmod
import threading
from mqtt_client import sub_run
from queue import LifoQueue
import ast
def crc16Add(str_data):
crc16 = crcmod.mkCrcFun(0x18005, rev=True, initCrc=0xFFFF, xorOut=0x0000)
data = str_data.replace(" ", "")
readcrcout = hex(crc16(unhexlify(data))).upper()
str_list = list(readcrcout)
if len(str_list) < 6:
str_list.insert(2, '0'*(6-len(str_list))) # 位数不足补0
crc_data = "".join(str_list)
return crc_data[2:4]+' '+crc_data[4:]
def ten2sixteen(num, length):
"""
十进制转十六进制
:param num: 十进制数字
:param length: 字节长度
:return:
"""
data = str(hex(eval(str(num))))[2:]
data_len = len(data)
if data_len % 2 == 1:
data = '0' + data
data_len += 1
sixteen_str = "00 " * (length - data_len//2) + data[0:2] + ' ' + data[2:]
return sixteen_str.strip()
def cmd2bytearray(cmd_str: str):
verify = crc16Add(cmd_str)
cmd = FRAME_HEAD + ' ' + cmd_str + ' ' + verify + ' ' + FRAME_TAIL
print(cmd)
return bytearray.fromhex(cmd)
def device_capluse():
"""
获取设备气路胶囊信息
:return:
"""
cmd_data = '00 00 00 01 0E 01 06 00 00'
return cmd2bytearray(cmd_data)
def start_play(scent: int, playtime: int):
play_cmd = '00 00 00 01 02 05'
scent_channel = ten2sixteen(scent, 1)
if playtime == 0: # 一直播放
playtime16 = 'FF FF FF FF'
else:
playtime16 = ten2sixteen(playtime, 4)
cmd_data = play_cmd + ' ' + scent_channel + ' ' + playtime16
return cmd2bytearray(cmd_data)
def multiple_play(scent_channels, play_times):
data = ["00"] * 6
for i in range(len(scent_channels)):
data[scent_channels[i] - 1] = "FF" if int(play_times[i]/100) > 255 else ten2sixteen(int(play_times[i]/100), 1)
play_cmd = "00 00 00 01 13 06 " + " ".join(data)
return cmd2bytearray(play_cmd)
def stop_play():
"""
停止播放
:return:
"""
stop_cmd = '00 00 00 01 00 01 00'
return cmd2bytearray(stop_cmd)
def status_check():
"""
检查工作状态
:return:
"""
status_cmd = '00 00 00 01 11 01 00 00 00'
return cmd2bytearray(status_cmd)
def msg_decoder(msg):
list_part = msg[msg.find('['):msg.find(']')+1]
# 使用 ast.literal_eval 安全地解析字符串
data = ast.literal_eval(list_part)
scent_channels = [item.get("channelId") for item in data]
play_times = [item.get("time") for item in data]
return scent_channels, play_times
async def get_services(mac_address: str):
async with BleakClient(mac_address) as client:
svcs = client.services
return svcs
async def find_and_get_services(device_name: str):
# 发现所有设备
devices = await BleakScanner.discover()
# 遍历设备列表,查找目标设备
for device in devices:
if device.name == device_name:
print(f"Found device: {device.name}, MAC Address: {device.address}")
# 获取服务
services = await get_services(device.address)
if services:
last_service = services.services[40]
return {
"device_name": device.name,
"mac_address": device.address,
"last_service_uuid": last_service.uuid,
}
else:
print("No services found for the device.")
return None
# 如果没有找到设备
print(f"Device with name '{device_name}' not found.")
return None
# 监听回调函数,此处为打印消息
def notification_handler(characteristic: BleakGATTCharacteristic, data: bytearray):
# print("rev data:", data)
print("rev data bytes2hex:", ' '.join(['%02x' % b for b in data]))
async def main():
print("starting scan...")
# 基于MAC地址查找设备
information = await find_and_get_services(device_name)
par_device_addr, svc_uuid = information["mac_address"], information["last_service_uuid"]
# 设备的Characteristic UUID
uuid_head = ["6e400003"]
# 设备的Characteristic UUID具备写属性Write
uuid_head_write = ["6e400002"]
uuid = svc_uuid.split('-')[1:]
par_notification_characteristic = "-".join(uuid_head+uuid)
par_write_characteristic = "-".join(uuid_head_write+uuid)
device = await BleakScanner.find_device_by_address(
par_device_addr, cb=dict(use_bdaddr=False)
)
if device is None:
print("could not find device with address '%s'" % par_device_addr)
return
# 事件定义
disconnected_event = asyncio.Event()
# 断开连接事件回调
def disconnected_callback(client):
print("Disconnected callback called!")
disconnected_event.set()
print("connecting to device...")
async with BleakClient(device, disconnected_callback=disconnected_callback) as client:
print("Connected")
await client.start_notify(par_notification_characteristic, notification_handler)
await client.write_gatt_char(par_write_characteristic, device_capluse()) # 获取设备气路胶囊信息
await asyncio.sleep(2.0)
while True:
msg = queue.get()
if msg == "0":
break
scent_channels, play_times = msg_decoder(msg)
print(scent_channels, play_times)
if len(scent_channels) == 1:
await client.write_gatt_char(par_write_characteristic, start_play(scent_channels[0], play_times[0])) # 发送开始播放指令
await asyncio.sleep(play_times[0] / 1000)
else:
await client.write_gatt_char(par_write_characteristic, multiple_play(scent_channels, play_times))
await asyncio.sleep(max(play_times) / 1000)
await client.write_gatt_char(par_write_characteristic, stop_play()) # 发送停止播放指令
await client.write_gatt_char(par_write_characteristic, status_check()) # 检查设备工作状态
await client.stop_notify(par_notification_characteristic)
await client.disconnect()
if __name__ == "__main__":
username = "stark"
password = "12345678"
broker = "47.120.70.16"
port = 1883
sub_topic = "/smell/cmd"
FRAME_HEAD = 'F5'
FRAME_TAIL = '55'
device_name = "scent08d1f90efa9a"
queue = LifoQueue()
t = threading.Thread(target=sub_run, args=(queue, username, password, broker, port, sub_topic),daemon=True)
t.start()
asyncio.run(main())