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)
@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