Skip to content

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

Python
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: str(ams_idx).

assigned_to_extruder ActiveTool

Target tool. Population: ActiveTool(h2d_toolhead_index).

chip_id str

Hardware serial. Population: m.get("sn").

circ_fan_on bool

Circ fan state. Population: p_ams["circ_fan_on"].

dry_time int

Minutes left. Population: int(float(r.get("dry_time"))).

drying_stage AMSDryingStage

Cycle stage. Population: _resolve_drying_stage.

exhaust_fan_on bool

Exhaust fan state. Population: p_ams["exhaust_fan_on"].

hardware_fault bool

Fault status. Population: p_ams["hardware_fault"].

heater_on bool

Heater state. Population: p_ams["heater_on"].

high_power_mode bool

Power mode. Population: p_ams.get("high_power_mode").

hub_sensor_triggered bool

Filament at hub. Population: p_ams["hub_sensor_triggered"].

humidity_index int

Humidity index. Population: int(float(r.get("humidity"))).

humidity_raw int

Raw humidity. Population: int(float(r.get("humidity_raw"))).

humidity_sensor_ok bool

Sensor health. Population: p_ams["humidity_sensor_ok"].

is_ams_lite bool

True if AMS Lite. Population: product_name check.

is_online bool

Connectivity. Population: p_ams["is_online"].

is_powered bool

Power status. Population: p_ams["is_powered"].

is_rotating bool

Rollers turning. Population: p_ams["is_rotating"].

rfid_ready bool

RFID status. Population: p_ams["rfid_ready"].

temp_actual float

Actual temp. Population: float(r.get("temp")).

temp_target int

Target drying temp. Population: trays[0].drying_temp.

tray_exists list[bool]

Slot presence. Population: Shifting tray_exist_bits.

venting_active bool

Venting state. Population: p_ams.get("venting_active").

ams_id instance-attribute

Python
ams_id: str

Unique ID. Population: str(ams_idx).

assigned_to_extruder class-attribute instance-attribute

Python
assigned_to_extruder: ActiveTool = SINGLE_EXTRUDER

Target tool. Population: ActiveTool(h2d_toolhead_index).

chip_id class-attribute instance-attribute

Python
chip_id: str = ''

Hardware serial. Population: m.get("sn").

circ_fan_on class-attribute instance-attribute

Python
circ_fan_on: bool = False

Circ fan state. Population: p_ams["circ_fan_on"].

dry_time class-attribute instance-attribute

Python
dry_time: int = 0

Minutes left. Population: int(float(r.get("dry_time"))).

drying_stage class-attribute instance-attribute

Python
drying_stage: AMSDryingStage = IDLE

Cycle stage. Population: _resolve_drying_stage.

exhaust_fan_on class-attribute instance-attribute

Python
exhaust_fan_on: bool = False

Exhaust fan state. Population: p_ams["exhaust_fan_on"].

hardware_fault class-attribute instance-attribute

Python
hardware_fault: bool = False

Fault status. Population: p_ams["hardware_fault"].

heater_on class-attribute instance-attribute

Python
heater_on: bool = False

Heater state. Population: p_ams["heater_on"].

high_power_mode class-attribute instance-attribute

Python
high_power_mode: bool = False

Power mode. Population: p_ams.get("high_power_mode").

hub_sensor_triggered class-attribute instance-attribute

Python
hub_sensor_triggered: bool = False

Filament at hub. Population: p_ams["hub_sensor_triggered"].

humidity_index class-attribute instance-attribute

Python
humidity_index: int = 0

Humidity index. Population: int(float(r.get("humidity"))).

humidity_raw class-attribute instance-attribute

Python
humidity_raw: int = 0

Raw humidity. Population: int(float(r.get("humidity_raw"))).

humidity_sensor_ok class-attribute instance-attribute

Python
humidity_sensor_ok: bool = False

Sensor health. Population: p_ams["humidity_sensor_ok"].

is_ams_lite class-attribute instance-attribute

Python
is_ams_lite: bool = False

True if AMS Lite. Population: product_name check.

is_online class-attribute instance-attribute

Python
is_online: bool = False

Connectivity. Population: p_ams["is_online"].

is_powered class-attribute instance-attribute

Python
is_powered: bool = False

Power status. Population: p_ams["is_powered"].

is_rotating class-attribute instance-attribute

Python
is_rotating: bool = False

Rollers turning. Population: p_ams["is_rotating"].

rfid_ready class-attribute instance-attribute

Python
rfid_ready: bool = False

RFID status. Population: p_ams["rfid_ready"].

temp_actual class-attribute instance-attribute

Python
temp_actual: float = 0.0

Actual temp. Population: float(r.get("temp")).

temp_target class-attribute instance-attribute

Python
temp_target: int = 0

Target drying temp. Population: trays[0].drying_temp.

tray_exists class-attribute instance-attribute

Python
tray_exists: list[bool] = field(default_factory=lambda: [False] * 4)

Slot presence. Population: Shifting tray_exist_bits.

venting_active class-attribute instance-attribute

Python
venting_active: bool = False

Venting state. Population: p_ams.get("venting_active").

BambuClimate dataclass

Python
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: device.airduct.state.

airduct_sub_mode int

Raw sub mode. Population: airduct.subMode.

bed_temp float

Bed temp. Population: float(p.get("bed_temper")).

bed_temp_target int

Bed target. Population: float(p.get("bed_target_temper")).

chamber_fan_speed_percent int

Chamber fan %. Population: scaleFanSpeed(p.big_fan1_speed).

chamber_temp float

Chamber temp. Population: unpackTemperature(ctc_root.info.temp).

chamber_temp_target int

Chamber target. Population: unpackTemperature(ctc_root.info.temp).

exhaust_fan_speed_percent int

Exhaust fan %. Population: scaleFanSpeed(p.big_fan2_speed).

filter_obstruction_detected bool

Filter pressure warning. Population: Bitwise airduct.state & 0x02.

has_active_filtration bool

Filter status. Population: Bitwise airduct.state & 0x08.

heatbreak_fan_speed_percent int

Heatbreak fan %. Population: scaleFanSpeed(p.heatbreak_fan_speed).

is_chamber_cooling bool

True if active cooling. Population: Bitwise airduct.state & 0x20.

is_chamber_heating bool

True if active heating. Population: Bitwise airduct.state & 0x10.

is_dryer_engaged bool

True if subMode Bit 0. Population: airduct.subMode & 0x01.

part_cooling_fan_speed_percent int

Part fan %. Population: scaleFanSpeed(p.cooling_fan_speed).

part_cooling_fan_speed_target_percent int

Part target %. Population: scaleFanSpeed(p.cooling_fan_target_speed).

top_vent_open bool

Vent status. Population: Bitwise airduct.state & 0x01.

zone_exhaust_percent int

Exhaust %. Population: airduct.parts ID 48.

zone_intake_percent int

Intake %. Population: airduct.parts ID 32.

zone_internal_percent int

Internal %. Population: airduct.parts ID 16.

zone_sync_error bool

Alignment error. Population: Bitwise airduct.state & 0x04.

ac_unit_power_percent class-attribute instance-attribute

Python
ac_unit_power_percent: int = 0

AC/Compressor power. Population: airduct.parts ID 96.

airduct_mode class-attribute instance-attribute

Python
airduct_mode: int = 0

Raw current mode. Population: airduct.modeCur.

airduct_state_raw class-attribute instance-attribute

Python
airduct_state_raw: int = 0

Raw airduct status mask. Population: device.airduct.state.

airduct_sub_mode class-attribute instance-attribute

Python
airduct_sub_mode: int = 0

Raw sub mode. Population: airduct.subMode.

bed_temp class-attribute instance-attribute

Python
bed_temp: float = 0.0

Bed temp. Population: float(p.get("bed_temper")).

bed_temp_target class-attribute instance-attribute

Python
bed_temp_target: int = 0

Bed target. Population: float(p.get("bed_target_temper")).

chamber_fan_speed_percent class-attribute instance-attribute

Python
chamber_fan_speed_percent: int = 0

Chamber fan %. Population: scaleFanSpeed(p.big_fan1_speed).

chamber_temp class-attribute instance-attribute

Python
chamber_temp: float = 0.0

Chamber temp. Population: unpackTemperature(ctc_root.info.temp).

chamber_temp_target class-attribute instance-attribute

Python
chamber_temp_target: int = 0

Chamber target. Population: unpackTemperature(ctc_root.info.temp).

exhaust_fan_speed_percent class-attribute instance-attribute

Python
exhaust_fan_speed_percent: int = 0

Exhaust fan %. Population: scaleFanSpeed(p.big_fan2_speed).

filter_obstruction_detected class-attribute instance-attribute

Python
filter_obstruction_detected: bool = False

Filter pressure warning. Population: Bitwise airduct.state & 0x02.

has_active_filtration class-attribute instance-attribute

Python
has_active_filtration: bool = False

Filter status. Population: Bitwise airduct.state & 0x08.

heatbreak_fan_speed_percent class-attribute instance-attribute

Python
heatbreak_fan_speed_percent: int = 0

Heatbreak fan %. Population: scaleFanSpeed(p.heatbreak_fan_speed).

is_chamber_cooling class-attribute instance-attribute

Python
is_chamber_cooling: bool = False

True if active cooling. Population: Bitwise airduct.state & 0x20.

is_chamber_heating class-attribute instance-attribute

Python
is_chamber_heating: bool = False

True if active heating. Population: Bitwise airduct.state & 0x10.

is_dryer_engaged class-attribute instance-attribute

Python
is_dryer_engaged: bool = False

True if subMode Bit 0. Population: airduct.subMode & 0x01.

part_cooling_fan_speed_percent class-attribute instance-attribute

Python
part_cooling_fan_speed_percent: int = 0

Part fan %. Population: scaleFanSpeed(p.cooling_fan_speed).

part_cooling_fan_speed_target_percent class-attribute instance-attribute

Python
part_cooling_fan_speed_target_percent: int = 0

Part target %. Population: scaleFanSpeed(p.cooling_fan_target_speed).

top_vent_open class-attribute instance-attribute

Python
top_vent_open: bool = False

Vent status. Population: Bitwise airduct.state & 0x01.

zone_exhaust_percent class-attribute instance-attribute

Python
zone_exhaust_percent: int = 0

Exhaust %. Population: airduct.parts ID 48.

zone_intake_percent class-attribute instance-attribute

Python
zone_intake_percent: int = 0

Intake %. Population: airduct.parts ID 32.

zone_internal_percent class-attribute instance-attribute

Python
zone_internal_percent: int = 0

Internal %. Population: airduct.parts ID 16.

zone_sync_error class-attribute instance-attribute

Python
zone_sync_error: bool = False

Alignment error. Population: Bitwise airduct.state & 0x04.

BambuState dataclass

Python
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 a_ext or p.

active_nozzle_temp_target int

Nozzle target. Population: Handoff from a_ext or p.

active_tool ActiveTool

Active toolhead. Population: extruder_root.state shift.

active_tray_id int

Current tray. Population: Computed in Tool Handoff.

active_tray_state TrayState

Loading enum. Population: ExtruderInfoState check.

active_tray_state_name str

Loading string. Population: active_tray_state.name.

ams_connected_count int

AMS count. Population: bin(ams_exist_bits).count("1").

ams_exist_bits int

AMS mask. Population: int(ams_root.ams_exist_bits, 16).

ams_handle_map dict[int, int]

Logical handles. Population: info.module parsing.

ams_status_raw int

Raw AMS status. Population: int(p.get("ams_status")).

ams_status_text str

Human AMS status. Population: parseAMSStatus.

ams_units list[AMSUnitState]

Unit details. Population: Result of unit iteration.

capabilities PrinterCapabilities

Machine flags. Population: PrinterCapabilities instantiation.

climate BambuClimate

Contains all climate related attributes

current_layer int

Layer index. Population: int(p.get("layer_num")).

current_stage_id int

Stage numeric ID. Population: int(p.get("stg_cur")).

current_stage_name str

Stage human name. Population: parseStage.

extruders list[ExtruderState]

Extruder details. Population: Result of extruder iteration.

gcode_state str

Execution state. Population: p.get("gcode_state").

hms_errors list[dict]

HMS list. Population: decodeHMS + decodeError synthesis.

is_external_spool_active bool

Ext spool flag. Population: active_tray_id in [254, 255].

print_error int

Main error. Population: int(p.get("print_error")).

print_percentage int

Completion %. Population: int(p.get("mc_percent")).

remaining_minutes int

Time left. Population: int(p.get("mc_remaining_time")).

target_tray_id int

Next tray. Population: Stage-specific targeting logic.

total_layers int

Layer total. Population: int(p.get("total_layer_num")).

active_nozzle_temp class-attribute instance-attribute

Python
active_nozzle_temp: float = 0.0

Nozzle temp. Population: Handoff from a_ext or p.

active_nozzle_temp_target class-attribute instance-attribute

Python
active_nozzle_temp_target: int = 0

Nozzle target. Population: Handoff from a_ext or p.

active_tool class-attribute instance-attribute

Python
active_tool: ActiveTool = SINGLE_EXTRUDER

Active toolhead. Population: extruder_root.state shift.

active_tray_id class-attribute instance-attribute

Python
active_tray_id: int = 255

Current tray. Population: Computed in Tool Handoff.

active_tray_state class-attribute instance-attribute

Python
active_tray_state: TrayState = UNLOADED

Loading enum. Population: ExtruderInfoState check.

active_tray_state_name class-attribute instance-attribute

Python
active_tray_state_name: str = name

Loading string. Population: active_tray_state.name.

ams_connected_count class-attribute instance-attribute

Python
ams_connected_count: int = 0

AMS count. Population: bin(ams_exist_bits).count("1").

ams_exist_bits class-attribute instance-attribute

Python
ams_exist_bits: int = 0

AMS mask. Population: int(ams_root.ams_exist_bits, 16).

ams_handle_map class-attribute instance-attribute

Python
ams_handle_map: dict[int, int] = field(default_factory=dict)

Logical handles. Population: info.module parsing.

ams_status_raw class-attribute instance-attribute

Python
ams_status_raw: int = 0

Raw AMS status. Population: int(p.get("ams_status")).

ams_status_text class-attribute instance-attribute

Python
ams_status_text: str = ''

Human AMS status. Population: parseAMSStatus.

ams_units class-attribute instance-attribute

Python
ams_units: list[AMSUnitState] = field(default_factory=list)

Unit details. Population: Result of unit iteration.

capabilities class-attribute instance-attribute

Python
capabilities: PrinterCapabilities = field(default_factory=PrinterCapabilities)

Machine flags. Population: PrinterCapabilities instantiation.

climate class-attribute instance-attribute

Python
climate: BambuClimate = field(default_factory=BambuClimate)

Contains all climate related attributes

current_layer class-attribute instance-attribute

Python
current_layer: int = 0

Layer index. Population: int(p.get("layer_num")).

current_stage_id class-attribute instance-attribute

Python
current_stage_id: int = 0

Stage numeric ID. Population: int(p.get("stg_cur")).

current_stage_name class-attribute instance-attribute

Python
current_stage_name: str = ''

Stage human name. Population: parseStage.

extruders class-attribute instance-attribute

Python
extruders: list[ExtruderState] = field(default_factory=list)

Extruder details. Population: Result of extruder iteration.

gcode_state class-attribute instance-attribute

Python
gcode_state: str = 'IDLE'

Execution state. Population: p.get("gcode_state").

hms_errors class-attribute instance-attribute

Python
hms_errors: list[dict] = field(default_factory=list)

HMS list. Population: decodeHMS + decodeError synthesis.

is_external_spool_active class-attribute instance-attribute

Python
is_external_spool_active: bool = False

Ext spool flag. Population: active_tray_id in [254, 255].

print_error class-attribute instance-attribute

Python
print_error: int = 0

Main error. Population: int(p.get("print_error")).

print_percentage class-attribute instance-attribute

Python
print_percentage: int = 0

Completion %. Population: int(p.get("mc_percent")).

remaining_minutes class-attribute instance-attribute

Python
remaining_minutes: int = 0

Time left. Population: int(p.get("mc_remaining_time")).

target_tray_id class-attribute instance-attribute

Python
target_tray_id: int = -1

Next tray. Population: Stage-specific targeting logic.

total_layers class-attribute instance-attribute

Python
total_layers: int = 0

Layer total. Population: int(p.get("total_layer_num")).

fromJson classmethod

Python
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
Python
@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

Python
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: int(r.hnow)

id int

Physical ID. Population: int(r.get("id")).

info_bits int

Raw bitmask. Population: int(r.get("info")).

state ExtruderInfoState

Filament status. Population: parseExtruderInfo.

status ExtruderStatus

Op state. Population: parseExtruderStatus.

target_tray_id int

The target tray for this extruder. Population: int(r.htar >> 8).

temp float

Current Temp. Population: unpackTemperature(r.temp).

temp_target int

Target Temp. Population: unpackTemperature(r.temp).

tray_state TrayState

The tray state of this extruder

active_tray_id class-attribute instance-attribute

Python
active_tray_id: int = -1

The active tray for this extruder. Population: int(r.hnow)

id class-attribute instance-attribute

Python
id: int = 0

Physical ID. Population: int(r.get("id")).

info_bits class-attribute instance-attribute

Python
info_bits: int = 0

Raw bitmask. Population: int(r.get("info")).

state class-attribute instance-attribute

Python
state: ExtruderInfoState = NO_NOZZLE

Filament status. Population: parseExtruderInfo.

status class-attribute instance-attribute

Python
status: ExtruderStatus = IDLE

Op state. Population: parseExtruderStatus.

target_tray_id class-attribute instance-attribute

Python
target_tray_id: int = -1

The target tray for this extruder. Population: int(r.htar >> 8).

temp class-attribute instance-attribute

Python
temp: float = 0.0

Current Temp. Population: unpackTemperature(r.temp).

temp_target class-attribute instance-attribute

Python
temp_target: int = 0

Target Temp. Population: unpackTemperature(r.temp).

tray_state class-attribute instance-attribute

Python
tray_state: TrayState = LOADED

The tray state of this extruder

PrinterCapabilities dataclass

Python
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: airduct block presence.

has_ams bool

True if an AMS unit is detected. Population: ams block presence.

has_camera bool

True if printer has camera. Population: Hardcoded True for H2D.

has_chamber_temp bool

True if CTC exists. Population: ctc_root block presence.

has_dual_extruder bool

True if H2D architecture. Population: len(extruder_root.info) > 1.

has_lidar bool

True if printer has LiDAR. Population: xcam block presence.

has_air_filtration class-attribute instance-attribute

Python
has_air_filtration: bool = False

True if airduct exists. Population: airduct block presence.

has_ams class-attribute instance-attribute

Python
has_ams: bool = False

True if an AMS unit is detected. Population: ams block presence.

has_camera class-attribute instance-attribute

Python
has_camera: bool = False

True if printer has camera. Population: Hardcoded True for H2D.

has_chamber_temp class-attribute instance-attribute

Python
has_chamber_temp: bool = False

True if CTC exists. Population: ctc_root block presence.

has_dual_extruder class-attribute instance-attribute

Python
has_dual_extruder: bool = False

True if H2D architecture. Population: len(extruder_root.info) > 1.

has_lidar class-attribute instance-attribute

Python
has_lidar: bool = False

True if printer has LiDAR. Population: xcam block presence.