Skip to content

Instantly share code, notes, and snippets.

@bitboy85
Last active September 23, 2023 16:16
Show Gist options
  • Save bitboy85/cdcd0e7e04082db414b5f1d23ab09005 to your computer and use it in GitHub Desktop.
Save bitboy85/cdcd0e7e04082db414b5f1d23ab09005 to your computer and use it in GitHub Desktop.
Circuit python 7 absolute mouse example
import usb_hid
# https://stackoverflow.com/questions/36750287/two-byte-report-count-for-hid-report-descriptor
absolute_mouse = usb_hid.Device(
report_descriptor=bytes(
# Absolute mouse
(0x05, 0x01) # Usage Page (Generic Desktop)
+ (0x09, 0x02) # Usage (Mouse)
+ (0xA1, 0x01) # Collection (Application)
+ (0x09, 0x01) # Usage (Pointer)
+ (0xA1, 0x00) # Collection (Physical)
+ (0x85, 0x0B) # Report ID [11 is SET at RUNTIME]
# Buttons
+ (0x05, 0x09) # Usage Page (Button)
+ (0x19, 0x01) # Usage Minimum (0x01)
+ (0x29, 0x05) # Usage Maximum (0x05)
+ (0x15, 0x00) # Logical Minimum (0)
+ (0x25, 0x01) # Logical Maximum (1)
+ (0x95, 0x05) # Report Count (5)
+ (0x75, 0x01) # Report Size (1)
+ (0x81, 0x02) # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
+ (0x75, 0x03) # Report Size (3)
+ (0x95, 0x01) # Report Count (1)
+ (0x81, 0x03) # Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
# Movement
+ (0x05, 0x01) # Usage Page (Generic Desktop Ctrls)
+ (0x09, 0x30) # Usage (X)
+ (0x09, 0x31) # Usage (Y)
+ (0x15, 0x00) # LOGICAL_MINIMUM (0) ; Note: 0x15 = 1 Byte; 0x16 = 2 Byte; 0x17 = 4 Byte
+ (0x26, 0xFF, 0x7F) # LOGICAL_MAXIMUM (32767) ; Note: 0x25 = 1 Byte, 0x26 = 2 Byte; 0x27 = 4 Byte Report
#+ (0x35, 0x00) # Physical Minimum (0)
#+ (0x46, 0xff, 0x7f) # Physical Maximum (32767)
+ (0x75, 0x10) # REPORT_SIZE (16)
+ (0x95, 0x02) # REPORT_COUNT (2)
+ (0x81, 0x02) # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
# Wheel
+ (0x09, 0x38) # Usage (Wheel)
+ (0x15, 0x81) # Logical Minimum (-127)
+ (0x25, 0x7F) # Logical Maximum (127)
#+ (0x35, 0x81) # Physical Minimum (same as logical)
#+ (0x45, 0x7f) # Physical Maximum (same as logical)
+ (0x75, 0x08) # Report Size (8)
+ (0x95, 0x01) # Report Count (1)
+ (0x81, 0x06) # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
+ (0xC0,) # End Collection
+ (0xC0,) # End Collection
),
usage_page=1,
usage=2,
in_report_lengths=(6,), # Number of bytes in the send report = 1 byte for buttons, 2 bytes for x, 2 bytes for y, 1 byte for wheel
out_report_lengths=(0,),
report_ids=(11,),
)
usb_hid.enable((usb_hid.Device.KEYBOARD,), boot_device=1)
usb_hid.enable((absolute_mouse,), boot_device=0)
# SPDX-FileCopyrightText: 2017 Dan Halbert for Adafruit Industries
#
# SPDX-License-Identifier: MIT
"""
`adafruit_hid.mouse.Mouse`
====================================================
* Author(s): Dan Halbert, Bitboy
"""
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)
#print(dir(devices))
# Reuse this bytearray to send mouse reports.
# report[0] buttons pressed (LEFT, MIDDLE, RIGHT)
# report[1] x1 movement
# report[2] x2 movement
# report[3] y1 movement
# report[4] y2 movement
# report[5] wheel movement
self.report = bytearray(6)
# 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: Set pointer on x axis. 32767 = 100% to the right
:param y: Set pointer on y axis. 32767 = 100% to the bottom
: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(1000, 3000, 0)
# Same, with keyword arguments.
m.move(x=1000, y=3000, wheel=0)
# Roll the mouse wheel away from the user.
m.move(wheel=1)
"""
# Wheel
while wheel != 0:
partial_wheel = self._limit(wheel)
print(wheel)
self.report[5] = partial_wheel & 0xFF
self._mouse_device.send_report(self.report)
wheel -= partial_wheel
# Coordinates
x = self._limit_coord(x)
y = self._limit_coord(y)
# HID reports use little endian
x1, x2 = (x & 0xFFFFFFFF).to_bytes(2, 'little')
y1, y2 = (y & 0xFFFFFFFF).to_bytes(2, 'little')
#print(x1)
#print(x2)
#print(y1)
#print(y2)
self.report[1] = x1
self.report[2] = x2
self.report[3] = y1
self.report[4] = y2
self._mouse_device.send_report(self.report)
def _send_no_move(self):
"""Send a button-only report."""
self.report[1] = 0
self.report[2] = 0
self.report[3] = 0
self.report[4] = 0
self._mouse_device.send_report(self.report)
@staticmethod
def _limit(dist):
return min(127, max(-127, dist))
@staticmethod
def _limit_coord(coord):
return min(32767, max(0, coord))
import usb_hid
from adafruit_hid.mouse_abs import Mouse
mouse = Mouse(usb_hid.devices)
# Note: Values are NOT pixels! 32767 = 100% (to right or to bottom)
mouse.move(x=20000, y=2000)
@hdo
Copy link

hdo commented Nov 26, 2021

Thanks for the example. Unfortunately i can't get it running with CurcuitPython 7.0 for PICO :-(

Adafruit CircuitPython 7.0.0 on 2021-09-20; Raspberry Pi Pico with rp2040
Board ID:raspberry_pi_pico
boot.py output:
Traceback (most recent call last):
  File "boot.py", line 46, in <module>
TypeError: 'report_ids' argument required

@bitboy85
Copy link
Author

bitboy85 commented Nov 27, 2021

I guess that param was added in 7.0 final, i used a cp 7 beta if i remember correctly.
Currently i'm not sure what its good for.
https://circuitpython.readthedocs.io/en/latest/shared-bindings/usb_hid/index.html
https://learn.adafruit.com/custom-hid-devices-in-circuitpython/report-descriptors

Edit: Updated. The parameter from line 50 to 52 in boot.py changed from int to a list. Tested with cp 7.1 Beta

@hdo
Copy link

hdo commented Nov 29, 2021

Thanks.

This finally works for me:

adafruit/circuitpython#5461 (comment)

@bitboy85
Copy link
Author

Well, as you can see in the comments of the source code, it is the exact same file :D

@hdo
Copy link

hdo commented Nov 30, 2021

You're right. I got the older revision of your gist where the 'report_ids=(11,)' part was missing. Thanks for the hint :-)

@nerviantone
Copy link

I am a newbie. Just started on RP PICO. How to test this on RP pico ? Should i create a boot.py , mouse_abs.py , sample.py in thonny and run it in RP pico circuit python. I tried this and it doesn't work. Can you tell me how to do this ?

@bitboy85
Copy link
Author

You need to add hid from https://github.com/adafruit/Adafruit_CircuitPython_Bundle/releases.
Then put mouse_abs.py in the same folder as the adafruit files.
boot.py is in the root of your pico. Sample.py is the one to start which moves the cursor to a specific position.

@dglaude
Copy link

dglaude commented Apr 20, 2022

(edit: No error with Adafruit CircuitPython 7.3.0-beta.1 on 2022-04-07; Adafruit Macropad RP2040 with rp2040)

I try to replicate the above with Feather RP2040 and CircuitPython 7.2.5 but I encounter an issue.

code.py output:
Traceback (most recent call last):
  File "code.py", line 4, in <module>
  File "/lib/adafruit_hid/mouse_abs.py", line 47, in __init__
  File "/lib/adafruit_hid/mouse_abs.py", line 151, in _send_no_move
ValueError: Buffer incorrect size. Should be 4 bytes.

I believe it is a mismatch on the report size of the normal mouse.py and the new mouse_abs.py.
42 define self.report = bytearray(6)
47 is self._send_no_move()
151 is self._mouse_device.send_report(self.report)

I don't really know where that check and message is coming from ValueError: Buffer incorrect size. Should be 4 bytes. and what may have change recently either in the firmware or in adafruit_hid.

@bitboy85
Copy link
Author

bitboy85 commented Apr 25, 2022

Don't have a feather. But in boot.py in line 50 it is specified, that the buffer is 6 bytes. So without further knowledge i would say the feather didn't process boot.py correctly.

@dglaude
Copy link

dglaude commented Apr 25, 2022

I think I know what the problem was, why a change of CircuitPython solved it.
I may not have reset or powered off the board, and so there is a mismatch between what is in the file and what is accepted at runtime in code.py. I had the same error but with 4 when moving back to relative mouse descriptor...
Regards.

@dglaude
Copy link

dglaude commented Sep 7, 2022

I would like to confirm that this "Buffer incorrect size." is occuring when you change the boot.py without powercycling the board.
You need the board to re-negotiate with the host at USB level... unplugging and replugging is the best way to garantee that.

Also I have used your code for this demo: https://github.com/dglaude/CircuitPython_udraw_Absolute_Mouse

@dglaude
Copy link

dglaude commented Apr 27, 2023

A fork of my library is now accepted in CircuitPython Community Bundle and can be found here: https://github.com/Neradoc/CircuitPython_Absolute_Mouse
There is a nice very simple example code there.

@bitboy85
Copy link
Author

Nice! Thanks for mentioning my example :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment