I'm trying to use a raspberry pi pico w to connect to my computer and act as a small keyboard using a few buttons. I have not yet implemented that part as I have been trying to get it to connect to the computer first.
In my code, I use the aioble module (imported as ble for my convenience) to define services:
- The device information service
- The battery level service
- The HID service (top-level)
The HID service is the only one advertised, and appears when I search for connections with my ble debug app, however when I try to connect the bluetooth connection fails.
import struct
from bluetooth import UUID
import asyncio
_ADV_INTERVAL_MS = 250_000
dis_service = ble.Service(UUID(0x180A))
dis_model_char = ble.Characteristic(dis_service, UUID(0x2A24), read = True)
dis_serial_char = ble.Characteristic(dis_service, UUID(0x2A25), read = True)
dis_firm_char = ble.Characteristic(dis_service, UUID(0x2A26), read = True)
dis_hard_char = ble.Characteristic(dis_service, UUID(0x2A27), read = True)
dis_soft_char = ble.Characteristic(dis_service, UUID(0x2A28), read = True)
dis_manu_char = ble.Characteristic(dis_service, UUID(0x2A29), read = True)
dis_pnp_char = ble.Characteristic(dis_service, UUID(0x2A50), read = True)
bas_service = ble.Service(UUID(0x180F))
bas_levl_char = ble.Characteristic(bas_service, UUID(0x2A19), notify = True)
hid_service = ble.Service(UUID(0x1812))
hid_info_char = ble.Characteristic(hid_service, UUID(0x2A4A), read = True)
hid_irmap_char = ble.Characteristic(hid_service, UUID(0x2A4B), read = True)
hid_control_char = ble.Characteristic(hid_service, UUID(0x2A4C), write = True)
hid_report_char = ble.Characteristic(hid_service, UUID(0x2A4D), notify = True, read = True)
hid_protocol_char = ble.Characteristic(hid_service, UUID(0x2A4E), write = True)
hid_report_desc = ble.Descriptor(hid_report_char, UUID(0x2908), read = True)
ble.register_services(dis_service, bas_service, hid_service)
print("Services registered")
HID_INPUT_REPORT = bytes([ # Report Description: describes what we communicate
0x05, 0x01, # USAGE_PAGE (Generic Desktop)
0x09, 0x06, # USAGE (Keyboard)
0xa1, 0x01, # COLLECTION (Application)
0x85, 0x01, # REPORT_ID (1)
0x75, 0x01, # Report Size (1)
0x95, 0x08, # Report Count (8)
0x05, 0x07, # Usage Page (Key Codes)
0x19, 0xE0, # Usage Minimum (224)
0x29, 0xE7, # Usage Maximum (231)
0x15, 0x00, # Logical Minimum (0)
0x25, 0x01, # Logical Maximum (1)
0x81, 0x02, # Input (Data, Variable, Absolute); Modifier byte
0x95, 0x01, # Report Count (1)
0x75, 0x08, # Report Size (8)
0x81, 0x01, # Input (Constant); Reserved byte
0x95, 0x05, # Report Count (5)
0x75, 0x01, # Report Size (1)
0x05, 0x08, # Usage Page (LEDs)
0x19, 0x01, # Usage Minimum (1)
0x29, 0x05, # Usage Maximum (5)
0x91, 0x02, # Output (Data, Variable, Absolute); LED report
0x95, 0x01, # Report Count (1)
0x75, 0x03, # Report Size (3)
0x91, 0x01, # Output (Constant); LED report padding
0x95, 0x06, # Report Count (6)
0x75, 0x08, # Report Size (8)
0x15, 0x00, # Logical Minimum (0)
0x25, 0x65, # Logical Maximum (101)
0x05, 0x07, # Usage Page (Key Codes)
0x19, 0x00, # Usage Minimum (0)
0x29, 0x65, # Usage Maximum (101)
0x81, 0x00, # Input (Data, Array); Key array (6 bytes)
0xc0 # END_COLLECTION
])
def pack_str(in_str):
return struct.pack(str(len(in_str))+"s", in_str.encode('UTF-8'))
pnp_manufacturer_source = 0x01 # Bluetooth uuid list
pnp_manufacturer_uuid = 0xaf73 # 0xFEB2 for Microsoft, 0xFE61 for Logitech, 0xFD65 for Razer
pnp_product_id = 0x01 # ID 1
pnp_product_version = 0x0123 # Version 1.2.3
battery_level = 100
appearance = 961
name = "Pi Pico Keyboard"
dis_model_char.write(pack_str("1"))
dis_serial_char.write(pack_str("1"))
dis_firm_char.write(pack_str("1"))
dis_hard_char.write(pack_str("1"))
dis_soft_char.write(pack_str("1"))
dis_manu_char.write(pack_str("Homebrew"))
dis_pnp_char.write(struct.pack("<BHHH", pnp_manufacturer_source, pnp_manufacturer_uuid, pnp_product_id, pnp_product_version))
bas_levl_char.write(struct.pack("<B", battery_level))
hid_info_char.write(b"\x01\x01\x00\x02")
hid_irmap_char.write(HID_INPUT_REPORT)
hid_report_desc.write(struct.pack("<BB", 1, 1))
#hid_report_desc.write(struct.pack("<BB", 1, 2))
hid_protocol_char.write(b"\x01")
print("Characteristics written")
async def advertise():
while True:
print("Advertising")
async with await ble.advertise(
_ADV_INTERVAL_MS,
name=name,
services=[UUID(0x1812)],
appearance=appearance,
) as connection:
print("Connection from", connection.device)
await connection.disconnected(timeout_ms=None)
# Run both tasks.
async def main():
t1 = asyncio.create_task(advertise())
await asyncio.gather(t1)
asyncio.run(main())
I've tried various different responses to connections, for example exchanging mtu await connection.exchange_mtu() but this seemed to make it worse.