""" The MIT License (MIT) Copyright © 2020 Walkline Wang (https://walkline.wang) Gitee: https://gitee.com/walkline/ESP32-BLE-Remote_Controller """ import ubluetooth as bt import struct from ble.profile.manager import ProfileManager from ble.services import * from ble.characteristics import * from ble.descriptors import * from ble.tools import BLETools from ble.const import BLEConst PACK = struct.pack """ REPORT_MAP_DATA 描述符了一个用户自定义的控制设备 用途为控制音量加减 """ REPORT_MAP_DATA = [ 0x05, 0x0C, # USAGE_PAGE (Consumer Devices) 0x09, 0x01, # USAGE (Consumer Control) 0xA1, 0x01, # COLLECTION (Application) # 定义 Report ID 和逻辑最大最小值 0x85, 0x01, # REPORT_ID (1) 0x15, 0x00, # LOGICAL_MINIMUM (0) 0x25, 0x01, # LOGICAL_MAXIMUM (1) # 定义 Report Count 为 1 bit,即只有 1 个数据段 0x95, 0x01, # REPORT_COUNT (1) # 定义 Report Size 为 1 bit,即每个数据段只能为 0 或 1,因此逻辑值为 0 和 1 0x75, 0x01, # REPORT_SIZE (1) # 定义用途为 音量减,控制位为 0b1 0x09, 0xEA, # USAGE (Volume Down) 0x81, 0x06, # INPUT (Data,Var,Rel) # 定义用途为 音量加,控制位为 0b10 0x09, 0xE9, # USAGE (Volume Up) 0x81, 0x06, # INPUT (Data,Var,Rel) # 补充 7 bit 空位常量凑足 1 byte # 8 bit - (Report Count * Report Size) 0x95, 0x01, # REPORT_COUNT (1) 0x75, 0x07, # REPORT_SIZE (7) 0x81, 0x03, # INPUT (Cnst,Var,Rel) 0xC0 # END_COLLECTION ] class BLERemoteController(object): def __init__(self, device_name="RemoteCon"): self.__ble = bt.BLE() self.__device_name = device_name self.__conn_handle = None self.__write = self.__ble.gatts_write self.__read = self.__ble.gatts_read self.__notify = self.__ble.gatts_notify self.__profile_manager_adv = ProfileManager() self.__profile_manager_resp = ProfileManager() # 定义 HID 设备必须的 3 个服务 self.__profile_manager_adv.add_services( HumanInterfaceDevice().add_characteristics( ReportMap(), Report(), ProtocolMode(), HIDInformation(), HIDControlPoint(), BootMouseInputReport(), ), DeviceInformation().add_characteristics( PNPID(), ), BatteryService().add_characteristics( BatteryLevel(), ), ) # 定义 BLE 设备必须的 2 个服务 self.__profile_manager_resp.add_services( GenericAccess().add_characteristics( DeviceName(), Appearance(), PeripheralPreferredConnectionParameters(), CentralAddressResolution(), ), GenericAttribute().add_characteristics( ServiceChanged(), ), ) self.__ble.active(False) print("activating ble...") self.__ble.active(True) print("ble activated") self.__services = self.__profile_manager_adv.get_services() + self.__profile_manager_resp.get_services() self.__ble.irq(self.__irq) self.__register_services() self.__adv_payload = BLETools.advertising_hid_payload( services=self.__profile_manager_adv.get_services_uuid(), appearance=BLEConst.Appearance.HUMAN_INTERFACE_DEVICE_HID ) self.__resp_payload = BLETools.advertising_resp_payload( services=self.__profile_manager_resp.get_services_uuid(), name=self.__device_name ) self.__advertise() def __register_services(self): ( ( self.__handle_report_map, self.__handle_report, self.__handle_protocol_mode, self.__handle_hid_information, self.__handle_hid_control_point, self.__handle_boot_mouse_input_report, ), ( self.__handle_pnp_id, ), ( self.__handle_battery_level, ), ( self.__handle_device_name, self.__handle_appearance, self.__handle_ppcp, self.__handle_central_address_resolution, ), ( self.__handle_service_changed, ), ) = self.__ble.gatts_register_services(self.__services) print("services registed") self.__setup_generic_access() self.__setup_hid() self.__setup_device_info() self.update_battery_level() def __advertise(self, interval_us=100000): self.__ble.gap_advertise(None) self.__ble.gap_advertise(interval_us, adv_data=self.__adv_payload, resp_data=self.__resp_payload) print("advertising...") def __irq(self, event, data): if event == BLEConst.IRQ.IRQ_CENTRAL_CONNECT: self.__conn_handle, _, addr, = data print("[{}] connected, handle: {}".format(BLETools.decode_mac(addr), self.__conn_handle)) self.__ble.gap_advertise(None) elif event == BLEConst.IRQ.IRQ_CENTRAL_DISCONNECT: self.__conn_handle, _, addr, = data print("[{}] disconnected, handle: {}".format(BLETools.decode_mac(addr), self.__conn_handle)) self.__conn_handle = None self.__advertise() else: print("event: {}, data: {}".format(event, data)) def __setup_hid(self): self.__write(self.__handle_report_map, bytes(REPORT_MAP_DATA)) self.__write(self.__handle_protocol_mode, PACK("<B", 1)) self.__write(self.__handle_hid_information, PACK("<Hbb", 0x0100, 0x00, 0b0011)) def __setup_device_info(self): self.__write(self.__handle_pnp_id, PACK("<BHHH", 1, 0x02E5, 0x01, 0x01)) # 由 Bluetooth SIG 分配的公司代码, 乐鑫信息科技, PID, PVer def __setup_generic_access(self): self.__write(self.__handle_device_name, PACK("<{}s".format(len(self.__device_name)), self.__device_name.encode())) self.__write(self.__handle_appearance, PACK("<h", BLEConst.Appearance.HUMAN_INTERFACE_DEVICE_HID)) self.__write(self.__handle_ppcp, PACK("<4h", 40, 80, 10, 300)) self.__write(self.__handle_central_address_resolution, PACK("<b", 1)) def update_battery_level(self): import random random.seed(random.randint(-2**16, 2**16)) self.__write(self.__handle_battery_level, PACK("<B", int(random.randint(1, 100)))) if self.__conn_handle is not None: self.__notify(self.__conn_handle, self.__handle_battery_level) def send_volume_key(self): if self.__conn_handle is not None: self.__write(self.__handle_report, bytes([0x1, 0b1])) # 0x1 为 REPORT_MAP_DATA 中定义的 Report ID self.__notify(self.__conn_handle, self.__handle_report) # 发送 0b0 释放按键 self.__write(self.__handle_report, bytes([0x1, 0b0])) self.__notify(self.__conn_handle, self.__handle_report) def main(): from machine import Pin def button_click_cb(p): remote_controller.send_volume_key() remote_controller = BLERemoteController() button = Pin(0, Pin.IN, Pin.PULL_UP) button.irq(button_click_cb, Pin.IRQ_RISING) print("button initialized") if __name__ == "__main__": main()