Skip to content

bambutools

bambutools contains a collection of methods used as tools (mostly internal).

Classes:

Name Description
AMSControlCommand

AMS Control Commands enum. The lowercase name is the ams_control param sent.

AMSDryerFanStatus

AMS drying fan status extracted from bits 18-21 of ams_info.

AMSDryerRefusal

Why an AMS dryer cannot start now, from each AMS block's dry_sf_reason list.

AMSDryerSubStatus

AMS drying sub-status extracted from bits 22-25 of ams_info.

AMSHeatingState

AMS Drying/Heater states extracted from bits 4-7 of ams_info.

AMSModel

AMS model enum

AMSSeries

AMS Series enum

AMSUserSetting

AMS User Settings enum

ActiveTool

The currently active extruder index.

AirConditioningMode

The mode the printer's air conditioning sub system is in.

DetectorSensitivity

Sensitivity level for X-Cam AI vision detectors (spaghetti, pile-up, clump, air-print).

ExtensionToolMountState

Toolhead extension-tool mount state reported in device.ext_tool.mount_3d.

ExtensionToolType

Toolhead extension-tool type reported in device.ext_tool.type.

ExtruderInfoState

Consolidated logical states for extruder sensor status.

ExtruderStatus

Operational states for physical extruders.

FilamentStep

The step of a filament load or unload, named as Bambu Studio's DevFilamentStep.

FilamentStepType

The kind of filament change under way, as Bambu Studio's FilamentStepType.

HMSAction

The dialog buttons Bambu Studio knows, by the ids its HMS action table uses

NozzleDiameter

Nozzle Diameter enum

NozzleFlowType

Canonical nozzle flow families used by BambuStudio/Orca nozzle identifiers.

NozzleMaterialCode

Canonical material codes used in encoded nozzle identifiers.

NozzleType

Canonical cross-model nozzle material type from telemetry.

PlateType

Used by BambuPrinter.print_3mf_file to specify which plate should be used when

PrintOption

Print Option enum

PrinterModel

Printer model enum

PrinterSeries

Printer Series enum

ServiceState

This enum is used by bambu-printer-manager to track the underlying state

SpeedLevel

Print speed profile levels. Maps human-readable names to the firmware integer codes

TrayState

Operational status of the filament tray.

Functions:

Name Description
build_nozzle_identifier

Build a canonical Bambu-style nozzle identifier.

cache_delete

Remove cache_dir/<key>.json if it exists.

cache_read

Load a persisted value from cache_dir/<key>.json.

cache_write

Persist value to cache_dir/<key>.json.

decodeError

Decodes a raw print_error integer into a full HMS dictionary.

decodeHMS

Decodes the raw HMS list from telemetry into a structured list of dictionaries.

dryerRefusalMessage

Explains why a dryer cannot start, the way BambuStudio does

getAMSHeatingState

Decodes the AMS drying/heater state from bits 4-7 of the ams_info value.

getAMSModelBySerial

Returns the hardware model based on the serial number prefix.

getAMSSeriesByModel

Returns the AMS series enum based on the provided model.

getPrinterModelBySerial

Returns the Printer model enum based on the provided serial #.

getPrinterSeriesByModel

Returns the Printer series enum based on the provided model.

get_file_md5

Generates an MD5 hex hash for a given file.

hmsActions

The dialog buttons Bambu Studio shows for print_error on this printer, in order.

jsonSerializer

Module-level JSON serializer for use with json.dumps(default=jsonSerializer).

make_cache_key

Sanitize a raw string into a filesystem-safe cache key.

nozzle_type_to_telemetry

Convert canonical NozzleType to telemetry/API string.

parseAMSInfo

Extracts all documented telemetry attributes from the AMS info hex string.

parseAMSStatus

Maps the ams_status code to human-readable descriptions.

parseExtruderInfo

Decodes the extruder 'info' bit-packed status using unique ExtruderInfoState names.

parseExtruderStatus

Decodes the operational extruder state using the exhaustive Enum map.

parseExtruderTrayState

Decodes an extruder's tray from its device.extruder.info[] hnow/snow (or

parseFilamentStep

Names the filament load or unload step, as Bambu Studio's StatusPanel does.

parseRFIDStatus

Can be used to parse ams_rfid_status

parseStage

Maps stg_cur numeric codes to human-readable print stages.

parse_nozzle_identifier

Parse a Bambu-style nozzle identifier into flow family, material, and diameter.

parse_nozzle_type

Resolve a nozzle type from cross-model telemetry strings or encoded IDs.

printer_cache_dir

One printer's own cache folder: bpm_cache_path/<serial>, or bpm_cache_path

resolve_external_spool_trays

Name the external spool holder that feeds each filament of a sliced plate on a

scaleFanSpeed

Scales proprietary 0-15 fan speed values to a 0-100 percentage.

sortFileTreeAlphabetically

Sorts a dict of file/directory nodes hierarchically.

unpackTemperature

Unpacks a 32-bit packed temperature integer into a tuple of (Actual, Target).

Attributes:

Name Type Description
FILAMENT_STEP_TEXT

Bambu Studio's step labels (Widgets/FilamentLoad.cpp).

HMS_ACTION_COMMAND

What bpm sends for a button, matching Studio's DeviceErrorDialog::on_button_click:

HMS_ACTION_LABEL

Bambu Studio's button text (DeviceErrorDialog::init_button_list).

HMS_LIVEVIEW_CODES

Errors Bambu Studio adds a View Liveview button to (s_jump_liveview_error_codes).

HMS_RETRY_CODES

Errors Bambu Studio answers with Retry and Confirm instead of its table's buttons

VIRTUAL_TRAY_DEPUTY_ID

Virtual tray id of the deputy (left on H2D) extruder's external spool holder.

VIRTUAL_TRAY_MAIN_ID

Virtual tray id of the main (right on H2D) extruder's external spool holder.

FILAMENT_STEP_TEXT module-attribute

Python
FILAMENT_STEP_TEXT = {
    IDLE: "",
    HEAT_NOZZLE: "Heat the nozzle",
    CUT_FILAMENT: "Cut filament",
    PULL_CURR_FILAMENT: "Pull back current filament",
    PUSH_NEW_FILAMENT: "Push new filament into extruder",
    PURGE_OLD_FILAMENT: "Purge old filament",
    CONFIRM_EXTRUDED: "Confirm extruded",
    CHECK_POSITION: "Check filament location",
}

Bambu Studio's step labels (Widgets/FilamentLoad.cpp).

HMS_ACTION_COMMAND module-attribute

Python
HMS_ACTION_COMMAND = {
    FILAMENT_EXTRUDED: "done",
    RETRY_FILAMENT_EXTRUDED: "resume",
    CONTINUE: "resume",
    RETRY_PROBLEM_SOLVED: "resume",
    ABORT: "abort",
    OK_BUTTON: "clean_print_error",
    CHECK_ASSISTANT: "assistant",
    CANCLE: "close",
    DBL_CHECK_CANCEL: "close",
    DBL_CHECK_DONE: "done",
    DBL_CHECK_RETRY: "resume",
    DBL_CHECK_OK: "clean_print_error",
}

What bpm sends for a button, matching Studio's DeviceErrorDialog::on_button_click: done, resume and abort are ams_control params, clean_print_error clears the error, assistant opens the error's url and close sends nothing. A known button not listed here sends a command bpm does not implement, so it carries no command.

HMS_ACTION_LABEL module-attribute

Python
HMS_ACTION_LABEL = {
    RESUME_PRINTING: "Resume Printing",
    RESUME_PRINTING_DEFECTS: "Resume (defects acceptable)",
    RESUME_PRINTING_PROBELM_SOLVED: "Resume (problem solved)",
    STOP_PRINTING: "Stop Printing",
    CHECK_ASSISTANT: "Check Assistant",
    FILAMENT_EXTRUDED: "Filament Extruded, Continue",
    RETRY_FILAMENT_EXTRUDED: "Not Extruded Yet, Retry",
    CONTINUE: "Finished, Continue",
    LOAD_VIRTUAL_TRAY: "Load Filament",
    OK_BUTTON: "OK",
    FILAMENT_LOAD_RESUME: "Filament Loaded, Resume",
    JUMP_TO_LIVEVIEW: "View Liveview",
    NO_REMINDER_NEXT_TIME: "No Reminder Next Time",
    REFRESH_NOZZLE: "Recheck",
    IGNORE_NO_REMINDER_NEXT_TIME: "Ignore. Don't Remind Next Time",
    IGNORE_RESUME: "Ignore this and Resume",
    PROBLEM_SOLVED_RESUME: "Problem Solved and Resume",
    TURN_OFF_FIRE_ALARM: "Got it, Turn off the Fire Alarm.",
    RETRY_PROBLEM_SOLVED: "Retry (problem solved)",
    STOP_DRYING: "Stop Drying",
    CANCLE: "Cancel",
    REMOVE_CLOSE_BTN: "",
    PROCEED: "Proceed",
    OK_JUMP_RACK: "OK",
    ABORT: "Abort",
    DISABLE_PURIFICATION: "Disable Purification for This Print",
    DONT_REMIND_NEXT_TIME: "Don't Remind Me",
    DBL_CHECK_CANCEL: "Cancel",
    DBL_CHECK_DONE: "Done",
    DBL_CHECK_RETRY: "Retry",
    DBL_CHECK_RESUME: "Resume",
    DBL_CHECK_OK: "Confirm",
}

Bambu Studio's button text (DeviceErrorDialog::init_button_list).

HMS_LIVEVIEW_CODES module-attribute

Python
HMS_LIVEVIEW_CODES = {'0300-8003', '0300-8002', '0300-800A'}

Errors Bambu Studio adds a View Liveview button to (s_jump_liveview_error_codes).

HMS_RETRY_CODES module-attribute

Python
HMS_RETRY_CODES = {
    "0701-8004",
    "0701-8005",
    "0701-8007",
    "0701-8012",
    "0702-8012",
    "0703-8012",
    "07FF-8012",
    "07FF-8013",
}

Errors Bambu Studio answers with Retry and Confirm instead of its table's buttons (DeviceErrorDialog.cpp message_containing_retry).

VIRTUAL_TRAY_DEPUTY_ID module-attribute

Python
VIRTUAL_TRAY_DEPUTY_ID = 254

Virtual tray id of the deputy (left on H2D) extruder's external spool holder.

VIRTUAL_TRAY_MAIN_ID module-attribute

Python
VIRTUAL_TRAY_MAIN_ID = 255

Virtual tray id of the main (right on H2D) extruder's external spool holder.

AMSControlCommand

Bases: Enum

AMS Control Commands enum. The lowercase name is the ams_control param sent.

  • DONE: filament extruded, the load may continue (Studio's "Filament Extruded, Continue").
  • ABORT: cancel the running load or unload (Studio's "Abort" and the load view's "Stop").

AMSDryerFanStatus

Bases: IntEnum

AMS drying fan status extracted from bits 18-21 of ams_info. Two independent fans (fan1: bits 18-19, fan2: bits 20-21). Mapped from BambuStudio's DryFanStatus enum.

AMSDryerRefusal

Bases: IntEnum

Why an AMS dryer cannot start now, from each AMS block's dry_sf_reason list. Mapped from BambuStudio's DevAms::CannotDryReason. The printer sends an empty list when a dry can start, and DRYING_IN_PROGRESS alone while one runs.

AMSDryerSubStatus

Bases: IntEnum

AMS drying sub-status extracted from bits 22-25 of ams_info. Indicates the specific phase of the drying cycle. Mapped from BambuStudio's DrySubStatus enum.

AMSHeatingState

Bases: IntEnum

AMS Drying/Heater states extracted from bits 4-7 of ams_info. Mapped directly from BambuStudio's DryStatus enum. Only supported on AMS 2 Pro (N3F) and AMS HT (N3S) models.

AMSModel

Bases: IntEnum

AMS model enum

AMSSeries

Bases: Enum

AMS Series enum

AMSUserSetting

Bases: Enum

AMS User Settings enum

ActiveTool

Bases: IntEnum

The currently active extruder index.

  • SINGLE_EXTRUDER (-1): Standard single-toolhead architecture (X1/P1/A1).
  • RIGHT_EXTRUDER (0): The primary/right toolhead in H2D (Dual Extruder) systems.
  • LEFT_EXTRUDER (1): The secondary/left toolhead in H2D (Dual Extruder) systems.
  • NOT_ACTIVE (15): The multi-extruder system is in a transitional state.

AirConditioningMode

Bases: IntEnum

The mode the printer's air conditioning sub system is in.

  • NOT_SUPPORTED (-1): This printer is not equipped with this feature.
  • COOL_MODE (0): The printer is currently not heating. The top vent may be open if the exhaust fan is running.
  • HEAT_MODE (1): The printer is actively heating the chamber with the recirculation fan active.

DetectorSensitivity

Bases: Enum

Sensitivity level for X-Cam AI vision detectors (spaghetti, pile-up, clump, air-print). The string value is sent directly in the halt_print_sensitivity MQTT field.

ExtensionToolMountState

Bases: IntEnum

Toolhead extension-tool mount state reported in device.ext_tool.mount_3d. Values are mapped from BambuStudio's DevExtensionTool MountState enum.

  • UNKNOWN (-1): No telemetry received yet, or unrecognized value.
  • NOT_MOUNTED (0): No extension tool mounted.
  • MOUNTED (1): Extension tool mounted and communicating.
  • NO_MODULE (2): Mount detected but no module present.
  • NO_CABLE (3): Module mounted but its cable is not connected.

ExtensionToolType

Bases: Enum

Toolhead extension-tool type reported in device.ext_tool.type. Values are the raw type codes mapped from BambuStudio's DevExtensionTool (TOOL_TYPE_* enum). An unrecognized code resolves to UNKNOWN; the raw code is preserved in ExtensionToolState.type_raw.

  • NONE (""): No extension tool attached (or the attached tool is unpowered).
  • CUTTING_MODULE ("CP00"): Blade cutting module.
  • LASER_MODULE ("LB00"): Laser module.
  • ENHANCED_COOLING_FAN ("F000"): Toolhead Enhanced Cooling Fan.
  • UNKNOWN ("?"): Unrecognized type code (future extension tool).

ExtruderInfoState

Bases: IntEnum

Consolidated logical states for extruder sensor status. Values represent unique state IDs to prevent bitmask collisions.

ExtruderStatus

Bases: IntEnum

Operational states for physical extruders. Values are mapped directly from the Bambu Studio source code (BBL_EXTRUDER_STATE).

FilamentStep

Bases: IntEnum

The step of a filament load or unload, named as Bambu Studio's DevFilamentStep.

FILAMENT_STEP_TEXT holds Studio's label for each step.

FilamentStepType

Bases: IntEnum

The kind of filament change under way, as Bambu Studio's FilamentStepType.

  • LOAD: a load from an AMS slot.
  • UNLOAD: an unload (the target slot is none).
  • VT_LOAD: a load from an external spool holder (254 or 255).

HMSAction

Bases: IntEnum

The dialog buttons Bambu Studio knows, by the ids its HMS action table uses (DeviceErrorDialog.hpp ActionButton). Ids the table carries but this enum lacks are buttons Studio does not show.

NozzleDiameter

Bases: Enum

Nozzle Diameter enum

NozzleFlowType

Bases: Enum

Canonical nozzle flow families used by BambuStudio/Orca nozzle identifiers.

Encoded as the second character in identifiers such as HS00-0.4.

NozzleMaterialCode

Bases: Enum

Canonical material codes used in encoded nozzle identifiers.

Encoded as characters 3-4 in identifiers such as HS00-0.4.

NozzleType

Bases: Enum

Canonical cross-model nozzle material type from telemetry.

This enum intentionally models only material/type categories that appear in telemetry and accessory APIs. Encoded variant identifiers such as HS01 are parsed via parse_nozzle_identifier() / parse_nozzle_type().

PlateType

Bases: Enum

Used by BambuPrinter.print_3mf_file to specify which plate should be used when starting a print job.

PrintOption

Bases: Enum

Print Option enum

PrinterModel

Bases: Enum

Printer model enum

PrinterSeries

Bases: Enum

Printer Series enum

ServiceState

Bases: Enum

This enum is used by bambu-printer-manager to track the underlying state of the mqtt connection to the printer.

SpeedLevel

Bases: IntEnum

Print speed profile levels. Maps human-readable names to the firmware integer codes sent in the print_speed MQTT command (param field) and reported back in spd_lvl.

Used by BambuPrinter.speed_level getter (returns the active level) and setter (accepts a SpeedLevel to change the active profile).

TrayState

Bases: IntEnum

Operational status of the filament tray.

build_nozzle_identifier

Python
build_nozzle_identifier(
    flow_type: NozzleFlowType, nozzle_type: NozzleType, diameter: float | str
) -> str

Build a canonical Bambu-style nozzle identifier.

Examples

HS00-0.4, HH01-0.6, HU00-0.4

Source code in src/bpm/bambutools.py
Python
def build_nozzle_identifier(
    flow_type: NozzleFlowType, nozzle_type: NozzleType, diameter: float | str
) -> str:
    """
    Build a canonical Bambu-style nozzle identifier.

    Examples
    --------
    `HS00-0.4`, `HH01-0.6`, `HU00-0.4`
    """

    material = _TYPE_TO_MATERIAL_CODE.get(nozzle_type)
    if material is None:
        raise ValueError(f"Unsupported nozzle_type for encoded ID: {nozzle_type}")

    if flow_type not in {
        NozzleFlowType.STANDARD,
        NozzleFlowType.HIGH_FLOW,
        NozzleFlowType.TPU_HIGH_FLOW,
    }:
        raise ValueError(f"Unsupported flow_type for encoded ID: {flow_type}")

    if isinstance(diameter, float):
        diameter_str = f"{diameter:.1f}"
    else:
        diameter_str = str(diameter)

    return f"H{flow_type.value}{material.value}-{diameter_str}"

cache_delete

Python
cache_delete(cache_dir: Path, key: str) -> None

Remove cache_dir/<key>.json if it exists.

Silently ignores missing files and all other errors.

Parameters:

Name Type Description Default

cache_dir

Path

Directory under bpm_cache_path.

required

key

str

Filename stem produced by :func:make_cache_key.

required
Source code in src/bpm/bambutools.py
Python
def cache_delete(cache_dir: Path, key: str) -> None:
    """
    Remove ``cache_dir/<key>.json`` if it exists.

    Silently ignores missing files and all other errors.

    Args:
        cache_dir: Directory under ``bpm_cache_path``.
        key:       Filename stem produced by :func:`make_cache_key`.
    """
    try:
        (cache_dir / f"{key}.json").unlink(missing_ok=True)
    except Exception:
        pass

cache_read

Python
cache_read(cache_dir: Path, key: str, default: Any = None) -> Any

Load a persisted value from cache_dir/<key>.json.

Returns default (None unless overridden) on any error — missing file, corrupt JSON, permission error, etc. Callers should always treat None / default as a cache miss and fall back gracefully.

Parameters:

Name Type Description Default

cache_dir

Path

Directory under bpm_cache_path.

required

key

str

Filename stem produced by :func:make_cache_key.

required

default

Any

Value to return on any read failure (default None).

None

Returns:

Type Description
Any

The deserialized value, or default.

Source code in src/bpm/bambutools.py
Python
def cache_read(cache_dir: Path, key: str, default: Any = None) -> Any:
    """
    Load a persisted value from ``cache_dir/<key>.json``.

    Returns *default* (``None`` unless overridden) on any error — missing
    file, corrupt JSON, permission error, etc.  Callers should always
    treat ``None`` / *default* as a cache miss and fall back gracefully.

    Args:
        cache_dir: Directory under ``bpm_cache_path``.
        key:       Filename stem produced by :func:`make_cache_key`.
        default:   Value to return on any read failure (default ``None``).

    Returns:
        The deserialized value, or *default*.
    """
    try:
        return json.loads((cache_dir / f"{key}.json").read_text())
    except Exception:
        return default

cache_write

Python
cache_write(cache_dir: Path, key: str, value: Any) -> None

Persist value to cache_dir/<key>.json.

value must be JSON-serializable (dict, list, scalar). The cache directory is created if it does not already exist. All errors are silently swallowed so callers never need to guard this call.

Parameters:

Name Type Description Default

cache_dir

Path

Directory under bpm_cache_path (e.g. …/elapsed).

required

key

str

Filename stem produced by :func:make_cache_key.

required

value

Any

Any JSON-serializable object to persist.

required
Source code in src/bpm/bambutools.py
Python
def cache_write(cache_dir: Path, key: str, value: Any) -> None:
    """
    Persist *value* to ``cache_dir/<key>.json``.

    *value* must be JSON-serializable (dict, list, scalar).  The cache
    directory is created if it does not already exist.  All errors are
    silently swallowed so callers never need to guard this call.

    Args:
        cache_dir: Directory under ``bpm_cache_path`` (e.g. ``…/elapsed``).
        key:       Filename stem produced by :func:`make_cache_key`.
        value:     Any JSON-serializable object to persist.
    """
    try:
        cache_dir.mkdir(parents=True, exist_ok=True)
        (cache_dir / f"{key}.json").write_text(json.dumps(value))
    except Exception:
        pass

decodeError

Python
decodeError(error: int) -> dict

Decodes a raw print_error integer into a full HMS dictionary.

Source code in src/bpm/bambutools.py
Python
def decodeError(error: int) -> dict:
    """
    Decodes a raw print_error integer into a full HMS dictionary.
    """
    if error == 0:
        return {}

    raw_hex = f"{error:08X}".upper()
    wiki_slug = "-".join(raw_hex[i : i + 4] for i in range(0, 8, 4))
    res = {
        "code": f"HMS_{wiki_slug}",
        "msg": "Unknown HMS Error",
        "module": "System",
        "severity": "Error",
        "is_critical": False,
        "type": "device_error",
        # e.bambulab.com has pages only for 16-digit HMS codes; an 8-digit print_error
        # lands on its home page, so point at Bambu's table of these codes instead
        "url": "https://wiki.bambulab.com/en/hms/error-code",
    }

    real_module = (error >> 24) & 0xFF
    module_map = {
        0x03: "Mainboard",
        0x05: "AMS",
        0x12: "AMS",
        0x07: "Toolhead",
        0x0B: "Webcam",
        0x10: "HMS",
    }
    res["module"] = module_map.get(real_module, "System")

    msg_list = HMS_STATUS.get("data", {}).get("device_error", {}).get("en", [])
    modules = ["03", "05", "07", "0B", "0C", "10", "12"]
    if raw_hex[:2] in modules:
        modules.remove(raw_hex[:2])
    modules.insert(0, raw_hex[:2])

    for module in modules:
        current_hex = f"{module}{raw_hex[2:]}"
        for entry in msg_list:
            if entry.get("ecode", "").upper() == current_hex.upper():
                res["msg"] = entry.get("intro", res["msg"])
                break
        if res["msg"] != "Unknown HMS Error":
            break

    mask = (error >> 16) & 0xFF
    if mask in (0x00, 0x01):
        res["severity"] = "Fatal" if mask == 0x00 else "Error"
        res["is_critical"] = True
    elif mask == 0x02:
        res["severity"] = "Warning"
        res["is_critical"] = False
    else:
        res["severity"] = "Info"
        res["is_critical"] = False

    return res

decodeHMS

Python
decodeHMS(hms_list: list) -> list[dict]

Decodes the raw HMS list from telemetry into a structured list of dictionaries.

Source code in src/bpm/bambutools.py
Python
def decodeHMS(hms_list: list) -> list[dict]:
    """
    Decodes the raw HMS list from telemetry into a structured list of dictionaries.
    """
    decoded_errors = []
    for item in hms_list:
        if isinstance(item, dict) and isinstance(item.get("code"), str):
            decoded_errors.append(item)
            continue
        try:
            attr = int(item.get("attr", 0))
            code = int(item.get("code", 0))
        except (AttributeError, ValueError, TypeError):
            continue
        if attr == 0:
            continue

        ecode = f"{attr:08X}{code:08X}"
        wiki_slug = "-".join(ecode[i : i + 4] for i in range(0, 16, 4))
        res = {
            "code": f"HMS_{wiki_slug}",
            "msg": "Unknown HMS Error",
            "module": "System",
            "severity": "Error",
            "is_critical": False,
            "type": "device_hms",
            "url": f"https://e.bambulab.com/?e={ecode}",
        }

        mid = (attr >> 24) & 0xFF
        mask = (attr >> 16) & 0xFF
        module_map = {
            0x03: "Mainboard",
            0x05: "AMS",
            0x12: "AMS",
            0x07: "Toolhead",
            0x0B: "Webcam",
            0x10: "HMS",
        }
        res["module"] = module_map.get(mid, "System")

        msg_list = HMS_STATUS.get("data", {}).get("device_hms", {}).get("en", [])
        for entry in msg_list:
            if entry.get("ecode", "").upper() == ecode:
                res["msg"] = entry.get("intro", res["msg"])
                break

        if mask in (0x00, 0x01):
            res["severity"], res["is_critical"] = (
                ("Fatal" if mask == 0x00 else "Error"),
                True,
            )
        elif mask == 0x02:
            res["severity"] = "Warning"
        else:
            res["severity"] = "Info"
        decoded_errors.append(res)
    return decoded_errors

dryerRefusalMessage

Python
dryerRefusalMessage(reasons: list[int]) -> str

Explains why a dryer cannot start, the way BambuStudio does (AMSDryControl.cpp update_state and organize_cannot_reasons_text).

Returns "" when a dry can start: no reasons, or only DRYING_IN_PROGRESS. Otherwise one line per reason shown: the first of task in progress, AMS busy or filament at the outlet; then insufficient power; then the remaining known reasons in the order received. When none of those apply (task occupied or a value this table does not know), Studio's "System is busy".

Source code in src/bpm/bambutools.py
Python
def dryerRefusalMessage(reasons: list[int]) -> str:
    """
    Explains why a dryer cannot start, the way BambuStudio does
    (AMSDryControl.cpp update_state and organize_cannot_reasons_text).

    Returns "" when a dry can start: no reasons, or only `DRYING_IN_PROGRESS`.
    Otherwise one line per reason shown: the first of task in progress, AMS busy
    or filament at the outlet; then insufficient power; then the remaining
    known reasons in the order received. When none of those apply (task
    occupied or a value this table does not know), Studio's "System is busy".
    """
    if not reasons or set(reasons) == {AMSDryerRefusal.DRYING_IN_PROGRESS}:
        return ""

    def line(reason: int) -> str:
        title, detail = AMS_DRYER_REFUSAL_TEXT.get(reason, AMS_DRYER_REFUSAL_DEFAULT_TEXT)
        return f"{title}. {detail}" if detail else f"{title}."

    lines = []
    for primary in (
        AMSDryerRefusal.DRYING_IN_PROGRESS,
        AMSDryerRefusal.AMS_BUSY,
        AMSDryerRefusal.FILAMENT_AT_OUTLET,
        AMSDryerRefusal.FILAMENT_AT_OUTLET_MANUAL_UNLOAD,
    ):
        if primary in reasons:
            lines.append(line(primary))
            break
    if AMSDryerRefusal.INSUFFICIENT_POWER in reasons:
        lines.append(line(AMSDryerRefusal.INSUFFICIENT_POWER))
    for reason in reasons:
        if reason in (
            AMSDryerRefusal.INSUFFICIENT_POWER_PLUG_IN,
            AMSDryerRefusal.NOT_SUPPORTED_IN_2D_MODE,
            AMSDryerRefusal.INITIATING_DRYING,
            AMSDryerRefusal.UPGRADING,
        ):
            lines.append(line(reason))
    if not lines:
        lines.append(line(-1))
    return "\n".join(lines)

getAMSHeatingState

Python
getAMSHeatingState(ams_info: int) -> AMSHeatingState

Decodes the AMS drying/heater state from bits 4-7 of the ams_info value.

This implementation follows BambuStudio's DevFilaSystem parsing logic, extracting DryStatus from the info field.

Parameters:

Name Type Description Default

ams_info

int

Numeric AMS info value containing drying status in bits 4-7.

required

Returns:

Type Description
AMSHeatingState

AMSHeatingState enum representing the current drying/heater state.

AMSHeatingState

Only AMS 2 Pro (N3F) and AMS HT (N3S) support active drying states.

Source code in src/bpm/bambutools.py
Python
def getAMSHeatingState(ams_info: int) -> AMSHeatingState:
    """
    Decodes the AMS drying/heater state from bits 4-7 of the ams_info value.

    This implementation follows BambuStudio's DevFilaSystem parsing logic,
    extracting DryStatus from the info field.

    Args:
        ams_info: Numeric AMS info value containing drying status in bits 4-7.

    Returns:
        AMSHeatingState enum representing the current drying/heater state.
        Only AMS 2 Pro (N3F) and AMS HT (N3S) support active drying states.
    """
    # Extract DryStatus from bits 4-7 (right-shift by 4, mask 0x0F)
    dry_status = (ams_info >> 4) & 0x0F

    return AMSHeatingState(dry_status)

getAMSModelBySerial

Python
getAMSModelBySerial(serial: str) -> AMSModel

Returns the hardware model based on the serial number prefix.

Source code in src/bpm/bambutools.py
Python
def getAMSModelBySerial(serial: str) -> AMSModel:
    """
    Returns the hardware model based on the serial number prefix.
    """
    prefix = serial[:3].upper()
    if prefix == "19C":
        return AMSModel.AMS_2_PRO
    if prefix == "19F":
        return AMSModel.AMS_HT
    if prefix == "006":
        return AMSModel.AMS_1
    if prefix == "03C":
        return AMSModel.AMS_LITE
    return AMSModel.UNKNOWN

getAMSSeriesByModel

Python
getAMSSeriesByModel(model: AMSModel) -> AMSSeries

Returns the AMS series enum based on the provided model.

Source code in src/bpm/bambutools.py
Python
def getAMSSeriesByModel(model: AMSModel) -> AMSSeries:
    """
    Returns the AMS series enum based on the provided model.
    """
    if model in (AMSModel.AMS_1, AMSModel.AMS_LITE):
        return AMSSeries.GEN_1
    if model in (AMSModel.AMS_HT, AMSModel.AMS_2_PRO):
        return AMSSeries.GEN_2
    return AMSSeries.UNKNOWN

getPrinterModelBySerial

Python
getPrinterModelBySerial(serial: str) -> PrinterModel

Returns the Printer model enum based on the provided serial #.

Source code in src/bpm/bambutools.py
Python
def getPrinterModelBySerial(serial: str) -> PrinterModel:
    """
    Returns the Printer model enum based on the provided serial #.
    """
    mapping = {
        "00M": PrinterModel.X1C,
        "00W": PrinterModel.X1,
        "03W": PrinterModel.X1E,
        "01S": PrinterModel.P1P,
        "01P": PrinterModel.P1S,
        "030": PrinterModel.A1_MINI,
        "039": PrinterModel.A1,
        "22E": PrinterModel.P2S,
        "093": PrinterModel.H2S,
        "094": PrinterModel.H2D,
    }
    prefix = serial[:3]
    return mapping.get(prefix, PrinterModel.UNKNOWN)

getPrinterSeriesByModel

Python
getPrinterSeriesByModel(model: PrinterModel) -> PrinterSeries

Returns the Printer series enum based on the provided model.

Source code in src/bpm/bambutools.py
Python
def getPrinterSeriesByModel(model: PrinterModel) -> PrinterSeries:
    """
    Returns the Printer series enum based on the provided model.
    """
    try:
        return PrinterSeries[model.name[:2]]
    except (KeyError, AttributeError):
        return PrinterSeries.UNKNOWN

get_file_md5

Python
get_file_md5(file_path: str | Path) -> str

Generates an MD5 hex hash for a given file.

Parameters:

Name Type Description Default

file_path

str | Path

The path to the file (string or Path object).

required

Returns:

Type Description
str

The 32-character MD5 hexadecimal string.

Raises:

Type Description
FileNotFoundError

If the file does not exist.

Source code in src/bpm/bambutools.py
Python
def get_file_md5(file_path: str | Path) -> str:
    """
    Generates an MD5 hex hash for a given file.

    Args:
        file_path: The path to the file (string or Path object).

    Returns:
        The 32-character MD5 hexadecimal string.

    Raises:
        FileNotFoundError: If the file does not exist.
    """
    path = Path(file_path)

    if not path.is_file():
        raise FileNotFoundError(f"No file found at: {path}")

    md5_hash = hashlib.md5()

    with path.open("rb") as f:
        for chunk in iter(lambda: f.read(65536), b""):
            md5_hash.update(chunk)

    return md5_hash.hexdigest().upper()

hmsActions

Python
hmsActions(serial_number: str, print_error: int) -> list[dict]

The dialog buttons Bambu Studio shows for print_error on this printer, in order.

As DeviceErrorDialog::apply_result: an HMS_RETRY_CODES error gets Retry and Confirm. Otherwise the error is looked up as %08X in HMS_ACTIONS under the serial's first three characters, and an HMS_LIVEVIEW_CODES error adds View Liveview. Ids Studio does not know, and REMOVE_CLOSE_BTN, are dropped.

Returns a list of {"id": int, "name": str, "label": str, "command": str}. command is empty for a button bpm cannot send (see HMS_ACTION_COMMAND).

Source code in src/bpm/bambutools.py
Python
def hmsActions(serial_number: str, print_error: int) -> list[dict]:
    """
    The dialog buttons Bambu Studio shows for `print_error` on this printer, in order.

    As `DeviceErrorDialog::apply_result`: an `HMS_RETRY_CODES` error gets Retry and
    Confirm. Otherwise the error is looked up as `%08X` in `HMS_ACTIONS` under the
    serial's first three characters, and an `HMS_LIVEVIEW_CODES` error adds View
    Liveview. Ids Studio does not know, and `REMOVE_CLOSE_BTN`, are dropped.

    Returns a list of `{"id": int, "name": str, "label": str, "command": str}`.
    `command` is empty for a button bpm cannot send (see `HMS_ACTION_COMMAND`).
    """
    if not print_error:
        return []
    ecode = f"{print_error & 0xFFFFFFFF:08X}"
    code = f"{ecode[:4]}-{ecode[4:]}"
    if code in HMS_RETRY_CODES:
        ids = (HMSAction.DBL_CHECK_RETRY, HMSAction.DBL_CHECK_OK)
    else:
        table = HMS_ACTIONS.get(str(serial_number)[:3].upper())
        ids = tuple(table["codes"].get(ecode, ())) if table else ()
        if code in HMS_LIVEVIEW_CODES:
            ids += (HMSAction.JUMP_TO_LIVEVIEW,)
    actions = []
    for action_id in ids:
        if action_id not in HMSAction._value2member_map_:
            continue
        action = HMSAction(action_id)
        if action == HMSAction.REMOVE_CLOSE_BTN:
            continue
        actions.append(
            {
                "id": action.value,
                "name": action.name,
                "label": HMS_ACTION_LABEL[action],
                "command": HMS_ACTION_COMMAND.get(action, ""),
            }
        )
    return actions

jsonSerializer

Python
jsonSerializer(obj: Any) -> Any

Module-level JSON serializer for use with json.dumps(default=jsonSerializer).

Handles dataclasses, objects with __dict__, and falls back to str(). Skips MQTT clients, threads, and mappingproxy instances that cannot be serialized meaningfully.

Source code in src/bpm/bambutools.py
Python
def jsonSerializer(obj: Any) -> Any:
    """
    Module-level JSON serializer for use with `json.dumps(default=jsonSerializer)`.

    Handles dataclasses, objects with `__dict__`, and falls back to `str()`.
    Skips MQTT clients, threads, and `mappingproxy` instances that cannot be
    serialized meaningfully.
    """
    try:
        if isinstance(obj, Thread):
            return ""
        if getattr(obj.__class__, "__module__", "").startswith("paho.mqtt"):
            return ""
        if str(obj.__class__).replace("<class '", "").replace("'>", "") == "mappingproxy":
            return ""
        return getattr(obj, "__dict__", str(obj))
    except Exception:
        return ""

make_cache_key

Python
make_cache_key(raw: str, max_len: int = 80) -> str | None

Sanitize a raw string into a filesystem-safe cache key.

Strips leading/trailing whitespace, replaces / and spaces with _, and truncates to max_len characters. Returns None when the result would be an empty string (i.e. the input carried no usable content).

Parameters:

Name Type Description Default

raw

str

The source identifier (e.g. subtask name, gcode file path).

required

max_len

int

Maximum length of the returned key (default 80).

80

Returns:

Type Description
str | None

A non-empty sanitized string, or None.

Source code in src/bpm/bambutools.py
Python
def make_cache_key(raw: str, max_len: int = 80) -> str | None:
    """
    Sanitize a raw string into a filesystem-safe cache key.

    Strips leading/trailing whitespace, replaces ``/`` and spaces with ``_``,
    and truncates to *max_len* characters.  Returns ``None`` when the result
    would be an empty string (i.e. the input carried no usable content).

    Args:
        raw:     The source identifier (e.g. subtask name, gcode file path).
        max_len: Maximum length of the returned key (default 80).

    Returns:
        A non-empty sanitized string, or ``None``.
    """
    sanitized = raw.strip().replace("/", "_").replace(" ", "_")
    return sanitized[:max_len] or None

nozzle_type_to_telemetry

Python
nozzle_type_to_telemetry(value: NozzleType) -> str

Convert canonical NozzleType to telemetry/API string.

Source code in src/bpm/bambutools.py
Python
def nozzle_type_to_telemetry(value: NozzleType) -> str:
    """
    Convert canonical `NozzleType` to telemetry/API string.
    """

    return _NOZZLE_TYPE_ENUM_TO_TELEMETRY.get(value, "unknown")

parseAMSInfo

Python
parseAMSInfo(info_hex: str) -> dict

Extracts all documented telemetry attributes from the AMS info hex string.

Based on BambuStudio's DevFilaSystemParser::ParseAmsInfo implementation. Complete bit field mapping (32-bit integer): - Bits 0-3: AMS type (1=AMS, 2=AMS_LITE, 3=AMS_2_PRO/N3F, 4=AMS_HT/N3S) - Bits 4-7: Dry status (OFF/CHECKING/DRYING/COOLING/STOPPING/ERROR/etc) - Bits 8-11: Extruder ID (for H2D toolhead assignment) - Bits 18-19: Dry fan 1 status (OFF=0, ON=1) - Bits 20-21: Dry fan 2 status (OFF=0, ON=1) - Bits 22-25: Dry sub-status (OFF/HEATING/DEHUMIDIFY)

Parameters:

Name Type Description Default

info_hex

str

Hexadecimal string representation of AMS info value

required

Returns:

Type Description
dict

Dictionary with all extracted telemetry fields

Source code in src/bpm/bambutools.py
Python
def parseAMSInfo(info_hex: str) -> dict:
    """
    Extracts all documented telemetry attributes from the AMS info hex string.

    Based on BambuStudio's DevFilaSystemParser::ParseAmsInfo implementation.
    Complete bit field mapping (32-bit integer):
    - Bits 0-3: AMS type (1=AMS, 2=AMS_LITE, 3=AMS_2_PRO/N3F, 4=AMS_HT/N3S)
    - Bits 4-7: Dry status (OFF/CHECKING/DRYING/COOLING/STOPPING/ERROR/etc)
    - Bits 8-11: Extruder ID (for H2D toolhead assignment)
    - Bits 18-19: Dry fan 1 status (OFF=0, ON=1)
    - Bits 20-21: Dry fan 2 status (OFF=0, ON=1)
    - Bits 22-25: Dry sub-status (OFF/HEATING/DEHUMIDIFY)

    Args:
        info_hex: Hexadecimal string representation of AMS info value

    Returns:
        Dictionary with all extracted telemetry fields
    """
    info = int(info_hex, 16)

    ams_type = info & 0x0F  # Bits 0-3
    dry_status = (info >> 4) & 0x0F  # Bits 4-7
    extruder_id = (info >> 8) & 0x0F  # Bits 8-11
    dry_fan1_status = (info >> 18) & 0x03  # Bits 18-19
    dry_fan2_status = (info >> 20) & 0x03  # Bits 20-21
    dry_sub_status = (info >> 22) & 0x0F  # Bits 22-25

    ret = {
        "ams_type": AMSModel(ams_type) if ams_type in range(5) else AMSModel.UNKNOWN,
        "heater_state": AMSHeatingState(dry_status),
        "extruder_id": extruder_id,
        "dry_fan1_status": AMSDryerFanStatus(dry_fan1_status),
        "dry_fan2_status": AMSDryerFanStatus(dry_fan2_status),
        "dry_sub_status": AMSDryerSubStatus(dry_sub_status),
    }

    # print(f"\r\n{ret}\r\n")
    return ret

parseAMSStatus

Python
parseAMSStatus(status_int: int) -> str

Maps the ams_status code to human-readable descriptions.

Source code in src/bpm/bambutools.py
Python
def parseAMSStatus(status_int: int) -> str:
    """
    Maps the ams_status code to human-readable descriptions.
    """
    main_status = (status_int >> 8) & 0xFF
    status_map = {
        0x00: "Idle",
        0x01: "Filament Changing",
        0x02: "RFID Identifying",
        0x03: "Assist/Engaged",
        0x04: "Calibration",
        0x10: "Self Check",
        0x20: "Debug",
        0xFF: "Unknown",
    }
    return status_map.get(main_status, "Idle")

parseExtruderInfo

Python
parseExtruderInfo(info_int: int) -> ExtruderInfoState

Decodes the extruder 'info' bit-packed status using unique ExtruderInfoState names.

Source code in src/bpm/bambutools.py
Python
def parseExtruderInfo(info_int: int) -> ExtruderInfoState:
    """
    Decodes the extruder 'info' bit-packed status using unique ExtruderInfoState names.
    """
    if not (info_int & 0x08):
        return ExtruderInfoState.NO_NOZZLE
    if info_int & 0x02:
        return ExtruderInfoState.LOADED
    if info_int & 0x04:
        return ExtruderInfoState.BUFFER_LOADED
    return ExtruderInfoState.EMPTY

parseExtruderStatus

Python
parseExtruderStatus(stat_int: int) -> ExtruderStatus

Decodes the operational extruder state using the exhaustive Enum map.

Source code in src/bpm/bambutools.py
Python
def parseExtruderStatus(stat_int: int) -> ExtruderStatus:
    """
    Decodes the operational extruder state using the exhaustive Enum map.
    """
    working_bits = (stat_int >> 8) & 0x03
    if working_bits in (0x02, 0x03):
        return ExtruderStatus.ACTIVE
    if stat_int & 0x01:
        return ExtruderStatus.HEATING
    return ExtruderStatus.IDLE

parseExtruderTrayState

Python
parseExtruderTrayState(extruder: int, hotend, slot) -> int

Decodes an extruder's tray from its device.extruder.info[] hnow/snow (or htar/star) pair.

Parameters

  • extruder : int - the extruder id (0 right, 1 left).
  • hotend : int - hnow or htar; 254 means the external holder.
  • slot : int - snow or star.

Returns the extruder's external holder (255 right, 254 left) when hotend is 254 or slot is 0xFF00 (right) / 0xFE00 (left); -1 when the low byte is 255 (no tray, e.g. 0xFFFF right or 0xFEFF left, an unload's target); otherwise the slot's low byte.

Source code in src/bpm/bambutools.py
Python
def parseExtruderTrayState(extruder: int, hotend, slot) -> int:
    """
    Decodes an extruder's tray from its `device.extruder.info[]` `hnow`/`snow` (or
    `htar`/`star`) pair.

    Parameters
    ----------
    * extruder : int - the extruder id (0 right, 1 left).
    * hotend : int - `hnow` or `htar`; 254 means the external holder.
    * slot : int - `snow` or `star`.

    Returns the extruder's external holder (255 right, 254 left) when `hotend` is 254 or
    `slot` is 0xFF00 (right) / 0xFE00 (left); -1 when the low byte is 255 (no tray, e.g.
    0xFFFF right or 0xFEFF left, an unload's target); otherwise the slot's low byte.
    """
    if (
        hotend == 254
        or (extruder == 0 and slot == 65280)
        or (extruder == 1 and slot == 65024)
    ):
        return 255 - extruder
    # the low byte 255 is no slot (0xFFFF right, 0xFEFF left): no tray, an unload's target
    if slot & 0xFF == 255:
        return -1
    else:
        return slot & 0xFF

parseFilamentStep

Python
parseFilamentStep(
    ams_status: int,
    target_tray_id: int,
    filament_at_extruder: bool = True,
    previous: FilamentStep = IDLE,
) -> tuple[FilamentStep, FilamentStepType]

Names the filament load or unload step, as Bambu Studio's StatusPanel does.

Parameters

  • ams_status : int - raw ams_status; the high byte 1 is a filament change and the low byte is the step code.
  • target_tray_id : int - the loading extruder's target tray: 254 or 255 is an external holder (VT_LOAD), -1 is no target (UNLOAD), anything else is an AMS load.
  • filament_at_extruder : bool - the printer's hw_switch_state (True when unknown). Studio names codes 0x05 and 0x07 of an AMS load differently when it is False.
  • previous : FilamentStep - the step before; code 0x09 is a wait that keeps it.

Returns (FilamentStep, FilamentStepType); IDLE when no change runs or the code is not one Studio names for that kind of change.

Source code in src/bpm/bambutools.py
Python
def parseFilamentStep(
    ams_status: int,
    target_tray_id: int,
    filament_at_extruder: bool = True,
    previous: FilamentStep = FilamentStep.IDLE,
) -> tuple[FilamentStep, FilamentStepType]:
    """
    Names the filament load or unload step, as Bambu Studio's `StatusPanel` does.

    Parameters
    ----------
    * ams_status : int - raw `ams_status`; the high byte 1 is a filament change and the
        low byte is the step code.
    * target_tray_id : int - the loading extruder's target tray: 254 or 255 is an external
        holder (`VT_LOAD`), -1 is no target (`UNLOAD`), anything else is an AMS load.
    * filament_at_extruder : bool - the printer's `hw_switch_state` (True when unknown).
        Studio names codes 0x05 and 0x07 of an AMS load differently when it is False.
    * previous : FilamentStep - the step before; code 0x09 is a wait that keeps it.

    Returns `(FilamentStep, FilamentStepType)`; `IDLE` when no change runs or the code
    is not one Studio names for that kind of change.
    """
    if (ams_status >> 8) & 0xFF != 0x01:
        return FilamentStep.IDLE, FilamentStepType.LOAD
    sub = ams_status & 0xFF
    if target_tray_id in (254, 255):
        vt_steps = {
            0x02: FilamentStep.HEAT_NOZZLE,
            0x05: FilamentStep.PUSH_NEW_FILAMENT,
            0x06: FilamentStep.CONFIRM_EXTRUDED,
            0x07: FilamentStep.PURGE_OLD_FILAMENT,
        }
        return vt_steps.get(sub, FilamentStep.IDLE), FilamentStepType.VT_LOAD
    unload = target_tray_id == -1
    step_type = FilamentStepType.UNLOAD if unload else FilamentStepType.LOAD
    with_temp = not unload and not filament_at_extruder
    steps = {
        0x02: FilamentStep.HEAT_NOZZLE,
        0x03: FilamentStep.CUT_FILAMENT,
        0x04: FilamentStep.PULL_CURR_FILAMENT,
        0x05: FilamentStep.CUT_FILAMENT if with_temp else FilamentStep.PUSH_NEW_FILAMENT,
        0x06: FilamentStep.PUSH_NEW_FILAMENT,
        0x07: FilamentStep.PULL_CURR_FILAMENT
        if with_temp
        else FilamentStep.PURGE_OLD_FILAMENT,
        0x08: FilamentStep.CHECK_POSITION,
        0x0B: FilamentStep.CHECK_POSITION,
    }
    if sub == 0x09:
        return previous, step_type
    if sub not in steps:
        return FilamentStep.IDLE, FilamentStepType.UNLOAD
    return steps[sub], step_type

parseRFIDStatus

Python
parseRFIDStatus(status)

Can be used to parse ams_rfid_status

Source code in src/bpm/bambutools.py
Python
def parseRFIDStatus(status):
    """
    Can be used to parse `ams_rfid_status`
    """
    rfid_map = {
        0: "RFID Idle",
        1: "RFID Reading",
        2: "GCode Translating",
        3: "GCode Running",
        4: "RFID Assistant",
        5: "Switch Filament",
        6: "Has Filament",
    }
    return rfid_map.get(status, "Unknown")

parseStage

Python
parseStage(stage_int: int) -> str

Maps stg_cur numeric codes to human-readable print stages.

Source code in src/bpm/bambutools.py
Python
def parseStage(stage_int: int) -> str:
    """
    Maps stg_cur numeric codes to human-readable print stages.
    """
    stage_map = {
        -1: "",
        0: "",
        1: "Auto bed leveling",
        2: "Heatbed preheating",
        3: "Sweeping XY mech mode",
        4: "Changing filament",
        5: "M400 pause",
        6: "Filament runout pause",
        7: "Heating hotend",
        8: "Calibrating extrusion",
        9: "Scanning bed surface",
        10: "Inspecting first layer",
        11: "Identifying build plate",
        12: "Calibrating Micro Lidar",
        13: "Homing toolhead",
        14: "Cleaning nozzle tip",
        15: "Temp check",
        16: "Paused by user",
        17: "Front cover falling",
        18: "Lidar calibration (alt)",
        19: "Calibrating flow",
        20: "Nozzle temp malfunction",
        21: "Bed temp malfunction",
        22: "Filament unloading",
        23: "Skip step pause",
        24: "Filament loading",
        25: "Motor noise calibration",
        26: "AMS lost pause",
        27: "Fan speed pause",
        28: "Chamber control error",
        29: "Cooling chamber",
        30: "Custom Gcode pause",
        31: "Motor noise showoff",
        32: "Nozzle cover pause",
        33: "Cutter error pause",
        34: "First layer error pause",
        35: "Nozzle clog pause",
        36: "Absolute accuracy pre-check",
        37: "Chamber control",
        38: "Absolute accuracy post-check",
        39: "Nozzle Offset Calibration",
        40: "Bed level high temperature",
        41: "Check quick release",
        42: "Check door and cover",
        43: "Laser calibration",
        44: "Check platform",
        45: "Check birdeye camera position",
        46: "Calibrate birdeye camera",
        47: "Bed level phase 1",
        48: "Bed level phase 2",
        49: "Heating chamber",
        50: "Heated bed cooling",
        51: "Print calibration lines",
        52: "Check material",
        53: "Calibrating live view camera",
        54: "Waiting for heatbed temperature",
        55: "Check material position",
        56: "Calibrating cutter model offset",
        57: "Measuring surface",
        58: "Thermal preconditioning",
        70: "Leading filament",
        71: "Reached toolhead",
        72: "Hotend type detection",
        73: "Build-plate alignment",
        74: "Heatbed object detection",
        75: "Heatbed object detection",
        76: "Cutting",
        77: "Tool switching",
        100: "Printing",
        245: "Enhanced fan full speed",
        246: "Enhanced fan half speed",
        247: "Enhanced fan off",
        255: "Completed",
    }
    return stage_map.get(stage_int, f"Stage [{stage_int}]")

parse_nozzle_identifier

Python
parse_nozzle_identifier(nozzle_id: str) -> tuple[NozzleFlowType, NozzleType, str]

Parse a Bambu-style nozzle identifier into flow family, material, and diameter.

Supported encoded formats include values such as HS00-0.4, HH01-0.6, and HU00-0.4.

Returns

tuple[NozzleFlowType, NozzleType, str] A tuple of (flow_type, nozzle_type, diameter). Unknown/unsupported parts are returned as UNKNOWN enum values.

Source code in src/bpm/bambutools.py
Python
def parse_nozzle_identifier(nozzle_id: str) -> tuple[NozzleFlowType, NozzleType, str]:
    """
    Parse a Bambu-style nozzle identifier into flow family, material, and diameter.

    Supported encoded formats include values such as `HS00-0.4`, `HH01-0.6`,
    and `HU00-0.4`.

    Returns
    -------
    tuple[NozzleFlowType, NozzleType, str]
        A tuple of `(flow_type, nozzle_type, diameter)`.
        Unknown/unsupported parts are returned as `UNKNOWN` enum values.
    """

    if not nozzle_id:
        return (NozzleFlowType.UNKNOWN, NozzleType.UNKNOWN, "")

    clean = nozzle_id.strip()
    if "-" in clean:
        encoded, diameter = clean.split("-", 1)
    else:
        encoded, diameter = clean, ""

    if len(encoded) < 4:
        return (NozzleFlowType.UNKNOWN, NozzleType.UNKNOWN, diameter)

    flow = _FLOW_CODE_TO_TYPE.get(encoded[1], NozzleFlowType.UNKNOWN)
    material_code = encoded[2:4]
    nozzle_type = _MATERIAL_CODE_TO_TYPE.get(material_code, NozzleType.UNKNOWN)

    return (flow, nozzle_type, diameter)

parse_nozzle_type

Python
parse_nozzle_type(value: str | None) -> NozzleType

Resolve a nozzle type from cross-model telemetry strings or encoded IDs.

Supports direct telemetry values (for example hardened_steel) and encoded forms (for example HH01-0.4, HS00, HU05).

Source code in src/bpm/bambutools.py
Python
def parse_nozzle_type(value: str | None) -> NozzleType:
    """
    Resolve a nozzle type from cross-model telemetry strings or encoded IDs.

    Supports direct telemetry values (for example `hardened_steel`) and encoded
    forms (for example `HH01-0.4`, `HS00`, `HU05`).
    """

    if value is None:
        return NozzleType.UNKNOWN

    clean = value.strip()
    if not clean:
        return NozzleType.UNKNOWN

    telemetry_key = clean.lower()
    if telemetry_key in _NOZZLE_TYPE_TELEMETRY_TO_ENUM:
        return _NOZZLE_TYPE_TELEMETRY_TO_ENUM[telemetry_key]

    if clean in _MATERIAL_CODE_TO_TYPE:
        return _MATERIAL_CODE_TO_TYPE[clean]

    _, nozzle_type, _ = parse_nozzle_identifier(clean)
    if nozzle_type != NozzleType.UNKNOWN:
        return nozzle_type

    try:
        return NozzleType[clean.upper()]
    except (KeyError, ValueError):
        return NozzleType.UNKNOWN

printer_cache_dir

Python
printer_cache_dir(bpm_cache_path: Path | None, serial_number: str | None) -> Path

One printer's own cache folder: bpm_cache_path/<serial>, or bpm_cache_path itself when no serial is set.

Every cached file is filed per printer so containers sharing one cache folder never read each other's job record, start time or project metadata.

Source code in src/bpm/bambutools.py
Python
def printer_cache_dir(bpm_cache_path: Path | None, serial_number: str | None) -> Path:
    """
    One printer's own cache folder: ``bpm_cache_path/<serial>``, or ``bpm_cache_path``
    itself when no serial is set.

    Every cached file is filed per printer so containers sharing one cache folder never
    read each other's job record, start time or project metadata.
    """
    base = bpm_cache_path or Path()
    return base / serial_number if serial_number else base

resolve_external_spool_trays

Python
resolve_external_spool_trays(
    filament_extruders: list[int],
    physical_extruder_map: list[int],
    used_filament_ids: list[int],
) -> list[int]

Name the external spool holder that feeds each filament of a sliced plate on a dual-nozzle printer.

The slicer fixes which extruder prints each filament, so the holder is derived from the plate, never chosen by the caller. filament_extruders (the plate's filament_maps in slice_info.config) gives each filament's 1-based logical extruder, and physical_extruder_map (from the plate gcode config block) maps that logical extruder to a physical one, 0 for the main extruder and 1 for the deputy. On H2D the main extruder is the right one and the map is 1,0, so logical extruder 1 is the LEFT extruder.

Parameters

  • filament_extruders : list[int] - 1-based logical extruder per filament, index i is filament id i + 1.
  • physical_extruder_map : list[int] - physical extruder per logical extruder, index j is logical extruder j + 1.
  • used_filament_ids : list[int] - 1-based ids of the filaments the plate uses.

Returns

A list the length of filament_extruders: VIRTUAL_TRAY_MAIN_ID (255) or VIRTUAL_TRAY_DEPUTY_ID (254) at each used filament's index, -1 elsewhere.

Raises

ValueError when the plate carries no extruder assignment or no physical map, or names an extruder or filament id outside them. A wrong holder prints on the wrong side, so this never guesses.

Source code in src/bpm/bambutools.py
Python
def resolve_external_spool_trays(
    filament_extruders: list[int],
    physical_extruder_map: list[int],
    used_filament_ids: list[int],
) -> list[int]:
    """
    Name the external spool holder that feeds each filament of a sliced plate on a
    dual-nozzle printer.

    The slicer fixes which extruder prints each filament, so the holder is derived
    from the plate, never chosen by the caller.  `filament_extruders` (the plate's
    `filament_maps` in `slice_info.config`) gives each filament's 1-based *logical*
    extruder, and `physical_extruder_map` (from the plate gcode config block) maps
    that logical extruder to a physical one, `0` for the main extruder and `1` for
    the deputy.  On H2D the main extruder is the right one and the map is `1,0`, so
    logical extruder 1 is the LEFT extruder.

    Parameters
    ----------
    * filament_extruders : list[int] - 1-based logical extruder per filament,
        index `i` is filament id `i + 1`.
    * physical_extruder_map : list[int] - physical extruder per logical extruder,
        index `j` is logical extruder `j + 1`.
    * used_filament_ids : list[int] - 1-based ids of the filaments the plate uses.

    Returns
    -------
    A list the length of `filament_extruders`: `VIRTUAL_TRAY_MAIN_ID` (255) or
    `VIRTUAL_TRAY_DEPUTY_ID` (254) at each used filament's index, `-1` elsewhere.

    Raises
    ------
    `ValueError` when the plate carries no extruder assignment or no physical map,
    or names an extruder or filament id outside them.  A wrong holder prints on the
    wrong side, so this never guesses.
    """
    if not filament_extruders:
        raise ValueError("plate has no filament extruder assignment (filament_maps)")
    if not physical_extruder_map:
        raise ValueError("plate has no physical_extruder_map")

    trays = [-1] * len(filament_extruders)
    for filament_id in used_filament_ids:
        index = filament_id - 1
        if not 0 <= index < len(filament_extruders):
            raise ValueError(
                f"filament id [{filament_id}] is outside the plate's filaments"
            )

        logical = filament_extruders[index]
        if not 1 <= logical <= len(physical_extruder_map):
            raise ValueError(
                f"filament id [{filament_id}] uses logical extruder [{logical}] outside physical_extruder_map"
            )

        physical = physical_extruder_map[logical - 1]
        if physical == 0:
            trays[index] = VIRTUAL_TRAY_MAIN_ID
        elif physical == 1:
            trays[index] = VIRTUAL_TRAY_DEPUTY_ID
        else:
            raise ValueError(f"physical extruder [{physical}] is neither 0 nor 1")

    return trays

scaleFanSpeed

Python
scaleFanSpeed(raw_val: Any) -> int

Scales proprietary 0-15 fan speed values to a 0-100 percentage.

Source code in src/bpm/bambutools.py
Python
def scaleFanSpeed(raw_val: Any) -> int:
    """
    Scales proprietary 0-15 fan speed values to a 0-100 percentage.
    """
    try:
        val = int(raw_val)
        return min(max(round((val / 15.0) * 100), 0), 100)
    except Exception:
        return 0

sortFileTreeAlphabetically

Python
sortFileTreeAlphabetically(source) -> dict

Sorts a dict of file/directory nodes hierarchically.

Source code in src/bpm/bambutools.py
Python
def sortFileTreeAlphabetically(source) -> dict:
    """
    Sorts a dict of file/directory nodes hierarchically.
    """

    def sort_node_list(node_list):
        for item in node_list:
            if item.get("id", "").endswith("/") and "children" in item:
                item["children"] = sort_node_list(item["children"])
        return sorted(
            node_list,
            key=lambda i: (not i.get("id", "").endswith("/"), i.get("name", "").lower()),
        )

    source["children"] = sort_node_list(source["children"])
    return source

unpackTemperature

Python
unpackTemperature(raw_temp: int) -> tuple[float, float]

Unpacks a 32-bit packed temperature integer into a tuple of (Actual, Target).

Source code in src/bpm/bambutools.py
Python
def unpackTemperature(raw_temp: int) -> tuple[float, float]:
    """
    Unpacks a 32-bit packed temperature integer into a tuple of (Actual, Target).
    """
    return float(raw_temp & 0xFFFF), float((raw_temp >> 16) & 0xFFFF)