hms_errors.py 3.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. """HMS fault descriptions.
  2. The texts come from Bambu Studio's own HMS files, generated into
  3. ``backend/app/data/hms_catalog.json`` by ``scripts/generate_hms_catalog.py``
  4. (issue #2728). Do not edit the JSON by hand; rerun the script.
  5. Two key spaces, never mixed:
  6. ``hms`` faults from the report's ``hms[]`` array, keyed by the 16-hex code
  7. the printer screen shows: ``attr`` then ``code``, i.e. module,
  8. module no., part, part no., alert level, error.
  9. ``error`` ``print_error`` faults, keyed by the 8-hex value.
  10. Each has a merged table plus, per 3-character serial prefix, the entries whose
  11. text differs on that model (0300_8001 is "paused by the user" on some models and
  12. "paused by a pause command in the file" on others). A code Bambu lists with
  13. empty text is stored as "" and reads as no description.
  14. Only English ships; Bambu Studio has more languages if that is ever wanted.
  15. """
  16. import json
  17. from pathlib import Path
  18. _DATA_FILE = Path(__file__).resolve().parent.parent / "data" / "hms_catalog.json"
  19. # Loaded once at import, like hms_actions.json. Absolute path so the load does
  20. # not depend on the working directory (systemd unit, Docker, tests).
  21. with _DATA_FILE.open(encoding="utf-8") as _f:
  22. _CATALOG: dict = json.load(_f)
  23. _TABLES: dict[int, tuple[dict[str, str], dict[str, dict[str, str]]]] = {
  24. 16: (_CATALOG["hms"], _CATALOG["hms_by_model"]),
  25. 8: (_CATALOG["error"], _CATALOG["error_by_model"]),
  26. }
  27. def lookup_fault(full_code: str | None, model: str | None = None) -> str | None:
  28. """Return the catalogue entry for a fault: its text, "" when Bambu lists the
  29. code without text, or None when the code is not listed at all.
  30. ``full_code`` is the identifier the firmware matches on: 8 hex chars for a
  31. ``print_error``, 16 for an ``hms[]`` entry. ``model`` is the printer's
  32. 3-character serial prefix; a model-specific text wins over the merged one,
  33. and an unknown or missing model uses the merged table.
  34. There is no fallback from one key space to the other. A 16-char code used to
  35. be collapsed to its first and last groups and looked up as a ``print_error``,
  36. but no real ``hms[]`` code has an error group at or above 0x4000, where every
  37. ``print_error`` key sits, so the collapse could only ever attach a
  38. neighbouring fault's sentence (#2728).
  39. """
  40. if not full_code:
  41. return None
  42. code = full_code.strip().upper()
  43. tables = _TABLES.get(len(code))
  44. if tables is None:
  45. return None
  46. merged, by_model = tables
  47. if model:
  48. specific = by_model.get(model.upper(), {}).get(code)
  49. if specific is not None:
  50. return specific
  51. return merged.get(code)
  52. def describe_fault(full_code: str | None, model: str | None = None) -> str | None:
  53. """The fault's description, or None when there is no text for it.
  54. Resolved once at parse time so every surface that reports a fault -- the
  55. status response, the WebSocket broadcast, the completion payload,
  56. notifications -- says the same thing (#2926). None covers both "not listed"
  57. and "listed without text"; ``lookup_fault`` tells the two apart.
  58. """
  59. return lookup_fault(full_code, model) or None
  60. def get_error_description(error_code: str, model: str | None = None) -> str | None:
  61. """Description for a ``print_error`` short code such as "0300_400C"."""
  62. return describe_fault(error_code.replace("_", ""), model)
  63. def alert_level_from_print_error(error: int) -> int:
  64. """Alert level of a ``print_error``, from the first hex digit of its error.
  65. A ``print_error`` is a bare 32-bit module/error word with no level field.
  66. Its error number carries the level instead: 0x4xxx stops the task, 0x8xxx
  67. pauses it, 0xCxxx is a prompt. Mapped onto the ``hms[]`` alert levels
  68. (1 error, 2 warning, 3 notification) so both kinds sort and filter the same
  69. way. 0 for anything else, which Bambu defines as an invalid level.
  70. """
  71. return {0x4: 1, 0x8: 2, 0xC: 3}.get((error >> 12) & 0xF, 0)