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. |
AMSUnitState
dataclass
AMSUnitState(
ams_id: int,
chip_id: str = "",
model: AMSModel = UNKNOWN,
temp_actual: float = 0.0,
temp_target: int = 0,
humidity_index: int = 0,
humidity_raw: int = 0,
ams_info: int = 0,
heater_state: AMSHeatingState = NO_POWER,
raw_extruder_id: int = -1,
dry_time: int = 0,
tray_exists: list[bool] = (lambda: [False] * 4)(),
assigned_to_extruder: ActiveTool = SINGLE_EXTRUDER,
)
State information about an individual AMS unit.
Attributes:
| Name | Type | Description |
|---|---|---|
ams_id |
int
|
Unique ID. Population: |
ams_info |
int
|
Underlying ams info value |
assigned_to_extruder |
ActiveTool
|
Target tool computed from raw_extruder_id |
chip_id |
str
|
Hardware serial. Population: |
dry_time |
int
|
Minutes left. Population: |
heater_state |
AMSHeatingState
|
The computed state of the AMS's heater |
humidity_index |
int
|
Humidity index. Population: |
humidity_raw |
int
|
Raw humidity. Population: |
model |
AMSModel
|
|
raw_extruder_id |
int
|
Raw extruder ID extracted from ams_info |
temp_actual |
float
|
Actual temp. Population: |
temp_target |
int
|
Target drying temp. Population: |
tray_exists |
list[bool]
|
Slot presence. Population: Shifting |
assigned_to_extruder
class-attribute
instance-attribute
assigned_to_extruder: ActiveTool = SINGLE_EXTRUDER
Target tool computed from raw_extruder_id
chip_id
class-attribute
instance-attribute
Hardware serial. Population: m.get("sn").
dry_time
class-attribute
instance-attribute
Minutes left. Population: int(float(r.get("dry_time"))).
heater_state
class-attribute
instance-attribute
heater_state: AMSHeatingState = NO_POWER
The computed state of the AMS's heater
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"))).
raw_extruder_id
class-attribute
instance-attribute
Raw extruder ID extracted from ams_info
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.
BambuClimate
dataclass
BambuClimate(
bed_temp: float = 0.0,
bed_temp_target: int = 0,
airduct_mode: int = -1,
airduct_sub_mode: int = -1,
chamber_temp: float = 0.0,
chamber_temp_target: int = 0,
air_conditioning_mode: AirConditioningMode = NOT_SUPPORTED,
part_cooling_fan_speed_percent: int = 0,
part_cooling_fan_speed_target_percent: int = 0,
aux_fan_speed_percent: int = 0,
exhaust_fan_speed_percent: int = 0,
heatbreak_fan_speed_percent: int = 0,
zone_intake_open: bool = False,
zone_part_fan_percent: int = 0,
zone_aux_percent: int = 0,
zone_exhaust_percent: int = 0,
zone_top_vent_open: bool = False,
is_chamber_door_open: bool = False,
is_chamber_lid_open: bool = False,
)
Contains all climate related attributes
Attributes:
| Name | Type | Description |
|---|---|---|
air_conditioning_mode |
AirConditioningMode
|
The mode the printer's AC is in if equipped with one. |
airduct_mode |
int
|
Raw current mode. Population: airduct.modeCur. |
airduct_sub_mode |
int
|
Raw sub mode. Population: airduct.subMode. |
aux_fan_speed_percent |
int
|
aux fan %. Population: |
bed_temp |
float
|
Bed temp. Population: |
bed_temp_target |
int
|
Bed target. Population: |
chamber_temp |
float
|
Chamber temp. Population: |
chamber_temp_target |
int
|
Chamber target. Population: |
exhaust_fan_speed_percent |
int
|
Exhaust (chamber) fan %. Population: |
heatbreak_fan_speed_percent |
int
|
Heatbreak fan %. Population: |
is_chamber_door_open |
bool
|
For printers that support it (see |
is_chamber_lid_open |
bool
|
For printers that support it (see |
part_cooling_fan_speed_percent |
int
|
Part fan %. Population: |
part_cooling_fan_speed_target_percent |
int
|
Part target %. Population: |
zone_aux_percent |
int
|
aux %. Population: |
zone_exhaust_percent |
int
|
Exhaust %. Population: |
zone_intake_open |
bool
|
Heater power. Population: airduct.parts ID 96. |
zone_part_fan_percent |
int
|
Internal %. Population: |
zone_top_vent_open |
bool
|
Top vent status - derived from exhaust fan on and cooling ac mode. |
air_conditioning_mode
class-attribute
instance-attribute
air_conditioning_mode: AirConditioningMode = NOT_SUPPORTED
The mode the printer's AC is in if equipped with one.
airduct_mode
class-attribute
instance-attribute
Raw current mode. Population: airduct.modeCur.
airduct_sub_mode
class-attribute
instance-attribute
Raw sub mode. Population: airduct.subMode.
aux_fan_speed_percent
class-attribute
instance-attribute
aux fan %. Population: scaleFanSpeed(p.big_fan1_speed).
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_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 (chamber) fan %. Population: scaleFanSpeed(p.big_fan2_speed).
heatbreak_fan_speed_percent
class-attribute
instance-attribute
Heatbreak fan %. Population: scaleFanSpeed(p.heatbreak_fan_speed).
is_chamber_door_open
class-attribute
instance-attribute
For printers that support it (see PrinterCapabilities.has_chamber_door_sensor), reports whether the chamber door is open
is_chamber_lid_open
class-attribute
instance-attribute
For printers that support it (see PrinterCapabilities.has_chamber_door_sensor), reports whether the chamber lid is open
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).
zone_aux_percent
class-attribute
instance-attribute
aux %. Population: airduct.parts ID 32.
zone_exhaust_percent
class-attribute
instance-attribute
Exhaust %. Population: airduct.parts ID 48.
zone_intake_open
class-attribute
instance-attribute
Heater power. Population: airduct.parts ID 96.
zone_part_fan_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,
monotonic_start_time: int = -1,
elapsed_minutes: int = 0,
remaining_minutes: float = 0.0,
current_layer: int = 0,
total_layers: int = 0,
active_ams_id: int = -1,
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(),
spools: list[BambuSpool] = list(),
print_error: int = 0,
hms_errors: list[dict] = list(),
wifi_signal_strength: str = "",
climate: BambuClimate = BambuClimate(),
stat: str = "0",
fun: str = "0",
)
Representation of the Bambu printer state synchronized via MQTT.
Methods:
| Name | Description |
|---|---|
fromJson |
Parses root MQTT payloads into a hierachical BambuState object. |
Attributes:
| Name | Type | Description |
|---|---|---|
active_ams_id |
int
|
Current active AMS unit id |
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_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. |
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: |
elapsed_minutes |
int
|
The elapsed time in minutes for this (or the last) job |
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: |
monotonic_start_time |
int
|
The monotonic time stamp of when this job started |
print_error |
int
|
Main error. Population: |
print_percentage |
int
|
Completion %. Population: |
remaining_minutes |
float
|
Time remaining in minutes for the current job. Population: |
spools |
list[BambuSpool]
|
All spools associated with this printer |
target_tray_id |
int
|
Next tray. Population: Stage-specific targeting logic. |
total_layers |
int
|
Layer total. Population: |
wifi_signal_strength |
str
|
Wi-Fi signal strength in dBm |
active_ams_id
class-attribute
instance-attribute
Current active AMS unit id
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_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.
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.
elapsed_minutes
class-attribute
instance-attribute
The elapsed time in minutes for this (or the last) job
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].
monotonic_start_time
class-attribute
instance-attribute
The monotonic time stamp of when this job started
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 remaining in minutes for the current job. Population: int(p.get("mc_remaining_time")).
spools
class-attribute
instance-attribute
spools: list[BambuSpool] = field(default_factory=list)
All spools associated with this printer
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")).
wifi_signal_strength
class-attribute
instance-attribute
Wi-Fi signal strength in dBm
fromJson
classmethod
fromJson(
data: dict[str, Any], current_state: BambuState, config: BambuConfig
) -> BambuState
Parses root MQTT payloads into a hierachical BambuState object.
Source code in src/bpm/bambustate.py
@classmethod
def fromJson(
cls, data: dict[str, Any], current_state: "BambuState", config: BambuConfig
) -> "BambuState":
"""Parses root MQTT payloads into a hierachical BambuState object."""
base = current_state if current_state else cls()
info = data.get("info", {})
p = data.get("print", {})
if (
p.get("command", "") == "ams_filament_drying"
and p.get("result", "") == "success"
):
ams_id = p.get("ams_id", -1)
ams = next((u for u in base.ams_units if u.ams_id == ams_id), None)
if ams:
ams.temp_target = int(p.get("temp", 0))
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(config.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_camera"] = True
xcam_data = p.get("xcam", None)
if xcam_data:
caps["has_lidar"] = xcam_data.get("first_layer_inspector", False)
else:
caps["has_lidar"] = config.capabilities.has_lidar
new_caps = 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["fun"] = p.get("fun", base.fun)
fun = int(updates["fun"], 16)
new_caps.has_chamber_door_sensor = bool((fun >> 12) & 0x01)
if new_caps.has_chamber_door_sensor:
updates["stat"] = p.get("stat", base.stat)
stat = int(updates["stat"], 16)
updates["climate"].is_chamber_door_open = bool((stat >> 23) & 0x01)
updates["climate"].is_chamber_lid_open = bool((stat >> 24) & 0x01)
if (
updates["gcode_state"] in ("FAILED", "FINISH")
and updates["gcode_state"] != base.gcode_state
):
updates["monotonic_start_time"] = -1
elif (
updates["gcode_state"] in ("PREPARE", "RUNNING")
and base.monotonic_start_time == -1
):
updates["monotonic_start_time"] = time.monotonic()
else:
if updates["gcode_state"] in ("PREPARE", "RUNNING"):
updates["elapsed_minutes"] = (
time.monotonic()
- updates.get("monotonic_start_time", base.monotonic_start_time)
) / 60.0
updates["current_layer"] = int(p.get("layer_num", base.current_layer))
updates["print_percentage"] = int(
p.get("mc_percent", base.print_percentage)
)
updates["total_layers"] = int(p.get("total_layer_num", base.total_layers))
updates["remaining_minutes"] = float(
p.get("mc_remaining_time", base.remaining_minutes)
)
# 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)
)
if updates["climate"].airduct_mode == 1:
updates["climate"].air_conditioning_mode = AirConditioningMode.HEAT_MODE
elif updates["climate"].airduct_mode == 0:
updates["climate"].air_conditioning_mode = AirConditioningMode.COOL_MODE
base.climate.chamber_temp_target = 0
else:
updates[
"climate"
].air_conditioning_mode = AirConditioningMode.NOT_SUPPORTED
parts = {part["id"]: part["state"] for part in airduct_root.get("parts", [])}
updates["climate"].zone_part_fan_percent = parts.get(
16, base.climate.zone_part_fan_percent
)
updates["climate"].zone_aux_percent = parts.get(
32, base.climate.zone_aux_percent
)
updates["climate"].zone_exhaust_percent = parts.get(
48, base.climate.zone_exhaust_percent
)
zone_intake_open = parts.get(96, -1)
updates["climate"].zone_intake_open = zone_intake_open not in (-1, 0)
updates["climate"].zone_top_vent_open = bool(
updates["climate"].zone_exhaust_percent > 0
and not updates["climate"].zone_intake_open
)
# bit 58 apparently has the top vent value
# is_top_vent_closed_bit = (fun >> 58) & 1
# print(f"\r\ntop bit=[{is_top_vent_closed_bit != 1}] update=[{updates['climate'].zone_top_vent_open}]\r\n")
# if (not is_top_vent_closed_bit) != updates["climate"].zone_top_vent_open:
# updates["climate"].zone_top_vent_open = not is_top_vent_closed_bit
# 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)
)
ctc_temp_target = 0
if ctc_root:
ctc_temp_raw = unpackTemperature(ctc_root.get("info", {}).get("temp", 0.0))
ctc_temp = ctc_temp_raw[0]
ctc_temp_target = int(ctc_temp_raw[1])
updates["climate"].chamber_temp = ctc_temp
updates["climate"].chamber_temp_target = ctc_temp_target
else:
updates["climate"].chamber_temp = p.get(
"chamber_temper", base.climate.chamber_temp
)
if (
ctc_temp_target == 0
and updates["climate"].air_conditioning_mode != AirConditioningMode.HEAT_MODE
):
updates["climate"].chamber_temp_target = base.climate.chamber_temp_target
if p.get("command", "") == "set_ctt" and p.get("result", "") == "success":
ctc_temp_target = int(p.get("ctt_val", -1))
updates["climate"].chamber_temp_target = ctc_temp_target
if ctc_temp_target < 45:
updates["climate"].air_conditioning_mode = AirConditioningMode.COOL_MODE
# EXTRUDERS
new_extruders = []
if "info" in extruder_root:
for new_ext in extruder_root["info"]:
raw_t = int(new_ext.get("temp", 0))
act_t, tar_t = unpackTemperature(raw_t)
sn = int(new_ext.get("snow", -1))
hn = int(new_ext.get("hnow", -1))
st = int(new_ext.get("star", -1))
ht = int(new_ext.get("htar", -1))
ext = ExtruderState()
ext.id = int(new_ext.get("id", 0))
ext.temp = act_t
ext.temp_target = int(tar_t)
ext.info_bits = int(new_ext.get("info", 0))
ext.state = parseExtruderInfo(ext.info_bits)
ext.status = parseExtruderStatus(int(new_ext.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 new_caps.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
cur_ams = {u.ams_id: u for u in base.ams_units}
for m in modules:
if (
m.get("name", "").startswith("n3f/")
or m.get("name", "").startswith("n3s/")
or m.get("name", "").startswith("ams")
):
ams_id = int(m["name"].split("/")[-1])
u = cur_ams.get(ams_id, AMSUnitState(ams_id=ams_id))
u.chip_id = m.get("sn", u.chip_id)
u.model = getAMSModelBySerial(u.chip_id)
cur_ams[ams_id] = u
for ams_u in ams_root.get("ams", []):
id = int(ams_u.get("id", 0))
u = cur_ams.get(id, AMSUnitState(ams_id=id))
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)
elif u.dry_time == 0:
u.temp_target = 0
if "info" in ams_u:
u.ams_info = int(ams_u["info"])
p_ams = parseAMSInfo(u.ams_info)
u.heater_state = p_ams["heater_state"]
u.raw_extruder_id = p_ams["extruder_id"]
if new_caps.has_dual_extruder:
u.assigned_to_extruder = ActiveTool(
p_ams.get("h2d_toolhead_index", 15)
)
updates["extruders"][
u.assigned_to_extruder.value
].assigned_to_ams_id = u.ams_id
rb = ams_root.get("tray_exist_bits")
if rb is not None:
eb = int(rb, 16) if isinstance(rb, str) else int(rb)
# Calculate the bit shift based on the unit ID
# Standard AMS: 0, 1, 2, 3 -> shift 0, 4, 8, 12
# AMS-HT: 128, 129, 130, 131 -> shift 16, 20, 24, 28
if id >= 128:
shift = 16 + (4 * (id - 128))
# AMS-HT is a 1-slot unit, so we check only range(1)
u.tray_exists = [bool((eb >> shift) & (1 << j)) for j in range(1)]
else:
shift = 4 * id
# Standard AMS is a 4-slot unit, so we check range(4)
u.tray_exists = [bool((eb >> shift) & (1 << j)) for j in range(4)]
cur_ams[id] = 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:
updates["active_ams_id"] = (
a_ext.assigned_to_ams_id if a_ext.active_tray_id not in (254, 255) else -1
)
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"])
part_cooling_fan_speed_percent = -1
if not config.capabilities.has_chamber_temp:
part_cooling_fan_speed_percent = (
scaleFanSpeed(p.get("cooling_fan_speed"))
if p.get("cooling_fan_speed", -1) != -1
else -1
)
else:
part_cooling_fan_speed_percent = updates["climate"].zone_part_fan_percent
if part_cooling_fan_speed_percent == -1:
part_cooling_fan_speed_percent = base.climate.part_cooling_fan_speed_percent
updates["climate"].part_cooling_fan_speed_percent = part_cooling_fan_speed_percent
updates["climate"].part_cooling_fan_speed_target_percent = updates[
"climate"
].part_cooling_fan_speed_percent
heatbreak_fan_speed_percent = scaleFanSpeed(p.get("heatbreak_fan_speed", -1))
if heatbreak_fan_speed_percent == -1:
heatbreak_fan_speed_percent = base.climate.heatbreak_fan_speed_percent
updates["climate"].heatbreak_fan_speed_percent = heatbreak_fan_speed_percent
exhaust_fan_speed_percent = -1
if not config.capabilities.has_chamber_temp:
exhaust_fan_speed_percent = scaleFanSpeed(p.get("big_fan2_speed", -1))
else:
exhaust_fan_speed_percent = updates["climate"].zone_exhaust_percent
if exhaust_fan_speed_percent == -1:
exhaust_fan_speed_percent = base.climate.exhaust_fan_speed_percent
updates["climate"].exhaust_fan_speed_percent = exhaust_fan_speed_percent
aux_fan_speed_percent = -1
if not config.capabilities.has_chamber_temp:
aux_fan_speed_percent = scaleFanSpeed(p.get("big_fan1_speed", -1))
else:
aux_fan_speed_percent = updates["climate"].zone_aux_percent
if aux_fan_speed_percent == -1:
aux_fan_speed_percent = base.climate.aux_fan_speed_percent
updates["climate"].aux_fan_speed_percent = aux_fan_speed_percent
updates["wifi_signal_strength"] = p.get("wifi_signal", base.wifi_signal_strength)
# 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)
# capabilities mapped to BambuConfig
config.capabilities = new_caps
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,
assigned_to_ams_id: int = -1,
)
State for an individual physical extruder toolhead.
Attributes:
| Name | Type | Description |
|---|---|---|
active_tray_id |
int
|
The active tray for this extruder. Population: |
assigned_to_ams_id |
int
|
The id of the ams associated with this extruder |
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)
assigned_to_ams_id
class-attribute
instance-attribute
The id of the ams associated with this extruder
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