diff --git a/Documentation/conf.py b/Documentation/conf.py index 10886a9fb03ead2822cafc09b08c717c2cc23b84..a09ee94ed0c473d97e1e1f216f6338df00294105 100644 --- a/Documentation/conf.py +++ b/Documentation/conf.py @@ -117,6 +117,7 @@ html_context = { autodoc_mock_imports = [ "buttons", "interrupt", + "sys_ble_hid", "sys_bme680", "sys_bhi160", "sys_display", diff --git a/Documentation/index.rst b/Documentation/index.rst index 1090ad48cb9d2fce0e4cc5ed58512cd4b133c737..2816dcd17ed6f00868c18b0b8b6ef83c21c9cf9f 100644 --- a/Documentation/index.rst +++ b/Documentation/index.rst @@ -23,6 +23,7 @@ Last but not least, if you want to start hacking the lower-level firmware, the pycardium/overview pycardium/stdlib pycardium/bhi160 + pycardium/ble_hid pycardium/bme680 pycardium/max30001 pycardium/max86150 diff --git a/Documentation/pycardium/ble_hid.rst b/Documentation/pycardium/ble_hid.rst new file mode 100644 index 0000000000000000000000000000000000000000..221d5a9c87b484618f46bd24d871de9f55eb1c4e --- /dev/null +++ b/Documentation/pycardium/ble_hid.rst @@ -0,0 +1,87 @@ +``ble_hid`` - BLE HID +=============== +The ``ble_hid`` module provides access to the BLE Human Interface Device functionality. + +.. note:: + Make sure to enable the BLE HID functionality in ``card10.cfg`` and reboot your card10 + if you want to use BLE HID. + + Also make sure that the ``adafruit_hid`` folder from the card10 release archive is placed + on the file system of your card10. + + +.. warning:: + At least Ubuntu Linux will keep auto connecting to BLE HID devices once they are + paired to the host computer. If you want to connect your card10 to a phone again, + you might have to temporarily turn off Bluetooth on your computer. + + +An example application can be found in the preload directory (named ``HID Demo``). It provides +examples how to use the card10 as keyboard, mouse or volume control. + +Please refer to the Adafruit CircuitPython HID library for examples how to use the HID service. +The card10 implements the same HID descriptors as the Adafruit CircuitPython BLE library and +should be compatible with all uses of the Adafruit CircuitPython HID library. + +**Example emulating a keyboard**: + +Adapted from https://github.com/adafruit/Adafruit_Learning_System_Guides/blob/master/CPB_Keybutton_BLE/cpb_keybutton_ble.py + +A more complete version of this example can be found in the HID Demo app on your card10. + +.. code-block:: python + + import ble_hid + from adafruit_hid.keyboard import Keyboard + from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS + from adafruit_hid.keycode import Keycode + + k = Keyboard(ble_hid.devices) + kl = KeyboardLayoutUS(k) + + k.send(Keycode.BACKSPACE) + + # use keyboard_layout for words + kl.write("Demo with a long text to show how fast a card10 can type!") + + # add shift modifier + k.send(Keycode.SHIFT, Keycode.F19) + + +**Example emulating a mouse**: + +.. code-block:: python + + import ble_hid + import bhi160 + import buttons + from adafruit_hid.mouse import Mouse + + m = Mouse(ble_hid.devices) + + def send_report(samples): + if len(samples) > 0: + x = -int(samples[0].z) + y = -int(samples[0].y) + m.move(x, y) + + sensor = bhi160.BHI160Orientation(sample_rate=10, callback=send_report) + + b_old = buttons.read() + while True: + b_new = buttons.read() + if not b_old == b_new: + print(b_new) + b_old = b_new + if b_new == buttons.TOP_RIGHT: + m.click(Mouse.MIDDLE_BUTTON) + elif b_new == buttons.BOTTOM_RIGHT: + m.click(Mouse.RIGHT_BUTTON) + elif b_new == buttons.BOTTOM_LEFT: + m.click(Mouse.LEFT_BUTTON) + + + + +.. automodule:: ble_hid + :members: diff --git a/pycardium/modules/py/ble_hid.py b/pycardium/modules/py/ble_hid.py index 3a1bcda03674cd61908fd0cccc66264da6987520..1b9a5b7b09aba972d519f925f3a21dc0229c9371 100644 --- a/pycardium/modules/py/ble_hid.py +++ b/pycardium/modules/py/ble_hid.py @@ -3,23 +3,83 @@ import time class Report: - def __init__(self, report_id, usage_page, usage): + """ + The Report class provides an interface to the Adafruit CircuitPython HID library + (https://github.com/adafruit/Adafruit_CircuitPython_HID/). + + ``ble_hid.devices`` exposes a list of reports for use with the CircuitPython HID + classes. You usually do not have to interact with a report yourself but you can make + use of its ``send_report`` method to send raw HID reports to the host. + + **Example using Adafruit CircuitPython HID library**: + + .. code-block:: python + + import ble_hid + from adafruit_hid.mouse import Mouse + + m = Mouse(ble_hid.devices) + m.click(Mouse.MIDDLE_BUTTON) + + **Example using raw non blocking access**: + .. code-block:: python + + import ble_hid + report = ble_hid.Report(report_id=3, blocking=False) # Consumer Control + report.send_report(b'\\xe9\\x00') # 0x00E9: Volume Increase + report.send_report(b'\\x00\\x00') # 0x0000: Release button + + + .. versionadded:: 1.17 + """ + + def __init__(self, report_id, blocking=False, usage_page=None, usage=None): + """ + Initializes a report. + + All parameters are available as properties of the resulting object and can be modified + during runtime. + + :param report_id: The id of the report. Currently supported: 1: Keyboard, 2: Mouse, 3: Consumer control + :param blocking: If True :py:func`send_report()` will try sending the report until + there is space in the queue (unless the host is not connected). + :param usage_page: Used by Adafruit CircuitPython HID library to identify report + :param usage: Used by Adafruit CircuitPython HID library to identify report + """ + self.report_id = report_id self.usage_page = usage_page self.usage = usage + self.blocking = True def send_report(self, data): - while True: - try: - sys_ble_hid.send_report(self.report_id, data) - break - except MemoryError: - time.sleep(0.1) + """ + Tries to send a report to the host. + + :param data: The data to be sent. Must not exceed the configured length of the report. + :rtype: bool + :returns: `True` if the report was sent, `False` if the report was queued for sending. + :raises OSError: if there is no connection to a host + :raises MemoryError: if there was no space in the queue (only raised if ``blocking`` was set to `False`) + """ + + if self.blocking: + # Loop until we are able to place the report in the queue + # Forward all other exceptions to the caller + while True: + try: + return sys_ble_hid.send_report(self.report_id, data) + break + except MemoryError: + time.sleep(0.1) + else: + # Forward all exceptions to the caller + return sys_ble_hid.send_report(self.report_id, data) # Reports as defined in the HID report map in epicardium/ble/hid.c devices = [ - Report(report_id=1, usage_page=0x01, usage=0x06), - Report(report_id=2, usage_page=0x01, usage=0x02), - Report(report_id=3, usage_page=0x0C, usage=0x01), + Report(report_id=1, blocking=True, usage_page=0x01, usage=0x06), + Report(report_id=2, blocking=True, usage_page=0x01, usage=0x02), + Report(report_id=3, blocking=True, usage_page=0x0C, usage=0x01), ] diff --git a/pycardium/modules/sys_ble_hid.c b/pycardium/modules/sys_ble_hid.c index f7cf77f4824cdde40a53c16743d88bfed9cacae3..7bc725babaf50cf47a53337fe6ca4e25b6521dea 100644 --- a/pycardium/modules/sys_ble_hid.c +++ b/pycardium/modules/sys_ble_hid.c @@ -21,7 +21,7 @@ static mp_obj_t mp_sys_ble_hid_send_report(mp_obj_t report_id, mp_obj_t data) mp_raise_OSError(-ret); } - return mp_obj_new_int(ret); + return ret == 0 ? mp_const_true : mp_const_false; } static MP_DEFINE_CONST_FUN_OBJ_2( sys_ble_hid_send_report_obj, mp_sys_ble_hid_send_report