mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-07 14:41:24 +02:00
The hourly AMS sensor recorder dispatched humidity and temperature alarms for every unit above threshold without checking whether the unit was actually loaded. Empty AMS units still report ambient readings, so users with one loaded + one empty AMS got useful alarms for the loaded one and hourly noise for the empty one. Disabling the whole alarm category killed both — not a real choice. New _ams_has_filament helper inspects tray_exist_bits (hex bitmap, "0" = empty) with fallback to the tray array's tray_type strings for shapes where the bitmap is missing. The recorder gates the alarm dispatch on this check per-AMS-unit, so a multi-AMS printer with one loaded + one empty still alarms on the loaded one. Sensor history still records regardless of the gate so the System page humidity charts stay continuous — only the outbound notification is suppressed. 9 unit tests cover the bitmap-zero case, bitmap-missing fallback, garbage/blank/int bitmap edges, and defensive malformed tray.
83 lines
3.9 KiB
Python
83 lines
3.9 KiB
Python
"""Tests for the empty-AMS alarm gate (#1619).
|
|
|
|
Empty AMS units still emit humidity/temperature sensor readings, but those
|
|
readings are ambient and not actionable — there's no filament to dry. Without
|
|
the gate every empty AMS spammed an hourly alarm. ``_ams_has_filament``
|
|
inspects the firmware-reported ``tray_exist_bits`` bitmap (fallback: ``tray``
|
|
array's ``tray_type`` strings) so the alarm dispatch in ``record_ams_history``
|
|
can skip empty units while still alarming on loaded ones in the same printer.
|
|
"""
|
|
|
|
from backend.app.main import _ams_has_filament
|
|
|
|
|
|
class TestAmsHasFilament:
|
|
def test_tray_exist_bits_zero_means_empty(self):
|
|
assert _ams_has_filament({"tray_exist_bits": "0"}) is False
|
|
# Real firmware sometimes pads with extra zeros or prefixes; all
|
|
# parseable forms of zero should resolve to "empty".
|
|
assert _ams_has_filament({"tray_exist_bits": "00"}) is False
|
|
assert _ams_has_filament({"tray_exist_bits": "0x0"}) is False
|
|
|
|
def test_tray_exist_bits_nonzero_means_loaded(self):
|
|
# Single tray loaded — e.g. AMS-Lite or AMS-HT.
|
|
assert _ams_has_filament({"tray_exist_bits": "1"}) is True
|
|
# Four-slot AMS with all slots full (bitmap 0xf == 0b1111).
|
|
assert _ams_has_filament({"tray_exist_bits": "f"}) is True
|
|
# Mixed — 0xa == 0b1010, two slots loaded.
|
|
assert _ams_has_filament({"tray_exist_bits": "a"}) is True
|
|
# The exact bitmap seen in #1622 / #1602 logs.
|
|
assert _ams_has_filament({"tray_exist_bits": "ed"}) is True
|
|
|
|
def test_falls_back_to_tray_array_when_bits_missing(self):
|
|
# Empty tray_type strings across the whole tray array → empty AMS.
|
|
ams_empty = {
|
|
"tray": [
|
|
{"id": 0, "tray_type": ""},
|
|
{"id": 1, "tray_type": ""},
|
|
]
|
|
}
|
|
assert _ams_has_filament(ams_empty) is False
|
|
# Any non-empty tray_type → loaded AMS.
|
|
ams_loaded = {
|
|
"tray": [
|
|
{"id": 0, "tray_type": ""},
|
|
{"id": 1, "tray_type": "PLA"},
|
|
]
|
|
}
|
|
assert _ams_has_filament(ams_loaded) is True
|
|
|
|
def test_missing_both_signals_returns_false(self):
|
|
# No tray_exist_bits AND no tray array — early-pushall shape; we
|
|
# treat it as "no info → don't alarm" rather than guessing loaded.
|
|
assert _ams_has_filament({}) is False
|
|
|
|
def test_unparseable_bitmap_falls_back_to_tray_array(self):
|
|
# Garbage in tray_exist_bits — must not raise and must fall through
|
|
# to the tray array check.
|
|
loaded = {"tray_exist_bits": "garbage", "tray": [{"id": 0, "tray_type": "PETG"}]}
|
|
assert _ams_has_filament(loaded) is True
|
|
empty = {"tray_exist_bits": "garbage", "tray": []}
|
|
assert _ams_has_filament(empty) is False
|
|
|
|
def test_empty_bits_string_falls_back_to_tray_array(self):
|
|
# Some pre-handshake pushall shapes set the field but leave it blank.
|
|
loaded = {"tray_exist_bits": "", "tray": [{"id": 0, "tray_type": "ABS"}]}
|
|
assert _ams_has_filament(loaded) is True
|
|
|
|
def test_whitespace_tray_type_is_not_loaded(self):
|
|
# A tray_type that's all whitespace doesn't count as a real material.
|
|
assert _ams_has_filament({"tray": [{"id": 0, "tray_type": " "}]}) is False
|
|
|
|
def test_non_dict_tray_entries_are_skipped(self):
|
|
# Defensive: malformed tray array shouldn't crash the helper.
|
|
assert _ams_has_filament({"tray": [None, "junk", 42]}) is False
|
|
|
|
def test_non_string_bits_falls_back(self):
|
|
# Some MQTT shapes send tray_exist_bits as int; we only parse strings,
|
|
# so an int falls through to the tray array.
|
|
loaded = {"tray_exist_bits": 0xED, "tray": [{"id": 0, "tray_type": "PLA"}]}
|
|
assert _ams_has_filament(loaded) is True
|
|
empty_int = {"tray_exist_bits": 0xED} # no tray array, int ignored
|
|
assert _ams_has_filament(empty_int) is False
|