Skip to content

Commit e4d6fe9

Browse files
committed
lint; first cut at HID; not working yet
1 parent 5e5c88b commit e4d6fe9

File tree

3 files changed

+271
-2
lines changed

3 files changed

+271
-2
lines changed

adafruit_ble/advertising.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
3030
"""
3131

32-
import bleio
3332
import struct
3433

3534
class AdvertisingPacket:

adafruit_ble/current_time_client.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ class CurrentTimeClient:
6464

6565
def __init__(self, name=None, tx_power=0):
6666
self._periph = Peripheral(name=name)
67-
self._advertisement = SolicitationAdvertisement(self._periph.name, (self.CTS_UUID,), tx_power=tx_power)
67+
self._advertisement = SolicitationAdvertisement(self._periph.name,
68+
(self.CTS_UUID,), tx_power=tx_power)
6869
self._current_time_char = self._local_time_char = None
6970

7071

adafruit_ble/hid.py

Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
# The MIT License (MIT)
2+
#
3+
# Copyright (c) 2019 Dan Halbert for Adafruit Industries
4+
#
5+
# Permission is hereby granted, free of charge, to any person obtaining a copy
6+
# of this software and associated documentation files (the "Software"), to deal
7+
# in the Software without restriction, including without limitation the rights
8+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
# copies of the Software, and to permit persons to whom the Software is
10+
# furnished to do so, subject to the following conditions:
11+
#
12+
# The above copyright notice and this permission notice shall be included in
13+
# all copies or substantial portions of the Software.
14+
#
15+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
# THE SOFTWARE.
22+
"""
23+
`adafruit_ble.hid_keyboard`
24+
====================================================
25+
26+
BLE HID
27+
28+
* Author(s): Dan Halbert for Adafruit Industries
29+
30+
"""
31+
import struct
32+
33+
from bleio import Attribute, Characteristic, Descriptor, Peripheral, Service, UUID
34+
from .advertising import ServerAdvertisement
35+
36+
class HID:
37+
"""
38+
Provide devices for HID over BLE.
39+
40+
:param str name: Name to advertise for server. If None, use default Peripheral name.
41+
42+
Example::
43+
44+
from adafruit_ble.hid import HID
45+
46+
hid = HID()
47+
"""
48+
49+
HUMAN_INTERFACE_DEVICE_UUID = UUID(0x1812)
50+
REPORT_UUID = UUID(0x2A4D)
51+
REPORT_MAP_UUID = UUID(0x2A4B)
52+
HID_INFORMATION_UUID = UUID(0x2A4A)
53+
HID_CONTROL_POINT_UUID = UUID(0x2A4C)
54+
REPORT_REF_DESCR_UUID = UUID(0x2908)
55+
_REPORT_TYPE_INPUT = 1
56+
# Boot keyboard and mouse not currently supported.
57+
# PROTOCOL_MODE_UUID = UUID(0x2A4E)
58+
# HID_BOOT_KEYBOARD_INPUT_REPORT_UUID = UUID(0x2A22)
59+
# HID_BOOT_KEYBOARD_OUTPUT_REPORT_UUID = UUID(0x2A32)
60+
# HID_BOOT_MOUSE_INPUT_REPORT_UUID = UUID(0x2A33)
61+
62+
#pylint: disable=line-too-long
63+
HID_DESCRIPTOR = (
64+
b'\x05\x01' # Usage Page (Generic Desktop Ctrls)
65+
b'\x09\x06' # Usage (Keyboard)
66+
b'\xA1\x01' # Collection (Application)
67+
b'\x85\x01' # Report ID (1)
68+
b'\x05\x07' # Usage Page (Kbrd/Keypad)
69+
b'\x19\xE0' # Usage Minimum (\xE0)
70+
b'\x29\xE7' # Usage Maximum (\xE7)
71+
b'\x15\x00' # Logical Minimum (0)
72+
b'\x25\x01' # Logical Maximum (1)
73+
b'\x75\x01' # Report Size (1)
74+
b'\x95\x08' # Report Count (8)
75+
b'\x81\x02' # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
76+
b'\x81\x01' # Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
77+
b'\x19\x00' # Usage Minimum (\x00)
78+
b'\x29\x65' # Usage Maximum (\x65)
79+
b'\x15\x00' # Logical Minimum (0)
80+
b'\x25\x65' # Logical Maximum (101)
81+
b'\x75\x08' # Report Size (8)
82+
b'\x95\x06' # Report Count (6)
83+
b'\x81\x00' # Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
84+
b'\x05\x08' # Usage Page (LEDs)
85+
b'\x19\x01' # Usage Minimum (Num Lock)
86+
b'\x29\x05' # Usage Maximum (Kana)
87+
b'\x15\x00' # Logical Minimum (0)
88+
b'\x25\x01' # Logical Maximum (1)
89+
b'\x75\x01' # Report Size (1)
90+
b'\x95\x05' # Report Count (5)
91+
b'\x91\x02' # Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
92+
b'\x95\x03' # Report Count (3)
93+
b'\x91\x01' # Output (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
94+
b'\xC0' # End Collection
95+
b'\x05\x01' # Usage Page (Generic Desktop Ctrls)
96+
b'\x09\x02' # Usage (Mouse)
97+
b'\xA1\x01' # Collection (Application)
98+
b'\x09\x01' # Usage (Pointer)
99+
b'\xA1\x00' # Collection (Physical)
100+
b'\x85\x02' # Report ID (2)
101+
b'\x05\x09' # Usage Page (Button)
102+
b'\x19\x01' # Usage Minimum (\x01)
103+
b'\x29\x05' # Usage Maximum (\x05)
104+
b'\x15\x00' # Logical Minimum (0)
105+
b'\x25\x01' # Logical Maximum (1)
106+
b'\x95\x05' # Report Count (5)
107+
b'\x75\x01' # Report Size (1)
108+
b'\x81\x02' # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
109+
b'\x95\x01' # Report Count (1)
110+
b'\x75\x03' # Report Size (3)
111+
b'\x81\x01' # Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
112+
b'\x05\x01' # Usage Page (Generic Desktop Ctrls)
113+
b'\x09\x30' # Usage (X)
114+
b'\x09\x31' # Usage (Y)
115+
b'\x15\x81' # Logical Minimum (-127)
116+
b'\x25\x7F' # Logical Maximum (127)
117+
b'\x75\x08' # Report Size (8)
118+
b'\x95\x02' # Report Count (2)
119+
b'\x81\x06' # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
120+
b'\x09\x38' # Usage (Wheel)
121+
b'\x15\x81' # Logical Minimum (-127)
122+
b'\x25\x7F' # Logical Maximum (127)
123+
b'\x75\x08' # Report Size (8)
124+
b'\x95\x01' # Report Count (1)
125+
b'\x81\x06' # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
126+
b'\xC0' # End Collection
127+
b'\xC0' # End Collection
128+
b'\x05\x0C' # Usage Page (Consumer)
129+
b'\x09\x01' # Usage (Consumer Control)
130+
b'\xA1\x01' # Collection (Application)
131+
b'\x85\x03' # Report ID (3)
132+
b'\x75\x10' # Report Size (16)
133+
b'\x95\x01' # Report Count (1)
134+
b'\x15\x01' # Logical Minimum (1)
135+
b'\x26\x8C\x02' # Logical Maximum (652)
136+
b'\x19\x01' # Usage Minimum (Consumer Control)
137+
b'\x2A\x8C\x02' # Usage Maximum (AC Send)
138+
b'\x81\x00' # Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
139+
b'\xC0' # End Collection
140+
b'\x05\x01' # Usage Page (Generic Desktop Ctrls)
141+
b'\x09\x05' # Usage (Game Pad)
142+
b'\xA1\x01' # Collection (Application)
143+
b'\x85\x05' # Report ID (5)
144+
b'\x05\x09' # Usage Page (Button)
145+
b'\x19\x01' # Usage Minimum (\x01)
146+
b'\x29\x10' # Usage Maximum (\x10)
147+
b'\x15\x00' # Logical Minimum (0)
148+
b'\x25\x01' # Logical Maximum (1)
149+
b'\x75\x01' # Report Size (1)
150+
b'\x95\x10' # Report Count (16)
151+
b'\x81\x02' # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
152+
b'\x05\x01' # Usage Page (Generic Desktop Ctrls)
153+
b'\x15\x81' # Logical Minimum (-127)
154+
b'\x25\x7F' # Logical Maximum (127)
155+
b'\x09\x30' # Usage (X)
156+
b'\x09\x31' # Usage (Y)
157+
b'\x09\x32' # Usage (Z)
158+
b'\x09\x35' # Usage (Rz)
159+
b'\x75\x08' # Report Size (8)
160+
b'\x95\x04' # Report Count (4)
161+
b'\x81\x02' # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
162+
b'\xC0' # End Collection
163+
)
164+
#pylint: enable=line-too-long
165+
166+
REPORT_ID_KEYBOARD = 1
167+
"""Keyboard device indicator, for use with `send_report()`."""
168+
REPORT_ID_MOUSE = 2
169+
"""Mouse device indicator, for use with `send_report()`."""
170+
REPORT_ID_CONSUMER_CONTROL = 3
171+
"""Consumer control device indicator, for use with `send_report()`."""
172+
REPORT_ID_GAMEPAD = 5
173+
"""Gamepad device indicator, for use with `send_report()`."""
174+
175+
REPORT_SIZES = {
176+
REPORT_ID_KEYBOARD : 8,
177+
REPORT_ID_MOUSE : 4,
178+
REPORT_ID_CONSUMER_CONTROL : 2,
179+
REPORT_ID_GAMEPAD : 6,
180+
}
181+
182+
def __init__(self, name=None, tx_power=0):
183+
self._input_chars = {}
184+
for report_id in sorted(self.REPORT_SIZES.keys()):
185+
desc = Descriptor(HID.REPORT_REF_DESCR_UUID,
186+
read_perm=Attribute.OPEN, write_perm=Attribute.NO_ACCESS)
187+
desc.value = struct.pack('<BB', report_id, self._REPORT_TYPE_INPUT)
188+
self._input_chars[report_id] = Characteristic(
189+
self.REPORT_UUID, properties=Characteristic.READ,
190+
read_perm=Attribute.ENCRYPT_NO_MITM, write_perm=Attribute.NO_ACCESS,
191+
max_length=self.REPORT_SIZES[report_id], fixed_length=True,
192+
descriptors=(desc,))
193+
194+
# This is the USB HID descriptor (not to be confused with a BLE Descriptor).
195+
self._report_map_char = Characteristic(
196+
self.REPORT_MAP_UUID, properties=Characteristic.READ,
197+
read_perm=Attribute.OPEN, write_perm=Attribute.NO_ACCESS,
198+
max_length=len(self.HID_DESCRIPTOR), fixed_length=True)
199+
self._report_map_char.value = self.HID_DESCRIPTOR
200+
201+
# bcdHID (version), bCountryCode (0 not localized), Flags: RemoteWake, NormallyConnectable
202+
self._hid_information_char = Characteristic(
203+
self.HID_INFORMATION_UUID, properties=Characteristic.READ,
204+
read_perm=Attribute.OPEN, write_perm=Attribute.NO_ACCESS)
205+
# bcd1.1, country = 0, flag = normal connect
206+
self._hid_information_char.value = b'\x01\x01\x00\x02'
207+
208+
# 0 = suspend; 1 = exit suspend
209+
self._hid_control_point_char = Characteristic(self.HID_CONTROL_POINT_UUID,
210+
properties=Characteristic.WRITE_NO_RESPONSE)
211+
212+
hid_service = Service(self.HUMAN_INTERFACE_DEVICE_UUID,
213+
tuple(self._input_chars.values()) +
214+
(self._report_map_char,
215+
self._hid_information_char,
216+
self._hid_control_point_char,
217+
))
218+
self._periph = Peripheral((hid_service,), name=name)
219+
self._advertisement = ServerAdvertisement(self._periph, tx_power=tx_power)
220+
221+
def start_advertising(self):
222+
"""Start advertising the service. When a client connects, advertising will stop.
223+
When the client disconnects, restart advertising by calling ``start_advertising()`` again.
224+
"""
225+
self._periph.start_advertising(self._advertisement.advertising_data_bytes,
226+
scan_response=self._advertisement.scan_response_bytes)
227+
228+
def stop_advertising(self):
229+
"""Stop advertising the service."""
230+
self._periph.stop_advertising()
231+
232+
@property
233+
def connected(self):
234+
"""True if someone connected to the server."""
235+
return self._periph.connected
236+
237+
def disconnect(self):
238+
"""Disconnect from peer."""
239+
self._periph.disconnect()
240+
241+
def _check_connected(self):
242+
if not self.connected:
243+
raise OSError("Not connected")
244+
245+
def pair(self):
246+
"""Pair with the connected central."""
247+
self._check_connected()
248+
self._periph.pair()
249+
250+
def send_report(self, report_id, report):
251+
"""Send a report to the specified device"""
252+
self._input_chars[report_id].value = report
253+
254+
255+
class HIDDevice:
256+
"""A single HID device: keyboard, mouse, consumer control, or gamepad.
257+
258+
:param HID hid: The HID object used for BLE communication
259+
:param int report: The report ID for this device:
260+
`HID.REPORT_ID_KEYBOARD`, `HID.REPORT_ID_MOUSE`, etc.
261+
"""
262+
263+
def __init__(self, hid, report_id):
264+
self._hid = hid
265+
self._report_id = report_id
266+
267+
def send_report(self, report):
268+
"""Send a report, via hid"""
269+
self._hid.send_report(self._report_id, report)

0 commit comments

Comments
 (0)