bambustate
bambustate provides a unified, thread-safe representation of a Bambu Lab printer's
operational state, synchronized via MQTT telemetry.
Classes:
| Name | Description |
|---|---|
AMSUnitState |
State information about an individual AMS unit. |
BambuClimate |
Contains all climate related attributes |
BambuState |
Representation of the Bambu printer state synchronized via MQTT. |
ExtruderState |
State for an individual physical extruder toolhead. |
PrinterCapabilities |
Discovery features based on hardware block presence in telemetry. |
AMSUnitState
dataclass
AMSUnitState(
ams_id: str,
chip_id: str = "",
is_ams_lite: bool = False,
temp_actual: float = 0.0,
temp_target: int = 0,
humidity_index: int = 0,
humidity_raw: int = 0,
is_online: bool = False,
is_powered: bool = False,
rfid_ready: bool = False,
hub_sensor_triggered: bool = False,
humidity_sensor_ok: bool = False,
heater_on: bool = False,
circ_fan_on: bool = False,
exhaust_fan_on: bool = False,
dry_time: int = 0,
is_rotating: bool = False,
venting_active: bool = False,
high_power_mode: bool = False,
hardware_fault: bool = False,
tray_exists: list[bool] = (lambda: [False] * 4)(),
assigned_to_extruder: ActiveTool = SINGLE_EXTRUDER,
drying_stage: AMSDryingStage = IDLE,
)
State information about an individual AMS unit.
Attributes:
| Name | Type | Description |
|---|---|---|
ams_id |
str
|
Unique ID. Population: |
assigned_to_extruder |
ActiveTool
|
Target tool. Population: |
chip_id |
str
|
Hardware serial. Population: |
circ_fan_on |
bool
|
Circ fan state. Population: |
dry_time |
int
|
Minutes left. Population: |
drying_stage |
AMSDryingStage
|
Cycle stage. Population: |
exhaust_fan_on |
bool
|
Exhaust fan state. Population: |
hardware_fault |
bool
|
Fault status. Population: |
heater_on |
bool
|
Heater state. Population: |
high_power_mode |
bool
|
Power mode. Population: |
hub_sensor_triggered |
bool
|
Filament at hub. Population: |
humidity_index |
int
|
Humidity index. Population: |
humidity_raw |
int
|
Raw humidity. Population: |
humidity_sensor_ok |
bool
|
Sensor health. Population: |
is_ams_lite |
bool
|
True if AMS Lite. Population: |
is_online |
bool
|
Connectivity. Population: |
is_powered |
bool
|
Power status. Population: |
is_rotating |
bool
|
Rollers turning. Population: |
rfid_ready |
bool
|
RFID status. Population: |
temp_actual |
float
|
Actual temp. Population: |
temp_target |
int
|
Target drying temp. Population: |
tray_exists |
list[bool]
|
Slot presence. Population: Shifting |
venting_active |
bool
|
Venting state. Population: |
assigned_to_extruder
class-attribute
instance-attribute
assigned_to_extruder: ActiveTool = SINGLE_EXTRUDER
Target tool. Population: ActiveTool(h2d_toolhead_index).
chip_id
class-attribute
instance-attribute
Hardware serial. Population: m.get("sn").
circ_fan_on
class-attribute
instance-attribute
Circ fan state. Population: p_ams["circ_fan_on"].
dry_time
class-attribute
instance-attribute
Minutes left. Population: int(float(r.get("dry_time"))).
drying_stage
class-attribute
instance-attribute
drying_stage: AMSDryingStage = IDLE
Cycle stage. Population: _resolve_drying_stage.
exhaust_fan_on
class-attribute
instance-attribute
Exhaust fan state. Population: p_ams["exhaust_fan_on"].
hardware_fault
class-attribute
instance-attribute
Fault status. Population: p_ams["hardware_fault"].
heater_on
class-attribute
instance-attribute
Heater state. Population: p_ams["heater_on"].
high_power_mode
class-attribute
instance-attribute
Power mode. Population: p_ams.get("high_power_mode").
hub_sensor_triggered
class-attribute
instance-attribute
Filament at hub. Population: p_ams["hub_sensor_triggered"].
humidity_index
class-attribute
instance-attribute
Humidity index. Population: int(float(r.get("humidity"))).
humidity_raw
class-attribute
instance-attribute
Raw humidity. Population: int(float(r.get("humidity_raw"))).
humidity_sensor_ok
class-attribute
instance-attribute
Sensor health. Population: p_ams["humidity_sensor_ok"].
is_ams_lite
class-attribute
instance-attribute
True if AMS Lite. Population: product_name check.
is_online
class-attribute
instance-attribute
Connectivity. Population: p_ams["is_online"].
is_powered
class-attribute
instance-attribute
Power status. Population: p_ams["is_powered"].
is_rotating
class-attribute
instance-attribute
Rollers turning. Population: p_ams["is_rotating"].
rfid_ready
class-attribute
instance-attribute
RFID status. Population: p_ams["rfid_ready"].
temp_actual
class-attribute
instance-attribute
Actual temp. Population: float(r.get("temp")).
temp_target
class-attribute
instance-attribute
Target drying temp. Population: trays[0].drying_temp.
tray_exists
class-attribute
instance-attribute
Slot presence. Population: Shifting tray_exist_bits.
BambuClimate
dataclass
BambuClimate(
bed_temp: float = 0.0,
bed_temp_target: int = 0,
airduct_mode: int = 0,
airduct_sub_mode: int = 0,
chamber_temp: float = 0.0,
chamber_temp_target: int = 0,
is_chamber_heating: bool = False,
is_chamber_cooling: bool = False,
is_dryer_engaged: bool = False,
ac_unit_power_percent: int = 0,
part_cooling_fan_speed_percent: int = 0,
part_cooling_fan_speed_target_percent: int = 0,
chamber_fan_speed_percent: int = 0,
exhaust_fan_speed_percent: int = 0,
heatbreak_fan_speed_percent: int = 0,
has_active_filtration: bool = False,
airduct_state_raw: int = 0,
zone_internal_percent: int = 0,
zone_intake_percent: int = 0,
zone_exhaust_percent: int = 0,
top_vent_open: bool = False,
filter_obstruction_detected: bool = False,
zone_sync_error: bool = False,
)
Contains all climate related attributes
Attributes:
| Name | Type | Description |
|---|---|---|
ac_unit_power_percent |
int
|
AC/Compressor power. Population: airduct.parts ID 96. |
airduct_mode |
int
|
Raw current mode. Population: airduct.modeCur. |
airduct_state_raw |
int
|
Raw airduct status mask. Population: |
airduct_sub_mode |
int
|
Raw sub mode. Population: airduct.subMode. |
bed_temp |
float
|
Bed temp. Population: |
bed_temp_target |
int
|
Bed target. Population: |
chamber_fan_speed_percent |
int
|
Chamber fan %. Population: |
chamber_temp |
float
|
Chamber temp. Population: |
chamber_temp_target |
int
|
Chamber target. Population: |
exhaust_fan_speed_percent |
int
|
Exhaust fan %. Population: |
filter_obstruction_detected |
bool
|
Filter pressure warning. Population: Bitwise |
has_active_filtration |
bool
|
Filter status. Population: Bitwise |
heatbreak_fan_speed_percent |
int
|
Heatbreak fan %. Population: |
is_chamber_cooling |
bool
|
True if active cooling. Population: Bitwise |
is_chamber_heating |
bool
|
True if active heating. Population: Bitwise |
is_dryer_engaged |
bool
|
True if subMode Bit 0. Population: airduct.subMode & 0x01. |
part_cooling_fan_speed_percent |
int
|
Part fan %. Population: |
part_cooling_fan_speed_target_percent |
int
|
Part target %. Population: |
top_vent_open |
bool
|
Vent status. Population: Bitwise |
zone_exhaust_percent |
int
|
Exhaust %. Population: |
zone_intake_percent |
int
|
Intake %. Population: |
zone_internal_percent |
int
|
Internal %. Population: |
zone_sync_error |
bool
|
Alignment error. Population: Bitwise |
ac_unit_power_percent
class-attribute
instance-attribute
AC/Compressor power. Population: airduct.parts ID 96.
airduct_mode
class-attribute
instance-attribute
Raw current mode. Population: airduct.modeCur.
airduct_state_raw
class-attribute
instance-attribute
Raw airduct status mask. Population: device.airduct.state.
airduct_sub_mode
class-attribute
instance-attribute
Raw sub mode. Population: airduct.subMode.
bed_temp
class-attribute
instance-attribute
Bed temp. Population: float(p.get("bed_temper")).
bed_temp_target
class-attribute
instance-attribute
Bed target. Population: float(p.get("bed_target_temper")).
chamber_fan_speed_percent
class-attribute
instance-attribute
Chamber fan %. Population: scaleFanSpeed(p.big_fan1_speed).
chamber_temp
class-attribute
instance-attribute
Chamber temp. Population: unpackTemperature(ctc_root.info.temp).
chamber_temp_target
class-attribute
instance-attribute
Chamber target. Population: unpackTemperature(ctc_root.info.temp).
exhaust_fan_speed_percent
class-attribute
instance-attribute
Exhaust fan %. Population: scaleFanSpeed(p.big_fan2_speed).
filter_obstruction_detected
class-attribute
instance-attribute
Filter pressure warning. Population: Bitwise airduct.state & 0x02.
has_active_filtration
class-attribute
instance-attribute
Filter status. Population: Bitwise airduct.state & 0x08.
heatbreak_fan_speed_percent
class-attribute
instance-attribute
Heatbreak fan %. Population: scaleFanSpeed(p.heatbreak_fan_speed).
is_chamber_cooling
class-attribute
instance-attribute
True if active cooling. Population: Bitwise airduct.state & 0x20.
is_chamber_heating
class-attribute
instance-attribute
True if active heating. Population: Bitwise airduct.state & 0x10.
is_dryer_engaged
class-attribute
instance-attribute
True if subMode Bit 0. Population: airduct.subMode & 0x01.
part_cooling_fan_speed_percent
class-attribute
instance-attribute
Part fan %. Population: scaleFanSpeed(p.cooling_fan_speed).
part_cooling_fan_speed_target_percent
class-attribute
instance-attribute
Part target %. Population: scaleFanSpeed(p.cooling_fan_target_speed).
top_vent_open
class-attribute
instance-attribute
Vent status. Population: Bitwise airduct.state & 0x01.
zone_exhaust_percent
class-attribute
instance-attribute
Exhaust %. Population: airduct.parts ID 48.
zone_intake_percent
class-attribute
instance-attribute
Intake %. Population: airduct.parts ID 32.
zone_internal_percent
class-attribute
instance-attribute
Internal %. Population: airduct.parts ID 16.
BambuState
dataclass
BambuState(
gcode_state: str = "IDLE",
current_stage_id: int = 0,
current_stage_name: str = "",
print_percentage: int = 0,
remaining_minutes: int = 0,
current_layer: int = 0,
total_layers: int = 0,
active_tray_id: int = 255,
active_tray_state: TrayState = UNLOADED,
active_tray_state_name: str = name,
target_tray_id: int = -1,
active_tool: ActiveTool = SINGLE_EXTRUDER,
is_external_spool_active: bool = False,
active_nozzle_temp: float = 0.0,
active_nozzle_temp_target: int = 0,
ams_status_raw: int = 0,
ams_status_text: str = "",
ams_exist_bits: int = 0,
ams_connected_count: int = 0,
ams_units: list[AMSUnitState] = list(),
extruders: list[ExtruderState] = list(),
ams_handle_map: dict[int, int] = dict(),
print_error: int = 0,
hms_errors: list[dict] = list(),
capabilities: PrinterCapabilities = PrinterCapabilities(),
climate: BambuClimate = BambuClimate(),
)
Representation of the Bambu printer state synchronized via MQTT.
Methods:
| Name | Description |
|---|---|
fromJson |
Parses root MQTT payloads into a unified BambuState with 100% attribute traceability. |
Attributes:
| Name | Type | Description |
|---|---|---|
active_nozzle_temp |
float
|
Nozzle temp. Population: Handoff from |
active_nozzle_temp_target |
int
|
Nozzle target. Population: Handoff from |
active_tool |
ActiveTool
|
Active toolhead. Population: |
active_tray_id |
int
|
Current tray. Population: Computed in Tool Handoff. |
active_tray_state |
TrayState
|
Loading enum. Population: |
active_tray_state_name |
str
|
Loading string. Population: |
ams_connected_count |
int
|
AMS count. Population: |
ams_exist_bits |
int
|
AMS mask. Population: |
ams_handle_map |
dict[int, int]
|
Logical handles. Population: |
ams_status_raw |
int
|
Raw AMS status. Population: |
ams_status_text |
str
|
Human AMS status. Population: |
ams_units |
list[AMSUnitState]
|
Unit details. Population: Result of unit iteration. |
capabilities |
PrinterCapabilities
|
Machine flags. Population: |
climate |
BambuClimate
|
Contains all climate related attributes |
current_layer |
int
|
Layer index. Population: |
current_stage_id |
int
|
Stage numeric ID. Population: |
current_stage_name |
str
|
Stage human name. Population: |
extruders |
list[ExtruderState]
|
Extruder details. Population: Result of extruder iteration. |
gcode_state |
str
|
Execution state. Population: |
hms_errors |
list[dict]
|
HMS list. Population: |
is_external_spool_active |
bool
|
Ext spool flag. Population: |
print_error |
int
|
Main error. Population: |
print_percentage |
int
|
Completion %. Population: |
remaining_minutes |
int
|
Time left. Population: |
target_tray_id |
int
|
Next tray. Population: Stage-specific targeting logic. |
total_layers |
int
|
Layer total. Population: |
active_nozzle_temp
class-attribute
instance-attribute
Nozzle temp. Population: Handoff from a_ext or p.
active_nozzle_temp_target
class-attribute
instance-attribute
Nozzle target. Population: Handoff from a_ext or p.
active_tool
class-attribute
instance-attribute
active_tool: ActiveTool = SINGLE_EXTRUDER
Active toolhead. Population: extruder_root.state shift.
active_tray_id
class-attribute
instance-attribute
Current tray. Population: Computed in Tool Handoff.
active_tray_state
class-attribute
instance-attribute
active_tray_state: TrayState = UNLOADED
Loading enum. Population: ExtruderInfoState check.
active_tray_state_name
class-attribute
instance-attribute
Loading string. Population: active_tray_state.name.
ams_connected_count
class-attribute
instance-attribute
AMS count. Population: bin(ams_exist_bits).count("1").
ams_exist_bits
class-attribute
instance-attribute
AMS mask. Population: int(ams_root.ams_exist_bits, 16).
ams_handle_map
class-attribute
instance-attribute
Logical handles. Population: info.module parsing.
ams_status_raw
class-attribute
instance-attribute
Raw AMS status. Population: int(p.get("ams_status")).
ams_status_text
class-attribute
instance-attribute
Human AMS status. Population: parseAMSStatus.
ams_units
class-attribute
instance-attribute
ams_units: list[AMSUnitState] = field(default_factory=list)
Unit details. Population: Result of unit iteration.
capabilities
class-attribute
instance-attribute
capabilities: PrinterCapabilities = field(default_factory=PrinterCapabilities)
Machine flags. Population: PrinterCapabilities instantiation.
climate
class-attribute
instance-attribute
climate: BambuClimate = field(default_factory=BambuClimate)
Contains all climate related attributes
current_layer
class-attribute
instance-attribute
Layer index. Population: int(p.get("layer_num")).
current_stage_id
class-attribute
instance-attribute
Stage numeric ID. Population: int(p.get("stg_cur")).
current_stage_name
class-attribute
instance-attribute
Stage human name. Population: parseStage.
extruders
class-attribute
instance-attribute
extruders: list[ExtruderState] = field(default_factory=list)
Extruder details. Population: Result of extruder iteration.
gcode_state
class-attribute
instance-attribute
Execution state. Population: p.get("gcode_state").
hms_errors
class-attribute
instance-attribute
HMS list. Population: decodeHMS + decodeError synthesis.
is_external_spool_active
class-attribute
instance-attribute
Ext spool flag. Population: active_tray_id in [254, 255].
print_error
class-attribute
instance-attribute
Main error. Population: int(p.get("print_error")).
print_percentage
class-attribute
instance-attribute
Completion %. Population: int(p.get("mc_percent")).
remaining_minutes
class-attribute
instance-attribute
Time left. Population: int(p.get("mc_remaining_time")).
target_tray_id
class-attribute
instance-attribute
Next tray. Population: Stage-specific targeting logic.
total_layers
class-attribute
instance-attribute
Layer total. Population: int(p.get("total_layer_num")).
fromJson
classmethod
fromJson(
data: dict[str, Any], current_state: Optional[BambuState] = None
) -> BambuState
Parses root MQTT payloads into a unified BambuState with 100% attribute traceability.
Source code in src/bpm/bambustate.py
@classmethod
def fromJson(
cls, data: dict[str, Any], current_state: Optional["BambuState"] = None
) -> "BambuState":
"""Parses root MQTT payloads into a unified BambuState with 100% attribute traceability."""
base = current_state if current_state else cls()
info = data.get("info", {})
p = data.get("print", {})
ams_root = p.get("ams", {})
device = p.get("device", {})
extruder_root = device.get("extruder", {})
ctc_root = device.get("ctc", {})
airduct_root = device.get("airduct", {})
modules = info.get("module", [])
updates = {}
# CAPABILITIES
caps = asdict(base.capabilities)
if ctc_root:
caps["has_chamber_temp"] = True
if "ams" in ams_root or "ams" in p:
caps["has_ams"] = True
if airduct_root:
caps["has_air_filtration"] = True
if len(extruder_root.get("info", [])) > 1:
caps["has_dual_extruder"] = True
caps["has_lidar"] = "xcam" in p or "xcam" in info
caps["has_camera"] = True
updates["capabilities"] = PrinterCapabilities(**caps)
climate = asdict(base.climate)
updates["climate"] = BambuClimate(**climate)
# STATUS & PROGRESS
updates["gcode_state"] = p.get("gcode_state", base.gcode_state)
updates["current_stage_id"] = int(p.get("stg_cur", base.current_stage_id))
updates["current_stage_name"] = parseStage(updates["current_stage_id"])
updates["print_percentage"] = int(p.get("mc_percent", base.print_percentage))
updates["remaining_minutes"] = int(
p.get("mc_remaining_time", base.remaining_minutes)
)
updates["current_layer"] = int(p.get("layer_num", base.current_layer))
updates["total_layers"] = int(p.get("total_layer_num", base.total_layers))
# THERMALS & CTC DECODING
updates["climate"].bed_temp = float(p.get("bed_temper", base.climate.bed_temp))
updates["climate"].bed_temp_target = int(
p.get("bed_target_temper", base.climate.bed_temp_target)
)
if ctc_root:
ctc_temp_raw = unpackTemperature(ctc_root.get("info", {}).get("temp", 0.0))
ctc_temp = ctc_temp_raw[0]
ctc_temp_target = ctc_temp_raw[1]
updates["climate"].chamber_temp = ctc_temp
updates["climate"].chamber_temp_target = int(ctc_temp_target)
else:
updates["climate"].chamber_temp = base.climate.chamber_temp
updates["climate"].chamber_temp_target = base.climate.chamber_temp_target
# AIRDUCT
if airduct_root:
updates["climate"].airduct_mode = int(
airduct_root.get("modeCur", base.climate.airduct_mode)
)
updates["climate"].airduct_sub_mode = int(
airduct_root.get("subMode", base.climate.airduct_sub_mode)
)
updates["climate"].is_chamber_heating = updates["climate"].airduct_mode == 1
updates["climate"].is_chamber_cooling = updates["climate"].airduct_mode == 2
updates["climate"].has_active_filtration = updates[
"climate"
].airduct_mode in [0, 2]
updates["climate"].is_dryer_engaged = bool(
updates["climate"].airduct_sub_mode & 0x01
)
updates["climate"].top_vent_open = bool(
updates["climate"].airduct_sub_mode & 0x01
)
parts = {part["id"]: part["state"] for part in airduct_root.get("parts", [])}
updates["climate"].zone_internal_percent = parts.get(
16, base.climate.zone_internal_percent
)
updates["climate"].zone_intake_percent = parts.get(
32, base.climate.zone_intake_percent
)
updates["climate"].zone_exhaust_percent = parts.get(
48, base.climate.zone_exhaust_percent
)
updates["climate"].ac_unit_power_percent = parts.get(
96, base.climate.ac_unit_power_percent
)
# EXTRUDERS
new_extruders = []
if "info" in extruder_root:
for ams_ex in extruder_root["info"]:
raw_t = int(ams_ex.get("temp", 0))
act_t, tar_t = unpackTemperature(raw_t)
sn = int(ams_ex.get("snow", -1))
hn = int(ams_ex.get("hnow", -1))
st = int(ams_ex.get("star", -1))
ht = int(ams_ex.get("htar", -1))
ext = ExtruderState()
ext.id = int(ams_ex.get("id", 0))
ext.temp = act_t
ext.temp_target = int(tar_t)
ext.info_bits = int(ams_ex.get("info", 0))
ext.state = parseExtruderInfo(ext.info_bits)
ext.status = parseExtruderStatus(int(ams_ex.get("stat", 0)))
ext.active_tray_id = parseExtruderTrayState(ext.id, hn, sn)
ext.target_tray_id = parseExtruderTrayState(ext.id, ht, st)
if base.extruders and len(base.extruders) > ext.id:
base_tray_state = base.extruders[ext.id].tray_state
else:
base_tray_state = (
TrayState.LOADED
if ext.state != ExtruderInfoState.EMPTY
else TrayState.UNLOADED
)
if (
ext.state == ExtruderInfoState.LOADED
and ext.status == ExtruderStatus.ACTIVE
):
ext.tray_state = TrayState.LOADED
elif (
ext.state == ExtruderInfoState.EMPTY
and ext.status == ExtruderStatus.IDLE
):
ext.tray_state = TrayState.UNLOADED
elif (
ext.state == ExtruderInfoState.LOADED
and ext.status == ExtruderStatus.HEATING
and base_tray_state not in (TrayState.LOADING, TrayState.UNLOADED)
):
ext.tray_state = TrayState.UNLOADING
elif ext.status is not ExtruderStatus.IDLE:
ext.tray_state = TrayState.LOADING
else:
ext.tray_state = base.active_tray_state
new_extruders.append(ext)
updates["extruders"] = new_extruders if new_extruders else base.extruders
# TOOL SELECTION
if "state" in extruder_root:
raw_t_idx = (int(extruder_root["state"]) >> 4) & 0xF
if updates["capabilities"].has_dual_extruder:
updates["active_tool"] = ActiveTool(raw_t_idx)
else:
updates["active_tool"] = ActiveTool.SINGLE_EXTRUDER
else:
updates["active_tool"] = base.active_tool
# AMS UNITS & HANDLE MAPPING
new_handle_map = base.ams_handle_map.copy()
cur_ams = {u.ams_id: u for u in base.ams_units}
for m in modules:
if m.get("name", "").startswith("n3f/"):
try:
idx = int(m["name"].split("/")[-1])
if match := re.search(r"\((\d+)\)", m.get("product_name", "")):
new_handle_map[idx] = int(match.group(1))
except (ValueError, IndexError):
pass
ams_id_str = str(idx)
u = cur_ams.get(ams_id_str, AMSUnitState(ams_id=ams_id_str))
u.chip_id = m.get("sn", u.chip_id)
u.is_ams_lite = "lite" in m.get("product_name", "").lower()
cur_ams[ams_id_str] = u
updates["ams_handle_map"] = new_handle_map
for ams_u in ams_root.get("ams", []):
id_s = str(ams_u.get("id", "0"))
u = cur_ams.get(id_s, AMSUnitState(ams_id=id_s))
u.temp_actual = float(ams_u.get("temp", u.temp_actual))
u.humidity_index = int(float(ams_u.get("humidity", u.humidity_index)))
u.humidity_raw = int(float(ams_u.get("humidity_raw", u.humidity_raw)))
u.dry_time = int(float(ams_u.get("dry_time", u.dry_time)))
# ugly hack for capturing target temp
if u.dry_time > 0 and u.temp_target < int(u.temp_actual) - 1:
u.temp_target = int(u.temp_actual)
if "info" in ams_u:
p_ams = parseAMSInfo(int(ams_u["info"]))
u.is_online = p_ams["is_online"]
u.is_powered = p_ams["is_powered"]
u.rfid_ready = p_ams["rfid_ready"]
u.hub_sensor_triggered = p_ams["hub_sensor_triggered"]
u.humidity_sensor_ok = p_ams["humidity_sensor_ok"]
u.heater_on = p_ams["heater_on"]
u.circ_fan_on = p_ams["circ_fan_on"]
u.exhaust_fan_on = p_ams["exhaust_fan_on"]
u.is_rotating = p_ams["is_rotating"]
u.venting_active = p_ams.get("venting_active", False)
u.high_power_mode = p_ams.get("high_power_mode", False)
u.hardware_fault = p_ams["hardware_fault"]
u.drying_stage = resolveAMSDryingStage(p_ams, u.dry_time)
if updates["capabilities"].has_dual_extruder:
u.assigned_to_extruder = ActiveTool(
p_ams.get("h2d_toolhead_index", 15)
)
rb = ams_u.get("tray_exist_bits", ams_root.get("tray_exist_bits"))
if rb is not None:
eb = int(rb, 16) if isinstance(rb, str) else int(rb)
u.tray_exists = [
bool((eb >> (4 * int(id_s))) & (1 << j)) for j in range(4)
]
cur_ams[id_s] = u
updates["ams_units"] = list(cur_ams.values())
# ACTIVE / TARGET TRAYS AND TOOL TEMP
# if multi-extruder return the active one
a_ext = next(
(e for e in updates["extruders"] if e.id == updates["active_tool"].value),
None,
)
if a_ext:
# if a_ext.status == ExtruderStatus.ACTIVE:
updates["active_tray_id"] = a_ext.active_tray_id
updates["target_tray_id"] = a_ext.target_tray_id
updates["active_tray_state"] = a_ext.tray_state
updates["active_nozzle_temp"] = a_ext.temp
updates["active_nozzle_temp_target"] = a_ext.temp_target
else:
# otherwise process a single extruder printer update
updates["active_nozzle_temp"] = float(
p.get("nozzle_temper", base.active_nozzle_temp)
)
updates["active_nozzle_temp_target"] = int(
p.get("nozzle_target_temper", base.active_nozzle_temp_target)
)
updates["active_tray_id"] = int(ams_root.get("tray_now", base.active_tray_id))
if updates["active_tray_id"] == 255:
updates["active_tray_id"] = -1
updates["active_tray_state"] = TrayState.UNLOADED
elif updates["current_stage_id"] == 24:
updates["active_tray_state"] = TrayState.LOADING
elif updates["current_stage_id"] == 22:
updates["active_tray_state"] = TrayState.UNLOADING
else:
updates["active_tray_state"] = TrayState.LOADED
if "active_tray_id" in updates:
updates["is_external_spool_active"] = updates["active_tray_id"] in [254, 255]
else:
updates["is_external_spool_active"] = False
if "active_tray_state" in updates:
updates["active_tray_state_name"] = updates["active_tray_state"].name
# GLOBAL METADATA & FANS
raw_exist = ams_root.get("ams_exist_bits", base.ams_exist_bits)
updates["ams_exist_bits"] = (
int(raw_exist, 16) if isinstance(raw_exist, str) else int(raw_exist)
)
updates["ams_connected_count"] = bin(updates["ams_exist_bits"]).count("1")
updates["ams_status_raw"] = int(p.get("ams_status", base.ams_status_raw))
updates["ams_status_text"] = parseAMSStatus(updates["ams_status_raw"])
updates["climate"].part_cooling_fan_speed_percent = scaleFanSpeed(
p.get("cooling_fan_speed", 0)
)
updates["climate"].part_cooling_fan_speed_target_percent = scaleFanSpeed(
p.get(
"cooling_fan_target_speed",
base.climate.part_cooling_fan_speed_target_percent,
)
)
updates["climate"].heatbreak_fan_speed_percent = scaleFanSpeed(
p.get("heatbreak_fan_speed", base.climate.heatbreak_fan_speed_percent)
)
updates["climate"].exhaust_fan_speed_percent = scaleFanSpeed(
p.get("big_fan2_speed", 0)
)
updates["climate"].chamber_fan_speed_percent = scaleFanSpeed(
p.get("big_fan1_speed", base.climate.chamber_fan_speed_percent)
)
# ERROR HANDLING
updates["print_error"] = int(p.get("print_error", base.print_error))
if updates["print_error"] != 0:
decoded_error = decodeError(updates["print_error"])
else:
decoded_error = {}
base.hms_errors = []
updates["hms_errors"] = decodeHMS(p.get("hms", base.hms_errors))
if decoded_error and decoded_error not in updates["hms_errors"]:
updates["hms_errors"].insert(0, decoded_error)
return replace(base, **updates)
ExtruderState
dataclass
ExtruderState(
id: int = 0,
temp: float = 0.0,
temp_target: int = 0,
info_bits: int = 0,
state: ExtruderInfoState = NO_NOZZLE,
status: ExtruderStatus = IDLE,
active_tray_id: int = -1,
target_tray_id: int = -1,
tray_state: TrayState = LOADED,
)
State for an individual physical extruder toolhead.
Attributes:
| Name | Type | Description |
|---|---|---|
active_tray_id |
int
|
The active tray for this extruder. Population: |
id |
int
|
Physical ID. Population: |
info_bits |
int
|
Raw bitmask. Population: |
state |
ExtruderInfoState
|
Filament status. Population: |
status |
ExtruderStatus
|
Op state. Population: |
target_tray_id |
int
|
The target tray for this extruder. Population: |
temp |
float
|
Current Temp. Population: |
temp_target |
int
|
Target Temp. Population: |
tray_state |
TrayState
|
The tray state of this extruder |
active_tray_id
class-attribute
instance-attribute
The active tray for this extruder. Population: int(r.hnow)
info_bits
class-attribute
instance-attribute
Raw bitmask. Population: int(r.get("info")).
state
class-attribute
instance-attribute
state: ExtruderInfoState = NO_NOZZLE
Filament status. Population: parseExtruderInfo.
status
class-attribute
instance-attribute
status: ExtruderStatus = IDLE
Op state. Population: parseExtruderStatus.
target_tray_id
class-attribute
instance-attribute
The target tray for this extruder. Population: int(r.htar >> 8).
temp
class-attribute
instance-attribute
Current Temp. Population: unpackTemperature(r.temp).
temp_target
class-attribute
instance-attribute
Target Temp. Population: unpackTemperature(r.temp).
tray_state
class-attribute
instance-attribute
tray_state: TrayState = LOADED
The tray state of this extruder
PrinterCapabilities
dataclass
PrinterCapabilities(
has_ams: bool = False,
has_lidar: bool = False,
has_camera: bool = False,
has_dual_extruder: bool = False,
has_air_filtration: bool = False,
has_chamber_temp: bool = False,
)
Discovery features based on hardware block presence in telemetry.
Attributes:
| Name | Type | Description |
|---|---|---|
has_air_filtration |
bool
|
True if airduct exists. Population: |
has_ams |
bool
|
True if an AMS unit is detected. Population: |
has_camera |
bool
|
True if printer has camera. Population: Hardcoded True for H2D. |
has_chamber_temp |
bool
|
True if CTC exists. Population: |
has_dual_extruder |
bool
|
True if H2D architecture. Population: |
has_lidar |
bool
|
True if printer has LiDAR. Population: |
has_air_filtration
class-attribute
instance-attribute
True if airduct exists. Population: airduct block presence.
has_ams
class-attribute
instance-attribute
True if an AMS unit is detected. Population: ams block presence.
has_camera
class-attribute
instance-attribute
True if printer has camera. Population: Hardcoded True for H2D.
has_chamber_temp
class-attribute
instance-attribute
True if CTC exists. Population: ctc_root block presence.
has_dual_extruder
class-attribute
instance-attribute
True if H2D architecture. Population: len(extruder_root.info) > 1.