mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-09-30 03:01:21 +02:00
140 lines
6.3 KiB
Python
140 lines
6.3 KiB
Python
"""The HMS catalogue generated from Bambu Studio's HMS files (#2728)."""
|
|
|
|
import json
|
|
import re
|
|
|
|
import pytest
|
|
|
|
from backend.app.services import hms_errors
|
|
from backend.app.services.hms_errors import (
|
|
alert_level_from_print_error,
|
|
describe_fault,
|
|
get_error_description,
|
|
lookup_fault,
|
|
)
|
|
|
|
CATALOG = json.loads(hms_errors._DATA_FILE.read_text(encoding="utf-8"))
|
|
EXTRA = json.loads((hms_errors._DATA_FILE.parent / "hms_catalog_extra.json").read_text(encoding="utf-8"))
|
|
|
|
|
|
class TestCatalogFile:
|
|
"""The file is generated by scripts/generate_hms_catalog.py and must stay in
|
|
the shape that script writes; these fail on a hand edit that breaks it."""
|
|
|
|
def test_records_where_it_came_from(self):
|
|
"""So "how old is our table" has an answer, and a regeneration shows up
|
|
in the diff as a new commit and new file versions."""
|
|
source = CATALOG["_source"]
|
|
assert source["generator"] == "scripts/generate_hms_catalog.py"
|
|
assert re.fullmatch(r"[0-9a-f]{7,40}|unknown", source["bambu_studio_commit"])
|
|
assert source["model_file_versions"]
|
|
assert all(isinstance(v, int) for v in source["model_file_versions"].values())
|
|
|
|
@pytest.mark.parametrize(("section", "length"), [("hms", 16), ("error", 8)])
|
|
def test_keys_are_uppercase_hex_of_the_right_length(self, section, length):
|
|
pattern = re.compile(rf"^[0-9A-F]{{{length}}}$")
|
|
assert CATALOG[section]
|
|
for code in CATALOG[section]:
|
|
assert pattern.match(code), code
|
|
for model, table in CATALOG[f"{section}_by_model"].items():
|
|
assert re.fullmatch(r"[0-9A-Z]{3}", model)
|
|
for code in table:
|
|
assert pattern.match(code), (model, code)
|
|
|
|
@pytest.mark.parametrize("section", ["hms", "error"])
|
|
def test_a_model_override_differs_from_the_merged_text(self, section):
|
|
"""Overrides hold only what differs; an identical one is dead weight and
|
|
means the file was not written by the script."""
|
|
for model, table in CATALOG[f"{section}_by_model"].items():
|
|
for code, text in table.items():
|
|
assert CATALOG[section].get(code) != text, (model, code)
|
|
|
|
def test_every_extra_code_is_in_the_catalogue(self):
|
|
"""The hand-kept extras are merged in where Studio has no entry, so a
|
|
regeneration cannot drop the texts Bambuddy already had."""
|
|
for code in EXTRA["error"]:
|
|
assert code in CATALOG["error"], code
|
|
|
|
def test_texts_are_strings(self):
|
|
for section in ("hms", "error"):
|
|
assert all(isinstance(t, str) for t in CATALOG[section].values())
|
|
|
|
|
|
class TestLookup:
|
|
def test_resolves_a_print_error_code(self):
|
|
assert describe_fault("03008004") == "Filament ran out. Please load new filament."
|
|
|
|
def test_resolves_an_hms_code_by_its_full_code(self):
|
|
"""A real P2S fault from #2728; the reporter traced it to a module and
|
|
firmware mismatch, which is what Bambu's text says."""
|
|
assert describe_fault("050003000002000E").startswith(
|
|
"Some modules are incompatible with the printer's firmware version"
|
|
)
|
|
|
|
def test_is_case_and_whitespace_tolerant(self):
|
|
assert describe_fault(" 0300400c ") == describe_fault("0300400C") is not None
|
|
|
|
def test_a_code_listed_without_text_is_empty_not_missing(self):
|
|
"""Two of the reporter's three P2S faults. Bambu lists them and publishes
|
|
no text; lookup_fault keeps that distinct from "not listed", and
|
|
describe_fault reads both as no description."""
|
|
assert lookup_fault("0500060000020070") == ""
|
|
assert lookup_fault("050002000003000A") == ""
|
|
assert describe_fault("0500060000020070") is None
|
|
|
|
def test_an_unlisted_code_is_none(self):
|
|
assert lookup_fault("99999999") is None
|
|
assert lookup_fault("FFFFFFFFFFFFFFFF") is None
|
|
|
|
def test_the_text_follows_the_printer_model(self):
|
|
"""0300_8001 is "paused by the user" on some models and "paused by a
|
|
pause command in the file" on others, and 0300_8013 the other way round.
|
|
A merged table alone would tell one set of owners the wrong thing."""
|
|
on_20p = describe_fault("03008001", "20P")
|
|
on_093 = describe_fault("03008001", "093")
|
|
assert "paused by the user" in on_20p
|
|
assert "pause command" in on_093
|
|
assert describe_fault("03008013", "20P") == on_093
|
|
|
|
def test_an_unknown_or_missing_model_uses_the_merged_text(self):
|
|
merged = CATALOG["error"]["03008001"]
|
|
assert describe_fault("03008001", "XXX") == merged
|
|
assert describe_fault("03008001") == merged
|
|
assert describe_fault("03008001", "") == merged
|
|
|
|
def test_a_sixteen_char_code_never_falls_back_to_a_print_error_key(self):
|
|
"""The old G1_G4 collapse looked an hms[] code up as a print_error. No
|
|
real hms[] code has an error group at or above 0x4000, where every
|
|
print_error key sits, so the collapse could only attach a neighbour's
|
|
sentence. Built here from a real print_error key to prove it's gone."""
|
|
key = "03004000"
|
|
assert describe_fault(key) is not None
|
|
forced = f"{key[:4]}02000001{key[4:]}"
|
|
assert forced not in CATALOG["hms"]
|
|
assert describe_fault(forced) is None
|
|
|
|
def test_no_real_hms_code_reaches_the_print_error_range(self):
|
|
"""The fact the previous test rests on, checked against the data."""
|
|
assert all(int(code[12:], 16) < 0x4000 for code in CATALOG["hms"])
|
|
|
|
def test_empty_missing_or_malformed_is_none(self):
|
|
assert describe_fault("") is None
|
|
assert describe_fault(None) is None
|
|
assert describe_fault("0300") is None
|
|
assert describe_fault("030080040") is None
|
|
|
|
def test_short_code_helper_matches_the_full_code(self):
|
|
assert get_error_description("0300_400c") == describe_fault("0300400C")
|
|
assert get_error_description("0300_8001", "20P") == describe_fault("03008001", "20P")
|
|
|
|
|
|
class TestPrintErrorAlertLevel:
|
|
@pytest.mark.parametrize(
|
|
("error", "level"),
|
|
[(0x4000, 1), (0x40FF, 1), (0x8004, 2), (0x8FFF, 2), (0xC003, 3), (0xCFFF, 3), (0x0002, 0), (0x5000, 0)],
|
|
)
|
|
def test_maps_the_first_hex_digit(self, error, level):
|
|
"""4xxx stops the task, 8xxx pauses it, Cxxx is a prompt -- the hms[]
|
|
levels 1, 2, 3. Anything else has no defined level."""
|
|
assert alert_level_from_print_error(error) == level
|