Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions .github/workflows/quality.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
14 changes: 10 additions & 4 deletions carveracontroller/addons/pendant/whb04.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
57 changes: 57 additions & 0 deletions tests/unit/test_whb04_input.py
Original file line number Diff line number Diff line change
@@ -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}
Loading