New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WIP: examples/bluetooth: Add BLE HID mouse and keyboard examples. #6559
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
# Implements a BLE HID keyboard | ||
|
||
from micropython import const | ||
import struct | ||
import bluetooth | ||
|
||
|
||
def ble_irq(event, data): | ||
global conn_handle | ||
if event == 1: | ||
print("connect") | ||
conn_handle = data[0] | ||
else: | ||
print("event:", event, data) | ||
|
||
|
||
ble = bluetooth.BLE() | ||
ble.active(1) | ||
ble.irq(ble_irq) | ||
|
||
UUID = bluetooth.UUID | ||
|
||
F_READ = bluetooth.FLAG_READ | ||
F_WRITE = bluetooth.FLAG_WRITE | ||
F_READ_WRITE = bluetooth.FLAG_READ | bluetooth.FLAG_WRITE | ||
F_READ_NOTIFY = bluetooth.FLAG_READ | bluetooth.FLAG_NOTIFY | ||
|
||
ATT_F_READ = 0x01 | ||
ATT_F_WRITE = 0x02 | ||
|
||
hid_service = ( | ||
UUID(0x1812), # Human Interface Device | ||
( | ||
(UUID(0x2A4A), F_READ), # HID information | ||
(UUID(0x2A4B), F_READ), # HID report map | ||
(UUID(0x2A4C), F_WRITE), # HID control point | ||
(UUID(0x2A4D), F_READ_NOTIFY, ((UUID(0x2908), ATT_F_READ),)), # HID report / reference | ||
(UUID(0x2A4D), F_READ_WRITE, ((UUID(0x2908), ATT_F_READ),)), # HID report / reference | ||
(UUID(0x2A4E), F_READ_WRITE), # HID protocol mode | ||
), | ||
) | ||
|
||
# fmt: off | ||
HID_REPORT_MAP = bytes([ | ||
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 | ||
]) | ||
# fmt: on | ||
|
||
# register services | ||
ble.config(gap_name="MP-keyboard") | ||
handles = ble.gatts_register_services((hid_service,)) | ||
print(handles) | ||
h_info, h_hid, _, h_rep, h_d1, _, h_d2, h_proto = handles[0] | ||
|
||
# set initial data | ||
ble.gatts_write(h_info, b"\x01\x01\x00\x02") # HID info: ver=1.1, country=0, flags=normal | ||
ble.gatts_write(h_hid, HID_REPORT_MAP) # HID report map | ||
ble.gatts_write(h_d1, struct.pack("<BB", 1, 1)) # report: id=1, type=input | ||
ble.gatts_write(h_d2, struct.pack("<BB", 1, 2)) # report: id=1, type=output | ||
ble.gatts_write(h_proto, b"\x01") # protocol mode: report | ||
|
||
# advertise | ||
adv = ( | ||
b"\x02\x01\x06" | ||
b"\x03\x03\x12\x18" # complete list of 16-bit service UUIDs: 0x1812 | ||
b"\x03\x19\xc1\x03" # appearance: keyboard | ||
b"\x0c\x09MP-keyboard" # complete local name | ||
) | ||
conn_handle = None | ||
ble.gap_advertise(100_000, adv) | ||
|
||
# once connected use the following to send reports | ||
|
||
|
||
def send_char(char): | ||
if char == " ": | ||
mod = 0 | ||
code = 0x2C | ||
elif ord("a") <= ord(char) <= ord("z"): | ||
mod = 0 | ||
code = 0x04 + ord(char) - ord("a") | ||
elif ord("A") <= ord(char) <= ord("Z"): | ||
mod = 2 | ||
code = 0x04 + ord(char) - ord("A") | ||
else: | ||
assert 0 | ||
ble.gatts_notify(conn_handle, h_rep, struct.pack("8B", mod, 0, code, 0, 0, 0, 0, 0)) | ||
ble.gatts_notify(conn_handle, h_rep, b"\x00\x00\x00\x00\x00\x00\x00\x00") | ||
|
||
|
||
def send_str(st): | ||
for c in st: | ||
send_char(c) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
# Implements a BLE HID mouse | ||
|
||
from micropython import const | ||
import struct | ||
import bluetooth | ||
|
||
|
||
def ble_irq(event, data): | ||
global conn_handle | ||
if event == 1: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Another suggestion would be to start again advertising upon disconnect.
Create global variable conn_handle and call advertise() here (in disconnect) in addition to calling it after registering the service. Thanks for this example, it was exactly what I needed to get myself started! |
||
print("connect") | ||
conn_handle = data[0] | ||
else: | ||
print("event:", event, data) | ||
|
||
|
||
ble = bluetooth.BLE() | ||
ble.active(1) | ||
ble.irq(ble_irq) | ||
|
||
UUID = bluetooth.UUID | ||
|
||
F_READ = bluetooth.FLAG_READ | ||
F_WRITE = bluetooth.FLAG_WRITE | ||
F_READ_WRITE = bluetooth.FLAG_READ | bluetooth.FLAG_WRITE | ||
F_READ_NOTIFY = bluetooth.FLAG_READ | bluetooth.FLAG_NOTIFY | ||
|
||
ATT_F_READ = 0x01 | ||
ATT_F_WRITE = 0x02 | ||
|
||
hid_service = ( | ||
UUID(0x1812), # Human Interface Device | ||
( | ||
(UUID(0x2A4A), F_READ), # HID information | ||
(UUID(0x2A4B), F_READ), # HID report map | ||
(UUID(0x2A4C), F_WRITE), # HID control point | ||
(UUID(0x2A4D), F_READ_NOTIFY, ((UUID(0x2908), ATT_F_READ),)), # HID report / reference | ||
(UUID(0x2A4E), F_READ_WRITE), # HID protocol mode | ||
), | ||
) | ||
|
||
# fmt: off | ||
HID_REPORT_MAP = bytes([ | ||
0x05, 0x01, # Usage Page (Generic Desktop) | ||
0x09, 0x02, # Usage (Mouse) | ||
0xA1, 0x01, # Collection (Application) | ||
0x09, 0x01, # Usage (Pointer) | ||
0xA1, 0x00, # Collection (Physical) | ||
0x85, 0x01, # Report ID (1) | ||
0x95, 0x03, # Report Count (3) | ||
0x75, 0x01, # Report Size (1) | ||
0x05, 0x09, # Usage Page (Buttons) | ||
0x19, 0x01, # Usage Minimum (1) | ||
0x29, 0x03, # Usage Maximum (3) | ||
0x15, 0x00, # Logical Minimum (0) | ||
0x25, 0x01, # Logical Maximum (1) | ||
0x81, 0x02, # Input(Data, Variable, Absolute); 3 button bits | ||
0x95, 0x01, # Report Count(1) | ||
0x75, 0x05, # Report Size(5) | ||
0x81, 0x01, # Input(Constant); 5 bit padding | ||
0x75, 0x08, # Report Size (8) | ||
0x95, 0x02, # Report Count (3) | ||
0x05, 0x01, # Usage Page (Generic Desktop) | ||
0x09, 0x30, # Usage (X) | ||
0x09, 0x31, # Usage (Y) | ||
0x09, 0x38, # Usage (Wheel) | ||
0x15, 0x81, # Logical Minimum (-127) | ||
0x25, 0x7F, # Logical Maximum (127) | ||
0x81, 0x06, # Input(Data, Variable, Relative); 3 position bytes (X,Y,Wheel) | ||
0xC0, # End Collection | ||
0xC0, # End Collection | ||
]) | ||
# fmt: on | ||
|
||
# register services | ||
ble.config(gap_name="MP-mouse") | ||
handles = ble.gatts_register_services((hid_service,)) | ||
print(handles) | ||
h_info, h_hid, _, h_rep, h_d1, h_proto = handles[0] | ||
|
||
# set initial data | ||
ble.gatts_write(h_info, b"\x01\x01\x00\x02") # HID info: ver=1.1, country=0, flags=normal | ||
ble.gatts_write(h_hid, HID_REPORT_MAP) # HID report map | ||
ble.gatts_write(h_d1, struct.pack("<BB", 1, 1)) # report: id=1, type=input | ||
ble.gatts_write(h_proto, b"\x01") # protocol mode: report | ||
|
||
# advertise | ||
adv = ( | ||
b"\x02\x01\x06" | ||
b"\x03\x03\x12\x18" # complete list of 16-bit service UUIDs: 0x1812 | ||
b"\x03\x19\xc2\x03" # appearance: mouse | ||
b"\x09\x09MP-mouse" # complete local name | ||
) | ||
conn_handle = None | ||
ble.gap_advertise(100_000, adv) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. import ble_advertising
_ADV_APPEARANCE_MOUSE = const(962)
adv = ble_advertising.advertising_payload(services=[UUID(0x1812)], appearance=_ADV_APPEARANCE_MOUSE, name="MP-mouse") There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I kind of wanted this example to be raw and just use the So maybe we can have a "raw" example (perhaps just mouse, it's simpler) and then a more "friendly" version which has both keyboard and mouse support (selectable via some option). And then eventually also a proper (async) HID library, but that's for the future. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK, sounds good! |
||
|
||
# once connected use the following to send reports | ||
|
||
|
||
def send_mouse(button_mask, x, y, wheel): | ||
ble.gatts_notify(conn_handle, h_rep, struct.pack("4B", button_mask, x, y, wheel)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if conn_handle is not None |
||
|
||
|
||
def send_click(button): | ||
send_mouse(1 << button, 0, 0, 0) | ||
send_mouse(0, 0, 0, 0) | ||
|
||
|
||
def send_motion(x, y): | ||
send_mouse(0, x, y, 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.