From 75f986617391f04ec401361afa1a7c23639fd9d9 Mon Sep 17 00:00:00 2001 From: RetiredWizard Date: Wed, 17 Sep 2025 23:37:13 -0400 Subject: [PATCH 1/7] Larsio_Paint_Music: refactor for use with adafruit_usb_host_mouse library --- Fruit_Jam/Larsio_Paint_Music/code.py | 15 +- .../Larsio_Paint_Music/display_manager.py | 2 + Fruit_Jam/Larsio_Paint_Music/input_handler.py | 222 ++++-------------- 3 files changed, 53 insertions(+), 186 deletions(-) diff --git a/Fruit_Jam/Larsio_Paint_Music/code.py b/Fruit_Jam/Larsio_Paint_Music/code.py index 4328fa54d..8beac0d3f 100755 --- a/Fruit_Jam/Larsio_Paint_Music/code.py +++ b/Fruit_Jam/Larsio_Paint_Music/code.py @@ -73,18 +73,9 @@ def run(self): MAX_ATTEMPTS = 5 RETRY_DELAY = 1 # seconds - mouse_found = False - for attempt in range(MAX_ATTEMPTS): - print(f"Mouse detection attempt {attempt+1}/{MAX_ATTEMPTS}") - if self.ui_manager.find_mouse(): - mouse_found = True - print("Mouse found successfully!") - break - - print(f"Mouse detection attempt {attempt+1} failed, retrying...") - time.sleep(RETRY_DELAY) - - if not mouse_found: + if self.ui_manager.find_mouse(): + print("Mouse found successfully!") + else: print("WARNING: Mouse not found after multiple attempts.") print("The application will run, but mouse control may be limited.") diff --git a/Fruit_Jam/Larsio_Paint_Music/display_manager.py b/Fruit_Jam/Larsio_Paint_Music/display_manager.py index 9699c3476..9d2818f88 100755 --- a/Fruit_Jam/Larsio_Paint_Music/display_manager.py +++ b/Fruit_Jam/Larsio_Paint_Music/display_manager.py @@ -6,6 +6,7 @@ """ # pylint: disable=import-error,invalid-name,no-member,too-many-instance-attributes,too-many-arguments,too-many-branches,too-many-statements +import supervisor import displayio import picodvi import framebufferio @@ -38,6 +39,7 @@ def initialize_display(self): # Create the display self.display = framebufferio.FramebufferDisplay(fb) + supervisor.runtime.display = self.display # Keep display on in runtime info # Create main group self.main_group = displayio.Group() diff --git a/Fruit_Jam/Larsio_Paint_Music/input_handler.py b/Fruit_Jam/Larsio_Paint_Music/input_handler.py index 1fb7e16d8..002bb5e89 100755 --- a/Fruit_Jam/Larsio_Paint_Music/input_handler.py +++ b/Fruit_Jam/Larsio_Paint_Music/input_handler.py @@ -6,12 +6,8 @@ """ import array -import time import gc - -# pylint: disable=import-error -import usb.core - +from adafruit_usb_host_mouse import find_and_init_boot_mouse # pylint: disable=invalid-name,no-member,too-many-instance-attributes,too-many-arguments # pylint: disable=too-many-branches,too-many-statements,broad-except @@ -34,182 +30,60 @@ def __init__(self, screen_width, screen_height, staff_y_start, staff_height): self.buf = None self.in_endpoint = None + self.sensitivity = 4 # Sensitivity factor for mouse movement + # Mouse position - self.mouse_x = screen_width // 2 - self.mouse_y = screen_height // 2 + self.mouse_x = (screen_width // 2) * self.sensitivity + self.mouse_y = (screen_height // 2) * self.sensitivity def find_mouse(self): - """Find the mouse device with multiple retry attempts""" - MAX_ATTEMPTS = 5 - RETRY_DELAY = 1 # seconds - - for attempt in range(MAX_ATTEMPTS): - try: - print(f"Mouse detection attempt {attempt+1}/{MAX_ATTEMPTS}") - - # Constants for USB control transfers - DIR_OUT = 0 - # DIR_IN = 0x80 # Unused variable - REQTYPE_CLASS = 1 << 5 - REQREC_INTERFACE = 1 << 0 - HID_REQ_SET_PROTOCOL = 0x0B - - # Find all USB devices - devices_found = False - for device in usb.core.find(find_all=True): - devices_found = True - print(f"Found device: {device.idVendor:04x}:{device.idProduct:04x}") - - try: - # Try to get device info - try: - manufacturer = device.manufacturer - product = device.product - except Exception: # pylint: disable=broad-except - manufacturer = "Unknown" - product = "Unknown" - - # Just use whatever device we find - self.mouse = device - - # Try to detach kernel driver - try: - has_kernel_driver = hasattr(device, 'is_kernel_driver_active') - if has_kernel_driver and device.is_kernel_driver_active(0): - device.detach_kernel_driver(0) - except Exception as e: # pylint: disable=broad-except - print(f"Error detaching kernel driver: {e}") - - # Set configuration - try: - device.set_configuration() - except Exception as e: # pylint: disable=broad-except - print(f"Error setting configuration: {e}") - continue # Try next device - - # Just assume endpoint 0x81 (common for mice) - self.in_endpoint = 0x81 - print(f"Using mouse: {manufacturer}, {product}") - - # Set to report protocol mode - try: - bmRequestType = DIR_OUT | REQTYPE_CLASS | REQREC_INTERFACE - bRequest = HID_REQ_SET_PROTOCOL - wValue = 1 # 1 = report protocol - wIndex = 0 # First interface - - buf = bytearray(1) - device.ctrl_transfer(bmRequestType, bRequest, wValue, wIndex, buf) - print("Set to report protocol mode") - except Exception as e: # pylint: disable=broad-except - print(f"Could not set protocol: {e}") - - # Buffer for reading data - self.buf = array.array("B", [0] * 4) - print("Created 4-byte buffer for mouse data") - - # Verify mouse works by reading from it - try: - # Try to read some data with a short timeout - data = device.read(self.in_endpoint, self.buf, timeout=100) - print(f"Mouse test read successful: {data} bytes") - return True - except usb.core.USBTimeoutError: - # Timeout is normal if mouse isn't moving - print("Mouse connected but not sending data (normal)") - return True - except Exception as e: # pylint: disable=broad-except - print(f"Mouse test read failed: {e}") - # Continue to try next device or retry - self.mouse = None - self.in_endpoint = None - continue - - except Exception as e: # pylint: disable=broad-except - print(f"Error initializing device: {e}") - continue - - if not devices_found: - print("No USB devices found") - - # If we get here without returning, no suitable mouse was found - print(f"No working mouse found on attempt {attempt+1}, retrying...") - gc.collect() - time.sleep(RETRY_DELAY) - - except Exception as e: # pylint: disable=broad-except - print(f"Error during mouse detection: {e}") - gc.collect() - time.sleep(RETRY_DELAY) - - print("Failed to find a working mouse after multiple attempts") - return False - - def process_mouse_input(self): - """Process mouse input - simplified version without wheel support""" - try: - # Attempt to read data from the mouse (10ms timeout) - count = self.mouse.read(self.in_endpoint, self.buf, timeout=10) - - if count >= 3: # We need at least buttons, X and Y - # Extract mouse button states - buttons = self.buf[0] - x = self.buf[1] - y = self.buf[2] - - # Convert to signed values if needed - if x > 127: - x = x - 256 - if y > 127: - y = y - 256 - - # Extract button states - current_left_button_state = buttons & 0x01 - current_right_button_state = (buttons & 0x02) >> 1 - - # Detect button presses - if current_left_button_state == 1 and self.last_left_button_state == 0: - self.left_button_pressed = True - else: - self.left_button_pressed = False - - if current_right_button_state == 1 and self.last_right_button_state == 0: - self.right_button_pressed = True - else: - self.right_button_pressed = False - - # Update button states - self.last_left_button_state = current_left_button_state - self.last_right_button_state = current_right_button_state - - # Update position - self.mouse_x += x - self.mouse_y += y - - # Ensure position stays within bounds - self.mouse_x = max(0, min(self.SCREEN_WIDTH - 1, self.mouse_x)) - self.mouse_y = max(0, min(self.SCREEN_HEIGHT - 1, self.mouse_y)) - - return True - + self.mouse = find_and_init_boot_mouse() + if self.mouse is None: + print("Failed to find a working mouse after multiple attempts") return False - except usb.core.USBError as e: - # Handle timeouts silently - if e.errno == 110: # Operation timed out - return False + # Change the mouse resolution so it's not too sensitive + self.mouse.display_size = \ + (self.SCREEN_WIDTH*self.sensitivity, self.SCREEN_HEIGHT*self.sensitivity) - # Handle disconnections - if e.errno == 19: # No such device - print("Mouse disconnected") - self.mouse = None - self.in_endpoint = None - gc.collect() + return True - return False - except Exception as e: # pylint: disable=broad-except - print(f"Error reading mouse: {type(e).__name__}") - return False + def process_mouse_input(self): + """Process mouse input - simplified version without wheel support""" + buttons = self.mouse.update() + + # Extract button states + if buttons is None: + current_left_button_state = 0 + current_right_button_state = 0 + else: + current_left_button_state = 1 if 'left' in buttons else 0 + current_right_button_state = 1 if 'right' in buttons else 0 + + # Detect button presses + if current_left_button_state == 1 and self.last_left_button_state == 0: + self.left_button_pressed = True + else: + self.left_button_pressed = False + + if current_right_button_state == 1 and self.last_right_button_state == 0: + self.right_button_pressed = True + else: + self.right_button_pressed = False + + # Update button states + self.last_left_button_state = current_left_button_state + self.last_right_button_state = current_right_button_state + + # Update position + self.mouse_x = self.mouse.x // self.sensitivity + self.mouse_y = self.mouse.y // self.sensitivity + + # Ensure position stays within bounds + self.mouse_x = max(0, min(self.SCREEN_WIDTH - 1, self.mouse_x)) + self.mouse_y = max(0, min(self.SCREEN_HEIGHT - 1, self.mouse_y)) + + return True def point_in_rect(self, x, y, rect_x, rect_y, rect_width, rect_height): """Check if a point is inside a rectangle""" From 3188d877dbe7a9b05a5af5d950dc0707cead0232 Mon Sep 17 00:00:00 2001 From: RetiredWizard Date: Thu, 18 Sep 2025 01:44:59 -0400 Subject: [PATCH 2/7] pylint fixes --- Fruit_Jam/Larsio_Paint_Music/code.py | 7 ++----- Fruit_Jam/Larsio_Paint_Music/input_handler.py | 2 ++ 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Fruit_Jam/Larsio_Paint_Music/code.py b/Fruit_Jam/Larsio_Paint_Music/code.py index 8beac0d3f..7f449e14d 100755 --- a/Fruit_Jam/Larsio_Paint_Music/code.py +++ b/Fruit_Jam/Larsio_Paint_Music/code.py @@ -69,14 +69,11 @@ def run(self): time.sleep(0.5) gc.collect() - # Try to find the mouse with multiple attempts - MAX_ATTEMPTS = 5 - RETRY_DELAY = 1 # seconds - + # Try to find the mouse if self.ui_manager.find_mouse(): print("Mouse found successfully!") else: - print("WARNING: Mouse not found after multiple attempts.") + print("WARNING: Mouse not found.") print("The application will run, but mouse control may be limited.") # Enter the main loop diff --git a/Fruit_Jam/Larsio_Paint_Music/input_handler.py b/Fruit_Jam/Larsio_Paint_Music/input_handler.py index 002bb5e89..607c5ed49 100755 --- a/Fruit_Jam/Larsio_Paint_Music/input_handler.py +++ b/Fruit_Jam/Larsio_Paint_Music/input_handler.py @@ -7,6 +7,8 @@ import array import gc + +# pylint: disable=import-error from adafruit_usb_host_mouse import find_and_init_boot_mouse # pylint: disable=invalid-name,no-member,too-many-instance-attributes,too-many-arguments From 0d38659f8abeaa6c12676211a07011cc937b0a8f Mon Sep 17 00:00:00 2001 From: RetiredWizard Date: Thu, 18 Sep 2025 01:51:05 -0400 Subject: [PATCH 3/7] pylint fixes --- Fruit_Jam/Larsio_Paint_Music/input_handler.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Fruit_Jam/Larsio_Paint_Music/input_handler.py b/Fruit_Jam/Larsio_Paint_Music/input_handler.py index 607c5ed49..6976d9a3c 100755 --- a/Fruit_Jam/Larsio_Paint_Music/input_handler.py +++ b/Fruit_Jam/Larsio_Paint_Music/input_handler.py @@ -5,10 +5,6 @@ # input_handler.py: CircuitPython Music Staff Application component """ -import array -import gc - -# pylint: disable=import-error from adafruit_usb_host_mouse import find_and_init_boot_mouse # pylint: disable=invalid-name,no-member,too-many-instance-attributes,too-many-arguments From 0215fced557b4f2a11571317aede18ab319e8e34 Mon Sep 17 00:00:00 2001 From: RetiredWizard Date: Thu, 18 Sep 2025 17:19:02 -0400 Subject: [PATCH 4/7] improve fallback logic with deinit and reusable display init --- Fruit_Jam/Larsio_Paint_Music/display_manager.py | 16 +++++----------- Fruit_Jam/Larsio_Paint_Music/input_handler.py | 2 +- Fruit_Jam/Larsio_Paint_Music/sound_manager.py | 1 + 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/Fruit_Jam/Larsio_Paint_Music/display_manager.py b/Fruit_Jam/Larsio_Paint_Music/display_manager.py index 9d2818f88..def5affb6 100755 --- a/Fruit_Jam/Larsio_Paint_Music/display_manager.py +++ b/Fruit_Jam/Larsio_Paint_Music/display_manager.py @@ -11,6 +11,7 @@ import picodvi import framebufferio import board +from adafruit_fruitjam.peripherals import request_display_config @@ -26,20 +27,13 @@ def __init__(self, width=320, height=240): def initialize_display(self): """Initialize the DVI display""" - # Release any existing displays - displayio.release_displays() + # Use the Fruit Jam library to set up display, that way on DAC fallback + # the display doesn't attempt to be re-initialized - # Initialize the DVI framebuffer - fb = picodvi.Framebuffer(self.SCREEN_WIDTH, self.SCREEN_HEIGHT, - clk_dp=board.CKP, clk_dn=board.CKN, - red_dp=board.D0P, red_dn=board.D0N, - green_dp=board.D1P, green_dn=board.D1N, - blue_dp=board.D2P, blue_dn=board.D2N, - color_depth=16) + request_display_config(self.SCREEN_WIDTH, self.SCREEN_HEIGHT,color_depth=16) # Create the display - self.display = framebufferio.FramebufferDisplay(fb) - supervisor.runtime.display = self.display # Keep display on in runtime info + self.display = supervisor.runtime.display # Create main group self.main_group = displayio.Group() diff --git a/Fruit_Jam/Larsio_Paint_Music/input_handler.py b/Fruit_Jam/Larsio_Paint_Music/input_handler.py index 6976d9a3c..890cbd744 100755 --- a/Fruit_Jam/Larsio_Paint_Music/input_handler.py +++ b/Fruit_Jam/Larsio_Paint_Music/input_handler.py @@ -35,7 +35,7 @@ def __init__(self, screen_width, screen_height, staff_y_start, staff_height): self.mouse_y = (screen_height // 2) * self.sensitivity def find_mouse(self): - self.mouse = find_and_init_boot_mouse() + self.mouse = find_and_init_boot_mouse(cursor_image="sprites/lars_note.bmp") if self.mouse is None: print("Failed to find a working mouse after multiple attempts") return False diff --git a/Fruit_Jam/Larsio_Paint_Music/sound_manager.py b/Fruit_Jam/Larsio_Paint_Music/sound_manager.py index 47c979a14..981e44ab9 100755 --- a/Fruit_Jam/Larsio_Paint_Music/sound_manager.py +++ b/Fruit_Jam/Larsio_Paint_Music/sound_manager.py @@ -126,6 +126,7 @@ def __init__(self, audio_output="pwm", seconds_per_eighth=0.25): except Exception as e: print(f"Error initializing TLV320 DAC: {e}") print("Falling back to PWM audio output") + fjPeriphs.deinit() # Fallback to PWM if I2S initialization fails self.audio = audiopwmio.PWMAudioOut(board.D10) From 94f874780569ff8ce4584444a7d407bca8d7fc42 Mon Sep 17 00:00:00 2001 From: RetiredWizard Date: Thu, 18 Sep 2025 17:23:54 -0400 Subject: [PATCH 5/7] pylint reminded me to remove unused imports --- Fruit_Jam/Larsio_Paint_Music/display_manager.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/Fruit_Jam/Larsio_Paint_Music/display_manager.py b/Fruit_Jam/Larsio_Paint_Music/display_manager.py index def5affb6..ede7e296a 100755 --- a/Fruit_Jam/Larsio_Paint_Music/display_manager.py +++ b/Fruit_Jam/Larsio_Paint_Music/display_manager.py @@ -8,9 +8,6 @@ import supervisor import displayio -import picodvi -import framebufferio -import board from adafruit_fruitjam.peripherals import request_display_config From b98fe5d71027d3a033f9023d6ad8b60d0a67d854 Mon Sep 17 00:00:00 2001 From: RetiredWizard Date: Thu, 18 Sep 2025 17:43:44 -0400 Subject: [PATCH 6/7] Use new sensitivity parameter in Host library --- Fruit_Jam/Larsio_Paint_Music/input_handler.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/Fruit_Jam/Larsio_Paint_Music/input_handler.py b/Fruit_Jam/Larsio_Paint_Music/input_handler.py index 890cbd744..7a2feb9ac 100755 --- a/Fruit_Jam/Larsio_Paint_Music/input_handler.py +++ b/Fruit_Jam/Larsio_Paint_Music/input_handler.py @@ -28,11 +28,9 @@ def __init__(self, screen_width, screen_height, staff_y_start, staff_height): self.buf = None self.in_endpoint = None - self.sensitivity = 4 # Sensitivity factor for mouse movement - # Mouse position - self.mouse_x = (screen_width // 2) * self.sensitivity - self.mouse_y = (screen_height // 2) * self.sensitivity + self.mouse_x = screen_width // 2 + self.mouse_y = screen_height // 2 def find_mouse(self): self.mouse = find_and_init_boot_mouse(cursor_image="sprites/lars_note.bmp") @@ -41,8 +39,7 @@ def find_mouse(self): return False # Change the mouse resolution so it's not too sensitive - self.mouse.display_size = \ - (self.SCREEN_WIDTH*self.sensitivity, self.SCREEN_HEIGHT*self.sensitivity) + self.mouse.sensitivity = 4 return True @@ -74,8 +71,8 @@ def process_mouse_input(self): self.last_right_button_state = current_right_button_state # Update position - self.mouse_x = self.mouse.x // self.sensitivity - self.mouse_y = self.mouse.y // self.sensitivity + self.mouse_x = self.mouse.x + self.mouse_y = self.mouse.y # Ensure position stays within bounds self.mouse_x = max(0, min(self.SCREEN_WIDTH - 1, self.mouse_x)) From a24f8cd1ce921128d34453dd93398f2118183d8e Mon Sep 17 00:00:00 2001 From: foamyguy Date: Mon, 29 Sep 2025 11:11:55 -0500 Subject: [PATCH 7/7] Use None for cursor_image --- Fruit_Jam/Larsio_Paint_Music/input_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Fruit_Jam/Larsio_Paint_Music/input_handler.py b/Fruit_Jam/Larsio_Paint_Music/input_handler.py index 7a2feb9ac..23e7d3151 100755 --- a/Fruit_Jam/Larsio_Paint_Music/input_handler.py +++ b/Fruit_Jam/Larsio_Paint_Music/input_handler.py @@ -33,7 +33,7 @@ def __init__(self, screen_width, screen_height, staff_y_start, staff_height): self.mouse_y = screen_height // 2 def find_mouse(self): - self.mouse = find_and_init_boot_mouse(cursor_image="sprites/lars_note.bmp") + self.mouse = find_and_init_boot_mouse(cursor_image=None) if self.mouse is None: print("Failed to find a working mouse after multiple attempts") return False