|
|
|
|
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())
|
|
|
|
|
|