|
@@ -59,7 +59,6 @@ A1_MODELS = frozenset(
|
|
|
[
|
|
[
|
|
|
# Display names
|
|
# Display names
|
|
|
"A1",
|
|
"A1",
|
|
|
- "A1 MINI",
|
|
|
|
|
"A1-MINI",
|
|
"A1-MINI",
|
|
|
"A1MINI",
|
|
"A1MINI",
|
|
|
# Internal codes (from MQTT/SSDP)
|
|
# Internal codes (from MQTT/SSDP)
|
|
@@ -82,6 +81,16 @@ STG_CUR_IDLE_BUG_MODELS = A1_MODELS | frozenset(
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+def _normalize_model(model: str) -> str:
|
|
|
|
|
+ """Upper case without spaces, the form the model sets below hold (#3335).
|
|
|
|
|
+
|
|
|
|
|
+ A printer is stored as "H2D Pro" or "A1 Mini"; upper-casing alone kept the
|
|
|
|
|
+ space, and "H2D PRO" matched no set. Dashes stay: the sets name the X1C's
|
|
|
|
|
+ code as "BL-P001".
|
|
|
|
|
+ """
|
|
|
|
|
+ return model.strip().upper().replace(" ", "")
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
def supports_chamber_temp(model: str | None) -> bool:
|
|
def supports_chamber_temp(model: str | None) -> bool:
|
|
|
"""Check if a printer model has a real chamber temperature sensor.
|
|
"""Check if a printer model has a real chamber temperature sensor.
|
|
|
|
|
|
|
@@ -90,15 +99,13 @@ def supports_chamber_temp(model: str | None) -> bool:
|
|
|
"""
|
|
"""
|
|
|
if not model:
|
|
if not model:
|
|
|
return False
|
|
return False
|
|
|
- # Normalize model name (uppercase, strip whitespace)
|
|
|
|
|
- model_upper = model.strip().upper()
|
|
|
|
|
- return model_upper in CHAMBER_TEMP_SUPPORTED_MODELS
|
|
|
|
|
|
|
+ return _normalize_model(model) in CHAMBER_TEMP_SUPPORTED_MODELS
|
|
|
|
|
|
|
|
|
|
|
|
|
# Models with an ACTIVE chamber heater (M141 has an effect).
|
|
# Models with an ACTIVE chamber heater (M141 has an effect).
|
|
|
# Many printers in CHAMBER_TEMP_SUPPORTED_MODELS only have a passive sensor —
|
|
# Many printers in CHAMBER_TEMP_SUPPORTED_MODELS only have a passive sensor —
|
|
|
-# X1C, X1E, P2S report chamber temperature but cannot actively heat it. Only
|
|
|
|
|
-# the models below ship a PTC heater that responds to M141.
|
|
|
|
|
|
|
+# X1C and P2S report chamber temperature but cannot actively heat it. Only
|
|
|
|
|
+# the models below ship a heater that responds to M141; the X1E heats to 60 °C.
|
|
|
CHAMBER_HEATER_MODELS = frozenset(
|
|
CHAMBER_HEATER_MODELS = frozenset(
|
|
|
[
|
|
[
|
|
|
# Display names
|
|
# Display names
|
|
@@ -106,8 +113,10 @@ CHAMBER_HEATER_MODELS = frozenset(
|
|
|
"H2D",
|
|
"H2D",
|
|
|
"H2DPRO",
|
|
"H2DPRO",
|
|
|
"H2S",
|
|
"H2S",
|
|
|
|
|
+ "X1E",
|
|
|
"X2D",
|
|
"X2D",
|
|
|
# Internal codes (from MQTT/SSDP)
|
|
# Internal codes (from MQTT/SSDP)
|
|
|
|
|
+ "C13", # X1E
|
|
|
"O1C", # H2C
|
|
"O1C", # H2C
|
|
|
"O1C2", # H2C dual-nozzle variant
|
|
"O1C2", # H2C dual-nozzle variant
|
|
|
"O1D", # H2D
|
|
"O1D", # H2D
|
|
@@ -123,17 +132,17 @@ def supports_chamber_heater(model: str | None) -> bool:
|
|
|
"""Check if a printer model has an active chamber heater (responds to M141).
|
|
"""Check if a printer model has an active chamber heater (responds to M141).
|
|
|
|
|
|
|
|
The chamber temperature SENSOR is more widely deployed than the chamber
|
|
The chamber temperature SENSOR is more widely deployed than the chamber
|
|
|
- HEATER — X1C/X1E/P2S report chamber temp but ignore M141. Only H2C, H2D,
|
|
|
|
|
- H2D Pro, H2S, X2D actually heat. Sensor-only models silently swallow the
|
|
|
|
|
|
|
+ HEATER — X1C/P2S report chamber temp but ignore M141. Only H2C, H2D,
|
|
|
|
|
+ H2D Pro, H2S, X1E, X2D actually heat. Sensor-only models silently swallow the
|
|
|
command at the firmware level, so we 400 at the route to surface that.
|
|
command at the firmware level, so we 400 at the route to surface that.
|
|
|
"""
|
|
"""
|
|
|
if not model:
|
|
if not model:
|
|
|
return False
|
|
return False
|
|
|
- return model.strip().upper() in CHAMBER_HEATER_MODELS
|
|
|
|
|
|
|
+ return _normalize_model(model) in CHAMBER_HEATER_MODELS
|
|
|
|
|
|
|
|
|
|
|
|
|
# Models with a cooling / heating airduct flap. Same set as the frontend
|
|
# Models with a cooling / heating airduct flap. Same set as the frontend
|
|
|
-# PrintersPage airduct-toggle whitelist (P2S, X2D, H2D, H2C, H2S, H2D Pro).
|
|
|
|
|
|
|
+# PrintersPage airduct-toggle whitelist (P2S, X2D, H2D, H2D Pro, H2C, H2S).
|
|
|
# X1E has a chamber heater but NO airduct flap — the warm-air recirculation
|
|
# X1E has a chamber heater but NO airduct flap — the warm-air recirculation
|
|
|
# happens via the fixed front-door inlet, so no `set_airduct` command is
|
|
# happens via the fixed front-door inlet, so no `set_airduct` command is
|
|
|
# needed (and the firmware ignores it). P2S has an airduct but no heater —
|
|
# needed (and the firmware ignores it). P2S has an airduct but no heater —
|
|
@@ -174,7 +183,7 @@ def supports_airduct(model: str | None) -> bool:
|
|
|
"""
|
|
"""
|
|
|
if not model:
|
|
if not model:
|
|
|
return False
|
|
return False
|
|
|
- return model.strip().upper() in CHAMBER_AIRDUCT_MODELS
|
|
|
|
|
|
|
+ return _normalize_model(model) in CHAMBER_AIRDUCT_MODELS
|
|
|
|
|
|
|
|
|
|
|
|
|
def has_stg_cur_idle_bug(model: str | None) -> bool:
|
|
def has_stg_cur_idle_bug(model: str | None) -> bool:
|
|
@@ -186,12 +195,11 @@ def has_stg_cur_idle_bug(model: str | None) -> bool:
|
|
|
"""
|
|
"""
|
|
|
if not model:
|
|
if not model:
|
|
|
return False
|
|
return False
|
|
|
- model_upper = model.strip().upper()
|
|
|
|
|
- return model_upper in STG_CUR_IDLE_BUG_MODELS
|
|
|
|
|
|
|
+ return _normalize_model(model) in STG_CUR_IDLE_BUG_MODELS
|
|
|
|
|
|
|
|
|
|
|
|
|
# Minimum firmware versions for AMS drying support (confirmed via capture testing)
|
|
# Minimum firmware versions for AMS drying support (confirmed via capture testing)
|
|
|
-# Keys are exact model names (upper-cased). Do NOT use substring matching — it would
|
|
|
|
|
|
|
+# Keys are exact model names (upper-cased, no spaces). Do NOT use substring matching — it would
|
|
|
# incorrectly gate X1E (matched by "X1") and H2D Pro (matched by "H2D").
|
|
# incorrectly gate X1E (matched by "X1") and H2D Pro (matched by "H2D").
|
|
|
_DRYING_MIN_FIRMWARE: dict[str, str] = {
|
|
_DRYING_MIN_FIRMWARE: dict[str, str] = {
|
|
|
"H2D": "01.02.30.00",
|
|
"H2D": "01.02.30.00",
|
|
@@ -205,7 +213,7 @@ _DRYING_MIN_FIRMWARE: dict[str, str] = {
|
|
|
"N7": "01.02.00.00", # P2S internal model code
|
|
"N7": "01.02.00.00", # P2S internal model code
|
|
|
}
|
|
}
|
|
|
# Models that definitely don't support AMS drying (no AMS 2 Pro / AMS-HT compatibility)
|
|
# Models that definitely don't support AMS drying (no AMS 2 Pro / AMS-HT compatibility)
|
|
|
-_DRYING_UNSUPPORTED_MODELS = frozenset({"A1", "A1MINI", "A1-MINI", "A1 MINI", "O1S", "N1", "N2S"})
|
|
|
|
|
|
|
+_DRYING_UNSUPPORTED_MODELS = frozenset({"A1", "A1MINI", "A1-MINI", "O1S", "N1", "N2S"})
|
|
|
|
|
|
|
|
# Models whose AMS can dry, but only from the printer's own touchscreen. Bambu's P1
|
|
# Models whose AMS can dry, but only from the printer's own touchscreen. Bambu's P1
|
|
|
# manual is explicit: "P1S connected AMS drying functions may only be controlled from
|
|
# manual is explicit: "P1S connected AMS drying functions may only be controlled from
|
|
@@ -227,7 +235,7 @@ def drying_screen_only(model: str | None) -> bool:
|
|
|
"""
|
|
"""
|
|
|
if not model:
|
|
if not model:
|
|
|
return False
|
|
return False
|
|
|
- return model.strip().upper() in _DRYING_SCREEN_ONLY_MODELS
|
|
|
|
|
|
|
+ return _normalize_model(model) in _DRYING_SCREEN_ONLY_MODELS
|
|
|
|
|
|
|
|
|
|
|
|
|
# Temperature keys the UI actually draws. `state.temperatures` is also working
|
|
# Temperature keys the UI actually draws. `state.temperatures` is also working
|
|
@@ -321,7 +329,7 @@ def supports_drying(model: str | None, firmware: str | None) -> bool:
|
|
|
"""
|
|
"""
|
|
|
if not model:
|
|
if not model:
|
|
|
return False
|
|
return False
|
|
|
- model_upper = model.strip().upper()
|
|
|
|
|
|
|
+ model_upper = _normalize_model(model)
|
|
|
if model_upper in _DRYING_UNSUPPORTED_MODELS or model_upper in _DRYING_SCREEN_ONLY_MODELS:
|
|
if model_upper in _DRYING_UNSUPPORTED_MODELS or model_upper in _DRYING_SCREEN_ONLY_MODELS:
|
|
|
return False
|
|
return False
|
|
|
if model_upper in _DRYING_MIN_FIRMWARE:
|
|
if model_upper in _DRYING_MIN_FIRMWARE:
|
|
@@ -338,7 +346,6 @@ def supports_drying(model: str | None, firmware: str | None) -> bool:
|
|
|
# will reject the command in those cases anyway via dry_sf_reason=[0] (TaskOccupied).
|
|
# will reject the command in those cases anyway via dry_sf_reason=[0] (TaskOccupied).
|
|
|
_DRY_WHILE_PRINTING_MIN_FIRMWARE: dict[str, str] = {
|
|
_DRY_WHILE_PRINTING_MIN_FIRMWARE: dict[str, str] = {
|
|
|
"H2D": "01.03.00.00",
|
|
"H2D": "01.03.00.00",
|
|
|
- "H2D PRO": "01.02.00.00",
|
|
|
|
|
"H2DPRO": "01.02.00.00",
|
|
"H2DPRO": "01.02.00.00",
|
|
|
"O1E": "01.02.00.00", # H2D Pro SSDP code
|
|
"O1E": "01.02.00.00", # H2D Pro SSDP code
|
|
|
"O2D": "01.02.00.00", # H2D Pro alternate code
|
|
"O2D": "01.02.00.00", # H2D Pro alternate code
|
|
@@ -369,7 +376,7 @@ def supports_drying_while_printing(model: str | None, firmware: str | None) -> b
|
|
|
"""
|
|
"""
|
|
|
if not model:
|
|
if not model:
|
|
|
return False
|
|
return False
|
|
|
- model_upper = model.strip().upper()
|
|
|
|
|
|
|
+ model_upper = _normalize_model(model)
|
|
|
if model_upper not in _DRY_WHILE_PRINTING_MIN_FIRMWARE:
|
|
if model_upper not in _DRY_WHILE_PRINTING_MIN_FIRMWARE:
|
|
|
return False
|
|
return False
|
|
|
return bool(firmware and firmware >= _DRY_WHILE_PRINTING_MIN_FIRMWARE[model_upper])
|
|
return bool(firmware and firmware >= _DRY_WHILE_PRINTING_MIN_FIRMWARE[model_upper])
|