diff --git a/epicardium/ble/hid_work.c b/epicardium/ble/hid_work.c index 4fdd0c1deb2ba0a46638e48a1896fe93b9d7e158..406f8bd0a9c37d05c6cb8b1134f1b10f4d5b8b63 100644 --- a/epicardium/ble/hid_work.c +++ b/epicardium/ble/hid_work.c @@ -114,7 +114,7 @@ static bool hid_dequeue_data(dmConnId_t connId) return true; } -int epic_hid_send_report(uint8_t reportId, uint8_t *data, uint8_t len) +int epic_ble_hid_send_report(uint8_t reportId, uint8_t *data, uint8_t len) { dmConnId_t connId = AppConnIsOpen(); if (connId == DM_CONN_ID_NONE) { diff --git a/epicardium/epicardium.h b/epicardium/epicardium.h index db5369ecb7b162c49a591442bbe07d6cad1ce92d..3a00a64783697be953fb5cedfd7d7ef19d0252a9 100644 --- a/epicardium/epicardium.h +++ b/epicardium/epicardium.h @@ -158,7 +158,7 @@ typedef _Bool bool; #define API_BLE_GET_LAST_PAIRING_NAME 0x145 #define API_BLE_GET_PEER_DEVICE_NAME 0x146 -#define API_HID_SEND_REPORT 0x150 +#define API_BLE_HID_SEND_REPORT 0x150 /* clang-format on */ @@ -2343,7 +2343,7 @@ API(API_BLE_GET_SCAN_REPORT, int epic_ble_get_scan_report(struct epic_scan_repor * - ``-EAGAIN``: There is no space in the queue available. Try again later. * */ -API(API_HID_SEND_REPORT, int epic_hid_send_report(uint8_t reportId, uint8_t *data, uint8_t len)); +API(API_BLE_HID_SEND_REPORT, int epic_ble_hid_send_report(uint8_t reportId, uint8_t *data, uint8_t len)); #endif /* _EPICARDIUM_H */ diff --git a/preload/adafruit_hid/__init__.py b/preload/adafruit_hid/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..5323dd3ed0396e74bdc9603f5e0d50211cc15ca6 --- /dev/null +++ b/preload/adafruit_hid/__init__.py @@ -0,0 +1,56 @@ +# The MIT License (MIT) +# +# Copyright (c) 2017 Scott Shawcroft 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_hid` +==================================================== + +This driver simulates USB HID devices. + +* Author(s): Scott Shawcroft, Dan Halbert + +Implementation Notes +-------------------- +**Software and Dependencies:** +* Adafruit CircuitPython firmware for the supported boards: + https://github.com/adafruit/circuitpython/releases +""" + +# imports + +__version__ = "0.0.0-auto.0" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_HID.git" + + +def find_device(devices, *, usage_page, usage): + """Search through the provided list of devices to find the one with the matching usage_page and + usage.""" + if hasattr(devices, "send_report"): + devices = [devices] + for device in devices: + if ( + device.usage_page == usage_page + and device.usage == usage + and hasattr(device, "send_report") + ): + return device + raise ValueError("Could not find matching HID device.") diff --git a/preload/adafruit_hid/consumer_control.py b/preload/adafruit_hid/consumer_control.py new file mode 100644 index 0000000000000000000000000000000000000000..56d2baf5702dcbc6ee49a74297ad08caa8272cc7 --- /dev/null +++ b/preload/adafruit_hid/consumer_control.py @@ -0,0 +1,87 @@ +# The MIT License (MIT) +# +# Copyright (c) 2018 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_hid.consumer_control.ConsumerControl` +==================================================== + +* Author(s): Dan Halbert +""" + +import sys + +if sys.implementation.version[0] < 3: + raise ImportError( + "{0} is not supported in CircuitPython 2.x or lower".format(__name__) + ) + +# pylint: disable=wrong-import-position +import struct +import time +from . import find_device + + +class ConsumerControl: + """Send ConsumerControl code reports, used by multimedia keyboards, remote controls, etc. + """ + + def __init__(self, devices): + """Create a ConsumerControl object that will send Consumer Control Device HID reports. + + Devices can be a list of devices that includes a Consumer Control device or a CC device + itself. A device is any object that implements ``send_report()``, ``usage_page`` and + ``usage``. + """ + self._consumer_device = find_device(devices, usage_page=0x0C, usage=0x01) + + # Reuse this bytearray to send consumer reports. + self._report = bytearray(2) + + # Do a no-op to test if HID device is ready. + # If not, wait a bit and try once more. + try: + self.send(0x0) + except OSError: + time.sleep(1) + self.send(0x0) + + def send(self, consumer_code): + """Send a report to do the specified consumer control action, + and then stop the action (so it will not repeat). + + :param consumer_code: a 16-bit consumer control code. + + Examples:: + + from adafruit_hid.consumer_control_code import ConsumerControlCode + + # Raise volume. + consumer_control.send(ConsumerControlCode.VOLUME_INCREMENT) + + # Advance to next track (song). + consumer_control.send(ConsumerControlCode.SCAN_NEXT_TRACK) + """ + struct.pack_into("<H", self._report, 0, consumer_code) + self._consumer_device.send_report(self._report) + self._report[0] = self._report[1] = 0x0 + self._consumer_device.send_report(self._report) diff --git a/preload/adafruit_hid/consumer_control_code.py b/preload/adafruit_hid/consumer_control_code.py new file mode 100644 index 0000000000000000000000000000000000000000..735f771b3c8883a6fc57a7292c4203f091ae2e43 --- /dev/null +++ b/preload/adafruit_hid/consumer_control_code.py @@ -0,0 +1,64 @@ +# The MIT License (MIT) +# +# Copyright (c) 2018 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_hid.consumer_control_code.ConsumerControlCode` +======================================================== + +* Author(s): Dan Halbert +""" + + +class ConsumerControlCode: + """USB HID Consumer Control Device constants. + + This list includes a few common consumer control codes from + https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf#page=75. + + *New in CircuitPython 3.0.* + """ + + # pylint: disable-msg=too-few-public-methods + + RECORD = 0xB2 + """Record""" + FAST_FORWARD = 0xB3 + """Fast Forward""" + REWIND = 0xB4 + """Rewind""" + SCAN_NEXT_TRACK = 0xB5 + """Skip to next track""" + SCAN_PREVIOUS_TRACK = 0xB6 + """Go back to previous track""" + STOP = 0xB7 + """Stop""" + EJECT = 0xB8 + """Eject""" + PLAY_PAUSE = 0xCD + """Play/Pause toggle""" + MUTE = 0xE2 + """Mute""" + VOLUME_DECREMENT = 0xEA + """Decrease volume""" + VOLUME_INCREMENT = 0xE9 + """Increase volume""" diff --git a/preload/adafruit_hid/gamepad.py b/preload/adafruit_hid/gamepad.py new file mode 100644 index 0000000000000000000000000000000000000000..468c0d6b04c8030bb6a13372d71ec0edb16775d0 --- /dev/null +++ b/preload/adafruit_hid/gamepad.py @@ -0,0 +1,177 @@ +# The MIT License (MIT) +# +# Copyright (c) 2018 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_hid.gamepad.Gamepad` +==================================================== + +* Author(s): Dan Halbert +""" + +import struct +import time + +from . import find_device + + +class Gamepad: + """Emulate a generic gamepad controller with 16 buttons, + numbered 1-16, and two joysticks, one controlling + ``x` and ``y`` values, and the other controlling ``z`` and + ``r_z`` (z rotation or ``Rz``) values. + + The joystick values could be interpreted + differently by the receiving program: those are just the names used here. + The joystick values are in the range -127 to 127. +""" + + def __init__(self, devices): + """Create a Gamepad object that will send USB gamepad HID reports. + + Devices can be a list of devices that includes a gamepad device or a gamepad device + itself. A device is any object that implements ``send_report()``, ``usage_page`` and + ``usage``. + """ + self._gamepad_device = find_device(devices, usage_page=0x1, usage=0x05) + + # Reuse this bytearray to send mouse reports. + # Typically controllers start numbering buttons at 1 rather than 0. + # report[0] buttons 1-8 (LSB is button 1) + # report[1] buttons 9-16 + # report[2] joystick 0 x: -127 to 127 + # report[3] joystick 0 y: -127 to 127 + # report[4] joystick 1 x: -127 to 127 + # report[5] joystick 1 y: -127 to 127 + self._report = bytearray(6) + + # Remember the last report as well, so we can avoid sending + # duplicate reports. + self._last_report = bytearray(6) + + # Store settings separately before putting into report. Saves code + # especially for buttons. + self._buttons_state = 0 + self._joy_x = 0 + self._joy_y = 0 + self._joy_z = 0 + self._joy_r_z = 0 + + # Send an initial report to test if HID device is ready. + # If not, wait a bit and try once more. + try: + self.reset_all() + except OSError: + time.sleep(1) + self.reset_all() + + def press_buttons(self, *buttons): + """Press and hold the given buttons. """ + for button in buttons: + self._buttons_state |= 1 << self._validate_button_number(button) - 1 + self._send() + + def release_buttons(self, *buttons): + """Release the given buttons. """ + for button in buttons: + self._buttons_state &= ~(1 << self._validate_button_number(button) - 1) + self._send() + + def release_all_buttons(self): + """Release all the buttons.""" + + self._buttons_state = 0 + self._send() + + def click_buttons(self, *buttons): + """Press and release the given buttons.""" + self.press_buttons(*buttons) + self.release_buttons(*buttons) + + def move_joysticks(self, x=None, y=None, z=None, r_z=None): + """Set and send the given joystick values. + The joysticks will remain set with the given values until changed + + One joystick provides ``x`` and ``y`` values, + and the other provides ``z`` and ``r_z`` (z rotation). + Any values left as ``None`` will not be changed. + + All values must be in the range -127 to 127 inclusive. + + Examples:: + + # Change x and y values only. + gp.move_joysticks(x=100, y=-50) + + # Reset all joystick values to center position. + gp.move_joysticks(0, 0, 0, 0) + """ + if x is not None: + self._joy_x = self._validate_joystick_value(x) + if y is not None: + self._joy_y = self._validate_joystick_value(y) + if z is not None: + self._joy_z = self._validate_joystick_value(z) + if r_z is not None: + self._joy_r_z = self._validate_joystick_value(r_z) + self._send() + + def reset_all(self): + """Release all buttons and set joysticks to zero.""" + self._buttons_state = 0 + self._joy_x = 0 + self._joy_y = 0 + self._joy_z = 0 + self._joy_r_z = 0 + self._send(always=True) + + def _send(self, always=False): + """Send a report with all the existing settings. + If ``always`` is ``False`` (the default), send only if there have been changes. + """ + struct.pack_into( + "<Hbbbb", + self._report, + 0, + self._buttons_state, + self._joy_x, + self._joy_y, + self._joy_z, + self._joy_r_z, + ) + + if always or self._last_report != self._report: + self._gamepad_device.send_report(self._report) + # Remember what we sent, without allocating new storage. + self._last_report[:] = self._report + + @staticmethod + def _validate_button_number(button): + if not 1 <= button <= 16: + raise ValueError("Button number must in range 1 to 16") + return button + + @staticmethod + def _validate_joystick_value(value): + if not -127 <= value <= 127: + raise ValueError("Joystick value must be in range -127 to 127") + return value diff --git a/preload/adafruit_hid/keyboard.py b/preload/adafruit_hid/keyboard.py new file mode 100644 index 0000000000000000000000000000000000000000..5c0e11deb73168f07e8ac5c65103bc96ea30a170 --- /dev/null +++ b/preload/adafruit_hid/keyboard.py @@ -0,0 +1,164 @@ +# The MIT License (MIT) +# +# Copyright (c) 2017 Dan Halbert +# +# 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_hid.keyboard.Keyboard` +==================================================== + +* Author(s): Scott Shawcroft, Dan Halbert +""" + +import time +from micropython import const + +from .keycode import Keycode + +from . import find_device + +_MAX_KEYPRESSES = const(6) + + +class Keyboard: + """Send HID keyboard reports.""" + + # No more than _MAX_KEYPRESSES regular keys may be pressed at once. + + def __init__(self, devices): + """Create a Keyboard object that will send keyboard HID reports. + + Devices can be a list of devices that includes a keyboard device or a keyboard device + itself. A device is any object that implements ``send_report()``, ``usage_page`` and + ``usage``. + """ + self._keyboard_device = find_device(devices, usage_page=0x1, usage=0x06) + + # Reuse this bytearray to send keyboard reports. + self.report = bytearray(8) + + # report[0] modifiers + # report[1] unused + # report[2:8] regular key presses + + # View onto byte 0 in report. + self.report_modifier = memoryview(self.report)[0:1] + + # List of regular keys currently pressed. + # View onto bytes 2-7 in report. + self.report_keys = memoryview(self.report)[2:] + + # Do a no-op to test if HID device is ready. + # If not, wait a bit and try once more. + try: + self.release_all() + except OSError: + time.sleep(1) + self.release_all() + + def press(self, *keycodes): + """Send a report indicating that the given keys have been pressed. + + :param keycodes: Press these keycodes all at once. + :raises ValueError: if more than six regular keys are pressed. + + Keycodes may be modifiers or regular keys. + No more than six regular keys may be pressed simultaneously. + + Examples:: + + from adafruit_hid.keycode import Keycode + + # Press ctrl-x. + kbd.press(Keycode.LEFT_CONTROL, Keycode.X) + + # Or, more conveniently, use the CONTROL alias for LEFT_CONTROL: + kbd.press(Keycode.CONTROL, Keycode.X) + + # Press a, b, c keys all at once. + kbd.press(Keycode.A, Keycode.B, Keycode.C) + """ + for keycode in keycodes: + self._add_keycode_to_report(keycode) + self._keyboard_device.send_report(self.report) + + def release(self, *keycodes): + """Send a USB HID report indicating that the given keys have been released. + + :param keycodes: Release these keycodes all at once. + + If a keycode to be released was not pressed, it is ignored. + + Example:: + + # release SHIFT key + kbd.release(Keycode.SHIFT) + """ + for keycode in keycodes: + self._remove_keycode_from_report(keycode) + self._keyboard_device.send_report(self.report) + + def release_all(self): + """Release all pressed keys.""" + for i in range(8): + self.report[i] = 0 + self._keyboard_device.send_report(self.report) + + def send(self, *keycodes): + """Press the given keycodes and then release all pressed keys. + + :param keycodes: keycodes to send together + """ + self.press(*keycodes) + self.release_all() + + def _add_keycode_to_report(self, keycode): + """Add a single keycode to the USB HID report.""" + modifier = Keycode.modifier_bit(keycode) + if modifier: + # Set bit for this modifier. + self.report_modifier[0] |= modifier + else: + # Don't press twice. + # (I'd like to use 'not in self.report_keys' here, but that's not implemented.) + for i in range(_MAX_KEYPRESSES): + if self.report_keys[i] == keycode: + # Already pressed. + return + # Put keycode in first empty slot. + for i in range(_MAX_KEYPRESSES): + if self.report_keys[i] == 0: + self.report_keys[i] = keycode + return + # All slots are filled. + raise ValueError("Trying to press more than six keys at once.") + + def _remove_keycode_from_report(self, keycode): + """Remove a single keycode from the report.""" + modifier = Keycode.modifier_bit(keycode) + if modifier: + # Turn off the bit for this modifier. + self.report_modifier[0] &= ~modifier + else: + # Check all the slots, just in case there's a duplicate. (There should not be.) + for i in range(_MAX_KEYPRESSES): + if self.report_keys[i] == keycode: + self.report_keys[i] = 0 diff --git a/preload/adafruit_hid/keyboard_layout_us.py b/preload/adafruit_hid/keyboard_layout_us.py new file mode 100644 index 0000000000000000000000000000000000000000..3c8137f1f67e878df01ac99bb91120d79719048b --- /dev/null +++ b/preload/adafruit_hid/keyboard_layout_us.py @@ -0,0 +1,256 @@ +# The MIT License (MIT) +# +# Copyright (c) 2017 Dan Halbert +# +# 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_hid.keyboard_layout_us.KeyboardLayoutUS` +======================================================= + +* Author(s): Dan Halbert +""" + +from .keycode import Keycode + + +class KeyboardLayoutUS: + """Map ASCII characters to appropriate keypresses on a standard US PC keyboard. + + Non-ASCII characters and most control characters will raise an exception. + """ + + # The ASCII_TO_KEYCODE bytes object is used as a table to maps ASCII 0-127 + # to the corresponding # keycode on a US 104-key keyboard. + # The user should not normally need to use this table, + # but it is not marked as private. + # + # Because the table only goes to 127, we use the top bit of each byte (ox80) to indicate + # that the shift key should be pressed. So any values 0x{8,9,a,b}* are shifted characters. + # + # The Python compiler will concatenate all these bytes literals into a single bytes object. + # Micropython/CircuitPython will store the resulting bytes constant in flash memory + # if it's in a .mpy file, so it doesn't use up valuable RAM. + # + # \x00 entries have no keyboard key and so won't be sent. + SHIFT_FLAG = 0x80 + ASCII_TO_KEYCODE = ( + b"\x00" # NUL + b"\x00" # SOH + b"\x00" # STX + b"\x00" # ETX + b"\x00" # EOT + b"\x00" # ENQ + b"\x00" # ACK + b"\x00" # BEL \a + b"\x2a" # BS BACKSPACE \b (called DELETE in the usb.org document) + b"\x2b" # TAB \t + b"\x28" # LF \n (called Return or ENTER in the usb.org document) + b"\x00" # VT \v + b"\x00" # FF \f + b"\x00" # CR \r + b"\x00" # SO + b"\x00" # SI + b"\x00" # DLE + b"\x00" # DC1 + b"\x00" # DC2 + b"\x00" # DC3 + b"\x00" # DC4 + b"\x00" # NAK + b"\x00" # SYN + b"\x00" # ETB + b"\x00" # CAN + b"\x00" # EM + b"\x00" # SUB + b"\x29" # ESC + b"\x00" # FS + b"\x00" # GS + b"\x00" # RS + b"\x00" # US + b"\x2c" # SPACE + b"\x9e" # ! x1e|SHIFT_FLAG (shift 1) + b"\xb4" # " x34|SHIFT_FLAG (shift ') + b"\xa0" # # x20|SHIFT_FLAG (shift 3) + b"\xa1" # $ x21|SHIFT_FLAG (shift 4) + b"\xa2" # % x22|SHIFT_FLAG (shift 5) + b"\xa4" # & x24|SHIFT_FLAG (shift 7) + b"\x34" # ' + b"\xa6" # ( x26|SHIFT_FLAG (shift 9) + b"\xa7" # ) x27|SHIFT_FLAG (shift 0) + b"\xa5" # * x25|SHIFT_FLAG (shift 8) + b"\xae" # + x2e|SHIFT_FLAG (shift =) + b"\x36" # , + b"\x2d" # - + b"\x37" # . + b"\x38" # / + b"\x27" # 0 + b"\x1e" # 1 + b"\x1f" # 2 + b"\x20" # 3 + b"\x21" # 4 + b"\x22" # 5 + b"\x23" # 6 + b"\x24" # 7 + b"\x25" # 8 + b"\x26" # 9 + b"\xb3" # : x33|SHIFT_FLAG (shift ;) + b"\x33" # ; + b"\xb6" # < x36|SHIFT_FLAG (shift ,) + b"\x2e" # = + b"\xb7" # > x37|SHIFT_FLAG (shift .) + b"\xb8" # ? x38|SHIFT_FLAG (shift /) + b"\x9f" # @ x1f|SHIFT_FLAG (shift 2) + b"\x84" # A x04|SHIFT_FLAG (shift a) + b"\x85" # B x05|SHIFT_FLAG (etc.) + b"\x86" # C x06|SHIFT_FLAG + b"\x87" # D x07|SHIFT_FLAG + b"\x88" # E x08|SHIFT_FLAG + b"\x89" # F x09|SHIFT_FLAG + b"\x8a" # G x0a|SHIFT_FLAG + b"\x8b" # H x0b|SHIFT_FLAG + b"\x8c" # I x0c|SHIFT_FLAG + b"\x8d" # J x0d|SHIFT_FLAG + b"\x8e" # K x0e|SHIFT_FLAG + b"\x8f" # L x0f|SHIFT_FLAG + b"\x90" # M x10|SHIFT_FLAG + b"\x91" # N x11|SHIFT_FLAG + b"\x92" # O x12|SHIFT_FLAG + b"\x93" # P x13|SHIFT_FLAG + b"\x94" # Q x14|SHIFT_FLAG + b"\x95" # R x15|SHIFT_FLAG + b"\x96" # S x16|SHIFT_FLAG + b"\x97" # T x17|SHIFT_FLAG + b"\x98" # U x18|SHIFT_FLAG + b"\x99" # V x19|SHIFT_FLAG + b"\x9a" # W x1a|SHIFT_FLAG + b"\x9b" # X x1b|SHIFT_FLAG + b"\x9c" # Y x1c|SHIFT_FLAG + b"\x9d" # Z x1d|SHIFT_FLAG + b"\x2f" # [ + b"\x31" # \ backslash + b"\x30" # ] + b"\xa3" # ^ x23|SHIFT_FLAG (shift 6) + b"\xad" # _ x2d|SHIFT_FLAG (shift -) + b"\x35" # ` + b"\x04" # a + b"\x05" # b + b"\x06" # c + b"\x07" # d + b"\x08" # e + b"\x09" # f + b"\x0a" # g + b"\x0b" # h + b"\x0c" # i + b"\x0d" # j + b"\x0e" # k + b"\x0f" # l + b"\x10" # m + b"\x11" # n + b"\x12" # o + b"\x13" # p + b"\x14" # q + b"\x15" # r + b"\x16" # s + b"\x17" # t + b"\x18" # u + b"\x19" # v + b"\x1a" # w + b"\x1b" # x + b"\x1c" # y + b"\x1d" # z + b"\xaf" # { x2f|SHIFT_FLAG (shift [) + b"\xb1" # | x31|SHIFT_FLAG (shift \) + b"\xb0" # } x30|SHIFT_FLAG (shift ]) + b"\xb5" # ~ x35|SHIFT_FLAG (shift `) + b"\x4c" # DEL DELETE (called Forward Delete in usb.org document) + ) + + def __init__(self, keyboard): + """Specify the layout for the given keyboard. + + :param keyboard: a Keyboard object. Write characters to this keyboard when requested. + + Example:: + + kbd = Keyboard(usb_hid.devices) + layout = KeyboardLayoutUS(kbd) + """ + + self.keyboard = keyboard + + def write(self, string): + """Type the string by pressing and releasing keys on my keyboard. + + :param string: A string of ASCII characters. + :raises ValueError: if any of the characters are not ASCII or have no keycode + (such as some control characters). + + Example:: + + # Write abc followed by Enter to the keyboard + layout.write('abc\\n') + """ + for char in string: + keycode = self._char_to_keycode(char) + # If this is a shifted char, clear the SHIFT flag and press the SHIFT key. + if keycode & self.SHIFT_FLAG: + keycode &= ~self.SHIFT_FLAG + self.keyboard.press(Keycode.SHIFT) + self.keyboard.press(keycode) + self.keyboard.release_all() + + def keycodes(self, char): + """Return a tuple of keycodes needed to type the given character. + + :param char: A single ASCII character in a string. + :type char: str of length one. + :returns: tuple of Keycode keycodes. + :raises ValueError: if ``char`` is not ASCII or there is no keycode for it. + + Examples:: + + # Returns (Keycode.TAB,) + keycodes('\t') + # Returns (Keycode.A,) + keycode('a') + # Returns (Keycode.SHIFT, Keycode.A) + keycode('A') + # Raises ValueError because it's a accented e and is not ASCII + keycode('é') + """ + keycode = self._char_to_keycode(char) + if keycode & self.SHIFT_FLAG: + return (Keycode.SHIFT, keycode & ~self.SHIFT_FLAG) + + return (keycode,) + + def _char_to_keycode(self, char): + """Return the HID keycode for the given ASCII character, with the SHIFT_FLAG possibly set. + + If the character requires pressing the Shift key, the SHIFT_FLAG bit is set. + You must clear this bit before passing the keycode in a USB report. + """ + char_val = ord(char) + if char_val > 128: + raise ValueError("Not an ASCII character.") + keycode = self.ASCII_TO_KEYCODE[char_val] + if keycode == 0: + raise ValueError("No keycode available for character.") + return keycode diff --git a/preload/adafruit_hid/keycode.py b/preload/adafruit_hid/keycode.py new file mode 100644 index 0000000000000000000000000000000000000000..b6fd7ad3a4fbee42a082870b9c66a14e38510158 --- /dev/null +++ b/preload/adafruit_hid/keycode.py @@ -0,0 +1,315 @@ +# The MIT License (MIT) +# +# Copyright (c) 2017 Scott Shawcroft 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_hid.keycode.Keycode` +==================================================== + +* Author(s): Scott Shawcroft, Dan Halbert +""" + + +class Keycode: + """USB HID Keycode constants. + + This list is modeled after the names for USB keycodes defined in + https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf#page=53. + This list does not include every single code, but does include all the keys on + a regular PC or Mac keyboard. + + Remember that keycodes are the names for key *positions* on a US keyboard, and may + not correspond to the character that you mean to send if you want to emulate non-US keyboard. + For instance, on a French keyboard (AZERTY instead of QWERTY), + the keycode for 'q' is used to indicate an 'a'. Likewise, 'y' represents 'z' on + a German keyboard. This is historical: the idea was that the keycaps could be changed + without changing the keycodes sent, so that different firmware was not needed for + different variations of a keyboard. + """ + + # pylint: disable-msg=invalid-name + A = 0x04 + """``a`` and ``A``""" + B = 0x05 + """``b`` and ``B``""" + C = 0x06 + """``c`` and ``C``""" + D = 0x07 + """``d`` and ``D``""" + E = 0x08 + """``e`` and ``E``""" + F = 0x09 + """``f`` and ``F``""" + G = 0x0A + """``g`` and ``G``""" + H = 0x0B + """``h`` and ``H``""" + I = 0x0C + """``i`` and ``I``""" + J = 0x0D + """``j`` and ``J``""" + K = 0x0E + """``k`` and ``K``""" + L = 0x0F + """``l`` and ``L``""" + M = 0x10 + """``m`` and ``M``""" + N = 0x11 + """``n`` and ``N``""" + O = 0x12 + """``o`` and ``O``""" + P = 0x13 + """``p`` and ``P``""" + Q = 0x14 + """``q`` and ``Q``""" + R = 0x15 + """``r`` and ``R``""" + S = 0x16 + """``s`` and ``S``""" + T = 0x17 + """``t`` and ``T``""" + U = 0x18 + """``u`` and ``U``""" + V = 0x19 + """``v`` and ``V``""" + W = 0x1A + """``w`` and ``W``""" + X = 0x1B + """``x`` and ``X``""" + Y = 0x1C + """``y`` and ``Y``""" + Z = 0x1D + """``z`` and ``Z``""" + + ONE = 0x1E + """``1`` and ``!``""" + TWO = 0x1F + """``2`` and ``@``""" + THREE = 0x20 + """``3`` and ``#``""" + FOUR = 0x21 + """``4`` and ``$``""" + FIVE = 0x22 + """``5`` and ``%``""" + SIX = 0x23 + """``6`` and ``^``""" + SEVEN = 0x24 + """``7`` and ``&``""" + EIGHT = 0x25 + """``8`` and ``*``""" + NINE = 0x26 + """``9`` and ``(``""" + ZERO = 0x27 + """``0`` and ``)``""" + ENTER = 0x28 + """Enter (Return)""" + RETURN = ENTER + """Alias for ``ENTER``""" + ESCAPE = 0x29 + """Escape""" + BACKSPACE = 0x2A + """Delete backward (Backspace)""" + TAB = 0x2B + """Tab and Backtab""" + SPACEBAR = 0x2C + """Spacebar""" + SPACE = SPACEBAR + """Alias for SPACEBAR""" + MINUS = 0x2D + """``-` and ``_``""" + EQUALS = 0x2E + """``=` and ``+``""" + LEFT_BRACKET = 0x2F + """``[`` and ``{``""" + RIGHT_BRACKET = 0x30 + """``]`` and ``}``""" + BACKSLASH = 0x31 + r"""``\`` and ``|``""" + POUND = 0x32 + """``#`` and ``~`` (Non-US keyboard)""" + SEMICOLON = 0x33 + """``;`` and ``:``""" + QUOTE = 0x34 + """``'`` and ``"``""" + GRAVE_ACCENT = 0x35 + r""":literal:`\`` and ``~``""" + COMMA = 0x36 + """``,`` and ``<``""" + PERIOD = 0x37 + """``.`` and ``>``""" + FORWARD_SLASH = 0x38 + """``/`` and ``?``""" + + CAPS_LOCK = 0x39 + """Caps Lock""" + + F1 = 0x3A + """Function key F1""" + F2 = 0x3B + """Function key F2""" + F3 = 0x3C + """Function key F3""" + F4 = 0x3D + """Function key F4""" + F5 = 0x3E + """Function key F5""" + F6 = 0x3F + """Function key F6""" + F7 = 0x40 + """Function key F7""" + F8 = 0x41 + """Function key F8""" + F9 = 0x42 + """Function key F9""" + F10 = 0x43 + """Function key F10""" + F11 = 0x44 + """Function key F11""" + F12 = 0x45 + """Function key F12""" + + PRINT_SCREEN = 0x46 + """Print Screen (SysRq)""" + SCROLL_LOCK = 0x47 + """Scroll Lock""" + PAUSE = 0x48 + """Pause (Break)""" + + INSERT = 0x49 + """Insert""" + HOME = 0x4A + """Home (often moves to beginning of line)""" + PAGE_UP = 0x4B + """Go back one page""" + DELETE = 0x4C + """Delete forward""" + END = 0x4D + """End (often moves to end of line)""" + PAGE_DOWN = 0x4E + """Go forward one page""" + + RIGHT_ARROW = 0x4F + """Move the cursor right""" + LEFT_ARROW = 0x50 + """Move the cursor left""" + DOWN_ARROW = 0x51 + """Move the cursor down""" + UP_ARROW = 0x52 + """Move the cursor up""" + + KEYPAD_NUMLOCK = 0x53 + """Num Lock (Clear on Mac)""" + KEYPAD_FORWARD_SLASH = 0x54 + """Keypad ``/``""" + KEYPAD_ASTERISK = 0x55 + """Keypad ``*``""" + KEYPAD_MINUS = 0x56 + """Keyapd ``-``""" + KEYPAD_PLUS = 0x57 + """Keypad ``+``""" + KEYPAD_ENTER = 0x58 + """Keypad Enter""" + KEYPAD_ONE = 0x59 + """Keypad ``1`` and End""" + KEYPAD_TWO = 0x5A + """Keypad ``2`` and Down Arrow""" + KEYPAD_THREE = 0x5B + """Keypad ``3`` and PgDn""" + KEYPAD_FOUR = 0x5C + """Keypad ``4`` and Left Arrow""" + KEYPAD_FIVE = 0x5D + """Keypad ``5``""" + KEYPAD_SIX = 0x5E + """Keypad ``6`` and Right Arrow""" + KEYPAD_SEVEN = 0x5F + """Keypad ``7`` and Home""" + KEYPAD_EIGHT = 0x60 + """Keypad ``8`` and Up Arrow""" + KEYPAD_NINE = 0x61 + """Keypad ``9`` and PgUp""" + KEYPAD_ZERO = 0x62 + """Keypad ``0`` and Ins""" + KEYPAD_PERIOD = 0x63 + """Keypad ``.`` and Del""" + KEYPAD_BACKSLASH = 0x64 + """Keypad ``\\`` and ``|`` (Non-US)""" + + APPLICATION = 0x65 + """Application: also known as the Menu key (Windows)""" + POWER = 0x66 + """Power (Mac)""" + KEYPAD_EQUALS = 0x67 + """Keypad ``=`` (Mac)""" + F13 = 0x68 + """Function key F13 (Mac)""" + F14 = 0x69 + """Function key F14 (Mac)""" + F15 = 0x6A + """Function key F15 (Mac)""" + F16 = 0x6B + """Function key F16 (Mac)""" + F17 = 0x6C + """Function key F17 (Mac)""" + F18 = 0x6D + """Function key F18 (Mac)""" + F19 = 0x6E + """Function key F19 (Mac)""" + + LEFT_CONTROL = 0xE0 + """Control modifier left of the spacebar""" + CONTROL = LEFT_CONTROL + """Alias for LEFT_CONTROL""" + LEFT_SHIFT = 0xE1 + """Shift modifier left of the spacebar""" + SHIFT = LEFT_SHIFT + """Alias for LEFT_SHIFT""" + LEFT_ALT = 0xE2 + """Alt modifier left of the spacebar""" + ALT = LEFT_ALT + """Alias for LEFT_ALT; Alt is also known as Option (Mac)""" + OPTION = ALT + """Labeled as Option on some Mac keyboards""" + LEFT_GUI = 0xE3 + """GUI modifier left of the spacebar""" + GUI = LEFT_GUI + """Alias for LEFT_GUI; GUI is also known as the Windows key, Command (Mac), or Meta""" + WINDOWS = GUI + """Labeled with a Windows logo on Windows keyboards""" + COMMAND = GUI + """Labeled as Command on Mac keyboards, with a clover glyph""" + RIGHT_CONTROL = 0xE4 + """Control modifier right of the spacebar""" + RIGHT_SHIFT = 0xE5 + """Shift modifier right of the spacebar""" + RIGHT_ALT = 0xE6 + """Alt modifier right of the spacebar""" + RIGHT_GUI = 0xE7 + """GUI modifier right of the spacebar""" + + # pylint: enable-msg=invalid-name + @classmethod + def modifier_bit(cls, keycode): + """Return the modifer bit to be set in an HID keycode report if this is a + modifier key; otherwise return 0.""" + return ( + 1 << (keycode - 0xE0) if cls.LEFT_CONTROL <= keycode <= cls.RIGHT_GUI else 0 + ) diff --git a/preload/adafruit_hid/mouse.py b/preload/adafruit_hid/mouse.py new file mode 100644 index 0000000000000000000000000000000000000000..404d7e19c729cc463febc72396002f4dfda2d190 --- /dev/null +++ b/preload/adafruit_hid/mouse.py @@ -0,0 +1,165 @@ +# The MIT License (MIT) +# +# Copyright (c) 2017 Dan Halbert +# +# 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_hid.mouse.Mouse` +==================================================== + +* Author(s): Dan Halbert +""" +import time + +from . import find_device + + +class Mouse: + """Send USB HID mouse reports.""" + + LEFT_BUTTON = 1 + """Left mouse button.""" + RIGHT_BUTTON = 2 + """Right mouse button.""" + MIDDLE_BUTTON = 4 + """Middle mouse button.""" + + def __init__(self, devices): + """Create a Mouse object that will send USB mouse HID reports. + + Devices can be a list of devices that includes a keyboard device or a keyboard device + itself. A device is any object that implements ``send_report()``, ``usage_page`` and + ``usage``. + """ + self._mouse_device = find_device(devices, usage_page=0x1, usage=0x02) + + # Reuse this bytearray to send mouse reports. + # report[0] buttons pressed (LEFT, MIDDLE, RIGHT) + # report[1] x movement + # report[2] y movement + # report[3] wheel movement + self.report = bytearray(4) + + # Do a no-op to test if HID device is ready. + # If not, wait a bit and try once more. + try: + self._send_no_move() + except OSError: + time.sleep(1) + self._send_no_move() + + def press(self, buttons): + """Press the given mouse buttons. + + :param buttons: a bitwise-or'd combination of ``LEFT_BUTTON``, + ``MIDDLE_BUTTON``, and ``RIGHT_BUTTON``. + + Examples:: + + # Press the left button. + m.press(Mouse.LEFT_BUTTON) + + # Press the left and right buttons simultaneously. + m.press(Mouse.LEFT_BUTTON | Mouse.RIGHT_BUTTON) + """ + self.report[0] |= buttons + self._send_no_move() + + def release(self, buttons): + """Release the given mouse buttons. + + :param buttons: a bitwise-or'd combination of ``LEFT_BUTTON``, + ``MIDDLE_BUTTON``, and ``RIGHT_BUTTON``. + """ + self.report[0] &= ~buttons + self._send_no_move() + + def release_all(self): + """Release all the mouse buttons.""" + self.report[0] = 0 + self._send_no_move() + + def click(self, buttons): + """Press and release the given mouse buttons. + + :param buttons: a bitwise-or'd combination of ``LEFT_BUTTON``, + ``MIDDLE_BUTTON``, and ``RIGHT_BUTTON``. + + Examples:: + + # Click the left button. + m.click(Mouse.LEFT_BUTTON) + + # Double-click the left button. + m.click(Mouse.LEFT_BUTTON) + m.click(Mouse.LEFT_BUTTON) + """ + self.press(buttons) + self.release(buttons) + + def move(self, x=0, y=0, wheel=0): + """Move the mouse and turn the wheel as directed. + + :param x: Move the mouse along the x axis. Negative is to the left, positive + is to the right. + :param y: Move the mouse along the y axis. Negative is upwards on the display, + positive is downwards. + :param wheel: Rotate the wheel this amount. Negative is toward the user, positive + is away from the user. The scrolling effect depends on the host. + + Examples:: + + # Move 100 to the left. Do not move up and down. Do not roll the scroll wheel. + m.move(-100, 0, 0) + # Same, with keyword arguments. + m.move(x=-100) + + # Move diagonally to the upper right. + m.move(50, 20) + # Same. + m.move(x=50, y=-20) + + # Roll the mouse wheel away from the user. + m.move(wheel=1) + """ + # Send multiple reports if necessary to move or scroll requested amounts. + while x != 0 or y != 0 or wheel != 0: + partial_x = self._limit(x) + partial_y = self._limit(y) + partial_wheel = self._limit(wheel) + self.report[1] = partial_x & 0xFF + self.report[2] = partial_y & 0xFF + self.report[3] = partial_wheel & 0xFF + self._mouse_device.send_report(self.report) + x -= partial_x + y -= partial_y + wheel -= partial_wheel + + def _send_no_move(self): + """Send a button-only report.""" + self.report[1] = 0 + self.report[2] = 0 + self.report[3] = 0 + self._mouse_device.send_report(self.report) + + @staticmethod + def _limit(dist): + return min(127, max(-127, dist)) diff --git a/preload/apps/hid.py b/preload/apps/hid.py index 86c4a84238f1f65fb5c074fe89e37235722f572a..25a134ba56d9b84b7d77736cf539af2abdb73d93 100644 --- a/preload/apps/hid.py +++ b/preload/apps/hid.py @@ -1,11 +1,10 @@ -import hid import buttons import color import display +import ble_hid +from adafruit_hid.consumer_control_code import ConsumerControlCode +from adafruit_hid.consumer_control import ConsumerControl -VOLUME_UP = 0xE9 -VOLUME_DOWN = 0xEA -PLAY_PAUSE = 0xCD disp = display.open() disp.clear() @@ -16,6 +15,8 @@ disp.print("Vol+", posy=40, posx=100, fg=color.RED) disp.print("Vol-", posy=60, posx=100, fg=color.GREEN) disp.update() +cc = ConsumerControl(ble_hid.devices) + b_old = buttons.read() while True: b_new = buttons.read() @@ -23,11 +24,8 @@ while True: print(b_new) b_old = b_new if b_new == buttons.TOP_RIGHT: - hid.set_control(VOLUME_UP) - hid.set_control(0) + cc.send(ConsumerControlCode.VOLUME_INCREMENT) elif b_new == buttons.BOTTOM_RIGHT: - hid.set_control(VOLUME_DOWN) - hid.set_control(0) + cc.send(ConsumerControlCode.VOLUME_DECREMENT) elif b_new == buttons.BOTTOM_LEFT: - hid.set_control(PLAY_PAUSE) - hid.set_control(0) + cc.send(ConsumerControlCode.PLAY_PAUSE) diff --git a/pycardium/meson.build b/pycardium/meson.build index 758c565bf4887f0f22479e0c7f5ed2f3d7bd40e7..8621e66128ca22cecdd3d53b021f5ee03d1fab1d 100644 --- a/pycardium/meson.build +++ b/pycardium/meson.build @@ -7,7 +7,6 @@ modsrc = files( 'modules/fat_file.c', 'modules/fat_reader_import.c', 'modules/gpio.c', - 'modules/hid.c', 'modules/interrupt.c', 'modules/light_sensor.c', 'modules/max30001-sys.c', @@ -18,6 +17,7 @@ modsrc = files( 'modules/power.c', 'modules/spo2_algo.c', 'modules/sys_ble.c', + 'modules/sys_ble_hid.c', 'modules/sys_bme680.c', 'modules/sys_display.c', 'modules/sys_leds.c', diff --git a/pycardium/modules/hid.c b/pycardium/modules/hid.c deleted file mode 100644 index 62e198fd6a468dab687967f7fbdb95400fd60ad4..0000000000000000000000000000000000000000 --- a/pycardium/modules/hid.c +++ /dev/null @@ -1,28 +0,0 @@ -#include "epicardium.h" - -#include "py/builtin.h" -#include "py/obj.h" -#include "py/runtime.h" - -static mp_obj_t mp_hid_set_control(mp_obj_t control_code) -{ - int code = mp_obj_get_int(control_code); - uint8_t data[] = { code, code >> 8 }; - int ret = epic_hid_send_report(3, data, sizeof(data)); - return mp_obj_new_int(ret); -} -static MP_DEFINE_CONST_FUN_OBJ_1(hid_set_control_obj, mp_hid_set_control); - -static const mp_rom_map_elem_t hid_module_globals_table[] = { - { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_hid) }, - { MP_ROM_QSTR(MP_QSTR_set_control), MP_ROM_PTR(&hid_set_control_obj) }, -}; -static MP_DEFINE_CONST_DICT(hid_module_globals, hid_module_globals_table); - -const mp_obj_module_t hid_module = { - .base = { &mp_type_module }, - .globals = (mp_obj_dict_t *)&hid_module_globals, -}; - -/* clang-format off */ -MP_REGISTER_MODULE(MP_QSTR_hid, hid_module, MODULE_HID_ENABLED); diff --git a/pycardium/modules/py/ble_hid.py b/pycardium/modules/py/ble_hid.py new file mode 100644 index 0000000000000000000000000000000000000000..9869f017b4b8a9b0abe556870301160502f29a9a --- /dev/null +++ b/pycardium/modules/py/ble_hid.py @@ -0,0 +1,19 @@ +import sys_ble_hid + + +class Report: + def __init__(self, report_id, usage_page, usage): + self.report_id = report_id + self.usage_page = usage_page + self.usage = usage + + def send_report(self, data): + 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), +] diff --git a/pycardium/modules/py/meson.build b/pycardium/modules/py/meson.build index b8d3310f053c8a46febe1935b15686cbcff86c6a..0130c75b8407fb77535b33bff087ab6160c3b5ff 100644 --- a/pycardium/modules/py/meson.build +++ b/pycardium/modules/py/meson.build @@ -1,6 +1,7 @@ python_modules = files( 'config.py', 'bhi160.py', + 'ble_hid.py', 'bme680.py', 'color.py', 'display.py', diff --git a/pycardium/modules/qstrdefs.h b/pycardium/modules/qstrdefs.h index fd33985e04a083572459c0be961051dd10031717..f804089f3c66b8abf03e408b0b7deeedaf5228e0 100644 --- a/pycardium/modules/qstrdefs.h +++ b/pycardium/modules/qstrdefs.h @@ -211,8 +211,8 @@ Q(EVENT_HANDLE_NUMERIC_COMPARISON) Q(EVENT_PAIRING_COMPLETE) Q(EVENT_PAIRING_FAILED) Q(EVENT_SCAN_REPORT) -Q(hid) -Q(set_control) +Q(sys_ble_hid) +Q(send_report) /* SpO2 */ Q(spo2_algo) diff --git a/pycardium/modules/sys_ble_hid.c b/pycardium/modules/sys_ble_hid.c new file mode 100644 index 0000000000000000000000000000000000000000..47e0228ab7594075c0ca934857dd16999e8dc812 --- /dev/null +++ b/pycardium/modules/sys_ble_hid.c @@ -0,0 +1,36 @@ +#include "epicardium.h" + +#include "py/builtin.h" +#include "py/obj.h" +#include "py/runtime.h" + +static mp_obj_t mp_sys_ble_hid_send_report(mp_obj_t report_id, mp_obj_t data) +{ + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(data, &bufinfo, MP_BUFFER_READ); + + int ret = epic_ble_hid_send_report( + mp_obj_get_int(report_id), bufinfo.buf, bufinfo.len + ); + return mp_obj_new_int(ret); +} +static MP_DEFINE_CONST_FUN_OBJ_2( + sys_ble_hid_send_report_obj, mp_sys_ble_hid_send_report +); + +static const mp_rom_map_elem_t sys_ble_hid_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_sys_ble_hid) }, + { MP_ROM_QSTR(MP_QSTR_send_report), + MP_ROM_PTR(&sys_ble_hid_send_report_obj) }, +}; +static MP_DEFINE_CONST_DICT( + sys_ble_hid_module_globals, sys_ble_hid_module_globals_table +); + +const mp_obj_module_t sys_ble_hid_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&sys_ble_hid_module_globals, +}; + +/* clang-format off */ +MP_REGISTER_MODULE(MP_QSTR_sys_ble_hid, sys_ble_hid_module, MODULE_HID_ENABLED);