1from __future__ import annotations 2import asyncio 3import grpc 4import grpc.aio 5import logging 6import struct 7import sys 8 9from bumble.device import Device 10from google.protobuf import empty_pb2 # pytype: disable=pyi-error 11 12from pandora_experimental.hid_grpc_aio import HIDServicer 13 14from bumble.pandora import utils 15from pandora_experimental.hid_pb2 import ( 16 ProtocolModeEvent, 17 ReportEvent, 18 PROTOCOL_REPORT_MODE, 19 PROTOCOL_BOOT_MODE, 20 PROTOCOL_UNSUPPORTED_MODE, 21) 22 23from bumble.core import ( 24 BT_BR_EDR_TRANSPORT, 25 BT_L2CAP_PROTOCOL_ID, 26 BT_HUMAN_INTERFACE_DEVICE_SERVICE, 27 BT_HIDP_PROTOCOL_ID, 28 UUID, 29 ProtocolError, 30) 31 32from bumble.hci import ( 33 HCI_StatusError, 34 HCI_CONNECTION_ALREADY_EXISTS_ERROR, 35 HCI_PAGE_TIMEOUT_ERROR, 36) 37from bumble.hid import ( 38 Device as HID_Device, 39 HID_CONTROL_PSM, 40 HID_INTERRUPT_PSM, 41 Message, 42) 43 44from bumble.sdp import ( 45 Client as SDP_Client, 46 DataElement, 47 ServiceAttribute, 48 SDP_PUBLIC_BROWSE_ROOT, 49 SDP_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID, 50 SDP_SERVICE_CLASS_ID_LIST_ATTRIBUTE_ID, 51 SDP_BLUETOOTH_PROFILE_DESCRIPTOR_LIST_ATTRIBUTE_ID, 52 SDP_ALL_ATTRIBUTES_RANGE, 53 SDP_LANGUAGE_BASE_ATTRIBUTE_ID_LIST_ATTRIBUTE_ID, 54 SDP_ADDITIONAL_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID, 55 SDP_SERVICE_RECORD_HANDLE_ATTRIBUTE_ID, 56 SDP_BROWSE_GROUP_LIST_ATTRIBUTE_ID, 57) 58from bumble.utils import AsyncRunner 59 60# ----------------------------------------------------------------------------- 61# SDP attributes for Bluetooth HID devices 62SDP_HID_SERVICE_NAME_ATTRIBUTE_ID = 0x0100 63SDP_HID_SERVICE_DESCRIPTION_ATTRIBUTE_ID = 0x0101 64SDP_HID_PROVIDER_NAME_ATTRIBUTE_ID = 0x0102 65SDP_HID_DEVICE_RELEASE_NUMBER_ATTRIBUTE_ID = 0x0200 # [DEPRECATED] 66SDP_HID_PARSER_VERSION_ATTRIBUTE_ID = 0x0201 67SDP_HID_DEVICE_SUBCLASS_ATTRIBUTE_ID = 0x0202 68SDP_HID_COUNTRY_CODE_ATTRIBUTE_ID = 0x0203 69SDP_HID_VIRTUAL_CABLE_ATTRIBUTE_ID = 0x0204 70SDP_HID_RECONNECT_INITIATE_ATTRIBUTE_ID = 0x0205 71SDP_HID_DESCRIPTOR_LIST_ATTRIBUTE_ID = 0x0206 72SDP_HID_LANGID_BASE_LIST_ATTRIBUTE_ID = 0x0207 73SDP_HID_SDP_DISABLE_ATTRIBUTE_ID = 0x0208 # [DEPRECATED] 74SDP_HID_BATTERY_POWER_ATTRIBUTE_ID = 0x0209 75SDP_HID_REMOTE_WAKE_ATTRIBUTE_ID = 0x020A 76SDP_HID_PROFILE_VERSION_ATTRIBUTE_ID = 0x020B # DEPRECATED] 77SDP_HID_SUPERVISION_TIMEOUT_ATTRIBUTE_ID = 0x020C 78SDP_HID_NORMALLY_CONNECTABLE_ATTRIBUTE_ID = 0x020D 79SDP_HID_BOOT_DEVICE_ATTRIBUTE_ID = 0x020E 80SDP_HID_SSR_HOST_MAX_LATENCY_ATTRIBUTE_ID = 0x020F 81SDP_HID_SSR_HOST_MIN_TIMEOUT_ATTRIBUTE_ID = 0x0210 82 83# Refer to HID profile specification v1.1.1, "5.3 Service Discovery Protocol (SDP)" for details 84# HID SDP attribute values 85LANGUAGE = 0x656E # 0x656E uint16 “en” (English) 86ENCODING = 0x6A # 0x006A uint16 UTF-8 encoding 87PRIMARY_LANGUAGE_BASE_ID = 0x100 # 0x0100 uint16 PrimaryLanguageBaseID 88VERSION_NUMBER = 0x0101 # 0x0101 uint16 version number (v1.1) 89SERVICE_NAME = b'Bumble HID' 90SERVICE_DESCRIPTION = b'Bumble' 91PROVIDER_NAME = b'Bumble' 92HID_PARSER_VERSION = 0x0111 # uint16 0x0111 (v1.1.1) 93HID_DEVICE_SUBCLASS = 0xC0 # Combo keyboard/pointing device 94HID_COUNTRY_CODE = 0x21 # 0x21 Uint8, USA 95HID_VIRTUAL_CABLE = True # Virtual cable enabled 96HID_RECONNECT_INITIATE = True # Reconnect initiate enabled 97REPORT_DESCRIPTOR_TYPE = 0x22 # 0x22 Type = Report Descriptor 98HID_LANGID_BASE_LANGUAGE = 0x0409 # 0x0409 Language = English (United States) 99HID_LANGID_BASE_BLUETOOTH_STRING_OFFSET = 0x100 # 0x0100 Default 100HID_BATTERY_POWER = True # Battery power enabled 101HID_REMOTE_WAKE = True # Remote wake enabled 102HID_SUPERVISION_TIMEOUT = 0xC80 # uint16 0xC80 (2s) 103HID_NORMALLY_CONNECTABLE = True # Normally connectable enabled 104HID_BOOT_DEVICE = True # Boot device support enabled 105HID_SSR_HOST_MAX_LATENCY = 0x640 # uint16 0x640 (1s) 106HID_SSR_HOST_MIN_TIMEOUT = 0xC80 # uint16 0xC80 (2s) 107HID_REPORT_MAP = bytes( # Text String, 50 Octet Report Descriptor 108 # pylint: disable=line-too-long 109 [ 110 0x05, 111 0x01, # Usage Page (Generic Desktop Ctrls) 112 0x09, 113 0x06, # Usage (Keyboard) 114 0xA1, 115 0x01, # Collection (Application) 116 0x85, 117 0x01, # . Report ID (1) 118 0x05, 119 0x07, # . Usage Page (Kbrd/Keypad) 120 0x19, 121 0xE0, # . Usage Minimum (0xE0) 122 0x29, 123 0xE7, # . Usage Maximum (0xE7) 124 0x15, 125 0x00, # . Logical Minimum (0) 126 0x25, 127 0x01, # . Logical Maximum (1) 128 0x75, 129 0x01, # . Report Size (1) 130 0x95, 131 0x08, # . Report Count (8) 132 0x81, 133 0x02, # . Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 134 0x95, 135 0x01, # . Report Count (1) 136 0x75, 137 0x08, # . Report Size (8) 138 0x81, 139 0x03, # . Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 140 0x95, 141 0x05, # . Report Count (5) 142 0x75, 143 0x01, # . Report Size (1) 144 0x05, 145 0x08, # . Usage Page (LEDs) 146 0x19, 147 0x01, # . Usage Minimum (Num Lock) 148 0x29, 149 0x05, # . Usage Maximum (Kana) 150 0x91, 151 0x02, # . Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) 152 0x95, 153 0x01, # . Report Count (1) 154 0x75, 155 0x03, # . Report Size (3) 156 0x91, 157 0x03, # . Output (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) 158 0x95, 159 0x06, # . Report Count (6) 160 0x75, 161 0x08, # . Report Size (8) 162 0x15, 163 0x00, # . Logical Minimum (0) 164 0x25, 165 0x65, # . Logical Maximum (101) 166 0x05, 167 0x07, # . Usage Page (Kbrd/Keypad) 168 0x19, 169 0x00, # . Usage Minimum (0x00) 170 0x29, 171 0x65, # . Usage Maximum (0x65) 172 0x81, 173 0x00, # . Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) 174 0xC0, # End Collection 175 0x05, 176 0x01, # Usage Page (Generic Desktop Ctrls) 177 0x09, 178 0x02, # Usage (Mouse) 179 0xA1, 180 0x01, # Collection (Application) 181 0x85, 182 0x02, # . Report ID (2) 183 0x09, 184 0x01, # . Usage (Pointer) 185 0xA1, 186 0x00, # . Collection (Physical) 187 0x05, 188 0x09, # . Usage Page (Button) 189 0x19, 190 0x01, # . Usage Minimum (0x01) 191 0x29, 192 0x03, # . Usage Maximum (0x03) 193 0x15, 194 0x00, # . Logical Minimum (0) 195 0x25, 196 0x01, # . Logical Maximum (1) 197 0x95, 198 0x03, # . Report Count (3) 199 0x75, 200 0x01, # . Report Size (1) 201 0x81, 202 0x02, # . Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 203 0x95, 204 0x01, # . Report Count (1) 205 0x75, 206 0x05, # . Report Size (5) 207 0x81, 208 0x03, # . Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 209 0x05, 210 0x01, # . Usage Page (Generic Desktop Ctrls) 211 0x09, 212 0x30, # . Usage (X) 213 0x09, 214 0x31, # . Usage (Y) 215 0x15, 216 0x81, # . Logical Minimum (-127) 217 0x25, 218 0x7F, # . Logical Maximum (127) 219 0x75, 220 0x08, # . Report Size (8) 221 0x95, 222 0x02, # . Report Count (2) 223 0x81, 224 0x06, # . Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position) 225 0xC0, # . End Collection (Physical) 226 0xC0, # End Collection (Application) 227 ]) 228 229# Default protocol mode set to report protocol 230protocol_mode = Message.ProtocolMode.REPORT_PROTOCOL 231 232from bumble.core import AdvertisingData 233from bumble.device import Device, Connection, Peer 234from bumble.gatt import ( 235 Descriptor, 236 Service, 237 Characteristic, 238 CharacteristicValue, 239 GATT_DEVICE_INFORMATION_SERVICE, 240 GATT_HUMAN_INTERFACE_DEVICE_SERVICE, 241 GATT_BATTERY_SERVICE, 242 GATT_BATTERY_LEVEL_CHARACTERISTIC, 243 GATT_MANUFACTURER_NAME_STRING_CHARACTERISTIC, 244 GATT_REPORT_CHARACTERISTIC, 245 GATT_REPORT_MAP_CHARACTERISTIC, 246 GATT_PROTOCOL_MODE_CHARACTERISTIC, 247 GATT_HID_INFORMATION_CHARACTERISTIC, 248 GATT_HID_CONTROL_POINT_CHARACTERISTIC, 249 GATT_REPORT_REFERENCE_DESCRIPTOR, 250) 251 252# ----------------------------------------------------------------------------- 253 254# Protocol Modes (HID Specification V1.1.1 Section 2.1.2) 255HID_BOOT_PROTOCOL = 0x00 256HID_REPORT_PROTOCOL = 0x01 257 258# Report Types (HID Specification V1.1.1 Section 2.1.1) 259HID_INPUT_REPORT = 0x01 260HID_OUTPUT_REPORT = 0x02 261HID_FEATURE_REPORT = 0x03 262 263# Report Map 264HID_KEYBOARD_REPORT_MAP = bytes( 265 # pylint: disable=line-too-long 266 [ 267 0x05, 268 0x01, # Usage Page (Generic Desktop Controls) 269 0x09, 270 0x06, # Usage (Keyboard) 271 0xA1, 272 0x01, # Collection (Application) 273 0x85, 274 0x01, # . Report ID (1) 275 0x05, 276 0x07, # . Usage Page (Keyboard/Keypad) 277 0x19, 278 0xE0, # . Usage Minimum (0xE0) 279 0x29, 280 0xE7, # . Usage Maximum (0xE7) 281 0x15, 282 0x00, # . Logical Minimum (0) 283 0x25, 284 0x01, # . Logical Maximum (1) 285 0x75, 286 0x01, # . Report Size (1) 287 0x95, 288 0x08, # . Report Count (8) 289 0x81, 290 0x02, # . Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 291 0x95, 292 0x01, # . Report Count (1) 293 0x75, 294 0x08, # . Report Size (8) 295 0x81, 296 0x01, # . Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) 297 0x95, 298 0x06, # . Report Count (6) 299 0x75, 300 0x08, # . Report Size (8) 301 0x15, 302 0x00, # . Logical Minimum (0x00) 303 0x25, 304 0x94, # . Logical Maximum (0x94) 305 0x05, 306 0x07, # . Usage Page (Keyboard/Keypad) 307 0x19, 308 0x00, # . Usage Minimum (0x00) 309 0x29, 310 0x94, # . Usage Maximum (0x94) 311 0x81, 312 0x00, # . Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) 313 0x95, 314 0x05, # . Report Count (5) 315 0x75, 316 0x01, # . Report Size (1) 317 0x05, 318 0x08, # . Usage Page (LEDs) 319 0x19, 320 0x01, # . Usage Minimum (Num Lock) 321 0x29, 322 0x05, # . Usage Maximum (Kana) 323 0x91, 324 0x02, # . Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) 325 0x95, 326 0x01, # . Report Count (1) 327 0x75, 328 0x03, # . Report Size (3) 329 0x91, 330 0x01, # . Output (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) 331 0xC0, # End Collection 332 ]) 333 334 335# ----------------------------------------------------------------------------- 336# pylint: disable=invalid-overridden-method 337class ServerListener(Device.Listener, Connection.Listener): 338 339 def __init__(self, device): 340 self.device = device 341 342 @AsyncRunner.run_in_task() 343 async def on_connection(self, connection): 344 logging.info(f'=== Connected to {connection}') 345 connection.listener = self 346 347 @AsyncRunner.run_in_task() 348 async def on_disconnection(self, reason): 349 logging.info(f'### Disconnected, reason={reason}') 350 351 352# ----------------------------------------------------------------------------- 353def on_hid_control_point_write(_connection, value): 354 logging.info(f'Control Point Write: {value}') 355 356 357# ----------------------------------------------------------------------------- 358def sdp_records(): 359 service_record_handle = 0x00010006 360 return { 361 service_record_handle: [ 362 ServiceAttribute( 363 SDP_SERVICE_RECORD_HANDLE_ATTRIBUTE_ID, 364 DataElement.unsigned_integer_32(service_record_handle), 365 ), 366 ServiceAttribute( 367 SDP_BROWSE_GROUP_LIST_ATTRIBUTE_ID, 368 DataElement.sequence([DataElement.uuid(SDP_PUBLIC_BROWSE_ROOT)]), 369 ), 370 ServiceAttribute( 371 SDP_SERVICE_CLASS_ID_LIST_ATTRIBUTE_ID, 372 DataElement.sequence([DataElement.uuid(BT_HUMAN_INTERFACE_DEVICE_SERVICE)]), 373 ), 374 ServiceAttribute( 375 SDP_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID, 376 DataElement.sequence([ 377 DataElement.sequence([ 378 DataElement.uuid(BT_L2CAP_PROTOCOL_ID), 379 DataElement.unsigned_integer_16(HID_CONTROL_PSM), 380 ]), 381 DataElement.sequence([ 382 DataElement.uuid(BT_HIDP_PROTOCOL_ID), 383 ]), 384 ]), 385 ), 386 ServiceAttribute( 387 SDP_LANGUAGE_BASE_ATTRIBUTE_ID_LIST_ATTRIBUTE_ID, 388 DataElement.sequence([ 389 DataElement.unsigned_integer_16(LANGUAGE), 390 DataElement.unsigned_integer_16(ENCODING), 391 DataElement.unsigned_integer_16(PRIMARY_LANGUAGE_BASE_ID), 392 ]), 393 ), 394 ServiceAttribute( 395 SDP_BLUETOOTH_PROFILE_DESCRIPTOR_LIST_ATTRIBUTE_ID, 396 DataElement.sequence([ 397 DataElement.sequence([ 398 DataElement.uuid(BT_HUMAN_INTERFACE_DEVICE_SERVICE), 399 DataElement.unsigned_integer_16(VERSION_NUMBER), 400 ]), 401 ]), 402 ), 403 ServiceAttribute( 404 SDP_ADDITIONAL_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID, 405 DataElement.sequence([ 406 DataElement.sequence([ 407 DataElement.sequence([ 408 DataElement.uuid(BT_L2CAP_PROTOCOL_ID), 409 DataElement.unsigned_integer_16(HID_INTERRUPT_PSM), 410 ]), 411 DataElement.sequence([ 412 DataElement.uuid(BT_HIDP_PROTOCOL_ID), 413 ]), 414 ]), 415 ]), 416 ), 417 ServiceAttribute( 418 SDP_HID_SERVICE_NAME_ATTRIBUTE_ID, 419 DataElement(DataElement.TEXT_STRING, SERVICE_NAME), 420 ), 421 ServiceAttribute( 422 SDP_HID_SERVICE_DESCRIPTION_ATTRIBUTE_ID, 423 DataElement(DataElement.TEXT_STRING, SERVICE_DESCRIPTION), 424 ), 425 ServiceAttribute( 426 SDP_HID_PROVIDER_NAME_ATTRIBUTE_ID, 427 DataElement(DataElement.TEXT_STRING, PROVIDER_NAME), 428 ), 429 ServiceAttribute( 430 SDP_HID_PARSER_VERSION_ATTRIBUTE_ID, 431 DataElement.unsigned_integer_32(HID_PARSER_VERSION), 432 ), 433 ServiceAttribute( 434 SDP_HID_DEVICE_SUBCLASS_ATTRIBUTE_ID, 435 DataElement.unsigned_integer_32(HID_DEVICE_SUBCLASS), 436 ), 437 ServiceAttribute( 438 SDP_HID_COUNTRY_CODE_ATTRIBUTE_ID, 439 DataElement.unsigned_integer_32(HID_COUNTRY_CODE), 440 ), 441 ServiceAttribute( 442 SDP_HID_VIRTUAL_CABLE_ATTRIBUTE_ID, 443 DataElement.boolean(HID_VIRTUAL_CABLE), 444 ), 445 ServiceAttribute( 446 SDP_HID_RECONNECT_INITIATE_ATTRIBUTE_ID, 447 DataElement.boolean(HID_RECONNECT_INITIATE), 448 ), 449 ServiceAttribute( 450 SDP_HID_DESCRIPTOR_LIST_ATTRIBUTE_ID, 451 DataElement.sequence([ 452 DataElement.sequence([ 453 DataElement.unsigned_integer_16(REPORT_DESCRIPTOR_TYPE), 454 DataElement(DataElement.TEXT_STRING, HID_REPORT_MAP), 455 ]), 456 ]), 457 ), 458 ServiceAttribute( 459 SDP_HID_LANGID_BASE_LIST_ATTRIBUTE_ID, 460 DataElement.sequence([ 461 DataElement.sequence([ 462 DataElement.unsigned_integer_16(HID_LANGID_BASE_LANGUAGE), 463 DataElement.unsigned_integer_16(HID_LANGID_BASE_BLUETOOTH_STRING_OFFSET), 464 ]), 465 ]), 466 ), 467 ServiceAttribute( 468 SDP_HID_BATTERY_POWER_ATTRIBUTE_ID, 469 DataElement.boolean(HID_BATTERY_POWER), 470 ), 471 ServiceAttribute( 472 SDP_HID_REMOTE_WAKE_ATTRIBUTE_ID, 473 DataElement.boolean(HID_REMOTE_WAKE), 474 ), 475 ServiceAttribute( 476 SDP_HID_SUPERVISION_TIMEOUT_ATTRIBUTE_ID, 477 DataElement.unsigned_integer_16(HID_SUPERVISION_TIMEOUT), 478 ), 479 ServiceAttribute( 480 SDP_HID_NORMALLY_CONNECTABLE_ATTRIBUTE_ID, 481 DataElement.boolean(HID_NORMALLY_CONNECTABLE), 482 ), 483 ServiceAttribute( 484 SDP_HID_BOOT_DEVICE_ATTRIBUTE_ID, 485 DataElement.boolean(HID_BOOT_DEVICE), 486 ), 487 ServiceAttribute( 488 SDP_HID_SSR_HOST_MAX_LATENCY_ATTRIBUTE_ID, 489 DataElement.unsigned_integer_16(HID_SSR_HOST_MAX_LATENCY), 490 ), 491 ServiceAttribute( 492 SDP_HID_SSR_HOST_MIN_TIMEOUT_ATTRIBUTE_ID, 493 DataElement.unsigned_integer_16(HID_SSR_HOST_MIN_TIMEOUT), 494 ), 495 ] 496 } 497 498 499# ----------------------------------------------------------------------------- 500def hogp_device(device): 501 # Create an 'input report' characteristic to send keyboard reports to the host 502 input_report_kb_characteristic = Characteristic( 503 GATT_REPORT_CHARACTERISTIC, 504 Characteristic.Properties.READ | Characteristic.Properties.WRITE | Characteristic.Properties.NOTIFY, 505 Characteristic.READABLE | Characteristic.WRITEABLE, 506 bytes([0, 0, 0, 0, 0, 0, 0, 0, 0]), 507 [Descriptor( 508 GATT_REPORT_REFERENCE_DESCRIPTOR, 509 Descriptor.READABLE, 510 bytes([0x01, HID_INPUT_REPORT]), 511 )], 512 ) 513 # Create an 'input report' characteristic to send mouse reports to the host 514 input_report_mouse_characteristic = Characteristic( 515 GATT_REPORT_CHARACTERISTIC, 516 Characteristic.Properties.READ | Characteristic.Properties.WRITE | Characteristic.Properties.NOTIFY, 517 Characteristic.READABLE | Characteristic.WRITEABLE, 518 bytes([0, 0, 0, 0]), 519 [Descriptor( 520 GATT_REPORT_REFERENCE_DESCRIPTOR, 521 Descriptor.READABLE, 522 bytes([0x02, HID_INPUT_REPORT]), 523 )], 524 ) 525 # Create an 'output report' characteristic to receive keyboard reports from the host 526 output_report_characteristic = Characteristic( 527 GATT_REPORT_CHARACTERISTIC, 528 Characteristic.Properties.READ | Characteristic.Properties.WRITE | Characteristic.WRITE_WITHOUT_RESPONSE, 529 Characteristic.READABLE | Characteristic.WRITEABLE, 530 bytes([0]), 531 [Descriptor( 532 GATT_REPORT_REFERENCE_DESCRIPTOR, 533 Descriptor.READABLE, 534 bytes([0x01, HID_OUTPUT_REPORT]), 535 )], 536 ) 537 538 # Add the services to the GATT sever 539 device.add_services([ 540 Service( 541 GATT_DEVICE_INFORMATION_SERVICE, 542 [ 543 Characteristic( 544 GATT_MANUFACTURER_NAME_STRING_CHARACTERISTIC, 545 Characteristic.Properties.READ, 546 Characteristic.READABLE, 547 'Bumble', 548 ) 549 ], 550 ), 551 Service( 552 GATT_HUMAN_INTERFACE_DEVICE_SERVICE, 553 [ 554 Characteristic( 555 GATT_PROTOCOL_MODE_CHARACTERISTIC, 556 Characteristic.Properties.READ, 557 Characteristic.READABLE, 558 bytes([HID_REPORT_PROTOCOL]), 559 ), 560 Characteristic( 561 GATT_HID_INFORMATION_CHARACTERISTIC, 562 Characteristic.Properties.READ, 563 Characteristic.READABLE, 564 # bcdHID=1.1, bCountryCode=0x00, 565 # Flags=RemoteWake|NormallyConnectable 566 bytes([0x11, 0x01, 0x00, 0x03]), 567 ), 568 Characteristic( 569 GATT_HID_CONTROL_POINT_CHARACTERISTIC, 570 Characteristic.WRITE_WITHOUT_RESPONSE, 571 Characteristic.WRITEABLE, 572 CharacteristicValue(write=on_hid_control_point_write), 573 ), 574 Characteristic( 575 GATT_REPORT_MAP_CHARACTERISTIC, 576 Characteristic.Properties.READ, 577 Characteristic.READABLE, 578 HID_KEYBOARD_REPORT_MAP, 579 ), 580 input_report_kb_characteristic, 581 input_report_mouse_characteristic, 582 output_report_characteristic, 583 ], 584 ), 585 Service( 586 GATT_BATTERY_SERVICE, 587 [ 588 Characteristic( 589 GATT_BATTERY_LEVEL_CHARACTERISTIC, 590 Characteristic.Properties.READ, 591 Characteristic.READABLE, 592 bytes([100]), 593 ) 594 ], 595 ), 596 ]) 597 598 # Debug print 599 for attribute in device.gatt_server.attributes: 600 logging.info(attribute) 601 602 # Set the advertising data 603 device.advertising_data = bytes( 604 AdvertisingData([ 605 ( 606 AdvertisingData.COMPLETE_LOCAL_NAME, 607 bytes('Bumble Keyboard', 'utf-8'), 608 ), 609 ( 610 AdvertisingData.INCOMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS, 611 bytes(GATT_HUMAN_INTERFACE_DEVICE_SERVICE), 612 ), 613 (AdvertisingData.APPEARANCE, struct.pack('<H', 0x03C1)), 614 (AdvertisingData.FLAGS, bytes([0x05])), 615 ])) 616 617 # Attach a listener 618 device.listener = ServerListener(device) 619 620 621async def handle_virtual_cable_unplug(): 622 hid_host_bd_addr = str(hid_device.remote_device_bd_address) 623 await hid_device.disconnect_interrupt_channel() 624 await hid_device.disconnect_control_channel() 625 await hid_device.device.keystore.delete(hid_host_bd_addr) # type: ignore 626 connection = hid_device.connection 627 if connection is not None: 628 await connection.disconnect() 629 630 631def on_get_report_cb(report_id: int, report_type: int, buffer_size: int): 632 retValue = hid_device.GetSetStatus() 633 logging.info("GET_REPORT report_id: " + str(report_id) + "report_type: " + str(report_type) + "buffer_size:" + 634 str(buffer_size)) 635 if report_type == Message.ReportType.INPUT_REPORT: 636 if report_id == 1: 637 retValue.data = bytearray([0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) 638 retValue.status = hid_device.GetSetReturn.SUCCESS 639 elif report_id == 2: 640 retValue.data = bytearray([0x02, 0x00, 0x00, 0x00]) 641 retValue.status = hid_device.GetSetReturn.SUCCESS 642 else: 643 retValue.status = hid_device.GetSetReturn.REPORT_ID_NOT_FOUND 644 645 return retValue 646 647 648def on_set_report_cb(report_id: int, report_type: int, report_size: int, data: bytes): 649 retValue = hid_device.GetSetStatus() 650 logging.info("SET_REPORT report_id: " + str(report_id) + "report_type: " + str(report_type) + "report_size " + 651 str(report_size) + "data:" + str(data)) 652 653 report = ReportEvent() 654 report.report_type = report_type 655 report.report_id = report_id 656 report.report_data = str(data.hex()) 657 658 if hid_report_queue: 659 hid_report_queue.put_nowait(report) 660 661 if report_type == Message.ReportType.FEATURE_REPORT: 662 retValue.status = hid_device.GetSetReturn.ERR_INVALID_PARAMETER 663 elif report_type == Message.ReportType.INPUT_REPORT: 664 if report_id == 1 and report_size != 9: 665 retValue.status = hid_device.GetSetReturn.ERR_INVALID_PARAMETER 666 elif report_id == 2 and report_size != 4: 667 retValue.status = hid_device.GetSetReturn.ERR_INVALID_PARAMETER 668 elif report_id == 3: 669 retValue.status = hid_device.GetSetReturn.REPORT_ID_NOT_FOUND 670 else: 671 retValue.status = hid_device.GetSetReturn.SUCCESS 672 else: 673 retValue.status = hid_device.GetSetReturn.SUCCESS 674 675 return retValue 676 677 678def on_get_protocol_cb(): 679 retValue = hid_device.GetSetStatus() 680 retValue.data = protocol_mode.to_bytes(length=1, byteorder=sys.byteorder) 681 retValue.status = hid_device.GetSetReturn.SUCCESS 682 return retValue 683 684 685def on_set_protocol_cb(protocol: int): 686 retValue = hid_device.GetSetStatus() 687 # We do not support SET_PROTOCOL. 688 logging.info(f"SET_PROTOCOL mode: {protocol}") 689 mode = ProtocolModeEvent() 690 if protocol == PROTOCOL_REPORT_MODE: 691 mode.protocol_mode = PROTOCOL_REPORT_MODE 692 elif protocol == PROTOCOL_BOOT_MODE: 693 mode.protocol_mode = PROTOCOL_BOOT_MODE 694 else: 695 mode.protocol_mode = PROTOCOL_UNSUPPORTED_MODE 696 hid_protoMode_queue.put_nowait(mode) 697 retValue.status = hid_device.GetSetReturn.ERR_UNSUPPORTED_REQUEST 698 return retValue 699 700 701def on_virtual_cable_unplug_cb(): 702 logging.info('Received Virtual Cable Unplug') 703 asyncio.create_task(handle_virtual_cable_unplug()) 704 705 706hid_protoMode_queue = None 707hid_report_queue = None 708 709# This class implements the Hid Pandora interface. 710class HIDService(HIDServicer): 711 712 hid_device = None 713 714 def __init__(self, device: Device) -> None: 715 super().__init__() 716 self.device = device 717 self.device.sdp_service_records.update(sdp_records()) 718 self.event_queue: Optional[asyncio.Queue[ProtocolModeEvent]] = None 719 hogp_device(self.device) 720 logging.info(f'Hid device register: ') 721 global hid_device 722 hid_device = HID_Device(self.device) 723 # Register for call backs 724 hid_device.register_get_report_cb(on_get_report_cb) 725 hid_device.register_set_report_cb(on_set_report_cb) 726 hid_device.register_get_protocol_cb(on_get_protocol_cb) 727 hid_device.register_set_protocol_cb(on_set_protocol_cb) 728 # Register for virtual cable unplug call back 729 hid_device.on('virtual_cable_unplug', on_virtual_cable_unplug_cb) 730 731 @utils.rpc 732 async def ConnectHost(self, request: empty_pb2.Empty, context: grpc.ServicerContext) -> empty_pb2.Empty: 733 734 logging.info(f'ConnectHost') 735 try: 736 hid_host_bd_addr = str(hid_device.remote_device_bd_address) 737 connection = await self.device.connect(hid_host_bd_addr, transport=BT_BR_EDR_TRANSPORT) 738 await connection.authenticate() 739 await connection.encrypt() 740 await hid_device.connect_control_channel() 741 await hid_device.connect_interrupt_channel() 742 except AttributeError as e: 743 logging.error(f'Device does not exist') 744 raise e 745 except (HCI_StatusError, ProtocolError) as e: 746 logging.error(f"Connection failure error: {e}") 747 raise e 748 749 return empty_pb2.Empty() 750 751 @utils.rpc 752 async def DisconnectHost(self, request: empty_pb2.Empty, context: grpc.ServicerContext) -> empty_pb2.Empty: 753 754 logging.info(f'DisconnectHost') 755 try: 756 await hid_device.disconnect_interrupt_channel() 757 await hid_device.disconnect_control_channel() 758 connection = hid_device.connection 759 if connection is not None: 760 await connection.disconnect() 761 else: 762 logging.info(f'Already disconnected from Hid Host') 763 except AttributeError as e: 764 logging.error(f'Device does not exist') 765 raise e 766 767 return empty_pb2.Empty() 768 769 @utils.rpc 770 async def VirtualCableUnplugHost(self, request: empty_pb2.Empty, context: grpc.ServicerContext) -> empty_pb2.Empty: 771 772 logging.info(f'VirtualCableUnplugHost') 773 try: 774 hid_device.virtual_cable_unplug() 775 try: 776 hid_host_bd_addr = str(hid_device.remote_device_bd_address) 777 await hid_device.device.keystore.delete(hid_host_bd_addr) 778 except KeyError: 779 logging.error(f'Device not found or Device already unpaired.') 780 raise 781 except AttributeError as e: 782 logging.exception(f'Device does not exist') 783 raise e 784 return empty_pb2.Empty() 785 786 @utils.rpc 787 async def OnSetProtocolMode(self, request: empty_pb2.Empty, 788 context: grpc.ServicerContext) -> AsyncGenerator[ProtocolModeEvent, None]: 789 logging.info(f'OnSetProtocolMode') 790 791 if self.event_queue is not None: 792 raise RuntimeError('already streaming OnSetProtocolMode events') 793 794 self.event_queue = asyncio.Queue() 795 global hid_protoMode_queue 796 hid_protoMode_queue = self.event_queue 797 798 try: 799 while event := await hid_protoMode_queue.get(): 800 yield event 801 802 finally: 803 self.event_queue = None 804 hid_protoMode_queue = None 805 806 @utils.rpc 807 async def OnSetReport(self, request: empty_pb2.Empty, 808 context: grpc.ServicerContext) -> AsyncGenerator[ReportEvent, None]: 809 logging.info(f'OnSetReport') 810 811 if self.event_queue is not None: 812 raise RuntimeError('already streaming OnSetReport events') 813 814 self.event_queue = asyncio.Queue() 815 global hid_report_queue 816 hid_report_queue = self.event_queue 817 818 try: 819 while event := await hid_report_queue.get(): 820 yield event 821 822 finally: 823 self.event_queue = None 824 hid_report_queue = None 825