From 0445d122574e043e0f54eb582af668118414b60f Mon Sep 17 00:00:00 2001 From: Dan Halbert Date: Sun, 25 Aug 2019 21:34:54 -0400 Subject: [PATCH 1/6] Initial BLE HID; works everywhere but iOS --- adafruit_ble/advertising.py | 10 +- adafruit_ble/device_information_service.py | 92 ++++++ adafruit_ble/hid.py | 269 ---------------- adafruit_ble/hid_server.py | 344 +++++++++++++++++++++ 4 files changed, 443 insertions(+), 272 deletions(-) create mode 100644 adafruit_ble/device_information_service.py delete mode 100644 adafruit_ble/hid.py create mode 100644 adafruit_ble/hid_server.py diff --git a/adafruit_ble/advertising.py b/adafruit_ble/advertising.py index 0e2d831..7b5b6fd 100644 --- a/adafruit_ble/advertising.py +++ b/adafruit_ble/advertising.py @@ -179,8 +179,9 @@ class Advertisement: consisting of an advertising data packet and an optional scan response packet. :param int flags: advertising flags. Default is general discovery, and BLE only (not classic) + :param int appearance: If not None, add appearance value to advertisement. """ - def __init__(self, flags=None, tx_power=None): + def __init__(self, flags=None, tx_power=None, appearance=None): self._packet = AdvertisingPacket() self._scan_response_packet = None if flags: @@ -190,6 +191,8 @@ def __init__(self, flags=None, tx_power=None): if tx_power is not None: self._packet.add_tx_power(tx_power) + if appearance is not None: + self._packet.add_appearance(appearance) def add_name(self, name): """Add name to advertisement. If it doesn't fit, add truncated name to packet, @@ -246,10 +249,11 @@ class ServerAdvertisement(Advertisement): :param Peripheral peripheral: the Peripheral to advertise. Use its services and name. :param int tx_power: transmit power in dBm at 0 meters (8 bit signed value). Default 0 dBm + :param int appearance: If not None, add appearance value to advertisement. """ - def __init__(self, peripheral, *, tx_power=0): - super().__init__() + def __init__(self, peripheral, *, tx_power=0, appearance=None): + super().__init__(tx_power=tx_power, appearance=appearance) uuids = [service.uuid for service in peripheral.services if not service.secondary] self.add_uuids(uuids, AdvertisingPacket.ALL_16_BIT_SERVICE_UUIDS, diff --git a/adafruit_ble/device_information_service.py b/adafruit_ble/device_information_service.py new file mode 100644 index 0000000..69896b2 --- /dev/null +++ b/adafruit_ble/device_information_service.py @@ -0,0 +1,92 @@ +# The MIT License (MIT) +# +# Copyright (c) 2019 Dan Halbert for Adafruit Industries +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +""" +`adafruit_ble.device_information` +==================================================== + +Device Information Service (DIS) + +* Author(s): Dan Halbert for Adafruit Industries + +""" +from bleio import Attribute, Characteristic, Service, UUID + +# pylint: disable=invalid-name +def DeviceInformationService(*, model_number=None, serial_number=None, firmware_revision=None, + hardware_revision='', software_revision='', manufacturer=''): + """ + Set up a Service with fixed Device Information Service charcteristics. + All values are optional. + + Note that this is a pseudo class: It returns a Service, but is not a subclass of Service, + because Service is a native class and doesn't handle subclassing. + + :param str model_number: Device model number. If None use `sys.platform`. + :param str serial_number: Device serial number. If None use a hex representation of + ``microcontroller.cpu.id``. + :param str firmware_revision: Device firmware revision. If None use ``os.uname().version``. + :param str hardware_revision: Device hardware revision. + :param str software_revision: Device software revision. + :param str manufacturer: Device manufacturer name + + Example:: + + dis = DeviceInformationService(software_revision="1.2.4", manufacturer="Acme Corp") + """ + + # Avoid creating constants with names if not necessary. Just takes up space. + # Device Information Service UUID = 0x180A + # Module Number UUID = 0x2A24 + # Serial Number UUID = 0x2A25 + # Firmware Revision UUID = 0x2A26 + # Hardware Revision UUID = 0x2A27 + # Software Revision UUID = 0x2A28 + # Manufacturer Name UUID = 0x2A29 + + characteristics = [] + # Values must correspond to UUID numbers. + + if model_number is None: + import sys + model_number = sys.platform + if serial_number is None: + import microcontroller + import binascii + serial_number = binascii.hexlify(microcontroller.cpu.uid).decode('utf-8') # pylint: disable=no-member + + if firmware_revision is None: + import os + firmware_revision = os.uname().version + + for uuid_num, string in zip( + range(0x2A24, 0x2A29+1), + (model_number, serial_number, + firmware_revision, hardware_revision, software_revision, + manufacturer)): + + char = Characteristic(UUID(uuid_num), properties=Characteristic.READ, + read_perm=Attribute.OPEN, write_perm=Attribute.NO_ACCESS, + fixed_length=True, max_length=len(string)) + char.value = string + characteristics.append(char) + + return Service(UUID(0x180A), characteristics) diff --git a/adafruit_ble/hid.py b/adafruit_ble/hid.py deleted file mode 100644 index 5195512..0000000 --- a/adafruit_ble/hid.py +++ /dev/null @@ -1,269 +0,0 @@ - # The MIT License (MIT) -# -# Copyright (c) 2019 Dan Halbert for Adafruit Industries -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -""" -`adafruit_ble.hid_keyboard` -==================================================== - -BLE HID - -* Author(s): Dan Halbert for Adafruit Industries - -""" -import struct - -from bleio import Attribute, Characteristic, Descriptor, Peripheral, Service, UUID -from .advertising import ServerAdvertisement - -class HID: - """ - Provide devices for HID over BLE. - - :param str name: Name to advertise for server. If None, use default Peripheral name. - - Example:: - - from adafruit_ble.hid import HID - - hid = HID() - """ - - HUMAN_INTERFACE_DEVICE_UUID = UUID(0x1812) - REPORT_UUID = UUID(0x2A4D) - REPORT_MAP_UUID = UUID(0x2A4B) - HID_INFORMATION_UUID = UUID(0x2A4A) - HID_CONTROL_POINT_UUID = UUID(0x2A4C) - REPORT_REF_DESCR_UUID = UUID(0x2908) - _REPORT_TYPE_INPUT = 1 - # Boot keyboard and mouse not currently supported. - # PROTOCOL_MODE_UUID = UUID(0x2A4E) - # HID_BOOT_KEYBOARD_INPUT_REPORT_UUID = UUID(0x2A22) - # HID_BOOT_KEYBOARD_OUTPUT_REPORT_UUID = UUID(0x2A32) - # HID_BOOT_MOUSE_INPUT_REPORT_UUID = UUID(0x2A33) - - #pylint: disable=line-too-long - HID_DESCRIPTOR = ( - b'\x05\x01' # Usage Page (Generic Desktop Ctrls) - b'\x09\x06' # Usage (Keyboard) - b'\xA1\x01' # Collection (Application) - b'\x85\x01' # Report ID (1) - b'\x05\x07' # Usage Page (Kbrd/Keypad) - b'\x19\xE0' # Usage Minimum (\xE0) - b'\x29\xE7' # Usage Maximum (\xE7) - b'\x15\x00' # Logical Minimum (0) - b'\x25\x01' # Logical Maximum (1) - b'\x75\x01' # Report Size (1) - b'\x95\x08' # Report Count (8) - b'\x81\x02' # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) - b'\x81\x01' # Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) - b'\x19\x00' # Usage Minimum (\x00) - b'\x29\x65' # Usage Maximum (\x65) - b'\x15\x00' # Logical Minimum (0) - b'\x25\x65' # Logical Maximum (101) - b'\x75\x08' # Report Size (8) - b'\x95\x06' # Report Count (6) - b'\x81\x00' # Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) - b'\x05\x08' # Usage Page (LEDs) - b'\x19\x01' # Usage Minimum (Num Lock) - b'\x29\x05' # Usage Maximum (Kana) - b'\x15\x00' # Logical Minimum (0) - b'\x25\x01' # Logical Maximum (1) - b'\x75\x01' # Report Size (1) - b'\x95\x05' # Report Count (5) - b'\x91\x02' # Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) - b'\x95\x03' # Report Count (3) - b'\x91\x01' # Output (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) - b'\xC0' # End Collection - b'\x05\x01' # Usage Page (Generic Desktop Ctrls) - b'\x09\x02' # Usage (Mouse) - b'\xA1\x01' # Collection (Application) - b'\x09\x01' # Usage (Pointer) - b'\xA1\x00' # Collection (Physical) - b'\x85\x02' # Report ID (2) - b'\x05\x09' # Usage Page (Button) - b'\x19\x01' # Usage Minimum (\x01) - b'\x29\x05' # Usage Maximum (\x05) - b'\x15\x00' # Logical Minimum (0) - b'\x25\x01' # Logical Maximum (1) - b'\x95\x05' # Report Count (5) - b'\x75\x01' # Report Size (1) - b'\x81\x02' # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) - b'\x95\x01' # Report Count (1) - b'\x75\x03' # Report Size (3) - b'\x81\x01' # Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) - b'\x05\x01' # Usage Page (Generic Desktop Ctrls) - b'\x09\x30' # Usage (X) - b'\x09\x31' # Usage (Y) - b'\x15\x81' # Logical Minimum (-127) - b'\x25\x7F' # Logical Maximum (127) - b'\x75\x08' # Report Size (8) - b'\x95\x02' # Report Count (2) - b'\x81\x06' # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position) - b'\x09\x38' # Usage (Wheel) - b'\x15\x81' # Logical Minimum (-127) - b'\x25\x7F' # Logical Maximum (127) - b'\x75\x08' # Report Size (8) - b'\x95\x01' # Report Count (1) - b'\x81\x06' # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position) - b'\xC0' # End Collection - b'\xC0' # End Collection - b'\x05\x0C' # Usage Page (Consumer) - b'\x09\x01' # Usage (Consumer Control) - b'\xA1\x01' # Collection (Application) - b'\x85\x03' # Report ID (3) - b'\x75\x10' # Report Size (16) - b'\x95\x01' # Report Count (1) - b'\x15\x01' # Logical Minimum (1) - b'\x26\x8C\x02' # Logical Maximum (652) - b'\x19\x01' # Usage Minimum (Consumer Control) - b'\x2A\x8C\x02' # Usage Maximum (AC Send) - b'\x81\x00' # Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) - b'\xC0' # End Collection - b'\x05\x01' # Usage Page (Generic Desktop Ctrls) - b'\x09\x05' # Usage (Game Pad) - b'\xA1\x01' # Collection (Application) - b'\x85\x05' # Report ID (5) - b'\x05\x09' # Usage Page (Button) - b'\x19\x01' # Usage Minimum (\x01) - b'\x29\x10' # Usage Maximum (\x10) - b'\x15\x00' # Logical Minimum (0) - b'\x25\x01' # Logical Maximum (1) - b'\x75\x01' # Report Size (1) - b'\x95\x10' # Report Count (16) - b'\x81\x02' # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) - b'\x05\x01' # Usage Page (Generic Desktop Ctrls) - b'\x15\x81' # Logical Minimum (-127) - b'\x25\x7F' # Logical Maximum (127) - b'\x09\x30' # Usage (X) - b'\x09\x31' # Usage (Y) - b'\x09\x32' # Usage (Z) - b'\x09\x35' # Usage (Rz) - b'\x75\x08' # Report Size (8) - b'\x95\x04' # Report Count (4) - b'\x81\x02' # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) - b'\xC0' # End Collection - ) - #pylint: enable=line-too-long - - REPORT_ID_KEYBOARD = 1 - """Keyboard device indicator, for use with `send_report()`.""" - REPORT_ID_MOUSE = 2 - """Mouse device indicator, for use with `send_report()`.""" - REPORT_ID_CONSUMER_CONTROL = 3 - """Consumer control device indicator, for use with `send_report()`.""" - REPORT_ID_GAMEPAD = 5 - """Gamepad device indicator, for use with `send_report()`.""" - - REPORT_SIZES = { - REPORT_ID_KEYBOARD : 8, - REPORT_ID_MOUSE : 4, - REPORT_ID_CONSUMER_CONTROL : 2, - REPORT_ID_GAMEPAD : 6, - } - - def __init__(self, name=None, tx_power=0): - self._input_chars = {} - for report_id in sorted(self.REPORT_SIZES.keys()): - desc = Descriptor(HID.REPORT_REF_DESCR_UUID, - read_perm=Attribute.OPEN, write_perm=Attribute.NO_ACCESS) - desc.value = struct.pack(' Date: Wed, 28 Aug 2019 11:05:11 -0400 Subject: [PATCH 2/6] restore unimplemented boot keyboard and mouse --- adafruit_ble/hid_server.py | 44 +++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/adafruit_ble/hid_server.py b/adafruit_ble/hid_server.py index fcc5581..d1c67ca 100644 --- a/adafruit_ble/hid_server.py +++ b/adafruit_ble/hid_server.py @@ -1,4 +1,4 @@ - # The MIT License (MIT) +# The MIT License (MIT) # # Copyright (c) 2019 Dan Halbert for Adafruit Industries # @@ -239,24 +239,24 @@ def __init__(self, name=None, tx_power=0): # Protocol mode: boot or report. Make it read-only for now because # it can't be changed - # protocol_mode_char = Characteristic( - # UUID(_PROTOCOL_MODE_UUID_NUM), properties=Characteristic.READ | Characteristic.WRITE_NO_RESPONSE, - # read_perm=Attribute.OPEN, write_perm=Attribute.OPEN, - # max_length=1, fixed_length=True) - # protocol_mode_char.value = _PROTOCOL_MODE_REPORT - - # boot_keyboard_input_report = Characteristic( - # UUID(_BOOT_KEYBOARD_INPUT_REPORT_UUID_NUM), - # properties=Characteristic.READ | Characteristic.NOTIFY, - # read_perm=Attribute.ENCRYPT_NO_MITM, write_perm=Attribute.NO_ACCESS, - # max_length=self._INPUT_REPORT_SIZES[self.REPORT_ID_KEYBOARD], fixed_length=True) - - # boot_keyboard_output_report = Characteristic( - # UUID(_BOOT_KEYBOARD_OUTPUT_REPORT_UUID_NUM), - # properties=(Characteristic.READ | Characteristic.WRITE | - # Characteristic.WRITE_NO_RESPONSE), - # read_perm=Attribute.ENCRYPT_NO_MITM, write_perm=Attribute.ENCRYPT_NO_MITM, - # max_length=self._OUTPUT_REPORT_SIZES[self.REPORT_ID_KEYBOARD], fixed_length=True) + protocol_mode_char = Characteristic( + UUID(_PROTOCOL_MODE_UUID_NUM), properties=Characteristic.READ | Characteristic.WRITE_NO_RESPONSE, + read_perm=Attribute.OPEN, write_perm=Attribute.OPEN, + max_length=1, fixed_length=True) + protocol_mode_char.value = _PROTOCOL_MODE_REPORT + + boot_keyboard_input_report = Characteristic( + UUID(_BOOT_KEYBOARD_INPUT_REPORT_UUID_NUM), + properties=Characteristic.READ | Characteristic.NOTIFY, + read_perm=Attribute.ENCRYPT_NO_MITM, write_perm=Attribute.NO_ACCESS, + max_length=self._INPUT_REPORT_SIZES[self.REPORT_ID_KEYBOARD], fixed_length=True) + + boot_keyboard_output_report = Characteristic( + UUID(_BOOT_KEYBOARD_OUTPUT_REPORT_UUID_NUM), + properties=(Characteristic.READ | Characteristic.WRITE | + Characteristic.WRITE_NO_RESPONSE), + read_perm=Attribute.ENCRYPT_NO_MITM, write_perm=Attribute.ENCRYPT_NO_MITM, + max_length=self._OUTPUT_REPORT_SIZES[self.REPORT_ID_KEYBOARD], fixed_length=True) # This is the USB HID descriptor (not to be confused with a BLE Descriptor). report_map_char = Characteristic( @@ -283,9 +283,9 @@ def __init__(self, name=None, tx_power=0): hid_service = Service(UUID(_HID_SERVICE_UUID_NUM), tuple(self._input_chars.values()) + tuple(self._output_chars.values()) + - (#boot_keyboard_input_report, - #boot_keyboard_output_report, - #protocol_mode_char, + (boot_keyboard_input_report, + boot_keyboard_output_report, + protocol_mode_char, report_map_char, hid_information_char, self._hid_control_point_char, From ff05feb459394c5e53693983503d049f0010c81c Mon Sep 17 00:00:00 2001 From: Dan Halbert Date: Wed, 28 Aug 2019 16:07:56 -0400 Subject: [PATCH 3/6] Update to use new bleio API --- adafruit_ble/beacon.py | 2 +- adafruit_ble/current_time_client.py | 2 +- adafruit_ble/device_information_service.py | 104 +++++++++++---------- adafruit_ble/hid_server.py | 84 ++++++++--------- adafruit_ble/uart_server.py | 13 ++- 5 files changed, 103 insertions(+), 102 deletions(-) diff --git a/adafruit_ble/beacon.py b/adafruit_ble/beacon.py index c7891cd..178b534 100644 --- a/adafruit_ble/beacon.py +++ b/adafruit_ble/beacon.py @@ -41,7 +41,7 @@ def __init__(self, advertising_packet): :param AdvertisingPacket advertising_packet """ - self._broadcaster = bleio.Peripheral(name=None) + self._broadcaster = bleio.Peripheral() self._advertising_packet = advertising_packet def start(self, interval=1.0): diff --git a/adafruit_ble/current_time_client.py b/adafruit_ble/current_time_client.py index 8ea5ffe..c558b88 100644 --- a/adafruit_ble/current_time_client.py +++ b/adafruit_ble/current_time_client.py @@ -66,7 +66,7 @@ class CurrentTimeClient: LOCAL_TIME_INFORMATION_UUID = UUID(0x2A0F) def __init__(self, name=None, tx_power=0): - self._periph = Peripheral(name=name) + self._periph = Peripheral(name) self._advertisement = SolicitationAdvertisement(self._periph.name, (self.CTS_UUID,), tx_power=tx_power) self._current_time_char = self._local_time_char = None diff --git a/adafruit_ble/device_information_service.py b/adafruit_ble/device_information_service.py index 69896b2..f6ab34d 100644 --- a/adafruit_ble/device_information_service.py +++ b/adafruit_ble/device_information_service.py @@ -28,65 +28,69 @@ * Author(s): Dan Halbert for Adafruit Industries """ -from bleio import Attribute, Characteristic, Service, UUID +from bleio import Attribute, Characteristic, UUID -# pylint: disable=invalid-name -def DeviceInformationService(*, model_number=None, serial_number=None, firmware_revision=None, - hardware_revision='', software_revision='', manufacturer=''): - """ - Set up a Service with fixed Device Information Service charcteristics. - All values are optional. +class DeviceInformationService: + """This is a factory class only, and has no instances.""" - Note that this is a pseudo class: It returns a Service, but is not a subclass of Service, - because Service is a native class and doesn't handle subclassing. + @staticmethod + def add_to_peripheral(peripheral, *, model_number=None, serial_number=None, + firmware_revision=None, hardware_revision='', + software_revision='', manufacturer=''): + """ + Add a Service with fixed Device Information Service characteristics to the given Peripheral. + All values are optional. - :param str model_number: Device model number. If None use `sys.platform`. - :param str serial_number: Device serial number. If None use a hex representation of - ``microcontroller.cpu.id``. - :param str firmware_revision: Device firmware revision. If None use ``os.uname().version``. - :param str hardware_revision: Device hardware revision. - :param str software_revision: Device software revision. - :param str manufacturer: Device manufacturer name + :param str model_number: Device model number. If None use `sys.platform`. + :param str serial_number: Device serial number. If None use a hex representation of + ``microcontroller.cpu.id``. + :param str firmware_revision: Device firmware revision. + If None use ``os.uname().version``. + :param str hardware_revision: Device hardware revision. + :param str software_revision: Device software revision. + :param str manufacturer: Device manufacturer name + :return: the created Service - Example:: + Example:: - dis = DeviceInformationService(software_revision="1.2.4", manufacturer="Acme Corp") - """ + peripheral = Peripheral() + dis = DeviceInformationService.add_to_peripheral( + peripheral, software_revision="1.2.4", manufacturer="Acme Corp") + """ - # Avoid creating constants with names if not necessary. Just takes up space. - # Device Information Service UUID = 0x180A - # Module Number UUID = 0x2A24 - # Serial Number UUID = 0x2A25 - # Firmware Revision UUID = 0x2A26 - # Hardware Revision UUID = 0x2A27 - # Software Revision UUID = 0x2A28 - # Manufacturer Name UUID = 0x2A29 + # Avoid creating constants with names if not necessary. Just takes up space. + # Device Information Service UUID = 0x180A + # Module Number UUID = 0x2A24 + # Serial Number UUID = 0x2A25 + # Firmware Revision UUID = 0x2A26 + # Hardware Revision UUID = 0x2A27 + # Software Revision UUID = 0x2A28 + # Manufacturer Name UUID = 0x2A29 - characteristics = [] - # Values must correspond to UUID numbers. + service = peripheral.add_service(UUID(0x180A)) - if model_number is None: - import sys - model_number = sys.platform - if serial_number is None: - import microcontroller - import binascii - serial_number = binascii.hexlify(microcontroller.cpu.uid).decode('utf-8') # pylint: disable=no-member + if model_number is None: + import sys + model_number = sys.platform + if serial_number is None: + import microcontroller + import binascii + serial_number = binascii.hexlify(microcontroller.cpu.uid).decode('utf-8') # pylint: disable=no-member - if firmware_revision is None: - import os - firmware_revision = os.uname().version + if firmware_revision is None: + import os + firmware_revision = os.uname().version - for uuid_num, string in zip( - range(0x2A24, 0x2A29+1), - (model_number, serial_number, - firmware_revision, hardware_revision, software_revision, - manufacturer)): + # Values must correspond to UUID numbers. + for uuid_num, value in zip( + range(0x2A24, 0x2A29+1), + (model_number, serial_number, + firmware_revision, hardware_revision, software_revision, + manufacturer)): - char = Characteristic(UUID(uuid_num), properties=Characteristic.READ, - read_perm=Attribute.OPEN, write_perm=Attribute.NO_ACCESS, - fixed_length=True, max_length=len(string)) - char.value = string - characteristics.append(char) + service.add_characteristic(UUID(uuid_num), properties=Characteristic.READ, + read_perm=Attribute.OPEN, write_perm=Attribute.NO_ACCESS, + fixed_length=True, max_length=len(value), + initial_value=value) - return Service(UUID(0x180A), characteristics) + return service diff --git a/adafruit_ble/hid_server.py b/adafruit_ble/hid_server.py index d1c67ca..7a09de4 100644 --- a/adafruit_ble/hid_server.py +++ b/adafruit_ble/hid_server.py @@ -35,7 +35,7 @@ # for __version__ import adafruit_ble -from bleio import Attribute, Characteristic, Descriptor, Peripheral, Service, UUID +from bleio import Attribute, Characteristic, Peripheral, UUID from .advertising import ServerAdvertisement from .device_information_service import DeviceInformationService @@ -208,50 +208,58 @@ class HIDServer: } def __init__(self, name=None, tx_power=0): + self._periph = Peripheral(name) + + # iOS requires Device Information Service. Android does not. + DeviceInformationService.add_to_peripheral( + self._periph, software_revision=adafruit_ble.__version__, + manufacturer="Adafruit Industries") + + service = self._periph.add_service(UUID(_HID_SERVICE_UUID_NUM)) + self._input_chars = {} for report_id in sorted(self._INPUT_REPORT_SIZES.keys()): - desc = Descriptor(self._REPORT_REF_DESCR_UUID, - read_perm=Attribute.ENCRYPT_NO_MITM, write_perm=Attribute.NO_ACCESS) - desc.value = struct.pack(' Date: Wed, 28 Aug 2019 23:14:39 -0400 Subject: [PATCH 4/6] another API rework: less abstraction leakage --- adafruit_ble/hid_server.py | 47 ++++++++++++++++++++----------------- adafruit_ble/uart_server.py | 12 +++++----- 2 files changed, 31 insertions(+), 28 deletions(-) diff --git a/adafruit_ble/hid_server.py b/adafruit_ble/hid_server.py index 7a09de4..aea2c48 100644 --- a/adafruit_ble/hid_server.py +++ b/adafruit_ble/hid_server.py @@ -35,7 +35,7 @@ # for __version__ import adafruit_ble -from bleio import Attribute, Characteristic, Peripheral, UUID +from bleio import Attribute, Characteristic, Descriptor, Peripheral, Service, UUID from .advertising import ServerAdvertisement from .device_information_service import DeviceInformationService @@ -215,30 +215,31 @@ def __init__(self, name=None, tx_power=0): self._periph, software_revision=adafruit_ble.__version__, manufacturer="Adafruit Industries") - service = self._periph.add_service(UUID(_HID_SERVICE_UUID_NUM)) + hid_service = Service.add_to_peripheral(UUID(_HID_SERVICE_UUID_NUM)) self._input_chars = {} for report_id in sorted(self._INPUT_REPORT_SIZES.keys()): - input_char = service.add_characteristic( - self._REPORT_UUID, properties=Characteristic.READ | Characteristic.NOTIFY, + input_char = Characteristic.add_to_service( + hid_service, self._REPORT_UUID, + properties=Characteristic.READ | Characteristic.NOTIFY, read_perm=Attribute.ENCRYPT_NO_MITM, write_perm=Attribute.NO_ACCESS, max_length=self._INPUT_REPORT_SIZES[report_id], fixed_length=True) - input_char.add_descriptor( - self._REPORT_REF_DESCR_UUID, + Descriptor.add_to_characteristic( + input_char, self._REPORT_REF_DESCR_UUID, read_perm=Attribute.ENCRYPT_NO_MITM, write_perm=Attribute.NO_ACCESS, initial_value=struct.pack(' Date: Thu, 29 Aug 2019 18:44:46 -0400 Subject: [PATCH 5/6] rename bleio to _bleio --- README.rst | 3 +- adafruit_ble/__init__.py | 2 +- adafruit_ble/beacon.py | 6 +- adafruit_ble/current_time_client.py | 2 +- adafruit_ble/device_information_service.py | 13 +-- adafruit_ble/hid_server.py | 100 ++++++++++----------- adafruit_ble/scanner.py | 9 +- adafruit_ble/uart.py | 2 +- adafruit_ble/uart_client.py | 4 +- adafruit_ble/uart_server.py | 2 +- adafruit_ble/uuid.py | 6 +- docs/conf.py | 2 +- 12 files changed, 77 insertions(+), 74 deletions(-) diff --git a/README.rst b/README.rst index b175d6b..d0c87ba 100644 --- a/README.rst +++ b/README.rst @@ -13,7 +13,8 @@ Introduction :target: https://travis-ci.com/adafruit/Adafruit_CircuitPython_ble :alt: Build Status -This module provides higher-level BLE (Bluetooth Low Energy) functionality, building on the native `bleio` module. +This module provides higher-level BLE (Bluetooth Low Energy) functionality, +building on the native `_bleio` module. Dependencies ============= diff --git a/adafruit_ble/__init__.py b/adafruit_ble/__init__.py index 47c2e21..20a3ca4 100644 --- a/adafruit_ble/__init__.py +++ b/adafruit_ble/__init__.py @@ -24,7 +24,7 @@ ==================================================== This module provides higher-level BLE (Bluetooth Low Energy) functionality, -building on the native `bleio` module. +building on the native `_bleio` module. * Author(s): Dan Halbert for Adafruit Industries diff --git a/adafruit_ble/beacon.py b/adafruit_ble/beacon.py index 178b534..7193b1a 100644 --- a/adafruit_ble/beacon.py +++ b/adafruit_ble/beacon.py @@ -30,7 +30,7 @@ """ import struct -import bleio +from _bleio import Peripheral from .advertising import AdvertisingPacket @@ -41,7 +41,7 @@ def __init__(self, advertising_packet): :param AdvertisingPacket advertising_packet """ - self._broadcaster = bleio.Peripheral() + self._broadcaster = Peripheral() self._advertising_packet = advertising_packet def start(self, interval=1.0): @@ -76,7 +76,7 @@ def __init__(self, company_id, uuid, major, minor, rssi): Example:: from adafruit_ble.beacon import LocationBeacon - from bleio import UUID + from adafruit_ble.uuid import UUID test_uuid = UUID('12345678-1234-1234-1234-123456789abc') test_company = 0xFFFF b = LocationBeacon(test_company, test_uuid, 123, 234, -54) diff --git a/adafruit_ble/current_time_client.py b/adafruit_ble/current_time_client.py index c558b88..5f036d0 100644 --- a/adafruit_ble/current_time_client.py +++ b/adafruit_ble/current_time_client.py @@ -31,7 +31,7 @@ import struct import time -from bleio import Peripheral, UUID +from _bleio import Peripheral, UUID from .advertising import SolicitationAdvertisement class CurrentTimeClient: diff --git a/adafruit_ble/device_information_service.py b/adafruit_ble/device_information_service.py index f6ab34d..a5d00ef 100644 --- a/adafruit_ble/device_information_service.py +++ b/adafruit_ble/device_information_service.py @@ -28,7 +28,7 @@ * Author(s): Dan Halbert for Adafruit Industries """ -from bleio import Attribute, Characteristic, UUID +from _bleio import Attribute, Characteristic, Service, UUID class DeviceInformationService: """This is a factory class only, and has no instances.""" @@ -67,7 +67,7 @@ def add_to_peripheral(peripheral, *, model_number=None, serial_number=None, # Software Revision UUID = 0x2A28 # Manufacturer Name UUID = 0x2A29 - service = peripheral.add_service(UUID(0x180A)) + service = Service.add_to_peripheral(peripheral, UUID(0x180A)) if model_number is None: import sys @@ -88,9 +88,10 @@ def add_to_peripheral(peripheral, *, model_number=None, serial_number=None, firmware_revision, hardware_revision, software_revision, manufacturer)): - service.add_characteristic(UUID(uuid_num), properties=Characteristic.READ, - read_perm=Attribute.OPEN, write_perm=Attribute.NO_ACCESS, - fixed_length=True, max_length=len(value), - initial_value=value) + Characteristic.add_to_service( + service, UUID(uuid_num), properties=Characteristic.READ, + read_perm=Attribute.OPEN, write_perm=Attribute.NO_ACCESS, + fixed_length=True, max_length=len(value), + initial_value=value) return service diff --git a/adafruit_ble/hid_server.py b/adafruit_ble/hid_server.py index aea2c48..3a9a758 100644 --- a/adafruit_ble/hid_server.py +++ b/adafruit_ble/hid_server.py @@ -35,7 +35,7 @@ # for __version__ import adafruit_ble -from bleio import Attribute, Characteristic, Descriptor, Peripheral, Service, UUID +from _bleio import Attribute, Characteristic, Descriptor, Peripheral, Service, UUID from .advertising import ServerAdvertisement from .device_information_service import DeviceInformationService @@ -116,52 +116,52 @@ class HIDServer: b'\x95\x03' # Report Count (3) b'\x91\x01' # Output (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) b'\xC0' # End Collection - # b'\x05\x01' # Usage Page (Generic Desktop Ctrls) - # b'\x09\x02' # Usage (Mouse) - # b'\xA1\x01' # Collection (Application) - # b'\x09\x01' # Usage (Pointer) - # b'\xA1\x00' # Collection (Physical) - # b'\x85\x02' # Report ID (2) - # b'\x05\x09' # Usage Page (Button) - # b'\x19\x01' # Usage Minimum (\x01) - # b'\x29\x05' # Usage Maximum (\x05) - # b'\x15\x00' # Logical Minimum (0) - # b'\x25\x01' # Logical Maximum (1) - # b'\x95\x05' # Report Count (5) - # b'\x75\x01' # Report Size (1) - # b'\x81\x02' # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) - # b'\x95\x01' # Report Count (1) - # b'\x75\x03' # Report Size (3) - # b'\x81\x01' # Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) - # b'\x05\x01' # Usage Page (Generic Desktop Ctrls) - # b'\x09\x30' # Usage (X) - # b'\x09\x31' # Usage (Y) - # b'\x15\x81' # Logical Minimum (-127) - # b'\x25\x7F' # Logical Maximum (127) - # b'\x75\x08' # Report Size (8) - # b'\x95\x02' # Report Count (2) - # b'\x81\x06' # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position) - # b'\x09\x38' # Usage (Wheel) - # b'\x15\x81' # Logical Minimum (-127) - # b'\x25\x7F' # Logical Maximum (127) - # b'\x75\x08' # Report Size (8) - # b'\x95\x01' # Report Count (1) - # b'\x81\x06' # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position) - # b'\xC0' # End Collection - # b'\xC0' # End Collection - # b'\x05\x0C' # Usage Page (Consumer) - # b'\x09\x01' # Usage (Consumer Control) - # b'\xA1\x01' # Collection (Application) - # b'\x85\x03' # Report ID (3) - # b'\x75\x10' # Report Size (16) - # b'\x95\x01' # Report Count (1) - # b'\x15\x01' # Logical Minimum (1) - # b'\x26\x8C\x02' # Logical Maximum (652) - # b'\x19\x01' # Usage Minimum (Consumer Control) - # b'\x2A\x8C\x02' # Usage Maximum (AC Send) - # b'\x81\x00' # Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) - # b'\xC0' # End Collection - # b'\x05\x01' # Usage Page (Generic Desktop Ctrls) + b'\x05\x01' # Usage Page (Generic Desktop Ctrls) + b'\x09\x02' # Usage (Mouse) + b'\xA1\x01' # Collection (Application) + b'\x09\x01' # Usage (Pointer) + b'\xA1\x00' # Collection (Physical) + b'\x85\x02' # Report ID (2) + b'\x05\x09' # Usage Page (Button) + b'\x19\x01' # Usage Minimum (\x01) + b'\x29\x05' # Usage Maximum (\x05) + b'\x15\x00' # Logical Minimum (0) + b'\x25\x01' # Logical Maximum (1) + b'\x95\x05' # Report Count (5) + b'\x75\x01' # Report Size (1) + b'\x81\x02' # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + b'\x95\x01' # Report Count (1) + b'\x75\x03' # Report Size (3) + b'\x81\x01' # Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) + b'\x05\x01' # Usage Page (Generic Desktop Ctrls) + b'\x09\x30' # Usage (X) + b'\x09\x31' # Usage (Y) + b'\x15\x81' # Logical Minimum (-127) + b'\x25\x7F' # Logical Maximum (127) + b'\x75\x08' # Report Size (8) + b'\x95\x02' # Report Count (2) + b'\x81\x06' # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position) + b'\x09\x38' # Usage (Wheel) + b'\x15\x81' # Logical Minimum (-127) + b'\x25\x7F' # Logical Maximum (127) + b'\x75\x08' # Report Size (8) + b'\x95\x01' # Report Count (1) + b'\x81\x06' # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position) + b'\xC0' # End Collection + b'\xC0' # End Collection + b'\x05\x0C' # Usage Page (Consumer) + b'\x09\x01' # Usage (Consumer Control) + b'\xA1\x01' # Collection (Application) + b'\x85\x03' # Report ID (3) + b'\x75\x10' # Report Size (16) + b'\x95\x01' # Report Count (1) + b'\x15\x01' # Logical Minimum (1) + b'\x26\x8C\x02' # Logical Maximum (652) + b'\x19\x01' # Usage Minimum (Consumer Control) + b'\x2A\x8C\x02' # Usage Maximum (AC Send) + b'\x81\x00' # Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) + b'\xC0' # End Collection + b'\x05\x01' # Usage Page (Generic Desktop Ctrls) # b'\x09\x05' # Usage (Game Pad) # b'\xA1\x01' # Collection (Application) # b'\x85\x05' # Report ID (5) @@ -198,8 +198,8 @@ class HIDServer: _INPUT_REPORT_SIZES = { REPORT_ID_KEYBOARD : 8, - # REPORT_ID_MOUSE : 4, - # REPORT_ID_CONSUMER_CONTROL : 2, + REPORT_ID_MOUSE : 4, + REPORT_ID_CONSUMER_CONTROL : 2, # REPORT_ID_GAMEPAD : 6, } @@ -215,7 +215,7 @@ def __init__(self, name=None, tx_power=0): self._periph, software_revision=adafruit_ble.__version__, manufacturer="Adafruit Industries") - hid_service = Service.add_to_peripheral(UUID(_HID_SERVICE_UUID_NUM)) + hid_service = Service.add_to_peripheral(self._periph, UUID(_HID_SERVICE_UUID_NUM)) self._input_chars = {} for report_id in sorted(self._INPUT_REPORT_SIZES.keys()): diff --git a/adafruit_ble/scanner.py b/adafruit_ble/scanner.py index d11b804..6e45915 100644 --- a/adafruit_ble/scanner.py +++ b/adafruit_ble/scanner.py @@ -30,7 +30,8 @@ """ import struct -import bleio +from _bleio import Scanner as _BLEIOScanner +from _bleio import UUID from .advertising import AdvertisingPacket @@ -46,7 +47,7 @@ class Scanner: """ def __init__(self): - self._scanner = bleio.Scanner() + self._scanner = _BLEIOScanner() def scan(self, timeout, *, interval=0.1, window=0.1): """Scan for advertisements from BLE devices. Suppress duplicates @@ -82,7 +83,7 @@ class ScanEntry: """ Information about an advertising packet from a BLE device received by a `Scanner`. - :param bleio.ScanEntry scan_entry: lower-level ScanEntry returned from `bleio.Scanner`. + :param _bleio.ScanEntry scan_entry: lower-level ScanEntry returned from `_bleio.Scanner`. This constructor is normally used only by `Scanner`. """ @@ -133,7 +134,7 @@ def service_uuids(self): for i in range(0, len(concat_uuids), 16): uuid_values.append(concat_uuids[i:i+16]) - return [bleio.UUID(value) for value in uuid_values] + return [UUID(value) for value in uuid_values] @property def manufacturer_specific_data(self): diff --git a/adafruit_ble/uart.py b/adafruit_ble/uart.py index f891358..bb9f48b 100644 --- a/adafruit_ble/uart.py +++ b/adafruit_ble/uart.py @@ -28,7 +28,7 @@ * Author(s): Dan Halbert for Adafruit Industries """ -from bleio import UUID +from _bleio import UUID NUS_SERVICE_UUID = UUID("6E400001-B5A3-F393-E0A9-E50E24DCCA9E") """Nordic UART Service UUID""" diff --git a/adafruit_ble/uart_client.py b/adafruit_ble/uart_client.py index a4df20c..645803c 100644 --- a/adafruit_ble/uart_client.py +++ b/adafruit_ble/uart_client.py @@ -28,7 +28,7 @@ * Author(s): Dan Halbert for Adafruit Industries """ -from bleio import Central, CharacteristicBuffer +from _bleio import Central, CharacteristicBuffer from .uart import NUS_SERVICE_UUID, NUS_RX_CHAR_UUID, NUS_TX_CHAR_UUID from .scanner import Scanner, ScanEntry @@ -65,7 +65,7 @@ def __init__(self, *, timeout=1.0, buffer_size=64): def connect(self, address, timeout): """Try to connect to the peripheral at the given address. - :param bleio.Address address: The address of the peripheral to connect to + :param Address address: The address of the peripheral to connect to :param float/int timeout: Try to connect for ``timeout`` seconds. Not related to the timeout passed to ``UARTClient()``. """ diff --git a/adafruit_ble/uart_server.py b/adafruit_ble/uart_server.py index 0687187..2e5ebc9 100644 --- a/adafruit_ble/uart_server.py +++ b/adafruit_ble/uart_server.py @@ -28,7 +28,7 @@ * Author(s): Dan Halbert for Adafruit Industries """ -from bleio import Attribute, Characteristic, CharacteristicBuffer, Peripheral, Service +from _bleio import Attribute, Characteristic, CharacteristicBuffer, Peripheral, Service from .advertising import ServerAdvertisement from .uart import NUS_SERVICE_UUID, NUS_RX_CHAR_UUID, NUS_TX_CHAR_UUID diff --git a/adafruit_ble/uuid.py b/adafruit_ble/uuid.py index 96805ee..f690eb9 100644 --- a/adafruit_ble/uuid.py +++ b/adafruit_ble/uuid.py @@ -29,7 +29,7 @@ """ -from bleio import UUID as bleio_UUID +from _bleio import UUID as _bleio_UUID -UUID = bleio_UUID -"""`adafruit_ble.UUID` is the same as `bleio.UUID`""" +UUID = _bleio_UUID +"""`adafruit_ble.UUID` is the same as `_bleio.UUID`""" diff --git a/docs/conf.py b/docs/conf.py index b2d4131..2edec32 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -20,7 +20,7 @@ # Uncomment the below if you use native CircuitPython modules such as # digitalio, micropython and busio. List the modules you use. Without it, the # autodoc module docs will fail to generate with a warning. -autodoc_mock_imports = ["bleio"] +autodoc_mock_imports = ["_bleio"] intersphinx_mapping = {'python': ('https://docs.python.org/3.4', None),'CircuitPython': ('https://circuitpython.readthedocs.io/en/latest/', None)} From 67b9341fef79117ef5c71d8a36ca2428609c8e35 Mon Sep 17 00:00:00 2001 From: Dan Halbert Date: Thu, 29 Aug 2019 22:50:31 -0400 Subject: [PATCH 6/6] explicate Appearance; forgot to add address.py --- adafruit_ble/address.py | 35 +++++++++++++++++++++++++++++++++++ adafruit_ble/advertising.py | 4 +++- 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 adafruit_ble/address.py diff --git a/adafruit_ble/address.py b/adafruit_ble/address.py new file mode 100644 index 0000000..8c93b01 --- /dev/null +++ b/adafruit_ble/address.py @@ -0,0 +1,35 @@ +# The MIT License (MIT) +# +# Copyright (c) 2019 Dan Halbert for Adafruit Industries +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +""" +`adafruit_ble.address` +==================================================== + +BLE Address + +* Author(s): Dan Halbert for Adafruit Industries + +""" + +from _bleio import UUID as _bleio_Address + +UUID = _bleio_Address +"""`adafruit_ble.Address` is the same as `_bleio.Address`""" diff --git a/adafruit_ble/advertising.py b/adafruit_ble/advertising.py index 7b5b6fd..3dd06eb 100644 --- a/adafruit_ble/advertising.py +++ b/adafruit_ble/advertising.py @@ -179,7 +179,9 @@ class Advertisement: consisting of an advertising data packet and an optional scan response packet. :param int flags: advertising flags. Default is general discovery, and BLE only (not classic) - :param int appearance: If not None, add appearance value to advertisement. + :param int appearance: If not None, add BLE Appearance value to advertisement. + An Appearance describes what kind of device is advertising (keyboard, clock, + glucose meter, etc.) """ def __init__(self, flags=None, tx_power=None, appearance=None): self._packet = AdvertisingPacket()