From 73430e241f92fc7704df27cd1bd3d17048f274a3 Mon Sep 17 00:00:00 2001 From: RedAces Date: Sat, 8 Aug 2026 11:28:33 +0200 Subject: [PATCH 1/2] Map the machine status "pause" to the "paused" status in HA. The Creator 5 Pro goes into this state e. g. when a clog is detected. Map "downloading" to "busy" - debatable whether an extra HA status is required --- flashforge/api/controls/info.py | 27 +++++++++++++++++++- tests/test_info.py | 45 +++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/flashforge/api/controls/info.py b/flashforge/api/controls/info.py index 8ffc6a3..030fefc 100644 --- a/flashforge/api/controls/info.py +++ b/flashforge/api/controls/info.py @@ -302,7 +302,21 @@ def _format_time_from_seconds(seconds: float) -> str: @staticmethod def _get_machine_state(status: str) -> MachineState: - """Map raw status strings into the public machine state enum.""" + """Map raw status strings into the public machine state enum. + + An unmapped value costs the consumer everything the field is for: it + becomes ``UNKNOWN``, which in Home Assistant renders as "unknown" - the + one state that says nothing, at the moment the user most needs to know + what the printer is doing. Both additions below were found that way, in + a log full of `Unknown machine status received` while a Creator 5 Pro sat + paused on a detected clog. + + Consumers may map this enum onto a fixed set of values - the Home + Assistant integration's Machine Status sensor is a `device_class=ENUM` + with an explicit `options` list - so a *new* member is a breaking change + for them, while mapping onto an existing one is not. Prefer the closest + existing state unless a new one is worth coordinating. + """ valid_status = status.lower() if isinstance(status, str) else "" state_mapping = { "ready": MachineState.READY, @@ -312,9 +326,20 @@ def _get_machine_state(status: str) -> MachineState: "heating": MachineState.HEATING, "printing": MachineState.PRINTING, "pausing": MachineState.PAUSING, + # The Creator 5 Pro reports "pause" for a print that is paused, + # where the documented value is "paused" - both are mapped, because + # firmware that reports one is not a reason to drop the other. + # Observed on pid 41, firmware 1.9.4, whenever the printer paused + # itself on a detected clog. + "pause": MachineState.PAUSED, "paused": MachineState.PAUSED, "cancel": MachineState.CANCELLED, "completed": MachineState.COMPLETED, + # Reported while a file is being transferred to the printer. Not a + # print, but not idle either, so BUSY is the honest existing fit; a + # dedicated state would need consumers to add it to their option + # lists first. + "downloading": MachineState.BUSY, } if valid_status in state_mapping: diff --git a/tests/test_info.py b/tests/test_info.py index b9e0889..3fb9060 100644 --- a/tests/test_info.py +++ b/tests/test_info.py @@ -208,3 +208,48 @@ async def test_convenience_wrappers_absorb_response_errors(): assert await info.is_printing() is False assert await info.get_status() is None assert await info.get_machine_state() is None + + +# --------------------------------------------------------------------------- # +# Status mapping +# --------------------------------------------------------------------------- # + + +@pytest.mark.parametrize( + ("reported", "expected"), + [ + ("ready", MachineState.READY), + ("printing", MachineState.PRINTING), + ("pausing", MachineState.PAUSING), + ("paused", MachineState.PAUSED), + # A Creator 5 Pro reports "pause", not the documented "paused". + ("pause", MachineState.PAUSED), + ("PAUSE", MachineState.PAUSED), + # Sent while a file is being transferred to the printer. + ("downloading", MachineState.BUSY), + ("completed", MachineState.COMPLETED), + ], +) +def test_machine_state_mapping(reported, expected): + """Every status a real printer has been seen to report must map.""" + assert MachineInfoParser._get_machine_state(reported) is expected + + +def test_unmapped_status_is_unknown_and_logged(caplog): + """An unrecognized status still degrades to UNKNOWN - and says so. + + The warning is the only trace an unmapped value leaves: the state itself + becomes UNKNOWN, which is indistinguishable from a printer that reported + nothing. Both "pause" and "downloading" were found this way, in a Home + Assistant log, after the sensor had been reading "unknown" for a day. + """ + with caplog.at_level("WARNING"): + assert MachineInfoParser._get_machine_state("teleporting") is MachineState.UNKNOWN + + assert "teleporting" in caplog.text + + +def test_empty_status_is_unknown_without_a_warning(): + """No status is not an unexpected status; it must not cry wolf.""" + assert MachineInfoParser._get_machine_state("") is MachineState.UNKNOWN + assert MachineInfoParser._get_machine_state(None) is MachineState.UNKNOWN From 8371b6e1ecb29bee3f0a407ff44a130c1fe2ccb4 Mon Sep 17 00:00:00 2001 From: RedAces Date: Sat, 8 Aug 2026 11:31:49 +0200 Subject: [PATCH 2/2] Changelog --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bfb189..dfffbab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Fixed + +- **A paused Creator 5 Pro no longer reports as "unknown".** The printer sends `status: "pause"`, while the mapping only knew the documented `"pausing"` and `"paused"` — so every pause fell through to `MachineState.UNKNOWN`. In Home Assistant that is the one state that says nothing, and it appeared at exactly the wrong moment: the printer pauses itself when it detects a clog, so the sensor went blank precisely when the user needed to know why the print had stopped. Found in a Home Assistant log carrying 84 `Unknown machine status received: 'pause'` warnings over a single day (pid 41, firmware 1.9.4). `"pause"` now maps to `PAUSED`; `"paused"` still does, because firmware reporting one is no reason to drop the other. + +- **`status: "downloading"` is mapped too.** Reported while a file is being transferred to the printer, and likewise unmapped until now. It maps to `BUSY` rather than to a new enum member on purpose: consumers may pin this enum to a fixed list — the Home Assistant integration's Machine Status sensor is a `device_class=ENUM` with an explicit `options` list — so adding a member breaks them until they ship a matching release, while an existing state does not. A dedicated `DOWNLOADING` state is worth discussing, but it is a coordinated change, not a bugfix. + ## [1.3.4] - 2026-07-26 ### Fixed