Skip to content

Commit

Permalink
Add functional implementation of Pico passthrough
Browse files Browse the repository at this point in the history
The protocol is similar to the Arduino passthrough except it needs an explicit mapping for each key
This includes all required libs for running the code on the Pico

- Add pico /lib exception to Add functional implementation of Pico
  passthrough

The protocol is similar to the Arduino passthrough except it needs an
explicit mapping for each key
This includes all required libs for running the code on the Pico

- Add pico /lib exception to .gitignore

Signed-off-by: Rune Haugaard <[email protected]>
  • Loading branch information
Noloxs committed Jun 25, 2024
1 parent d4aa504 commit ad787ea
Show file tree
Hide file tree
Showing 6 changed files with 675 additions and 37 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#Hell snake specific ignores
/settings.json
!/pico_passthrough/lib/**

# Created by https://www.toptal.com/developers/gitignore/api/python
# Edit at https://www.toptal.com/developers/gitignore?templates=python
Expand All @@ -24,7 +25,7 @@ dist/
downloads/
eggs/
.eggs/
lib/
lib/*
lib64/
parts/
sdist/
Expand Down
41 changes: 19 additions & 22 deletions pico_passthrough/code.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import time
import usb_cdc
import usb_hid
import binascii
from adafruit_hid.keyboard import Keyboard
from adafruit_hid.keycode import Keycode

Expand All @@ -10,29 +11,25 @@
# Initialize USB HID keyboard
keyboard = Keyboard(usb_hid.devices)

def emulate_key_press(key_char):
# Convert the key_char to an appropriate keycode if needed
# This example assumes key_char is a string that maps directly to a key
if key_char.isalpha(): # For alphabetic characters
keycode = getattr(Keycode, key_char.upper())
keyboard.press(keycode)
keyboard.release_all()
def unsigned_to_signed(unsigned_val):
# Ensure the input is within the range of a 2-byte unsigned integer
if not (0 <= unsigned_val <= 0xFFFF):
raise ValueError("Input should be a 2-byte unsigned integer (0 to 65535)")

# If the value is greater than the maximum for a signed 2-byte integer, convert it
if unsigned_val > 0x7FFF: # 32767 in decimal
return unsigned_val - 0x10000 # 65536 in decimal
else:
# For other characters, you'd need a mapping or handle accordingly
pass
return unsigned_val

while True:
if serial.in_waiting >= 1: # Wait for at least 3 bytes (1 byte key + 2 bytes delay)
key_byte = serial.read(1) # Read 1 byte for the key
#print("bytes: "+ str(key_byte))
key = key_byte.decode("utf-8")
print("key: "+key)
if serial.in_waiting >= 3: # Wait for at least 3 bytes (1 byte key + 2 bytes delay)
key_byte = int.from_bytes(serial.read(1), 'big') # Read 1 byte for the key
keyboard.press(int(key_byte))

delay_bytes = serial.read(2) # Read 2 bytes for the delay
delay = int.from_bytes(delay_bytes, 'big') # Convert bytes to integer (big-endian)

print(str(delay))
# Emulate the key press
#emulate_key_press(key)

# Wait for the specified delay
#time.sleep(delay / 1000) # Delay is in milliseconds
delay = unsigned_to_signed(int.from_bytes(delay_bytes, 'big')) # Convert bytes to signed integer (big-endian)
time.sleep(abs(delay)/1000)

if delay >= 0:
keyboard.release(int(key_byte))
90 changes: 90 additions & 0 deletions pico_passthrough/lib/adafruit_hid/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# SPDX-FileCopyrightText: 2017 Scott Shawcroft for Adafruit Industries
#
# SPDX-License-Identifier: MIT

"""
`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
from __future__ import annotations
import time

try:
import supervisor
except ImportError:
supervisor = None

try:
from typing import Sequence
except ImportError:
pass

# usb_hid may not exist on some boards that still provide BLE or other HID devices.
try:
from usb_hid import Device
except ImportError:
Device = None

__version__ = "0.0.0+auto.0"
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_HID.git"


def find_device(
devices: Sequence[object],
*,
usage_page: int,
usage: int,
timeout: int = None,
) -> object:
"""Search through the provided sequence of devices to find the one with the matching
usage_page and usage.
:param timeout: Time in seconds to wait for USB to become ready before timing out.
Defaults to None to wait indefinitely.
Ignored if device is not a `usb_hid.Device`; it might be BLE, for instance."""

if hasattr(devices, "send_report"):
devices = [devices] # type: ignore
device = None
for dev in devices:
if (
dev.usage_page == usage_page
and dev.usage == usage
and hasattr(dev, "send_report")
):
device = dev
break
if device is None:
raise ValueError("Could not find matching HID device.")

# Wait for USB to be connected only if this is a usb_hid.Device.
if Device and isinstance(device, Device):
if supervisor is None:
# Blinka doesn't have supervisor (see issue Adafruit_Blinka#711), so wait
# one second for USB to become ready
time.sleep(1.0)
elif timeout is None:
# default behavior: wait indefinitely for USB to become ready
while not supervisor.runtime.usb_connected:
time.sleep(1.0)
else:
# wait up to timeout seconds for USB to become ready
for _ in range(timeout):
if supervisor.runtime.usb_connected:
return device
time.sleep(1.0)
raise OSError("Failed to initialize HID device. Is USB connected?")

return device
207 changes: 207 additions & 0 deletions pico_passthrough/lib/adafruit_hid/keyboard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
# SPDX-FileCopyrightText: 2017 Dan Halbert for Adafruit Industries
#
# SPDX-License-Identifier: MIT

"""
`adafruit_hid.keyboard.Keyboard`
====================================================
* Author(s): Scott Shawcroft, Dan Halbert
"""

from micropython import const
import usb_hid

from .keycode import Keycode

from . import find_device

try:
from typing import Sequence
except: # pylint: disable=bare-except
pass

_MAX_KEYPRESSES = const(6)


class Keyboard:
"""Send HID keyboard reports."""

LED_NUM_LOCK = 0x01
"""LED Usage ID for Num Lock"""
LED_CAPS_LOCK = 0x02
"""LED Usage ID for Caps Lock"""
LED_SCROLL_LOCK = 0x04
"""LED Usage ID for Scroll Lock"""
LED_COMPOSE = 0x08
"""LED Usage ID for Compose"""

# No more than _MAX_KEYPRESSES regular keys may be pressed at once.

def __init__(self, devices: Sequence[usb_hid.Device], timeout: int = None) -> None:
"""Create a Keyboard object that will send keyboard HID reports.
:param timeout: Time in seconds to wait for USB to become ready before timing out.
Defaults to None to wait indefinitely.
Devices can be a sequence 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, timeout=timeout
)

# 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:]

# No keyboard LEDs on.
self._led_status = b"\x00"

def press(self, *keycodes: int) -> None:
"""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: int) -> None:
"""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) -> None:
"""Release all pressed keys."""
for i in range(8):
self.report[i] = 0
self._keyboard_device.send_report(self.report)

def send(self, *keycodes: int) -> None:
"""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: int) -> None:
"""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:
report_keys = self.report_keys
# Don't press twice.
for i in range(_MAX_KEYPRESSES):
report_key = report_keys[i]
if report_key == 0:
# Put keycode in first empty slot. Since the report_keys
# are compact and unique, this is not a repeated key
report_keys[i] = keycode
return
if report_key == keycode:
# Already pressed.
return
# All slots are filled. Shuffle down and reuse last slot
for i in range(_MAX_KEYPRESSES - 1):
report_keys[i] = report_keys[i + 1]
report_keys[-1] = keycode

def _remove_keycode_from_report(self, keycode: int) -> None:
"""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:
report_keys = self.report_keys
# Clear the at most one matching slot and move remaining keys down
j = 0
for i in range(_MAX_KEYPRESSES):
pressed = report_keys[i]
if not pressed:
break # Handled all used report slots
if pressed == keycode:
continue # Remove this entry
if i != j:
report_keys[j] = report_keys[i]
j += 1
# Clear any remaining slots
while j < _MAX_KEYPRESSES and report_keys[j]:
report_keys[j] = 0
j += 1

@property
def led_status(self) -> bytes:
"""Returns the last received report"""
# get_last_received_report() returns None when nothing was received
led_report = self._keyboard_device.get_last_received_report()
if led_report is not None:
self._led_status = led_report
return self._led_status

def led_on(self, led_code: int) -> bool:
"""Returns whether an LED is on based on the led code
Examples::
import usb_hid
from adafruit_hid.keyboard import Keyboard
from adafruit_hid.keycode import Keycode
import time
# Initialize Keyboard
kbd = Keyboard(usb_hid.devices)
# Press and release CapsLock.
kbd.press(Keycode.CAPS_LOCK)
time.sleep(.09)
kbd.release(Keycode.CAPS_LOCK)
# Check status of the LED_CAPS_LOCK
print(kbd.led_on(Keyboard.LED_CAPS_LOCK))
"""
return bool(self.led_status[0] & led_code)
Loading

0 comments on commit ad787ea

Please sign in to comment.