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: Sanitize missing or malformed spindle values before updating the WHB04 display
- 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: 13 additions & 1 deletion carveracontroller/addons/pendant/pendant.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import json
import logging
import math
from collections.abc import Callable

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -181,7 +182,18 @@ def _handle_display_update(self, daemon: whb04.Daemon) -> None:
except ValueError:
pass
# end of feedrate fix
daemon.set_display_spindle_speed(self._cnc.vars["curspindle"])
spindle_value = self._cnc.vars.get("curspindle", 0)
try:
raw_spindle = float(spindle_value)
except OverflowError:
# A finite integer can exceed float's range; clamp it by sign.
raw_spindle = 65535.0 if spindle_value > 0 else 0.0
except (TypeError, ValueError):
raw_spindle = 0.0
if not math.isfinite(raw_spindle):
raw_spindle = 0.0
safe_spindle = max(0.0, min(raw_spindle, 65535.0))
daemon.set_display_spindle_speed(safe_spindle)

# Update the step indicator to reflect current jog mode
if self._controller.jog_mode == self._controller.JOG_MODE_CONTINUOUS:
Expand Down
49 changes: 49 additions & 0 deletions tests/unit/test_pendant_display.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"""Tests for defensive pendant display updates."""

from types import SimpleNamespace

import pytest

from carveracontroller.addons.pendant import pendant as pendant_module
from carveracontroller.addons.pendant import whb04


@pytest.mark.parametrize(
("spindle_vars", "expected_speed"),
[
({}, 0),
({"curspindle": None}, 0),
({"curspindle": "invalid"}, 0),
({"curspindle": -1}, 0),
({"curspindle": 70000}, 65535),
({"curspindle": "1234.5"}, 1234),
({"curspindle": float("nan")}, 0),
({"curspindle": float("inf")}, 0),
({"curspindle": float("-inf")}, 0),
pytest.param({"curspindle": 10**10000}, 65535, id="huge-positive-integer"),
pytest.param({"curspindle": -(10**10000)}, 0, id="huge-negative-integer"),
],
)
def test_whb04_display_sanitizes_spindle_speed(spindle_vars, expected_speed):
pendant = pendant_module.WHB04.__new__(pendant_module.WHB04)
pendant._cnc = SimpleNamespace(
vars={
"wx": 0.0,
"wy": 0.0,
"wz": 0.0,
"wa": 0.0,
"curfeed": 0.0,
**spindle_vars,
}
)
pendant._controller = SimpleNamespace(
jog_mode=0,
JOG_MODE_CONTINUOUS=1,
JOG_MODE_STEP=0,
)
daemon = whb04.Daemon()
daemon.set_display_spindle_speed(4321)

pendant._handle_display_update(daemon)

assert daemon._display_spindle_speed == expected_speed