summaryrefslogtreecommitdiff
path: root/pandora
diff options
context:
space:
mode:
author Gopi Sakshihally Bhuthaiah (xWF) <bhuthaiah@google.com> 2024-07-30 02:37:50 +0000
committer Gerrit Code Review <noreply-gerritcodereview@google.com> 2024-07-30 02:37:50 +0000
commitd9cfa4a4b1f8ebbd8d5067574f4a90f8c9e5c9db (patch)
tree085139542e74119d910d156d45c2d7fcd6d9fdc5 /pandora
parentd798dfdb5743934dfc89ce93f22d09544a8cb699 (diff)
parent6cf9f6cd62cff0c5d8c93bf1107f1ddd512b9912 (diff)
Merge "Bumble Java HID Test cases" into main
Diffstat (limited to 'pandora')
-rw-r--r--pandora/interfaces/pandora_experimental/hid.proto8
-rw-r--r--pandora/server/bumble_experimental/hid.py395
2 files changed, 402 insertions, 1 deletions
diff --git a/pandora/interfaces/pandora_experimental/hid.proto b/pandora/interfaces/pandora_experimental/hid.proto
index 1b95b8cbc2..1eb22768c6 100644
--- a/pandora/interfaces/pandora_experimental/hid.proto
+++ b/pandora/interfaces/pandora_experimental/hid.proto
@@ -4,7 +4,13 @@ package pandora;
option java_outer_classname = "HidProto";
+import "google/protobuf/empty.proto";
+
service HID {
+ // Connect HID Host
+ rpc ConnectHost(google.protobuf.Empty) returns (google.protobuf.Empty);
+ // Disconnect HID Host
+ rpc DisconnectHost(google.protobuf.Empty) returns (google.protobuf.Empty);
// Send a SET_REPORT command, acting as a HID host, to a connected HID device
rpc SendHostReport(SendHostReportRequest) returns (SendHostReportResponse);
}
@@ -25,4 +31,4 @@ message SendHostReportRequest {
message SendHostReportResponse {
-} \ No newline at end of file
+}
diff --git a/pandora/server/bumble_experimental/hid.py b/pandora/server/bumble_experimental/hid.py
new file mode 100644
index 0000000000..60b4ce48b3
--- /dev/null
+++ b/pandora/server/bumble_experimental/hid.py
@@ -0,0 +1,395 @@
+from __future__ import annotations
+import asyncio
+import grpc
+import grpc.aio
+import logging
+
+from bumble.device import Device
+from google.protobuf import empty_pb2 # pytype: disable=pyi-error
+
+from pandora_experimental.hid_grpc_aio import HIDServicer
+
+from bumble.core import (
+ BT_BR_EDR_TRANSPORT,
+ BT_L2CAP_PROTOCOL_ID,
+ BT_HUMAN_INTERFACE_DEVICE_SERVICE,
+ BT_HIDP_PROTOCOL_ID,
+ UUID,
+)
+
+from bumble.hid import (
+ Device as HID_Device,
+ HID_CONTROL_PSM,
+ HID_INTERRUPT_PSM,
+ Message,
+)
+
+from bumble.sdp import (
+ Client as SDP_Client,
+ DataElement,
+ ServiceAttribute,
+ SDP_PUBLIC_BROWSE_ROOT,
+ SDP_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID,
+ SDP_SERVICE_CLASS_ID_LIST_ATTRIBUTE_ID,
+ SDP_BLUETOOTH_PROFILE_DESCRIPTOR_LIST_ATTRIBUTE_ID,
+ SDP_ALL_ATTRIBUTES_RANGE,
+ SDP_LANGUAGE_BASE_ATTRIBUTE_ID_LIST_ATTRIBUTE_ID,
+ SDP_ADDITIONAL_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID,
+ SDP_SERVICE_RECORD_HANDLE_ATTRIBUTE_ID,
+ SDP_BROWSE_GROUP_LIST_ATTRIBUTE_ID,
+)
+from bumble.utils import AsyncRunner
+
+# -----------------------------------------------------------------------------
+# SDP attributes for Bluetooth HID devices
+SDP_HID_SERVICE_NAME_ATTRIBUTE_ID = 0x0100
+SDP_HID_SERVICE_DESCRIPTION_ATTRIBUTE_ID = 0x0101
+SDP_HID_PROVIDER_NAME_ATTRIBUTE_ID = 0x0102
+SDP_HID_DEVICE_RELEASE_NUMBER_ATTRIBUTE_ID = 0x0200 # [DEPRECATED]
+SDP_HID_PARSER_VERSION_ATTRIBUTE_ID = 0x0201
+SDP_HID_DEVICE_SUBCLASS_ATTRIBUTE_ID = 0x0202
+SDP_HID_COUNTRY_CODE_ATTRIBUTE_ID = 0x0203
+SDP_HID_VIRTUAL_CABLE_ATTRIBUTE_ID = 0x0204
+SDP_HID_RECONNECT_INITIATE_ATTRIBUTE_ID = 0x0205
+SDP_HID_DESCRIPTOR_LIST_ATTRIBUTE_ID = 0x0206
+SDP_HID_LANGID_BASE_LIST_ATTRIBUTE_ID = 0x0207
+SDP_HID_SDP_DISABLE_ATTRIBUTE_ID = 0x0208 # [DEPRECATED]
+SDP_HID_BATTERY_POWER_ATTRIBUTE_ID = 0x0209
+SDP_HID_REMOTE_WAKE_ATTRIBUTE_ID = 0x020A
+SDP_HID_PROFILE_VERSION_ATTRIBUTE_ID = 0x020B # DEPRECATED]
+SDP_HID_SUPERVISION_TIMEOUT_ATTRIBUTE_ID = 0x020C
+SDP_HID_NORMALLY_CONNECTABLE_ATTRIBUTE_ID = 0x020D
+SDP_HID_BOOT_DEVICE_ATTRIBUTE_ID = 0x020E
+SDP_HID_SSR_HOST_MAX_LATENCY_ATTRIBUTE_ID = 0x020F
+SDP_HID_SSR_HOST_MIN_TIMEOUT_ATTRIBUTE_ID = 0x0210
+
+# Refer to HID profile specification v1.1.1, "5.3 Service Discovery Protocol (SDP)" for details
+# HID SDP attribute values
+LANGUAGE = 0x656E # 0x656E uint16 “en” (English)
+ENCODING = 0x6A # 0x006A uint16 UTF-8 encoding
+PRIMARY_LANGUAGE_BASE_ID = 0x100 # 0x0100 uint16 PrimaryLanguageBaseID
+VERSION_NUMBER = 0x0101 # 0x0101 uint16 version number (v1.1)
+SERVICE_NAME = b'Bumble HID'
+SERVICE_DESCRIPTION = b'Bumble'
+PROVIDER_NAME = b'Bumble'
+HID_PARSER_VERSION = 0x0111 # uint16 0x0111 (v1.1.1)
+HID_DEVICE_SUBCLASS = 0xC0 # Combo keyboard/pointing device
+HID_COUNTRY_CODE = 0x21 # 0x21 Uint8, USA
+HID_VIRTUAL_CABLE = True # Virtual cable enabled
+HID_RECONNECT_INITIATE = True # Reconnect initiate enabled
+REPORT_DESCRIPTOR_TYPE = 0x22 # 0x22 Type = Report Descriptor
+HID_LANGID_BASE_LANGUAGE = 0x0409 # 0x0409 Language = English (United States)
+HID_LANGID_BASE_BLUETOOTH_STRING_OFFSET = 0x100 # 0x0100 Default
+HID_BATTERY_POWER = True # Battery power enabled
+HID_REMOTE_WAKE = True # Remote wake enabled
+HID_SUPERVISION_TIMEOUT = 0xC80 # uint16 0xC80 (2s)
+HID_NORMALLY_CONNECTABLE = True # Normally connectable enabled
+HID_BOOT_DEVICE = True # Boot device support enabled
+HID_SSR_HOST_MAX_LATENCY = 0x640 # uint16 0x640 (1s)
+HID_SSR_HOST_MIN_TIMEOUT = 0xC80 # uint16 0xC80 (2s)
+HID_REPORT_MAP = bytes( # Text String, 50 Octet Report Descriptor
+ # pylint: disable=line-too-long
+ [
+ 0x05,
+ 0x01, # Usage Page (Generic Desktop Ctrls)
+ 0x09,
+ 0x06, # Usage (Keyboard)
+ 0xA1,
+ 0x01, # Collection (Application)
+ 0x85,
+ 0x01, # . Report ID (1)
+ 0x05,
+ 0x07, # . Usage Page (Kbrd/Keypad)
+ 0x19,
+ 0xE0, # . Usage Minimum (0xE0)
+ 0x29,
+ 0xE7, # . Usage Maximum (0xE7)
+ 0x15,
+ 0x00, # . Logical Minimum (0)
+ 0x25,
+ 0x01, # . Logical Maximum (1)
+ 0x75,
+ 0x01, # . Report Size (1)
+ 0x95,
+ 0x08, # . Report Count (8)
+ 0x81,
+ 0x02, # . Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
+ 0x95,
+ 0x01, # . Report Count (1)
+ 0x75,
+ 0x08, # . Report Size (8)
+ 0x81,
+ 0x03, # . Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
+ 0x95,
+ 0x05, # . Report Count (5)
+ 0x75,
+ 0x01, # . Report Size (1)
+ 0x05,
+ 0x08, # . Usage Page (LEDs)
+ 0x19,
+ 0x01, # . Usage Minimum (Num Lock)
+ 0x29,
+ 0x05, # . Usage Maximum (Kana)
+ 0x91,
+ 0x02, # . Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
+ 0x95,
+ 0x01, # . Report Count (1)
+ 0x75,
+ 0x03, # . Report Size (3)
+ 0x91,
+ 0x03, # . Output (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
+ 0x95,
+ 0x06, # . Report Count (6)
+ 0x75,
+ 0x08, # . Report Size (8)
+ 0x15,
+ 0x00, # . Logical Minimum (0)
+ 0x25,
+ 0x65, # . Logical Maximum (101)
+ 0x05,
+ 0x07, # . Usage Page (Kbrd/Keypad)
+ 0x19,
+ 0x00, # . Usage Minimum (0x00)
+ 0x29,
+ 0x65, # . Usage Maximum (0x65)
+ 0x81,
+ 0x00, # . Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
+ 0xC0, # End Collection
+ 0x05,
+ 0x01, # Usage Page (Generic Desktop Ctrls)
+ 0x09,
+ 0x02, # Usage (Mouse)
+ 0xA1,
+ 0x01, # Collection (Application)
+ 0x85,
+ 0x02, # . Report ID (2)
+ 0x09,
+ 0x01, # . Usage (Pointer)
+ 0xA1,
+ 0x00, # . Collection (Physical)
+ 0x05,
+ 0x09, # . Usage Page (Button)
+ 0x19,
+ 0x01, # . Usage Minimum (0x01)
+ 0x29,
+ 0x03, # . Usage Maximum (0x03)
+ 0x15,
+ 0x00, # . Logical Minimum (0)
+ 0x25,
+ 0x01, # . Logical Maximum (1)
+ 0x95,
+ 0x03, # . Report Count (3)
+ 0x75,
+ 0x01, # . Report Size (1)
+ 0x81,
+ 0x02, # . Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
+ 0x95,
+ 0x01, # . Report Count (1)
+ 0x75,
+ 0x05, # . Report Size (5)
+ 0x81,
+ 0x03, # . Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
+ 0x05,
+ 0x01, # . Usage Page (Generic Desktop Ctrls)
+ 0x09,
+ 0x30, # . Usage (X)
+ 0x09,
+ 0x31, # . Usage (Y)
+ 0x15,
+ 0x81, # . Logical Minimum (-127)
+ 0x25,
+ 0x7F, # . Logical Maximum (127)
+ 0x75,
+ 0x08, # . Report Size (8)
+ 0x95,
+ 0x02, # . Report Count (2)
+ 0x81,
+ 0x06, # . Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
+ 0xC0, # . End Collection
+ 0xC0, # End Collection
+ ])
+
+# Default protocol mode set to report protocol
+protocol_mode = Message.ProtocolMode.REPORT_PROTOCOL
+
+
+# -----------------------------------------------------------------------------
+def sdp_records():
+ service_record_handle = 0x00010002
+ return {
+ service_record_handle: [
+ ServiceAttribute(
+ SDP_SERVICE_RECORD_HANDLE_ATTRIBUTE_ID,
+ DataElement.unsigned_integer_32(service_record_handle),
+ ),
+ ServiceAttribute(
+ SDP_BROWSE_GROUP_LIST_ATTRIBUTE_ID,
+ DataElement.sequence([DataElement.uuid(SDP_PUBLIC_BROWSE_ROOT)]),
+ ),
+ ServiceAttribute(
+ SDP_SERVICE_CLASS_ID_LIST_ATTRIBUTE_ID,
+ DataElement.sequence([DataElement.uuid(BT_HUMAN_INTERFACE_DEVICE_SERVICE)]),
+ ),
+ ServiceAttribute(
+ SDP_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID,
+ DataElement.sequence([
+ DataElement.sequence([
+ DataElement.uuid(BT_L2CAP_PROTOCOL_ID),
+ DataElement.unsigned_integer_16(HID_CONTROL_PSM),
+ ]),
+ DataElement.sequence([
+ DataElement.uuid(BT_HIDP_PROTOCOL_ID),
+ ]),
+ ]),
+ ),
+ ServiceAttribute(
+ SDP_LANGUAGE_BASE_ATTRIBUTE_ID_LIST_ATTRIBUTE_ID,
+ DataElement.sequence([
+ DataElement.unsigned_integer_16(LANGUAGE),
+ DataElement.unsigned_integer_16(ENCODING),
+ DataElement.unsigned_integer_16(PRIMARY_LANGUAGE_BASE_ID),
+ ]),
+ ),
+ ServiceAttribute(
+ SDP_BLUETOOTH_PROFILE_DESCRIPTOR_LIST_ATTRIBUTE_ID,
+ DataElement.sequence([
+ DataElement.sequence([
+ DataElement.uuid(BT_HUMAN_INTERFACE_DEVICE_SERVICE),
+ DataElement.unsigned_integer_16(VERSION_NUMBER),
+ ]),
+ ]),
+ ),
+ ServiceAttribute(
+ SDP_ADDITIONAL_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID,
+ DataElement.sequence([
+ DataElement.sequence([
+ DataElement.sequence([
+ DataElement.uuid(BT_L2CAP_PROTOCOL_ID),
+ DataElement.unsigned_integer_16(HID_INTERRUPT_PSM),
+ ]),
+ DataElement.sequence([
+ DataElement.uuid(BT_HIDP_PROTOCOL_ID),
+ ]),
+ ]),
+ ]),
+ ),
+ ServiceAttribute(
+ SDP_HID_SERVICE_NAME_ATTRIBUTE_ID,
+ DataElement(DataElement.TEXT_STRING, SERVICE_NAME),
+ ),
+ ServiceAttribute(
+ SDP_HID_SERVICE_DESCRIPTION_ATTRIBUTE_ID,
+ DataElement(DataElement.TEXT_STRING, SERVICE_DESCRIPTION),
+ ),
+ ServiceAttribute(
+ SDP_HID_PROVIDER_NAME_ATTRIBUTE_ID,
+ DataElement(DataElement.TEXT_STRING, PROVIDER_NAME),
+ ),
+ ServiceAttribute(
+ SDP_HID_PARSER_VERSION_ATTRIBUTE_ID,
+ DataElement.unsigned_integer_32(HID_PARSER_VERSION),
+ ),
+ ServiceAttribute(
+ SDP_HID_DEVICE_SUBCLASS_ATTRIBUTE_ID,
+ DataElement.unsigned_integer_32(HID_DEVICE_SUBCLASS),
+ ),
+ ServiceAttribute(
+ SDP_HID_COUNTRY_CODE_ATTRIBUTE_ID,
+ DataElement.unsigned_integer_32(HID_COUNTRY_CODE),
+ ),
+ ServiceAttribute(
+ SDP_HID_VIRTUAL_CABLE_ATTRIBUTE_ID,
+ DataElement.boolean(HID_VIRTUAL_CABLE),
+ ),
+ ServiceAttribute(
+ SDP_HID_RECONNECT_INITIATE_ATTRIBUTE_ID,
+ DataElement.boolean(HID_RECONNECT_INITIATE),
+ ),
+ ServiceAttribute(
+ SDP_HID_DESCRIPTOR_LIST_ATTRIBUTE_ID,
+ DataElement.sequence([
+ DataElement.sequence([
+ DataElement.unsigned_integer_16(REPORT_DESCRIPTOR_TYPE),
+ DataElement(DataElement.TEXT_STRING, HID_REPORT_MAP),
+ ]),
+ ]),
+ ),
+ ServiceAttribute(
+ SDP_HID_LANGID_BASE_LIST_ATTRIBUTE_ID,
+ DataElement.sequence([
+ DataElement.sequence([
+ DataElement.unsigned_integer_16(HID_LANGID_BASE_LANGUAGE),
+ DataElement.unsigned_integer_16(HID_LANGID_BASE_BLUETOOTH_STRING_OFFSET),
+ ]),
+ ]),
+ ),
+ ServiceAttribute(
+ SDP_HID_BATTERY_POWER_ATTRIBUTE_ID,
+ DataElement.boolean(HID_BATTERY_POWER),
+ ),
+ ServiceAttribute(
+ SDP_HID_REMOTE_WAKE_ATTRIBUTE_ID,
+ DataElement.boolean(HID_REMOTE_WAKE),
+ ),
+ ServiceAttribute(
+ SDP_HID_SUPERVISION_TIMEOUT_ATTRIBUTE_ID,
+ DataElement.unsigned_integer_16(HID_SUPERVISION_TIMEOUT),
+ ),
+ ServiceAttribute(
+ SDP_HID_NORMALLY_CONNECTABLE_ATTRIBUTE_ID,
+ DataElement.boolean(HID_NORMALLY_CONNECTABLE),
+ ),
+ ServiceAttribute(
+ SDP_HID_BOOT_DEVICE_ATTRIBUTE_ID,
+ DataElement.boolean(HID_BOOT_DEVICE),
+ ),
+ ServiceAttribute(
+ SDP_HID_SSR_HOST_MAX_LATENCY_ATTRIBUTE_ID,
+ DataElement.unsigned_integer_16(HID_SSR_HOST_MAX_LATENCY),
+ ),
+ ServiceAttribute(
+ SDP_HID_SSR_HOST_MIN_TIMEOUT_ATTRIBUTE_ID,
+ DataElement.unsigned_integer_16(HID_SSR_HOST_MIN_TIMEOUT),
+ ),
+ ]
+ }
+
+
+# This class implements the Hid Pandora interface.
+class HIDService(HIDServicer):
+
+ hid_device = None
+
+ def __init__(self, device: Device) -> None:
+ super().__init__()
+ self.device = device
+ self.device.sdp_service_records.update(sdp_records())
+ logging.info(f'Hid device register: ')
+ global hid_device
+ hid_device = HID_Device(self.device)
+
+ async def ConnectHost(self, request: empty_pb2.Empty, context: grpc.ServicerContext) -> empty_pb2.Empty:
+
+ logging.info(f'ConnectHidHost')
+ hid_host_bd_addr = str(hid_device.remote_device_bd_address)
+ connection = await self.device.connect(hid_host_bd_addr, transport=BT_BR_EDR_TRANSPORT)
+ await connection.authenticate()
+ await connection.encrypt()
+ if hid_device is not None:
+ await hid_device.connect_control_channel()
+ await hid_device.connect_interrupt_channel()
+
+ return empty_pb2.Empty()
+
+ async def DisconnectHost(self, request: empty_pb2.Empty, context: grpc.ServicerContext) -> empty_pb2.Empty:
+
+ logging.info(f'DisconnectHidHost')
+ if hid_device is not None:
+ await hid_device.disconnect_interrupt_channel()
+ await hid_device.disconnect_control_channel()
+ connection = hid_device.connection
+ if connection is not None:
+ await connection.disconnect()
+ else:
+ logging.info(f'Already disconnected from Hid Host')
+ return empty_pb2.Empty()