From 51de21689dcf7bf98c0f2db0a9136a2ada91114e Mon Sep 17 00:00:00 2001 From: Wes Ladd Date: Mon, 13 Jul 2026 01:36:18 -0500 Subject: [PATCH 1/2] Ignore unknown WHB04 button codes --- CHANGELOG.md | 1 + carveracontroller/addons/pendant/whb04.py | 14 ++++-- tests/unit/test_whb04_input.py | 57 +++++++++++++++++++++++ 3 files changed, 68 insertions(+), 4 deletions(-) create mode 100644 tests/unit/test_whb04_input.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 603ca7b9..12c8a0ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ +- Fixed: Ignore unknown WHB04 button values without reconnecting or dropping valid paired inputs - Enhancement: Add multi-select to the remote file browser - Enhancement: Display error message in halt popup. Requires halt errors to start with "ERROR: " in the firmware - Enhancement: Add popup notice when using stock firmware instead of the Community Firmware diff --git a/carveracontroller/addons/pendant/whb04.py b/carveracontroller/addons/pendant/whb04.py index 4143775e..29e58c41 100644 --- a/carveracontroller/addons/pendant/whb04.py +++ b/carveracontroller/addons/pendant/whb04.py @@ -408,10 +408,16 @@ def _process_input_packet(self, data: bytes) -> None: return pressed_buttons = set() - if button1 != 0: - pressed_buttons.add(Button(button1)) - if button2 != 0: - pressed_buttons.add(Button(button2)) + for raw_button in (button1, button2): + if raw_button == 0: + continue + try: + pressed_buttons.add(Button(raw_button)) + except ValueError: + # Fast or noisy inputs can produce transient button values, + # just as they do for the rotary controls below. Preserve any + # valid button from the same packet instead of reconnecting. + pass newly_pressed = pressed_buttons - self._pressed_buttons newly_released = self._pressed_buttons - pressed_buttons self._pressed_buttons = pressed_buttons diff --git a/tests/unit/test_whb04_input.py b/tests/unit/test_whb04_input.py new file mode 100644 index 00000000..60301457 --- /dev/null +++ b/tests/unit/test_whb04_input.py @@ -0,0 +1,57 @@ +"""Tests for defensive WHB04 input packet handling.""" + +import importlib.util +import struct +import sys +import types +from pathlib import Path + +import pytest + +try: + import hid # noqa: F401 +except ModuleNotFoundError: + hid = types.ModuleType("hid") + hid.Device = type("Device", (), {}) + hid.DeviceInfo = dict + hid.enumerate = lambda: [] + sys.modules["hid"] = hid + +MODULE_PATH = Path(__file__).parents[2] / "carveracontroller/addons/pendant/whb04.py" +SPEC = importlib.util.spec_from_file_location("whb04_under_test", MODULE_PATH) +assert SPEC is not None and SPEC.loader is not None +whb04 = importlib.util.module_from_spec(SPEC) +sys.modules[SPEC.name] = whb04 +SPEC.loader.exec_module(whb04) + + +@pytest.mark.parametrize( + ("valid_button", "unknown_first"), + [ + (whb04.Button.STOP, False), + (whb04.Button.STOP, True), + (whb04.Button.RESET, False), + (whb04.Button.RESET, True), + ], +) +def test_unknown_button_does_not_drop_valid_button_from_same_packet(valid_button, unknown_first): + daemon = whb04.Daemon() + pressed = [] + daemon.on_button_press = lambda _daemon, button: pressed.append(button) + button1, button2 = (0xFF, valid_button.value) if unknown_first else (valid_button.value, 0xFF) + packet = struct.pack( + "BBBBBBbB", + 0x04, + 0, + button1, + button2, + whb04.StepSize.LEAD.value, + whb04.Axis.OFF.value, + 0, + 0, + ) + + daemon._process_input_packet(packet) + + assert pressed == [valid_button] + assert daemon.pressed_buttons == {valid_button} From dba9568690d690700acd0bf9afabf7d6576563a6 Mon Sep 17 00:00:00 2001 From: Wes Ladd Date: Mon, 13 Jul 2026 19:26:20 -0500 Subject: [PATCH 2/2] Install Linux prerequisites in quality workflow --- .github/workflows/quality.yaml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml index 6b9806ec..15c71577 100644 --- a/.github/workflows/quality.yaml +++ b/.github/workflows/quality.yaml @@ -28,17 +28,18 @@ jobs: path: .venv key: ${{ runner.os }}-quality-py3.12-${{ hashFiles('poetry.lock') }} - - name: Install Linux test prerequisites - run: | - sudo apt-get update - sudo apt-get install -y xvfb libgl1 libgles2 libmtdev1 - - name: Install poetry and dependencies uses: ./.github/actions/bootstrap-poetry with: os: linux install-args: --only main,lint,test + - name: Install Linux prerequisites + run: poetry run ./scripts/install_linux_prereqs.sh + + - name: Install Linux test prerequisites + run: sudo apt-get install -y xvfb libgl1 libgles2 libmtdev1 + - name: Run quality hooks run: poetry run pre-commit run --all-files