bambuprinter
The main bambu-printer-manager class BambuPrinter lives here.
Classes:
| Name | Description |
|---|---|
BambuPrinter |
|
BambuPrinter
BambuPrinter(config: BambuConfig | None = None)
BambuPrinter is the main class within bambu-printer-manager
It is responsible for interacting with and managing your Bambu Lab 3d printer.
It provides an object oriented abstraction layer between your project and the
mqtt and ftps based mechanisms in place for communicating with your printer.
Parameters
- config : Optional[BambuConfig] = None
An optional
BambuConfiginstance that provides configuration information for the printer connection. If not provided, a defaultBambuConfiginstance will be created.
Methods:
| Name | Description |
|---|---|
clean_print_error |
Clears an active print_error on the printer. Sends a clean_print_error |
clean_print_error_uiop |
Sends the UI dialog-close acknowledgment that BambuStudio sends alongside |
clear_command_errors |
Drops every refused-command entry ( |
delete_sdcard_file |
Delete the specified file on the printer's SDCard and returns an updated dict of all files on the printer |
delete_sdcard_folder |
Delete the specified folder on the printer's SDCard and returns an updated dict of all files on the printer |
download_sdcard_file |
Downloads a file from the printer |
ftp_connection |
Open an FTPS connection to the printer's SD card for file management operations. |
get_current_bind_list |
Build the |
get_sdcard_3mf_files |
Returns a |
get_sdcard_contents |
Returns a |
load_filament |
Requests the printer to load filament into the extruder from one AMS slot or an |
make_sdcard_directory |
Creates the specified directory on the printer and returns an updated dict of all files on the printer |
pause_printing |
Pauses the current print job if one is running. |
pause_session |
Pauses the |
print_3mf_file |
Submits a request to print a |
quit |
Shuts down all threads. Your |
refresh |
Triggers a full data refresh from the printer (if it is connected). You should use this |
refresh_nozzles |
Requests the printer to push back current nozzle state. |
refresh_spool_rfid |
Request the printer to re-read the RFID tag for the specified AMS slot. |
rename_printer |
Rename the printer to the specified new name. |
rename_sdcard_file |
Renames the specified file on the printer and returns an updated dict of all files on the printer |
resume_printing |
Resumes the current print job if one is paused. |
resume_session |
Resumes a previously paused session by re-subscribing to the /report topic. |
sdcard_file_exists |
Checks to see if a file exists on the printer at the |
select_extrusion_calibration_profile |
Sets the k factor profile for the specified tray. |
send_ams_control_command |
Send an |
send_anything |
Publish an arbitrary JSON string directly to the printer's MQTT request topic. |
send_gcode |
Submit one, or more, gcode commands to the printer. To submit multiple gcode commands, separate them with a newline (\n) character. |
send_hms_action |
Press a printer prompt's button, sending what Bambu Studio sends for it. |
set_active_tool |
Set the active extruder on multi-tool machines (H2D / H2D Pro). |
set_airprinting_detector |
Enable or disable the air-printing / no-extrusion detector (X-Cam AI vision). |
set_ams_user_setting |
Enable or disable one of the |
set_aux_fan_speed_target_percent |
sets the aux (chamber recirculation) fan speed target represented in percent |
set_bed_temp_target |
Sets the bed temperature target. |
set_buildplate_marker_detector |
Enable or disable the buildplate marker detector (X-Cam AI vision). |
set_chamber_temp |
for printers that do not have managed chambers, this enables you to inject |
set_chamber_temp_target |
set chamber temperature target if printer supports it, otherwise just |
set_enhanced_cooling_fan_speed_target_percent |
sets the toolhead enhanced cooling fan (M106 P9) speed target represented in percent |
set_exhaust_fan_speed_target_percent |
sets the exhaust (chamber) fan speed target represented in percent |
set_nozzle_details |
Inform the printer of the nozzle currently installed. |
set_nozzle_temp_target |
Sets the nozzle temperature target. |
set_nozzleclumping_detector |
Enable or disable the nozzle clumping / blob detector (X-Cam AI vision). |
set_part_cooling_fan_speed_target_percent |
sets the part cooling fan speed target represented in percent |
set_print_option |
Enable or disable one of the |
set_purgechutepileup_detector |
Enable or disable the purge chute pile-up detector (X-Cam AI vision). |
set_spaghetti_detector |
Enable or disable the spaghetti / failed print detector (X-Cam AI vision). |
set_spool_details |
Sets spool / tray details such as filament type, color, and nozzle temperature range. |
set_spool_k_factor |
Set the linear advance (pressure advance) k factor for a specific spool tray. |
skip_objects |
Instructs the printer to skip (cancel) a list of objects during the current print job. |
start_session |
Initiates a connection to the Bambu Lab printer and provides a stateful |
stop_printing |
Requests the printer to stop printing if a job is currently running. |
toJson |
Returns a |
turn_off_ams_dryer |
Sends a command to the printer to turn off the AMS dryer. |
turn_on_ams_dryer |
Sends a command to the printer to turn on the AMS dryer with specified parameters. |
unload_filament |
Requests the printer to unload whatever filament / spool is currently loaded. |
upload_sdcard_file |
Uploads the local filesystem file to the printer and returns an updated dict of all files on the printer |
Attributes:
| Name | Type | Description |
|---|---|---|
active_job_info |
ActiveJobInfo
|
Details related to the current / last active job |
bed_temp_target_time |
Timestamp of the last change to the heatbed target temperature. |
|
cached_sd_card_3mf_files |
A list of only the 3mf files found on the SD card. |
|
cached_sd_card_contents |
A list of all files and folders found on the printer's SD card. |
|
chamber_temp_target_time |
Timestamp of the last change to the chamber target temperature. |
|
client |
Client
|
The networking client used to communicate with the printer. |
config |
BambuConfig
|
The settings used to connect to and configure the printer's behavior. |
fan_speed_target_time |
Timestamp of the last change to the part fan target speed. |
|
internalException |
The last error captured during printer communication. |
|
light_state |
The status of the printer lights. Toggling this will update all lights on the machine. |
|
nozzle_diameter |
NozzleDiameter
|
The diameter of the nozzle currently installed on the printer. |
nozzle_type |
NozzleType
|
The type of nozzle currently installed on the printer. |
on_update |
The callback function executed whenever the printer's state is updated. |
|
printer_state |
BambuState
|
The current status and sensor data for the printer. |
recent_update |
Indicates if the printer's state has been updated recently. |
|
service_state |
The current service connection state. |
|
skipped_objects |
A list of objects that have been excluded from the current print. |
|
speed_level |
SpeedLevel | int
|
The active print speed mode. Returns a SpeedLevel enum member when the firmware |
tool_temp_target_time |
Timestamp of the last change to the nozzle target temperature. |
Source code in src/bpm/bambuprinter.py
def __init__(self, config: BambuConfig | None = None):
"""
Sets up all internal storage attributes for `BambuPrinter` and bootstraps the
logging engine.
Parameters
----------
* config : Optional[BambuConfig] = None
An optional `BambuConfig` instance that provides configuration information
for the printer connection. If not provided, a default `BambuConfig` instance
will be created.
"""
self._mqtt_client_thread = None
self._watchdog_thread = None
self._internalException = None
# Guards `_printer_state` swaps against command-side edits of the same state, so a
# cleared refusal cannot come back from a frame parsed at the same moment.
self._state_lock = threading.RLock()
self._lastMessageTime = None
self._recent_update = False
if config is None:
config = BambuConfig("", "", "")
self._config = config
self._service_state = ServiceState.NO_STATE
self._client = None
self._on_update = None
self._tool_temp_target_time = 0
self._bed_temp_target_time = 0
self._chamber_temp_target_time = 0
self._fan_speed_target_time = 0
self._light_state = ""
self._speed_level = 0
self._printer_state = BambuState()
self._active_job_info = ActiveJobInfo()
# The persisted project_file record for the running job (GH #59); see
# _persist_job_record for why it exists and how it is trusted.
self._task_id = ""
self._job_record_sealed = True
self._job_record_checked = False
self._sdcard_contents = None
self._sdcard_3mf_files = None
self._print_type = ""
self._skipped_objects = []
self._nozzle_type = ""
self._nozzle_diameter = 0.0
active_job_info
property
active_job_info: ActiveJobInfo
Details related to the current / last active job
bed_temp_target_time
property
Timestamp of the last change to the heatbed target temperature.
cached_sd_card_3mf_files
property
A list of only the 3mf files found on the SD card.
cached_sd_card_contents
property
A list of all files and folders found on the printer's SD card.
chamber_temp_target_time
property
Timestamp of the last change to the chamber target temperature.
client
property
writable
The networking client used to communicate with the printer.
This is a private property
config
property
writable
config: BambuConfig
The settings used to connect to and configure the printer's behavior.
fan_speed_target_time
property
Timestamp of the last change to the part fan target speed.
internalException
property
The last error captured during printer communication.
light_state
property
writable
The status of the printer lights. Toggling this will update all lights on the machine.
nozzle_diameter
property
nozzle_diameter: NozzleDiameter
The diameter of the nozzle currently installed on the printer.
Deprecated
This property is deprecated (v1.0.0). Use _printer_state.active_nozzle.diameter_mm instead.
nozzle_type
property
nozzle_type: NozzleType
The type of nozzle currently installed on the printer.
Deprecated
This property is deprecated (v1.0.0). Use _printer_state.active_nozzle.material instead.
on_update
property
writable
The callback function executed whenever the printer's state is updated.
printer_state
property
printer_state: BambuState
The current status and sensor data for the printer.
recent_update
property
Indicates if the printer's state has been updated recently.
skipped_objects
property
A list of objects that have been excluded from the current print.
speed_level
property
writable
speed_level: SpeedLevel | int
The active print speed mode. Returns a SpeedLevel enum member when the firmware value is recognised (1–4); returns the raw integer otherwise (e.g. 0 at startup before the first telemetry push).
tool_temp_target_time
property
Timestamp of the last change to the nozzle target temperature.
clean_print_error
Clears an active print_error on the printer. Sends a clean_print_error command acknowledged by a push_status with print_error reset to 0.
the subtask_id of the failed job (from ActiveJobInfo). Pass
empty string to clear without a specific subtask context.
print_error: the integer error code to clear (e.g. 50348044 for 0x0300400C "task was canceled"). Pass 0 to clear any active error.
Source code in src/bpm/bambuprinter.py
def clean_print_error(self, subtask_id: str = "", print_error: int = 0):
"""
Clears an active print_error on the printer. Sends a clean_print_error
command acknowledged by a push_status with print_error reset to 0.
subtask_id: the subtask_id of the failed job (from ActiveJobInfo). Pass
empty string to clear without a specific subtask context.
print_error: the integer error code to clear (e.g. 50348044 for 0x0300400C
"task was canceled"). Pass 0 to clear any active error.
"""
import copy
cmd = copy.deepcopy(CLEAN_PRINT_ERROR_TEMPLATE)
cmd["print"]["subtask_id"] = subtask_id
cmd["print"]["print_error"] = print_error
self.client.publish(
f"device/{self.config.serial_number}/request",
json.dumps(cmd),
)
logger.debug(
f"clean_print_error - published CLEAN_PRINT_ERROR to "
f"[device/{self.config.serial_number}/request] subtask_id=[{subtask_id}] print_error=[{print_error}]"
)
clean_print_error_uiop
Sends the UI dialog-close acknowledgment that BambuStudio sends alongside clean_print_error when dismissing an error dialog.
Without this signal the printer remains in a "waiting for UI acknowledgment" state. Any open BambuStudio session will re-raise print_error on every push_status until it receives this acknowledgment. Always call this immediately after clean_print_error() to ensure the error stays cleared regardless of whether BambuStudio is open.
the integer error code being cleared (e.g. 50348044 for
HMS_0300-400C "task was canceled"). Used to populate the "err" field as an uppercase 8-character hex string (e.g. "0300400C").
BambuStudio src/slic3r/GUI/DeviceManager.cpp,
command_clean_print_error_uiop() (lines 1253–1268).
Source code in src/bpm/bambuprinter.py
def clean_print_error_uiop(self, print_error: int = 0):
"""
Sends the UI dialog-close acknowledgment that BambuStudio sends alongside
clean_print_error when dismissing an error dialog.
Without this signal the printer remains in a "waiting for UI acknowledgment"
state. Any open BambuStudio session will re-raise print_error on every
push_status until it receives this acknowledgment. Always call this immediately
after clean_print_error() to ensure the error stays cleared regardless of
whether BambuStudio is open.
print_error: the integer error code being cleared (e.g. 50348044 for
HMS_0300-400C "task was canceled"). Used to populate the "err" field as
an uppercase 8-character hex string (e.g. "0300400C").
Source: BambuStudio src/slic3r/GUI/DeviceManager.cpp,
command_clean_print_error_uiop() (lines 1253–1268).
"""
import copy
cmd = copy.deepcopy(CLEAN_PRINT_ERROR_UIOP_TEMPLATE)
cmd["system"]["err"] = f"{print_error:08X}"
self.client.publish(
f"device/{self.config.serial_number}/request",
json.dumps(cmd),
)
logger.debug(
f"clean_print_error_uiop - published uiop dialog-close to "
f"[device/{self.config.serial_number}/request] err=[{print_error:08X}]"
)
clear_command_errors
Drops every refused-command entry (type command_error) from
BambuState.hms_errors, e.g. when the user dismisses it. Nothing is sent to the
printer.
delete_sdcard_file
Delete the specified file on the printer's SDCard and returns an updated dict of all files on the printer
Parameters
- file : str - the full path filename to be deleted
Source code in src/bpm/bambuprinter.py
def delete_sdcard_file(self, file: str):
"""
Delete the specified file on the printer's SDCard and returns an updated dict of all files on the printer
Parameters
----------
* file : str - the full path filename to be deleted
"""
logger.debug(f"delete_sdcard_file - deleting remote file: [{file}]")
with self.ftp_connection() as ftps:
ftps.delete_file(file)
# Invalidate all cached plate metadata for this file
filename = file.lstrip("/").replace("/", "-")
serial = self.config.serial_number
cache_path = self.config.bpm_cache_path if self.config.bpm_cache_path else Path()
if serial:
cache_path = cache_path / serial
for cached in (
(cache_path / "metadata").glob(f"{filename}-*.json")
if (cache_path / "metadata").exists()
else []
):
cached.unlink(missing_ok=True)
logger.debug(f"delete_sdcard_file - removed cache entry [{cached.name}]")
def search_for_and_remove_file(file: str, entry: dict):
if "children" in entry:
entry["children"] = list(
filter(lambda i: i["id"] != file, entry["children"])
)
for child in entry["children"]:
search_for_and_remove_file(file, child)
if self._sdcard_contents:
search_for_and_remove_file(file, self._sdcard_contents)
if self._sdcard_3mf_files:
search_for_and_remove_file(file, self._sdcard_3mf_files)
logger.debug(f"delete_sdcard_file - deleted file [{file}] from sdcard")
return self._sdcard_contents
delete_sdcard_folder
Delete the specified folder on the printer's SDCard and returns an updated dict of all files on the printer
Parameters
- path : str - the full path to folder to be deleted
Source code in src/bpm/bambuprinter.py
def delete_sdcard_folder(self, path: str):
"""
Delete the specified folder on the printer's SDCard and returns an updated dict of all files on the printer
Parameters
----------
* path : str - the full path to folder to be deleted
"""
logger.debug(f"delete_sdcard_folder - deleting remote folder: [{path}]")
def delete_all_contents(ftps: IoTFTPSClient, path: str):
fs = ftps.list_files_ex(path)
if fs is not None:
for item in fs:
if item.is_dir:
delete_all_contents(ftps, item.path)
else:
ftps.delete_file(item.path)
ftps.delete_folder(path)
with self.ftp_connection() as ftps:
delete_all_contents(ftps, path)
# Invalidate all cached plate metadata for files under this folder
prefix = path.strip("/").replace("/", "-")
serial = self.config.serial_number
cache_path = self.config.bpm_cache_path if self.config.bpm_cache_path else Path()
if serial:
cache_path = cache_path / serial
metadata_dir = cache_path / "metadata"
if metadata_dir.exists():
for cached in metadata_dir.glob(f"{prefix}-*.json"):
cached.unlink(missing_ok=True)
logger.debug(
f"delete_sdcard_folder - removed cache entry [{cached.name}]"
)
def search_for_and_remove_folder(path: str, entry: dict):
if not path.endswith("/"):
path = f"{path}/"
if "children" in entry:
entry["children"] = list(
filter(lambda i: not i["id"].startswith(path), entry["children"])
)
for child in entry["children"]:
search_for_and_remove_folder(path, child)
if self._sdcard_contents is not None:
search_for_and_remove_folder(path, self._sdcard_contents)
if self._sdcard_3mf_files is not None:
search_for_and_remove_folder(path, self._sdcard_3mf_files)
return self._sdcard_contents
download_sdcard_file
Downloads a file from the printer
Parameters
- src : str - the full path filename on the printer to be downloaded to the host
- dest : str - the full path filename on the host to store the downloaded file
Source code in src/bpm/bambuprinter.py
def download_sdcard_file(self, src: str, dest: str):
"""
Downloads a file from the printer
Parameters
----------
* src : str - the full path filename on the printer to be downloaded to the host
* dest : str - the full path filename on the host to store the downloaded file
"""
logger.debug(
f"download_sdcard_file - downloading file src: [{src}] dest: [{dest}]"
)
with self.ftp_connection() as ftps:
ftps.download_file(src, dest)
ftp_connection
Open an FTPS connection to the printer's SD card for file management operations.
Intended to be used as a context manager so the connection is closed
automatically on exit. All SD card file operations (upload_sdcard_file,
download_sdcard_file, delete_sdcard_file, etc.) use this internally.
Example
with printer.ftp_connection() as ftps:
ftps.upload_file("/local/myfile.3mf", "/jobs/myfile.3mf")
Source code in src/bpm/bambuprinter.py
@contextlib.contextmanager
def ftp_connection(self):
"""
Open an FTPS connection to the printer's SD card for file management operations.
Intended to be used as a context manager so the connection is closed
automatically on exit. All SD card file operations (`upload_sdcard_file`,
`download_sdcard_file`, `delete_sdcard_file`, etc.) use this internally.
Example
-------
```python
with printer.ftp_connection() as ftps:
ftps.upload_file("/local/myfile.3mf", "/jobs/myfile.3mf")
```
"""
ftps = None
try:
ftps = IoTFTPSClient(
self._config.hostname,
990,
self._config.mqtt_username,
self._config.access_code,
ssl_implicit=True,
timeout=self._config.ftps_connection_timeout,
)
yield ftps
finally:
if ftps and ftps.ftps_session:
ftps.disconnect()
get_current_bind_list
get_current_bind_list(state: BambuState) -> list[dict[str, Any]]
Build the manual_ams_bind list required by the H2D dual-extruder firmware.
Maps each connected AMS unit to its assigned extruder using the H2D's hardware register inversion (RIGHT_EXTRUDER logical index 0 → hardware index 1, LEFT_EXTRUDER logical index 1 → hardware index 0).
When only one AMS unit is connected the firmware still requires a two-entry array; a sentinel placeholder entry (Unit ID 1) is appended automatically to satisfy that requirement.
Parameters
- state : BambuState - Current printer state supplying
ams_unitsandams_connected_count.
Returns
list[dict] - List of dicts with keys ams_f_bind (int), ams_s_bind (int),
and extruder (int, hardware index).
Source code in src/bpm/bambuprinter.py
def get_current_bind_list(self, state: "BambuState") -> list[dict[str, Any]]:
"""
Build the `manual_ams_bind` list required by the H2D dual-extruder firmware.
Maps each connected AMS unit to its assigned extruder using the H2D's
hardware register inversion (RIGHT_EXTRUDER logical index 0 → hardware index 1,
LEFT_EXTRUDER logical index 1 → hardware index 0).
When only one AMS unit is connected the firmware still requires a two-entry
array; a sentinel placeholder entry (Unit ID 1) is appended automatically to
satisfy that requirement.
Parameters
----------
* state : BambuState - Current printer state supplying `ams_units` and
`ams_connected_count`.
Returns
-------
list[dict] - List of dicts with keys `ams_f_bind` (int), `ams_s_bind` (int),
and `extruder` (int, hardware index).
"""
bind_list = []
# 1. Map physical units based on current logical assignments
for unit in state.ams_units:
# Hardware register inversion logic
hw_target = 1 if unit.assigned_to_extruder == ActiveTool.RIGHT_EXTRUDER else 0
bind_list.append(
{"ams_f_bind": int(unit.ams_id), "ams_s_bind": 0, "extruder": hw_target}
)
# 2. Dual-Extruder Array Completeness Requirement
# If only one AMS is connected, firmware requires a placeholder for the unused register.
if state.ams_connected_count == 1 and len(bind_list) == 1:
existing_assignment = bind_list[0]
# Determine the unassigned hardware register
placeholder_hw_target = 0 if existing_assignment["extruder"] == 1 else 1
# Add sentinel placeholder (Unit ID 1) to complete the dual-path mapping
bind_list.append(
{
"ams_f_bind": 1, # Placeholder Unit ID
"ams_s_bind": 0,
"extruder": placeholder_hw_target,
}
)
return bind_list
get_sdcard_3mf_files
Returns a dict (json document) of all .3mf files on the printer's SD card.
Usage
The return value of this method is very useful for binding to things like a clientside TreeView
Source code in src/bpm/bambuprinter.py
def get_sdcard_3mf_files(self):
"""
Returns a `dict` (json document) of all `.3mf` files on the printer's SD card.
Usage
-----
The return value of this method is very useful for binding to things like a clientside `TreeView`
"""
logger.debug("get_sdcard_3mf_files - returning sdcard_3mf_files")
self.get_sdcard_contents()
return self._sdcard_3mf_files
get_sdcard_contents
Returns a dict (json document) of ALL files on the printer's SD card.
The private class level _sdcard_contents attribute is also populated.
Returns None when the listing failed (a timeout, a dropped connection, or a folder
that could not be listed), and clears the cached trees. An empty card is a dict
whose children list is empty, so None always means an error and never "no files".
The same holds for get_sdcard_3mf_files().
Usage
The return value of this method is very useful for binding to things like a clientside TreeView
Source code in src/bpm/bambuprinter.py
def get_sdcard_contents(self):
"""
Returns a `dict` (json document) of ALL files on the printer's SD card.
The private class level `_sdcard_contents` attribute is also populated.
Returns `None` when the listing failed (a timeout, a dropped connection, or a folder
that could not be listed), and clears the cached trees. An empty card is a `dict`
whose `children` list is empty, so `None` always means an error and never "no files".
The same holds for `get_sdcard_3mf_files()`.
Usage
-----
The return value of this method is very useful for binding to things like a clientside `TreeView`
"""
with self.ftp_connection() as ftps:
fs = self._get_sftp_files(ftps, "/")
if fs is None:
logger.warning("get_sdcard_contents - failed to retrieve files from sdcard")
self._sdcard_contents = None
self._sdcard_3mf_files = None
return None
self._sdcard_contents = sortFileTreeAlphabetically(fs)
def search_for_and_remove_all_other_files(mask: str, entry: dict):
if "children" in entry:
entry["children"] = [
x
for x in entry["children"]
if x["id"].endswith(mask) or "children" in x
]
for child in entry["children"]:
search_for_and_remove_all_other_files(mask, child)
self._sdcard_3mf_files = json.loads(json.dumps(self._sdcard_contents))
search_for_and_remove_all_other_files(".3mf", self._sdcard_3mf_files)
logger.debug("get_sdcard_contents - retrieved all files from sdcard")
return fs
load_filament
Requests the printer to load filament into the extruder from one AMS slot or an external spool holder.
Parameters
slot_id : int
The slot within the AMS unit (0-3; 0 for an AMS HT). For an external holder
pass its tray id here (254, or 255 for the right-hand holder of a
dual-nozzle printer), or pass it as ams_id with slot_id 0.
ams_id : int
The AMS unit (0-3 for AMS / AMS 2 Pro / AMS Lite, 128+ for AMS HT).
The command is encoded as Bambu Studio's command_ams_change_filament does: target
is the absolute tray id (ams_id * 4 + slot_id) for an AMS unit, and the unit id
itself for an AMS HT or an external holder (whose ams_id is 254 or 255, slot_id 0).
slot_id 255 with target 255 is the unload command, never a load.
The printer answers with an ams_change_filament reply; a refusal (e.g. the AMS
is drying) appears in BambuState.hms_errors as a command_error entry.
Source code in src/bpm/bambuprinter.py
def load_filament(self, slot_id: int, ams_id: int = 0):
"""
Requests the printer to load filament into the extruder from one AMS slot or an
external spool holder.
Parameters
----------
slot_id : int
The slot within the AMS unit (0-3; 0 for an AMS HT). For an external holder
pass its tray id here (`254`, or `255` for the right-hand holder of a
dual-nozzle printer), or pass it as `ams_id` with `slot_id` 0.
ams_id : int
The AMS unit (`0`-`3` for AMS / AMS 2 Pro / AMS Lite, `128`+ for AMS HT).
The command is encoded as Bambu Studio's `command_ams_change_filament` does: `target`
is the absolute tray id (`ams_id * 4 + slot_id`) for an AMS unit, and the unit id
itself for an AMS HT or an external holder (whose `ams_id` is 254 or 255, `slot_id` 0).
`slot_id` 255 with `target` 255 is the unload command, never a load.
The printer answers with an `ams_change_filament` reply; a refusal (e.g. the AMS
is drying) appears in `BambuState.hms_errors` as a `command_error` entry.
"""
if slot_id in (VIRTUAL_TRAY_DEPUTY_ID, VIRTUAL_TRAY_MAIN_ID):
ams_id, slot_id = slot_id, 0
tray_id = ams_id * 4 + slot_id if ams_id < 16 else 0
msg = copy.deepcopy(AMS_CHANGE_FILAMENT)
msg["print"]["ams_id"] = ams_id
msg["print"]["target"] = tray_id if tray_id else ams_id
msg["print"]["slot_id"] = slot_id
msg["print"]["soft_temp"] = 0
msg["print"]["tar_temp"] = -1
msg["print"]["curr_temp"] = -1
self._clear_command_error("ams_change_filament")
self.client.publish(
f"device/{self.config.serial_number}/request", json.dumps(msg)
)
logger.debug(
f"load_filament - published AMS_CHANGE_FILAMENT to [device/{self.config.serial_number}/request] - target: [{msg['print']['target']}], bambu_msg: [{msg}]"
)
make_sdcard_directory
Creates the specified directory on the printer and returns an updated dict of all files on the printer
Parameters
- dir : str - the full path directory name to be created
Source code in src/bpm/bambuprinter.py
def make_sdcard_directory(self, dir: str):
"""
Creates the specified directory on the printer and returns an updated dict of all files on the printer
Parameters
----------
* dir : str - the full path directory name to be created
"""
logger.debug(f"make_sdcard_directory - creating remote directory [{dir}]")
with self.ftp_connection() as ftps:
ftps.mkdir(dir)
return self.get_sdcard_contents()
pause_printing
Pauses the current print job if one is running.
Source code in src/bpm/bambuprinter.py
pause_session
Pauses the BambuPrinter session is it is active. Under the covers this
method unsubscribes from the /report topic, essentially disabling all
printer data refreshes.
Source code in src/bpm/bambuprinter.py
def pause_session(self):
"""
Pauses the `BambuPrinter` session is it is active. Under the covers this
method unsubscribes from the `/report` topic, essentially disabling all
printer data refreshes.
"""
if self.service_state != ServiceState.PAUSED:
self.client.unsubscribe(f"device/{self.config.serial_number}/report")
logger.debug(
f"pause_session - unsubscribed from [device/{self.config.serial_number}/report]"
)
self.service_state = ServiceState.PAUSED
print_3mf_file
print_3mf_file(
name: str,
plate: int,
bed: PlateType,
use_ams: bool,
ams_mapping: str | None = "",
bedlevel: bool | None = True,
flow: bool | None = True,
timelapse: bool | None = False,
)
Submits a request to print a .3mf file already stored on the printer's SD card.
ProjectInfo.metadata["ams_mapping"] from get_project_info is only a
placeholder in this parameter's SHAPE (filament ids, not tray ids) — the
3MF stores no slot assignment. Build the real value from the spools
currently loaded: match each project filament to a loaded spool by type
and colour, then encode that spool's tray id as in the table below.
Parameters
- name : str - Full SD card path to the
.3mffile, including leading/(e.g."/jobs/my_project.3mf"or"/jobs/my_project.gcode.3mf"). - plate : int - The 1-indexed plate number from the slicer to print.
Available plate numbers are in
ProjectInfo.plates. - bed : PlateType - Bed surface type to use (e.g.
PlateType.HOT_PLATE). PassPlateType.AUTOto let the printer decide based on slicer metadata. - use_ams : bool -
Trueto route filament through the AMS;Falseto print from the external spool. For an external-spool print, leaveams_mappingempty —bpmauto-encodes the firmware-required external mapping (see "External spool" below). - ams_mapping : Optional[str] =
""- JSON array string indexed by filament id (index 0 = filament 1) holding each filament's absolute AMS tray ID,-1for an unused id. Resolved from the loaded spools (see above);ProjectInfo.metadata["ams_mapping"]is not a usable source.ams_mapping2(per-filament{"ams_id": int, "slot_id": int}dicts) is auto-generated from this value for firmware compatibility. Pass""orNonewithuse_ams=Falsefor an external-spool print. - bedlevel : Optional[bool] =
True- Auto-level the bed before printing. - flow : Optional[bool] =
True- Run extrusion flow calibration before printing. - timelapse : Optional[bool] =
False- Record a timelapse during printing.
Examples
print_3mf_file("/jobs/my_project.3mf", 1, PlateType.HOT_PLATE, False, "")— AMS disabledprint_3mf_file("/jobs/my_project.gcode.3mf", 1, PlateType.HOT_PLATE, True, "[0,1,2]")— 3 filaments from AMS trays 1–3
AMS Mapping Encoding
The ams_mapping string is a JSON integer array. Each element is an absolute
tray ID whose encoding depends on the AMS unit type:
| AMS type | Formula | Range |
|---|---|---|
| Standard 4-slot (AMS 2 / LITE / N3F) | ams_id * 4 + slot_id |
0–103 |
| Single-slot (N3S / AMS HT) | ams_id (starts at 128) |
128–135 |
| External spool | do not pass — see below | — |
| Unmapped / no AMS | -1 |
— |
| ams_mapping | Meaning |
|---|---|
"[0]" |
1 filament → AMS 0 slot 0 |
"[0,1,4]" |
3 filaments → AMS 0 slots 0–1, AMS 1 slot 0 |
"[0,-1,5]" |
3 filaments → AMS 0 slot 0, unmapped, AMS 1 slot 1 |
"[0,1,2,3]" |
4 filaments → all 4 slots of AMS 0 |
"[0,4,128]" |
3 filaments → AMS 0 slot 0, AMS 1 slot 0, AMS HT |
The array is generated by the slicer (BambuStudio / OrcaSlicer) via
color-distance matching and stored in Metadata/slice_info.config inside
the .3mf. get_project_info parses it into ProjectInfo.metadata["ams_mapping"].
See BambuStudio DevMapping.cpp / DeviceManager.cpp for the encoding logic.
See OpenBambuAPI spec: https://github.com/Doridian/OpenBambuAPI/blob/main/mqtt.md#ams-mapping-configuration-ams_mapping
External spool
To print from an external spool pass use_ams=False and leave ams_mapping
empty. bpm builds the rest, so callers never handle tray ids for it.
The slicer-file 254 external-spool encoding is not used in the runtime
project_file command. The reporter of issue #55 found that current firmware
raises HMS 07FF_8012 "Failed to get AMS mapping table" for a raw 254/255
(or an omitted) flat ams_mapping; that has not been reproduced here. What
BambuStudio sends, and what bpm sends, is a flat ams_mapping of -1 per
filament with the external feed named only in ams_mapping2. This is
selected from has_dual_extruder (telemetry-derived, so a call made before
the printer's first status report takes the single-nozzle branch), not the
serial or firmware family.
- Single-nozzle printers (everything else, including the single-nozzle H2S)
send
ams_mapping=[-1]andams_mapping2=[{"ams_id": 255, "slot_id": 0}]. - Dual-nozzle printers (H2D, H2D Pro, H2C, X2D) send arrays sized to the
plate's filament count, exactly as BambuStudio does. Each used filament
gets the holder of its own extruder,
255for the main (right) extruder or254for the deputy (left) one, and every other entry is{"ams_id": 255, "slot_id": 255}. The slicer fixes each filament's extruder, so the holder is derived from the.3mf'sfilament_mapsand the plate gcode'sphysical_extruder_map, never chosen by the caller.ValueErroris raised when the plate lacks either, rather than guessing a side. Several filaments on one extruder share its holder and log a warning, as BambuStudio warns.
A dual-nozzle external-spool print reads the plate's metadata before it
publishes, from the cache or by downloading the .3mf from the SD card, so it
can also raise the library's generic Exception when the file is not on the
SD card.
H2-family field typing
H2-family firmware (PrinterSeries.H2: H2S, H2D, H2D Pro, H2C, X2D) expects
bed_leveling, flow_cali, layer_inspect, and vibration_cali as integer
0/1; other families accept JSON booleans. use_ams and timelapse
remain booleans on all models.
Source code in src/bpm/bambuprinter.py
def print_3mf_file(
self,
name: str,
plate: int,
bed: PlateType,
use_ams: bool,
ams_mapping: str | None = "",
bedlevel: bool | None = True,
flow: bool | None = True,
timelapse: bool | None = False,
):
"""
Submits a request to print a `.3mf` file already stored on the printer's SD card.
`ProjectInfo.metadata["ams_mapping"]` from `get_project_info` is only a
placeholder in this parameter's SHAPE (filament ids, not tray ids) — the
3MF stores no slot assignment. Build the real value from the spools
currently loaded: match each project filament to a loaded spool by type
and colour, then encode that spool's tray id as in the table below.
Parameters
----------
* name : str - Full SD card path to the `.3mf` file, including leading `/`
(e.g. `"/jobs/my_project.3mf"` or `"/jobs/my_project.gcode.3mf"`).
* plate : int - The 1-indexed plate number from the slicer to print.
Available plate numbers are in `ProjectInfo.plates`.
* bed : PlateType - Bed surface type to use (e.g. `PlateType.HOT_PLATE`).
Pass `PlateType.AUTO` to let the printer decide based on slicer metadata.
* use_ams : bool - `True` to route filament through the AMS; `False` to print
from the external spool. For an external-spool print, leave `ams_mapping`
empty — `bpm` auto-encodes the firmware-required external mapping (see
"External spool" below).
* ams_mapping : Optional[str] = `""` - JSON array string indexed by filament
id (index 0 = filament 1) holding each filament's absolute AMS tray ID,
`-1` for an unused id. Resolved from the loaded spools (see above);
`ProjectInfo.metadata["ams_mapping"]` is not a usable source.
`ams_mapping2` (per-filament `{"ams_id": int, "slot_id": int}` dicts) is
auto-generated from this value for firmware compatibility.
Pass `""` or `None` with `use_ams=False` for an external-spool print.
* bedlevel : Optional[bool] = `True` - Auto-level the bed before printing.
* flow : Optional[bool] = `True` - Run extrusion flow calibration before printing.
* timelapse : Optional[bool] = `False` - Record a timelapse during printing.
Examples
--------
* `print_3mf_file("/jobs/my_project.3mf", 1, PlateType.HOT_PLATE, False, "")` — AMS disabled
* `print_3mf_file("/jobs/my_project.gcode.3mf", 1, PlateType.HOT_PLATE, True, "[0,1,2]")` — 3 filaments from AMS trays 1–3
AMS Mapping Encoding
--------------------
The `ams_mapping` string is a JSON integer array. Each element is an absolute
**tray ID** whose encoding depends on the AMS unit type:
| AMS type | Formula | Range |
|---------------------------------|-----------------------------|---------|
| Standard 4-slot (AMS 2 / LITE / N3F) | `ams_id * 4 + slot_id` | 0–103 |
| Single-slot (N3S / AMS HT) | `ams_id` (starts at 128) | 128–135 |
| External spool | do not pass — see below | — |
| Unmapped / no AMS | `-1` | — |
| ams_mapping | Meaning |
|----------------|----------------------------------------------------|
| `"[0]"` | 1 filament → AMS 0 slot 0 |
| `"[0,1,4]"` | 3 filaments → AMS 0 slots 0–1, AMS 1 slot 0 |
| `"[0,-1,5]"` | 3 filaments → AMS 0 slot 0, unmapped, AMS 1 slot 1 |
| `"[0,1,2,3]"` | 4 filaments → all 4 slots of AMS 0 |
| `"[0,4,128]"` | 3 filaments → AMS 0 slot 0, AMS 1 slot 0, AMS HT |
The array is generated by the slicer (BambuStudio / OrcaSlicer) via
color-distance matching and stored in `Metadata/slice_info.config` inside
the `.3mf`. `get_project_info` parses it into `ProjectInfo.metadata["ams_mapping"]`.
See BambuStudio `DevMapping.cpp` / `DeviceManager.cpp` for the encoding logic.
See OpenBambuAPI spec: <https://github.com/Doridian/OpenBambuAPI/blob/main/mqtt.md#ams-mapping-configuration-ams_mapping>
External spool
--------------
To print from an external spool pass `use_ams=False` and leave `ams_mapping`
empty. `bpm` builds the rest, so callers never handle tray ids for it.
The slicer-file `254` external-spool encoding is not used in the runtime
`project_file` command. The reporter of issue #55 found that current firmware
raises HMS `07FF_8012` "Failed to get AMS mapping table" for a raw `254`/`255`
(or an omitted) flat `ams_mapping`; that has not been reproduced here. What
BambuStudio sends, and what `bpm` sends, is a flat `ams_mapping` of `-1` per
filament with the external feed named only in `ams_mapping2`. This is
selected from `has_dual_extruder` (telemetry-derived, so a call made before
the printer's first status report takes the single-nozzle branch), not the
serial or firmware family.
* Single-nozzle printers (everything else, including the single-nozzle H2S)
send `ams_mapping=[-1]` and `ams_mapping2=[{"ams_id": 255, "slot_id": 0}]`.
* Dual-nozzle printers (H2D, H2D Pro, H2C, X2D) send arrays sized to the
plate's filament count, exactly as BambuStudio does. Each used filament
gets the holder of its own extruder, `255` for the main (right) extruder
or `254` for the deputy (left) one, and every other entry is
`{"ams_id": 255, "slot_id": 255}`. The slicer fixes each filament's
extruder, so the holder is derived from the `.3mf`'s `filament_maps` and
the plate gcode's `physical_extruder_map`, never chosen by the caller.
`ValueError` is raised when the plate lacks either, rather than guessing a
side. Several filaments on one extruder share its holder and log a
warning, as BambuStudio warns.
A dual-nozzle external-spool print reads the plate's metadata before it
publishes, from the cache or by downloading the `.3mf` from the SD card, so it
can also raise the library's generic `Exception` when the file is not on the
SD card.
H2-family field typing
----------------------
H2-family firmware (`PrinterSeries.H2`: H2S, H2D, H2D Pro, H2C, X2D) expects
`bed_leveling`, `flow_cali`, `layer_inspect`, and `vibration_cali` as integer
`0`/`1`; other families accept JSON booleans. `use_ams` and `timelapse`
remain booleans on all models.
"""
_3mf_file = f"{name}"
_plate_num = int(plate)
cmd = copy.deepcopy(PRINT_3MF_FILE)
subtask = name[name.rindex("/") + 1 : :] if "/" in name else name
subtask = (
subtask[::-1].replace(".3mf"[::-1], "", 1)[::-1]
if subtask.endswith(".3mf")
else subtask
)
subtask = (
subtask[::-1].replace(".gcode"[::-1], "", 1)[::-1]
if subtask.endswith(".gcode")
else subtask
)
cmd["print"]["file"] = _3mf_file
if getPrinterSeriesByModel(self.config.printer_model) in (
PrinterSeries.A1,
PrinterSeries.P1,
):
cmd["print"]["url"] = f"file:///sdcard{_3mf_file}"
else:
cmd["print"]["url"] = f"ftp://{_3mf_file}"
cmd["print"]["subtask_name"] = subtask
cmd["print"]["bed_type"] = bed.name.lower()
cmd["print"]["param"] = cmd["print"]["param"].replace("#", str(_plate_num))
cmd["print"]["use_ams"] = use_ams
def decode_ams_mapping_entry(tray_id: int) -> tuple[int, int]:
if tray_id < 0:
return 255, 255
if tray_id in (254, 255):
return tray_id, 0
# Legacy HT shorthand from some clients: 128..253 => ams_id, slot 0
if 128 <= tray_id <= 253:
return tray_id, 0
# Standard protocol tray IDs (including normalized HT values like 512)
return tray_id // 4, tray_id % 4
# Parse ams_mapping JSON array. Values are absolute tray IDs from BambuStudio/OrcaSlicer:
# 0-103 for 4-slot units, 128-135 for 1-slot units, -1 for unmapped.
# See: https://github.com/bambulab/BambuStudio/blob/main/src/slic3r/GUI/DeviceCore/DevMapping.cpp
if ams_mapping and len(ams_mapping) > 0:
parsed_ams_mapping = [int(x) for x in json.loads(ams_mapping)]
parsed_ams_mapping2 = []
for tray_id in parsed_ams_mapping:
ams_id, slot_id = decode_ams_mapping_entry(tray_id)
parsed_ams_mapping2.append({"ams_id": ams_id, "slot_id": slot_id})
cmd["print"]["ams_mapping"] = parsed_ams_mapping
cmd["print"]["ams_mapping2"] = parsed_ams_mapping2
elif not use_ams:
# External spool / no-AMS print. The #55 reporter saw HMS 07FF_8012
# "Failed to get AMS mapping table" for a raw 254/255 or an omitted flat
# mapping (not reproduced here). BambuStudio's runtime project_file command
# differs from the slicer-file encoding: the flat array is -1 per filament,
# and the external feed is named only in `ams_mapping2` via `ams_id`.
# Drive the split off has_dual_extruder (telemetry-derived), not the
# serial / firmware family: H2S shares the H2 series with H2D but is
# single-nozzle, and future dual models may not yet be enumerated.
if self.config.capabilities.has_dual_extruder:
# A dual-nozzle plate names the holder of each filament's own
# extruder, in arrays sized to the plate's filament count, exactly
# as BambuStudio sends them (255 right/main, 254 left/deputy).
trays = self._resolve_external_spool_trays(_3mf_file, _plate_num)
cmd["print"]["ams_mapping"] = [-1] * len(trays)
cmd["print"]["ams_mapping2"] = [
{"ams_id": tray, "slot_id": 0}
if tray != -1
else {"ams_id": VIRTUAL_TRAY_MAIN_ID, "slot_id": 255}
for tray in trays
]
else:
cmd["print"]["ams_mapping"] = [-1]
cmd["print"]["ams_mapping2"] = [
{"ams_id": VIRTUAL_TRAY_MAIN_ID, "slot_id": 0}
]
cmd["print"]["bed_leveling"] = bedlevel
cmd["print"]["flow_cali"] = flow
cmd["print"]["timelapse"] = timelapse
# H2-family firmware (H2D, H2D Pro, H2C, X2D, and single-nozzle H2S) expects
# the calibration/inspection flags as integer 0/1, not JSON booleans; other
# families accept booleans. use_ams and timelapse stay boolean on all models.
if getPrinterSeriesByModel(self.config.printer_model) == PrinterSeries.H2:
for _field in (
"bed_leveling",
"flow_cali",
"layer_inspect",
"vibration_cali",
):
cmd["print"][_field] = int(bool(cmd["print"][_field]))
self.client.publish(
f"device/{self.config.serial_number}/request", json.dumps(cmd)
)
logger.debug(
f"print_3mf_file - published PRINT_3MF_FILE to [device/{self.config.serial_number}/request] print_command: [{cmd}]"
)
quit
Shuts down all threads. Your BambuPrinter instance should probably be
considered dead after making this call although you may be able to restart a
session with start_session().
Source code in src/bpm/bambuprinter.py
def quit(self):
"""
Shuts down all threads. Your `BambuPrinter` instance should probably be
considered dead after making this call although you may be able to restart a
session with [start_session](#bpm.bambuprinter.BambuPrinter.start_session)().
"""
if self._client:
# always call disconnect as is_connected will return False in case of connection loss w/ retry
self._client.disconnect()
logger.debug("quit - mqtt client is now disconnected")
else:
logger.debug("quit - mqtt client was already disconnected")
self._service_state = ServiceState.QUIT
self._notify_update()
if self._mqtt_client_thread and self._mqtt_client_thread.is_alive():
# this will block forever if the client hasn't been disconnected properly
self._mqtt_client_thread.join()
if self._watchdog_thread and self._watchdog_thread.is_alive():
self._watchdog_thread.join()
logger.debug("quit - all threads have terminated")
refresh
Triggers a full data refresh from the printer (if it is connected). You should use this method sparingly as resorting to it indicates something is not working properly.
Source code in src/bpm/bambuprinter.py
def refresh(self):
"""
Triggers a full data refresh from the printer (if it is connected). You should use this
method sparingly as resorting to it indicates something is not working properly.
"""
if self.service_state == ServiceState.CONNECTED:
logger.debug(
f"refresh - publishing ANNOUNCE_VERSION to [device/{self.config.serial_number}/request]"
)
self.client.publish(
f"device/{self.config.serial_number}/request",
json.dumps(ANNOUNCE_VERSION),
)
logger.debug(
f"refresh - publishing ANNOUNCE_PUSH to [device/{self.config.serial_number}/request]"
)
self.client.publish(
f"device/{self.config.serial_number}/request",
json.dumps(ANNOUNCE_PUSH),
)
refresh_nozzles
Requests the printer to push back current nozzle state. Used for multi-extruder models (H2D, H2D Pro) where nozzles are manually swapped.
Source code in src/bpm/bambuprinter.py
def refresh_nozzles(self):
"""
Requests the printer to push back current nozzle state.
Used for multi-extruder models (H2D, H2D Pro) where nozzles are manually swapped.
"""
cmd = copy.deepcopy(REFRESH_NOZZLE)
self.client.publish(
f"device/{self.config.serial_number}/request", json.dumps(cmd)
)
logger.debug(
f"refresh_nozzles - published REFRESH_NOZZLE to [device/{self.config.serial_number}/request] bambu_msg: [{cmd}]"
)
refresh_spool_rfid
Request the printer to re-read the RFID tag for the specified AMS slot.
Only RFID-equipped Bambu Lab spools carry tag data. The printer will push an updated telemetry message containing the spool details after scanning.
Parameters
- slot_id : int - The slot within the AMS unit to scan (0–3 for standard AMS).
- ams_id : int = 0 - The AMS unit containing the slot (default is 0).
Source code in src/bpm/bambuprinter.py
def refresh_spool_rfid(self, slot_id: int, ams_id: int = 0):
"""
Request the printer to re-read the RFID tag for the specified AMS slot.
Only RFID-equipped Bambu Lab spools carry tag data. The printer will push
an updated telemetry message containing the spool details after scanning.
Parameters
----------
* slot_id : int - The slot within the AMS unit to scan (0–3 for standard AMS).
* ams_id : int = 0 - The AMS unit containing the slot (default is 0).
"""
cmd = copy.deepcopy(AMS_GET_RFID)
cmd["print"]["ams_id"] = ams_id
cmd["print"]["slot_id"] = slot_id
self.client.publish(
f"device/{self.config.serial_number}/request", json.dumps(cmd)
)
logger.debug(
f"refresh_spool_rfid - published AMS_GET_RFID to [device/{self.config.serial_number}/request] command: [{cmd}]"
)
rename_printer
Rename the printer to the specified new name.
Source code in src/bpm/bambuprinter.py
def rename_printer(self, new_name: str):
"""
Rename the printer to the specified new name.
"""
cmd = copy.deepcopy(RENAME_PRINTER)
cmd["update"]["name"] = new_name
self.client.publish(
f"device/{self.config.serial_number}/request", json.dumps(cmd)
)
logger.debug(
f"rename_printerq - published RENAME_PRINTER to [device/{self.config.serial_number}/request] command: [{cmd}]"
)
rename_sdcard_file
Renames the specified file on the printer and returns an updated dict of all files on the printer
Parameters
- src : str - the full path name to be renamed
- dest : str - the full path name to be renamed to
Source code in src/bpm/bambuprinter.py
def rename_sdcard_file(self, src: str, dest: str):
"""
Renames the specified file on the printer and returns an updated dict of all files on the printer
Parameters
----------
* src : str - the full path name to be renamed
* dest : str - the full path name to be renamed to
"""
logger.debug(f"rename_sdcard_file - renaming printer file [{src}] to [{dest}]")
with self.ftp_connection() as ftps:
ftps.move_file(src, dest)
return self.get_sdcard_contents()
resume_printing
Resumes the current print job if one is paused.
Source code in src/bpm/bambuprinter.py
resume_session
Resumes a previously paused session by re-subscribing to the /report topic.
Source code in src/bpm/bambuprinter.py
def resume_session(self):
"""
Resumes a previously paused session by re-subscribing to the /report topic.
"""
if (
self.client
and self.client.is_connected()
and self.service_state == ServiceState.PAUSED
):
self.client.subscribe(f"device/{self.config.serial_number}/report")
logger.debug(
f"resume_session - subscribed to [device/{self.config.serial_number}/report]"
)
self._lastMessageTime = time.monotonic()
self.service_state = ServiceState.CONNECTED
return
self.service_state = ServiceState.QUIT
sdcard_file_exists
Checks to see if a file exists on the printer at the path specified
Parameters
- path : str - the full path name to check
Source code in src/bpm/bambuprinter.py
def sdcard_file_exists(self, path: str) -> bool:
"""
Checks to see if a file exists on the printer at the `path` specified
Parameters
----------
* path : str - the full path name to check
"""
logger.debug(f"sdcard_file_exists - checking if printer file [{path}] exists")
with self.ftp_connection() as ftps:
return ftps.fexists(path)
select_extrusion_calibration_profile
Sets the k factor profile for the specified tray.
Parameters
tray_id : int - tray id cali_idx : calibration index , optional, defaults to -1 (the default profile)
Source code in src/bpm/bambuprinter.py
def select_extrusion_calibration_profile(self, tray_id: int, cali_idx: int = -1):
"""
Sets the k factor profile for the specified tray.
Parameters
----------
tray_id : int - tray id
cali_idx : calibration index , optional, defaults to -1 (the default profile)
"""
cmd = copy.deepcopy(EXTRUSION_CALI_SEL)
ams_id = math.floor(tray_id / 4)
slot_id = tray_id % 4
if tray_id == 254 or tray_id == 255:
ams_id = tray_id
slot_id = 0
cmd["print"]["ams_id"] = ams_id
cmd["print"]["tray_id"] = tray_id
cmd["print"]["slot_id"] = slot_id
cmd["print"]["cali_idx"] = cali_idx
self.client.publish(
f"device/{self.config.serial_number}/request", json.dumps(cmd)
)
logger.debug(
f"select_extrusion_calibration_profile - published EXTRUSION_CALI_SEL to [device/{self.config.serial_number}/request] cmd: [{cmd}]"
)
send_ams_control_command
send_ams_control_command(ams_control_cmd: AMSControlCommand, resume_print: bool = True)
Send an ams_control command: pause, resume, reset, done or abort.
DONE tells a waiting load the filament is extruded, and ABORT cancels a
running load or unload. By default AMSControlCommand.RESUME also calls
resume_printing() to restart a paused print job.
Parameters
- ams_control_cmd : AMSControlCommand - The control command to send.
- resume_print : bool - With
RESUME, also resume the print (default True). Bambu Studio's load dialog buttons send only theams_control, sosend_hms_actionpasses False.
Source code in src/bpm/bambuprinter.py
def send_ams_control_command(
self, ams_control_cmd: AMSControlCommand, resume_print: bool = True
):
"""
Send an `ams_control` command: pause, resume, reset, done or abort.
`DONE` tells a waiting load the filament is extruded, and `ABORT` cancels a
running load or unload. By default `AMSControlCommand.RESUME` also calls
`resume_printing()` to restart a paused print job.
Parameters
----------
* ams_control_cmd : AMSControlCommand - The control command to send.
* resume_print : bool - With `RESUME`, also resume the print (default True).
Bambu Studio's load dialog buttons send only the `ams_control`, so
`send_hms_action` passes False.
"""
ams_cmd = ams_control_cmd.name.lower()
cmd = copy.deepcopy(AMS_CONTROL)
cmd["print"]["param"] = ams_cmd
self.client.publish(
f"device/{self.config.serial_number}/request", json.dumps(cmd)
)
logger.debug(
f"send_ams_control_commandpublished AMS_CONTROL to [device/{self.config.serial_number}/request] bambu_msg: [{cmd}]"
)
# trigger resume print for good measure
if ams_control_cmd == AMSControlCommand.RESUME and resume_print:
self.resume_printing()
send_anything
Publish an arbitrary JSON string directly to the printer's MQTT request topic.
Intended for advanced use / debugging — bypasses all validation and command abstraction. The string is parsed then re-serialised before publishing, so it must be valid JSON.
Parameters
- anything : str - A valid JSON string to publish.
Source code in src/bpm/bambuprinter.py
def send_anything(self, anything: str):
"""
Publish an arbitrary JSON string directly to the printer's MQTT request topic.
Intended for advanced use / debugging — bypasses all validation and command
abstraction. The string is parsed then re-serialised before publishing, so
it must be valid JSON.
Parameters
----------
* anything : str - A valid JSON string to publish.
"""
self.client.publish(
f"device/{self.config.serial_number}/request",
json.dumps(json.loads(anything)),
)
logger.debug(
f"send_anything - published message to [device/{self.config.serial_number}/request] message: [{anything}]"
)
send_gcode
Submit one, or more, gcode commands to the printer. To submit multiple gcode commands, separate them with a newline (\n) character.
Parameters
gcode : str
Examples
send_gcode("G91\nG0 X0\nG0 X50")- queues 3 gcode commands on the printer for processingsend_gcode("G28")- queues 1 gcode command on the printer for processing
Source code in src/bpm/bambuprinter.py
def send_gcode(self, gcode: str):
"""
Submit one, or more, gcode commands to the printer. To submit multiple gcode commands, separate them with a newline (\\n) character.
Parameters
----------
gcode : str
Examples
--------
* `send_gcode("G91\\nG0 X0\\nG0 X50")` - queues 3 gcode commands on the printer for processing
* `send_gcode("G28")` - queues 1 gcode command on the printer for processing
"""
cmd = copy.deepcopy(SEND_GCODE_TEMPLATE)
cmd["print"]["param"] = f"{gcode} \n"
self.client.publish(
f"device/{self.config.serial_number}/request", json.dumps(cmd)
)
logger.debug(
f"send_gcode - published SEND_GCODE_TEMPLATE to [device/{self.config.serial_number}/request] gcode: [{gcode}]"
)
send_hms_action
send_hms_action(action: HMSAction | int) -> bool
Press a printer prompt's button, sending what Bambu Studio sends for it.
The buttons for the current print_error are the actions of its
device_error entry in hms_errors (bambutools.hmsActions). resume,
done and abort go out as ams_control alone, clean_print_error clears
the current print_error, and assistant and close send nothing.
Parameters
- action : HMSAction | int - the button id.
Returns True when a command was published, False for a button that sends
nothing. Raises ValueError for an id bpm cannot send.
Source code in src/bpm/bambuprinter.py
def send_hms_action(self, action: HMSAction | int) -> bool:
"""
Press a printer prompt's button, sending what Bambu Studio sends for it.
The buttons for the current `print_error` are the `actions` of its
`device_error` entry in `hms_errors` (`bambutools.hmsActions`). `resume`,
`done` and `abort` go out as `ams_control` alone, `clean_print_error` clears
the current `print_error`, and `assistant` and `close` send nothing.
Parameters
----------
* action : HMSAction | int - the button id.
Returns True when a command was published, False for a button that sends
nothing. Raises `ValueError` for an id bpm cannot send.
"""
command = HMS_ACTION_COMMAND.get(HMSAction(action), "")
if command in ("resume", "done", "abort"):
self.send_ams_control_command(
AMSControlCommand[command.upper()], resume_print=False
)
return True
if command == "clean_print_error":
self.clean_print_error(print_error=self.printer_state.print_error)
return True
if command in ("assistant", "close"):
return False
raise ValueError(f"send_hms_action - bpm cannot send button [{action}]")
set_active_tool
Set the active extruder on multi-tool machines (H2D / H2D Pro).
Sends a SET_ACTIVE_TOOL command that switches which extruder the printer
uses for subsequent moves and extrusions.
Parameters
- id : int - The extruder index to activate (
0= right extruder,1= left extruder on H2D; seeActiveToolenum for named constants).
Source code in src/bpm/bambuprinter.py
def set_active_tool(self, id: int):
"""
Set the active extruder on multi-tool machines (H2D / H2D Pro).
Sends a `SET_ACTIVE_TOOL` command that switches which extruder the printer
uses for subsequent moves and extrusions.
Parameters
----------
* id : int - The extruder index to activate (`0` = right extruder, `1` = left extruder
on H2D; see `ActiveTool` enum for named constants).
"""
cmd = copy.deepcopy(SET_ACTIVE_TOOL)
cmd["print"]["extruder_index"] = id
self.client.publish(
f"device/{self.config.serial_number}/request", json.dumps(cmd)
)
logger.debug(
f"set_active_tool - published SET_ACTIVE_TOOL to [device/{self.config.serial_number}/request] command: [{cmd}]"
)
set_airprinting_detector
set_airprinting_detector(enabled: bool, sensitivity: DetectorSensitivity = MEDIUM)
Enable or disable the air-printing / no-extrusion detector (X-Cam AI vision).
When triggered, the printer halts the print because the nozzle is detected to
be extruding into open air rather than onto the bed or model. State is persisted
to config.airprinting_detector and config.airprinting_detector_sensitivity.
Parameters
- enabled : bool -
Trueto enable the detector,Falseto disable it. - sensitivity : DetectorSensitivity =
DetectorSensitivity.MEDIUM- Detection threshold.
Source code in src/bpm/bambuprinter.py
def set_airprinting_detector(
self, enabled: bool, sensitivity: DetectorSensitivity = DetectorSensitivity.MEDIUM
):
"""
Enable or disable the air-printing / no-extrusion detector (X-Cam AI vision).
When triggered, the printer halts the print because the nozzle is detected to
be extruding into open air rather than onto the bed or model. State is persisted
to `config.airprinting_detector` and `config.airprinting_detector_sensitivity`.
Parameters
----------
* enabled : bool - `True` to enable the detector, `False` to disable it.
* sensitivity : DetectorSensitivity = `DetectorSensitivity.MEDIUM` - Detection threshold.
"""
cmd = copy.deepcopy(XCAM_CONTROL_SET)
cmd["xcam"]["module_name"] = "airprint_detector"
cmd["xcam"]["control"] = enabled
cmd["xcam"]["enable"] = enabled
cmd["xcam"]["print_halt"] = True
cmd["xcam"]["halt_print_sensitivity"] = sensitivity.value
self.client.publish(
f"device/{self.config.serial_number}/request", json.dumps(cmd)
)
self.config.airprinting_detector = enabled
self.config.airprinting_detector_sensitivity = sensitivity.value
logger.debug(
f"set_airprinting_detector - published XCAM_CONTROL_SET to [device/{self.config.serial_number}/request] bambu_msg: [{cmd}]"
)
set_ams_user_setting
set_ams_user_setting(setting: AMSUserSetting, enabled: bool, ams_id: int = 0)
Enable or disable one of the AMSUserSetting options on the specified AMS unit.
The three settings — CALIBRATE_REMAIN_FLAG, STARTUP_READ_OPTION, and
TRAY_READ_OPTION — are all sent together in a single command, with only
setting toggled to the new value. The corresponding BambuConfig attribute
is also updated.
Parameters
- setting : AMSUserSetting - The AMS user setting to change.
- enabled : bool -
Trueto enable the setting,Falseto disable it. - ams_id : int = 0 - The AMS unit to target (default is 0).
Source code in src/bpm/bambuprinter.py
def set_ams_user_setting(
self, setting: AMSUserSetting, enabled: bool, ams_id: int = 0
):
"""
Enable or disable one of the `AMSUserSetting` options on the specified AMS unit.
The three settings — `CALIBRATE_REMAIN_FLAG`, `STARTUP_READ_OPTION`, and
`TRAY_READ_OPTION` — are all sent together in a single command, with only
`setting` toggled to the new value. The corresponding `BambuConfig` attribute
is also updated.
Parameters
----------
* setting : AMSUserSetting - The AMS user setting to change.
* enabled : bool - `True` to enable the setting, `False` to disable it.
* ams_id : int = 0 - The AMS unit to target (default is 0).
"""
cmd = copy.deepcopy(AMS_USER_SETTING)
cmd["print"]["ams_id"] = ams_id
cmd["print"][AMSUserSetting.CALIBRATE_REMAIN_FLAG.name.lower()] = (
self.config.calibrate_remain_flag
)
cmd["print"][AMSUserSetting.STARTUP_READ_OPTION.name.lower()] = (
self.config.startup_read_option
)
cmd["print"][AMSUserSetting.TRAY_READ_OPTION.name.lower()] = (
self.config.tray_read_option
)
cmd["print"][setting.name.lower()] = enabled
if setting == AMSUserSetting.STARTUP_READ_OPTION:
self.config.startup_read_option = enabled
elif setting == AMSUserSetting.TRAY_READ_OPTION:
self.config.tray_read_option = enabled
elif setting == AMSUserSetting.CALIBRATE_REMAIN_FLAG:
self.config.calibrate_remain_flag = enabled
self.client.publish(
f"device/{self.config.serial_number}/request", json.dumps(cmd)
)
logger.debug(
f"set_ams_user_setting - published AMS_USER_SETTING to [device/{self.config.serial_number}/request] bambu_msg: [{cmd}]"
)
set_aux_fan_speed_target_percent
sets the aux (chamber recirculation) fan speed target represented in percent
Parameters
- value : int - The target speed in percent
Source code in src/bpm/bambuprinter.py
def set_aux_fan_speed_target_percent(self, value: int):
"""
sets the aux (chamber recirculation) fan speed target represented in percent
Parameters
----------
* value : int - The target speed in percent
"""
if value < 0:
value = 0
self._printer_state.climate.zone_aux_percent = value
speed = round(value * 2.55, 0)
gcode = SEND_GCODE_TEMPLATE
gcode["print"]["param"] = f"M106 P2 S{speed}\n"
self.client.publish(
f"device/{self.config.serial_number}/request", json.dumps(gcode)
)
logger.debug(
f"set_aux_fan_speed_target_percent - published SEND_GCODE_TEMPLATE to [device/{self.config.serial_number}/request] command: [{gcode}]"
)
set_bed_temp_target
Sets the bed temperature target.
Parameters
- value : float - The target bed temperature.
Source code in src/bpm/bambuprinter.py
def set_bed_temp_target(self, value: int):
"""
Sets the bed temperature target.
Parameters
----------
* value : float - The target bed temperature.
"""
if value < 0:
value = 0
gcode = SEND_GCODE_TEMPLATE
gcode["print"]["param"] = f"M140 S{value}\n"
self.client.publish(
f"device/{self.config.serial_number}/request", json.dumps(gcode)
)
logger.debug(
f"set_bed_temp_target - published SEND_GCODE_TEMPLATE to [device/{self.config.serial_number}/request] command: [{gcode}]"
)
self._bed_temp_target_time = round(time.time())
set_buildplate_marker_detector
Enable or disable the buildplate marker detector (X-Cam AI vision).
When enabled, the printer's camera checks for the calibration marker on the
build plate before starting a print. The state is also written to
config.buildplate_marker_detector.
Parameters
- enabled : bool -
Trueto enable the detector,Falseto disable it.
Source code in src/bpm/bambuprinter.py
def set_buildplate_marker_detector(self, enabled: bool):
"""
Enable or disable the buildplate marker detector (X-Cam AI vision).
When enabled, the printer's camera checks for the calibration marker on the
build plate before starting a print. The state is also written to
`config.buildplate_marker_detector`.
Parameters
----------
* enabled : bool - `True` to enable the detector, `False` to disable it.
"""
cmd = copy.deepcopy(XCAM_CONTROL_SET)
cmd["xcam"]["module_name"] = "buildplate_marker_detector"
cmd["xcam"]["control"] = enabled
cmd["xcam"]["enable"] = enabled
self.client.publish(
f"device/{self.config.serial_number}/request", json.dumps(cmd)
)
self.config.buildplate_marker_detector = enabled
logger.debug(
f"set_buildplate_marker_detector - published XCAM_CONTROL_SET to [device/{self.config.serial_number}/request] bambu_msg: [{cmd}]"
)
set_chamber_temp
for printers that do not have managed chambers, this enables you to inject a chamber temperature value from an external source
Parameters
- value : float - The chamber temperature.
Source code in src/bpm/bambuprinter.py
set_chamber_temp_target
set chamber temperature target if printer supports it, otherwise just store the value
Parameters
- value : float - The target chamber temperature.
- temper_check : OPTIONAL bool - perform a temperature check?
Source code in src/bpm/bambuprinter.py
def set_chamber_temp_target(self, value: int, temper_check: bool = True):
"""
set chamber temperature target if printer supports it, otherwise just
store the value
Parameters
----------
* value : float - The target chamber temperature.
* temper_check : OPTIONAL bool - perform a temperature check?
"""
if self.config.capabilities.has_chamber_temp:
cmd = copy.deepcopy(SET_CHAMBER_TEMP_TARGET)
cmd["print"]["ctt_val"] = value
cmd["print"]["temper_check"] = temper_check
self.client.publish(
f"device/{self.config.serial_number}/request", json.dumps(cmd)
)
logger.debug(
f"set_chamber_temp_target - published SET_CHAMBER_TEMP_TARGET to [device/{self.config.serial_number}/request] command: [{cmd}]"
)
cmd = copy.deepcopy(SET_CHAMBER_AC_MODE)
if value < 40:
cmd["print"]["modeId"] = 0
else:
cmd["print"]["modeId"] = 1
self.client.publish(
f"device/{self.config.serial_number}/request", json.dumps(cmd)
)
logger.debug(
f"set_chamber_temp_target - published SET_CHAMBER_AC_MODE to [device/{self.config.serial_number}/request] command: [{cmd}]"
)
self._printer_state.climate.chamber_temp_target = value
self._chamber_temp_target_time = round(time.time())
set_enhanced_cooling_fan_speed_target_percent
sets the toolhead enhanced cooling fan (M106 P9) speed target represented in percent
The printer publishes no telemetry for this fan, so the commanded value
is recorded as sticky state (BambuClimate.enhanced_cooling_fan_target_percent)
and is reset only when the extension tool leaves the MOUNTED state
(see BambuState.extension_tool). Firmware-observed behavior is
effectively on/off — stock slicer G-code only issues S255 or S0;
intermediate PWM values are untested. Commands sent while the fan is
unplugged are acknowledged by the printer and are harmless no-ops.
Parameters
- value : int - The target speed in percent
Source code in src/bpm/bambuprinter.py
def set_enhanced_cooling_fan_speed_target_percent(self, value: int):
"""
sets the toolhead enhanced cooling fan (M106 P9) speed target represented in percent
The printer publishes no telemetry for this fan, so the commanded value
is recorded as sticky state (`BambuClimate.enhanced_cooling_fan_target_percent`)
and is reset only when the extension tool leaves the MOUNTED state
(see `BambuState.extension_tool`). Firmware-observed behavior is
effectively on/off — stock slicer G-code only issues S255 or S0;
intermediate PWM values are untested. Commands sent while the fan is
unplugged are acknowledged by the printer and are harmless no-ops.
Parameters
----------
* value : int - The target speed in percent
"""
if value < 0:
value = 0
self._printer_state.climate.enhanced_cooling_fan_target_percent = value
speed = round(value * 2.55, 0)
gcode = SEND_GCODE_TEMPLATE
gcode["print"]["param"] = f"M106 P9 S{speed}\n"
self.client.publish(
f"device/{self.config.serial_number}/request", json.dumps(gcode)
)
logger.debug(
f"set_enhanced_cooling_fan_speed_target_percent - published SEND_GCODE_TEMPLATE to [device/{self.config.serial_number}/request] command: [{gcode}]"
)
set_exhaust_fan_speed_target_percent
sets the exhaust (chamber) fan speed target represented in percent
Parameters
- value : int - The target speed in percent
Source code in src/bpm/bambuprinter.py
def set_exhaust_fan_speed_target_percent(self, value: int):
"""
sets the exhaust (chamber) fan speed target represented in percent
Parameters
----------
* value : int - The target speed in percent
"""
if value < 0:
value = 0
self._printer_state.climate.zone_exhaust_percent = value
speed = round(value * 2.55, 0)
gcode = SEND_GCODE_TEMPLATE
gcode["print"]["param"] = f"M106 P3 S{speed}\n"
self.client.publish(
f"device/{self.config.serial_number}/request", json.dumps(gcode)
)
logger.debug(
f"set_exhaust_fan_speed_target_percent - published SEND_GCODE_TEMPLATE to [device/{self.config.serial_number}/request] command: [{gcode}]"
)
set_nozzle_details
set_nozzle_details(
nozzle_diameter: NozzleDiameter,
nozzle_type: NozzleType,
nozzle_flow: NozzleFlowType = STANDARD,
extruder_id: int = -1,
)
Inform the printer of the nozzle currently installed.
For single extruder setups, sends a SET_ACCESSORIES command so the
firmware can apply the correct temperature limits, flow rates, and material
compatibility checks.
For dual extruder setups, sends a SET_NOZZLE command so the firmware can
apply the correct temperature limits, flow rates, and material compatibility
checks.
Parameters
- nozzle_diameter : NozzleDiameter - The physical nozzle diameter
(e.g.
NozzleDiameter.DIAMETER_0_4). - nozzle_type : NozzleType - The nozzle material / type
(e.g.
NozzleType.HARDENED_STEEL,NozzleType.STAINLESS_STEEL). - nozzle_flow : NozzleFlowType - The flow type of the nozzle (default is
NozzleFlowType.STANDARD). - extruder_id : int - The extruder ID where the nozzle is installed (default is -1 (use current)).
Source code in src/bpm/bambuprinter.py
def set_nozzle_details(
self,
nozzle_diameter: NozzleDiameter,
nozzle_type: NozzleType,
nozzle_flow: NozzleFlowType = NozzleFlowType.STANDARD,
extruder_id: int = -1,
):
"""
Inform the printer of the nozzle currently installed.
For single extruder setups, sends a `SET_ACCESSORIES` command so the
firmware can apply the correct temperature limits, flow rates, and material
compatibility checks.
For dual extruder setups, sends a `SET_NOZZLE` command so the firmware can
apply the correct temperature limits, flow rates, and material compatibility
checks.
Parameters
----------
* nozzle_diameter : NozzleDiameter - The physical nozzle diameter
(e.g. `NozzleDiameter.DIAMETER_0_4`).
* nozzle_type : NozzleType - The nozzle material / type
(e.g. `NozzleType.HARDENED_STEEL`, `NozzleType.STAINLESS_STEEL`).
* nozzle_flow : NozzleFlowType - The flow type of the nozzle (default is `NozzleFlowType.STANDARD`).
* extruder_id : int - The extruder ID where the nozzle is installed (default is -1 (use current)).
"""
if not self.config.capabilities.has_dual_extruder:
cmd = copy.deepcopy(SET_ACCESSORIES)
cmd["system"]["nozzle_diameter"] = nozzle_diameter.value
cmd["system"]["nozzle_type"] = nozzle_type_to_telemetry(nozzle_type)
self.client.publish(
f"device/{self.config.serial_number}/request", json.dumps(cmd)
)
logger.debug(
f"set_nozzle_details - published SET_ACCESSORIES to [device/{self.config.serial_number}/request] bambu_msg: [{cmd}]"
)
else:
cmd = copy.deepcopy(SET_NOZZLE)
cmd["print"]["id"] = (
extruder_id if extruder_id != -1 else self.printer_state.active_tool
)
cmd["print"]["diameter"] = nozzle_diameter.value
cmd["print"]["type"] = build_nozzle_identifier(
nozzle_flow, nozzle_type, nozzle_diameter.value
).split("-")[0]
self.client.publish(
f"device/{self.config.serial_number}/request", json.dumps(cmd)
)
logger.debug(
f"set_nozzle_details - published SET_NOZZLE to [device/{self.config.serial_number}/request] bambu_msg: [{cmd}]"
)
set_nozzle_temp_target
Sets the nozzle temperature target.
Parameters
- value : float - The target nozzle temperature.
- tool_num : int - The tool number (default is 0).
Source code in src/bpm/bambuprinter.py
def set_nozzle_temp_target(self, value: int, tool_num: int = -1):
"""
Sets the nozzle temperature target.
Parameters
----------
* value : float - The target nozzle temperature.
* tool_num : int - The tool number (default is 0).
"""
if value < 0:
value = 0
gcode = SEND_GCODE_TEMPLATE
gcode["print"]["param"] = (
f"M104 S{value}{'' if tool_num == -1 else ' T' + str(tool_num)}\n"
)
self.client.publish(
f"device/{self.config.serial_number}/request", json.dumps(gcode)
)
logger.debug(
f"set_nozzle_temp_target - published SEND_GCODE_TEMPLATE to [device/{self.config.serial_number}/request] command: [{gcode}]"
)
self._tool_temp_target_time = round(time.time())
set_nozzleclumping_detector
set_nozzleclumping_detector(enabled: bool, sensitivity: DetectorSensitivity = MEDIUM)
Enable or disable the nozzle clumping / blob detector (X-Cam AI vision).
When triggered, the printer halts the print to prevent damage from filament
build-up on the nozzle. State is persisted to config.nozzleclumping_detector
and config.nozzleclumping_detector_sensitivity.
Parameters
- enabled : bool -
Trueto enable the detector,Falseto disable it. - sensitivity : DetectorSensitivity =
DetectorSensitivity.MEDIUM- Detection threshold.
Source code in src/bpm/bambuprinter.py
def set_nozzleclumping_detector(
self, enabled: bool, sensitivity: DetectorSensitivity = DetectorSensitivity.MEDIUM
):
"""
Enable or disable the nozzle clumping / blob detector (X-Cam AI vision).
When triggered, the printer halts the print to prevent damage from filament
build-up on the nozzle. State is persisted to `config.nozzleclumping_detector`
and `config.nozzleclumping_detector_sensitivity`.
Parameters
----------
* enabled : bool - `True` to enable the detector, `False` to disable it.
* sensitivity : DetectorSensitivity = `DetectorSensitivity.MEDIUM` - Detection threshold.
"""
cmd = copy.deepcopy(XCAM_CONTROL_SET)
cmd["xcam"]["module_name"] = "clump_detector"
cmd["xcam"]["control"] = enabled
cmd["xcam"]["enable"] = enabled
cmd["xcam"]["print_halt"] = True
cmd["xcam"]["halt_print_sensitivity"] = sensitivity.value
self.client.publish(
f"device/{self.config.serial_number}/request", json.dumps(cmd)
)
self.config.nozzleclumping_detector = enabled
self.config.nozzleclumping_detector_sensitivity = sensitivity.value
logger.debug(
f"set_nozzleclumping_detector - published XCAM_CONTROL_SET to [device/{self.config.serial_number}/request] bambu_msg: [{cmd}]"
)
set_part_cooling_fan_speed_target_percent
sets the part cooling fan speed target represented in percent
Parameters
- value : int - The target speed in percent
Source code in src/bpm/bambuprinter.py
def set_part_cooling_fan_speed_target_percent(self, value: int):
"""
sets the part cooling fan speed target represented in percent
Parameters
----------
* value : int - The target speed in percent
"""
if value < 0:
value = 0
self._printer_state.climate.part_cooling_fan_speed_target_percent = value
speed = round(value * 2.55, 0)
gcode = SEND_GCODE_TEMPLATE
gcode["print"]["param"] = f"M106 P1 S{speed}\n"
self.client.publish(
f"device/{self.config.serial_number}/request", json.dumps(gcode)
)
logger.debug(
f"set_part_cooling_fan_speed_target_percent - published SEND_GCODE_TEMPLATE to [device/{self.config.serial_number}/request] command: [{gcode}]"
)
self._fan_speed_target_time = round(time.time())
set_print_option
set_print_option(option: PrintOption, enabled: bool)
Enable or disable one of the PrintOption options.
The corresponding BambuConfig attribute is also updated so the new state
persists in the local configuration object.
Parameters
- option : PrintOption - The print option to change (e.g.
PrintOption.AUTO_RECOVERY). - enabled : bool -
Trueto enable the option,Falseto disable it.
Source code in src/bpm/bambuprinter.py
def set_print_option(self, option: PrintOption, enabled: bool):
"""
Enable or disable one of the `PrintOption` options.
The corresponding `BambuConfig` attribute is also updated so the new state
persists in the local configuration object.
Parameters
----------
* option : PrintOption - The print option to change (e.g. `PrintOption.AUTO_RECOVERY`).
* enabled : bool - `True` to enable the option, `False` to disable it.
"""
cmd = PRINT_OPTION_COMMAND
cmd["print"][option.name.lower()] = enabled
if option == PrintOption.AUTO_RECOVERY:
cmd["print"]["option"] = 1 if enabled else 0
self.config.auto_recovery = enabled
elif option == PrintOption.AUTO_SWITCH_FILAMENT:
self.config.auto_switch_filament = enabled
elif option == PrintOption.FILAMENT_TANGLE_DETECT:
self.config.filament_tangle_detect = enabled
elif option == PrintOption.SOUND_ENABLE:
self.config.sound_enable = enabled
elif option == PrintOption.NOZZLE_BLOB_DETECT:
self.config.nozzle_blob_detect = enabled
elif option == PrintOption.AIR_PRINT_DETECT:
self.config.air_print_detect = enabled
self.client.publish(
f"device/{self.config.serial_number}/request", json.dumps(cmd)
)
logger.debug(
f"set_print_option - published PRINT_OPTION_COMMAND to [device/{self.config.serial_number}/request] bambu_msg: [{cmd}]"
)
set_purgechutepileup_detector
set_purgechutepileup_detector(enabled: bool, sensitivity: DetectorSensitivity = MEDIUM)
Enable or disable the purge chute pile-up detector (X-Cam AI vision).
When triggered, the printer halts the print to prevent purge waste from
blocking the toolhead. State is persisted to config.purgechutepileup_detector
and config.purgechutepileup_detector_sensitivity.
Parameters
- enabled : bool -
Trueto enable the detector,Falseto disable it. - sensitivity : DetectorSensitivity =
DetectorSensitivity.MEDIUM- Detection threshold.
Source code in src/bpm/bambuprinter.py
def set_purgechutepileup_detector(
self, enabled: bool, sensitivity: DetectorSensitivity = DetectorSensitivity.MEDIUM
):
"""
Enable or disable the purge chute pile-up detector (X-Cam AI vision).
When triggered, the printer halts the print to prevent purge waste from
blocking the toolhead. State is persisted to `config.purgechutepileup_detector`
and `config.purgechutepileup_detector_sensitivity`.
Parameters
----------
* enabled : bool - `True` to enable the detector, `False` to disable it.
* sensitivity : DetectorSensitivity = `DetectorSensitivity.MEDIUM` - Detection threshold.
"""
cmd = copy.deepcopy(XCAM_CONTROL_SET)
cmd["xcam"]["module_name"] = "pileup_detector"
cmd["xcam"]["control"] = enabled
cmd["xcam"]["enable"] = enabled
cmd["xcam"]["print_halt"] = True
cmd["xcam"]["halt_print_sensitivity"] = sensitivity.value
self.client.publish(
f"device/{self.config.serial_number}/request", json.dumps(cmd)
)
self.config.purgechutepileup_detector = enabled
self.config.purgechutepileup_detector_sensitivity = sensitivity.value
logger.debug(
f"set_purgechutepileup_detector - published XCAM_CONTROL_SET to [device/{self.config.serial_number}/request] bambu_msg: [{cmd}]"
)
set_spaghetti_detector
set_spaghetti_detector(enabled: bool, sensitivity: DetectorSensitivity = MEDIUM)
Enable or disable the spaghetti / failed print detector (X-Cam AI vision).
When triggered, the printer halts the print. Sensitivity controls how
aggressively the camera flags anomalies. State is persisted to
config.spaghetti_detector and config.spaghetti_detector_sensitivity.
Parameters
- enabled : bool -
Trueto enable the detector,Falseto disable it. - sensitivity : DetectorSensitivity =
DetectorSensitivity.MEDIUM- Detection threshold.
Source code in src/bpm/bambuprinter.py
def set_spaghetti_detector(
self, enabled: bool, sensitivity: DetectorSensitivity = DetectorSensitivity.MEDIUM
):
"""
Enable or disable the spaghetti / failed print detector (X-Cam AI vision).
When triggered, the printer halts the print. Sensitivity controls how
aggressively the camera flags anomalies. State is persisted to
`config.spaghetti_detector` and `config.spaghetti_detector_sensitivity`.
Parameters
----------
* enabled : bool - `True` to enable the detector, `False` to disable it.
* sensitivity : DetectorSensitivity = `DetectorSensitivity.MEDIUM` - Detection threshold.
"""
cmd = copy.deepcopy(XCAM_CONTROL_SET)
cmd["xcam"]["module_name"] = "spaghetti_detector"
cmd["xcam"]["control"] = enabled
cmd["xcam"]["enable"] = enabled
cmd["xcam"]["print_halt"] = True
cmd["xcam"]["halt_print_sensitivity"] = sensitivity.value
self.client.publish(
f"device/{self.config.serial_number}/request", json.dumps(cmd)
)
self.config.spaghetti_detector = enabled
self.config.spaghetti_detector_sensitivity = sensitivity.value
logger.debug(
f"set_spaghetti_detector - published XCAM_CONTROL_SET to [device/{self.config.serial_number}/request] bambu_msg: [{cmd}]"
)
set_spool_details
set_spool_details(
tray_id: int,
tray_info_idx: str,
tray_id_name: str | None = "",
tray_type: str | None = "",
tray_color: str | None = "",
nozzle_temp_min: int | None = -1,
nozzle_temp_max: int | None = -1,
ams_id: int | None = 0,
)
Sets spool / tray details such as filament type, color, and nozzle temperature range.
ams_id and slot_id are derived automatically from tray_id.
For the external tray (254), pass "no_filament" as tray_info_idx to
clear the tray entirely. Colors may be supplied as CSS color names (e.g.
"red") or as 6- or 8-character hex strings (e.g. "FF0000" / "FF0000FF").
A single-nozzle printer (has_dual_extruder false) addresses its external
spool as ams_id 255 with tray_id 254, as BambuStudio does. The A1 ignores
the command with ams_id 254, and it is the only model measured. A
dual-nozzle printer keeps ams_id equal to tray_id.
Parameters
- tray_id : int - Absolute tray ID. For standard 4-slot AMS:
ams_id * 4 + slot_id. Use254for the external spool holder. - tray_info_idx : str - Filament info index string as defined by Bambu Lab (e.g.
"GFA00"). Pass"no_filament"to clear the tray. - tray_id_name : Optional[str] - Friendly filament name (e.g.
"Bambu PLA Basic"). - tray_type : Optional[str] - Short filament type string (e.g.
"PLA","PETG","ABS"). - tray_color : Optional[str] - Filament color as a CSS name or RRGGBB/RRGGBBAA hex string.
- nozzle_temp_min : Optional[int] = -1 - Minimum nozzle temperature in °C (pass -1 to leave unchanged).
- nozzle_temp_max : Optional[int] = -1 - Maximum nozzle temperature in °C (pass -1 to leave unchanged).
- ams_id : Optional[int] = 0 - Unused; derived automatically from
tray_id.
Source code in src/bpm/bambuprinter.py
def set_spool_details(
self,
tray_id: int,
tray_info_idx: str,
tray_id_name: str | None = "",
tray_type: str | None = "",
tray_color: str | None = "",
nozzle_temp_min: int | None = -1,
nozzle_temp_max: int | None = -1,
ams_id: int | None = 0,
):
"""
Sets spool / tray details such as filament type, color, and nozzle temperature range.
`ams_id` and `slot_id` are derived automatically from `tray_id`.
For the external tray (254), pass `"no_filament"` as `tray_info_idx` to
clear the tray entirely. Colors may be supplied as CSS color names (e.g.
`"red"`) or as 6- or 8-character hex strings (e.g. `"FF0000"` / `"FF0000FF"`).
A single-nozzle printer (`has_dual_extruder` false) addresses its external
spool as `ams_id` 255 with `tray_id` 254, as BambuStudio does. The A1 ignores
the command with `ams_id` 254, and it is the only model measured. A
dual-nozzle printer keeps `ams_id` equal to `tray_id`.
Parameters
----------
* tray_id : int - Absolute tray ID. For standard 4-slot AMS: `ams_id * 4 + slot_id`.
Use `254` for the external spool holder.
* tray_info_idx : str - Filament info index string as defined by Bambu Lab (e.g. `"GFA00"`).
Pass `"no_filament"` to clear the tray.
* tray_id_name : Optional[str] - Friendly filament name (e.g. `"Bambu PLA Basic"`).
* tray_type : Optional[str] - Short filament type string (e.g. `"PLA"`, `"PETG"`, `"ABS"`).
* tray_color : Optional[str] - Filament color as a CSS name or RRGGBB/RRGGBBAA hex string.
* nozzle_temp_min : Optional[int] = -1 - Minimum nozzle temperature in °C (pass -1 to leave unchanged).
* nozzle_temp_max : Optional[int] = -1 - Maximum nozzle temperature in °C (pass -1 to leave unchanged).
* ams_id : Optional[int] = 0 - Unused; derived automatically from `tray_id`.
"""
cmd = copy.deepcopy(AMS_FILAMENT_SETTING)
ams_id = math.floor(tray_id / 4)
slot_id = tray_id % 4
# external spool handling
if tray_id == 254 or tray_id == 255:
ams_id = tray_id
slot_id = 0
# single extruder external spool handling
if tray_id == 254 and not self.config.capabilities.has_dual_extruder:
ams_id = 255
cmd["print"]["ams_id"] = ams_id
cmd["print"]["tray_id"] = tray_id
cmd["print"]["slot_id"] = slot_id
if tray_info_idx == "no_filament":
tray_info_idx = ""
tray_id_name = ""
tray_type = ""
tray_color = "FFFFFF00"
nozzle_temp_min = 0
nozzle_temp_max = 0
cmd["print"]["tray_info_idx"] = tray_info_idx
cmd["print"]["tray_id_name"] = tray_id_name
cmd["print"]["tray_type"] = tray_type
if tray_color and tray_color != "":
color = ""
try:
color = f"{name_to_hex(tray_color)}FF".replace("#", "").upper()
except Exception:
color = tray_color
cmd["print"]["tray_color"] = color
cmd["print"]["nozzle_temp_min"] = nozzle_temp_min
cmd["print"]["nozzle_temp_max"] = nozzle_temp_max
self.client.publish(
f"device/{self.config.serial_number}/request", json.dumps(cmd)
)
logger.debug(
f"set_spool_details - published AMS_FILAMENT_SETTING to [device/{self.config.serial_number}/request] bambu_msg: [{cmd}]"
)
set_spool_k_factor
set_spool_k_factor(
tray_id: int,
k_value: float,
n_coef: float | None = 1.399999976158142,
nozzle_temp: int | None = -1,
bed_temp: int | None = -1,
max_volumetric_speed: int | None = -1,
)
Set the linear advance (pressure advance) k factor for a specific spool tray.
Deprecated
Broken in recent Bambu firmware. Use
select_extrusion_calibration_profile
instead.
Parameters
- tray_id : int - Absolute tray ID (
ams_id * 4 + slot_id, or254for external). - k_value : float - The linear advance k factor to apply.
- n_coef : Optional[float] = 1.4 - Pressure advance n coefficient.
- nozzle_temp : Optional[int] = -1 - Nozzle temperature in °C (pass -1 to omit).
- bed_temp : Optional[int] = -1 - Bed temperature in °C (pass -1 to omit).
- max_volumetric_speed : Optional[int] = -1 - Max volumetric speed (pass -1 to omit).
Source code in src/bpm/bambuprinter.py
def set_spool_k_factor(
self,
tray_id: int,
k_value: float,
n_coef: float | None = 1.399999976158142,
nozzle_temp: int | None = -1,
bed_temp: int | None = -1,
max_volumetric_speed: int | None = -1,
):
"""
Set the linear advance (pressure advance) k factor for a specific spool tray.
!!! warning "Deprecated"
Broken in recent Bambu firmware. Use
[`select_extrusion_calibration_profile`][bpm.bambuprinter.BambuPrinter.select_extrusion_calibration_profile]
instead.
Parameters
----------
* tray_id : int - Absolute tray ID (`ams_id * 4 + slot_id`, or `254` for external).
* k_value : float - The linear advance k factor to apply.
* n_coef : Optional[float] = 1.4 - Pressure advance n coefficient.
* nozzle_temp : Optional[int] = -1 - Nozzle temperature in °C (pass -1 to omit).
* bed_temp : Optional[int] = -1 - Bed temperature in °C (pass -1 to omit).
* max_volumetric_speed : Optional[int] = -1 - Max volumetric speed (pass -1 to omit).
"""
cmd = copy.deepcopy(EXTRUSION_CALI_SET)
cmd["print"]["tray_id"] = tray_id
cmd["print"]["slot_id"] = tray_id % 4
cmd["print"]["k_value"] = k_value
cmd["print"]["n_coef"] = n_coef
if nozzle_temp != -1:
cmd["print"]["nozzle_temp"] = nozzle_temp
if bed_temp != -1:
cmd["print"]["bed_temp"] = bed_temp
if max_volumetric_speed != -1:
cmd["print"]["max_volumetric_speed"] = max_volumetric_speed
self.client.publish(
f"device/{self.config.serial_number}/request", json.dumps(cmd)
)
logger.debug(
f"set_spool_k_factor - published EXTRUSION_CALI_SET to [device/{self.config.serial_number}/request] bambu_msg: [{cmd}]"
)
skip_objects
Instructs the printer to skip (cancel) a list of objects during the current print job.
The printhead physically avoids skipped objects for the remainder of the print. This is equivalent to the "Cancel Object" feature in BambuStudio.
Object ID mapping
Object IDs sent here are the identify_id values from Metadata/slice_info.config
inside the .3mf. get_project_info extracts them and writes each one into the
corresponding bbox_objects[N]["id"] entry in ProjectInfo.metadata["map"].
To get the IDs for the current plate:
info = get_project_info(file_id, printer, plate_num=1)
ids = [obj["id"] for obj in info.metadata["map"]["bbox_objects"] if "id" in obj]
printer.skip_objects(ids) # cancel all objects on the plate
The bbox_objects list also carries name and bounding-box coordinates so a UI
can let the user pick individual objects before calling this method.
Parameters
- objects : list[int | str] - One or more
identify_idvalues to cancel. Values are coerced tointbefore being sent to the printer.
Source code in src/bpm/bambuprinter.py
def skip_objects(self, objects):
"""
Instructs the printer to skip (cancel) a list of objects during the current print job.
The printhead physically avoids skipped objects for the remainder of the print.
This is equivalent to the "Cancel Object" feature in BambuStudio.
**Object ID mapping**
Object IDs sent here are the `identify_id` values from `Metadata/slice_info.config`
inside the `.3mf`. `get_project_info` extracts them and writes each one into the
corresponding `bbox_objects[N]["id"]` entry in `ProjectInfo.metadata["map"]`.
To get the IDs for the current plate:
```python
info = get_project_info(file_id, printer, plate_num=1)
ids = [obj["id"] for obj in info.metadata["map"]["bbox_objects"] if "id" in obj]
printer.skip_objects(ids) # cancel all objects on the plate
```
The `bbox_objects` list also carries `name` and bounding-box coordinates so a UI
can let the user pick individual objects before calling this method.
Parameters
----------
* objects : list[int | str] - One or more `identify_id` values to cancel.
Values are coerced to `int` before being sent to the printer.
"""
objs = []
for obj in objects:
objs.append(int(obj))
cmd = copy.deepcopy(SKIP_OBJECTS)
cmd["print"]["obj_list"] = objs
self.client.publish(
f"device/{self.config.serial_number}/request", json.dumps(cmd)
)
logger.debug(
f"skip_objects - published SKIP_OBJECTS to [device/{self.config.serial_number}/request] bambu_msg: [{cmd}]"
)
start_session
Initiates a connection to the Bambu Lab printer and provides a stateful
session, with built-in recovery in the event BambuPrinter
becomes disconnected from the machine.
This method is required to be called before any commands or data collection / callbacks can take place with the machine.
Source code in src/bpm/bambuprinter.py
def start_session(self):
"""
Initiates a connection to the Bambu Lab printer and provides a stateful
session, with built-in recovery in the event `BambuPrinter`
becomes disconnected from the machine.
This method is required to be called before any commands or data
collection / callbacks can take place with the machine.
"""
logger.debug("start_session - starting session")
if (
self.config.hostname is None
or self.config.access_code is None
or self.config.serial_number is None
):
raise Exception("hostname, access_code, and serial_number are required")
if self.client and self.client.is_connected():
raise Exception("a session is already active")
def on_connect(client, userdata, flags, reason_code, properties):
logger.debug("on_connect - session on_connect")
if self.service_state != ServiceState.PAUSED:
self.service_state = ServiceState.CONNECTED
client.subscribe(f"device/{self.config.serial_number}/report")
logger.debug(
f"on_connect -subscribed to [device/{self.config.serial_number}/report]"
)
def on_disconnect(client, userdata, flags, reason_code, properties):
logger.debug("on_disconnect - session on_disconnect")
if self._internalException:
logger.exception("on_disconnect - an internal exception occurred")
self.service_state = ServiceState.QUIT
raise self._internalException
if self.service_state != ServiceState.PAUSED:
self.service_state = ServiceState.DISCONNECTED
def on_message(client, userdata, msg):
if self._lastMessageTime and self._recent_update:
self._lastMessageTime = time.monotonic()
self._on_message(msg.payload.decode("utf-8"))
def loop_forever(printer: BambuPrinter):
logger.debug("loop_forever - starting mqtt loop_forever")
try:
printer.client.loop_forever(retry_first_connection=True)
except Exception as e:
logger.exception("loop_forever - an internal exception occurred")
printer._internalException = e
if printer.client and printer.client.is_connected():
printer.client.disconnect()
printer.service_state = ServiceState.QUIT
self.client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2) # type: ignore
self.client.on_connect = on_connect
self.client.on_disconnect = on_disconnect
self.client.on_message = on_message
self.client.tls_set(tls_version=ssl.PROTOCOL_TLS, cert_reqs=ssl.CERT_NONE)
self.client.tls_insecure_set(True)
self.client.reconnect_delay_set(min_delay=1, max_delay=1)
self.client.username_pw_set(
self.config.mqtt_username, password=self.config.access_code
)
self.client.user_data_set(self.config.mqtt_client_id)
self.client.connect_timeout = self.config.mqtt_connection_timeout
try:
self.client.connect(self.config.hostname, self.config.mqtt_port)
except Exception as e:
self._internalException = e
logger.warning(
f"start_session - unable to connect to printer - reason: [{e}] stacktrace: [{traceback.format_exc()}]"
)
self.service_state = ServiceState.QUIT
return
self._mqtt_client_thread = threading.Thread(
target=loop_forever, name="bambuprinter-session", args=(self,)
)
self._mqtt_client_thread.start()
self._start_watchdog()
stop_printing
Requests the printer to stop printing if a job is currently running.
Source code in src/bpm/bambuprinter.py
def stop_printing(self):
"""
Requests the printer to stop printing if a job is currently running.
"""
self.client.publish(
f"device/{self.config.serial_number}/request",
json.dumps(STOP_PRINT),
)
logger.debug(
f"stop_printing - published STOP_PRINT to [device/{self.config.serial_number}/request]"
)
toJson
Returns a dict (json document) representing this object's private class
level attributes that are serializable (most are).
Source code in src/bpm/bambuprinter.py
def toJson(self):
"""
Returns a `dict` (json document) representing this object's private class
level attributes that are serializable (most are).
"""
response = json.dumps(self, default=jsonSerializer, indent=4, sort_keys=True)
# logger.debug(f"toJson - json: [{response}]")
return json.loads(response)
turn_off_ams_dryer
Sends a command to the printer to turn off the AMS dryer.
Raises ValueError before publishing if ams_id does not match a
connected AMS unit, or the unit has no dryer.
Parameters
- ams_id : int = 0 - The AMS unit whose dryer should be turned off (default is 0).
Source code in src/bpm/bambuprinter.py
def turn_off_ams_dryer(self, ams_id: int = 0):
"""
Sends a command to the printer to turn off the AMS dryer.
Raises `ValueError` before publishing if `ams_id` does not match a
connected AMS unit, or the unit has no dryer.
Parameters
----------
* ams_id : int = 0 - The AMS unit whose dryer should be turned off (default is 0).
"""
self._require_ams_dryer(ams_id)
cmd = copy.deepcopy(AMS_FILAMENT_DRYING)
cmd["print"]["ams_id"] = ams_id
cmd["print"]["mode"] = 0 # Turn off drying mode
self.client.publish(
f"device/{self.config.serial_number}/request", json.dumps(cmd)
)
logger.debug(
f"turn_off_ams_dryer - published AMS_FILAMENT_DRYING to [device/{self.config.serial_number}/request] command: [{cmd}]"
)
turn_on_ams_dryer
turn_on_ams_dryer(
target_temp: int,
duration: int,
target_humidity: int = 0,
cooling_temp: int = 45,
rotate_tray: bool = False,
ams_id: int = 0,
filament_type: str = "",
)
Sends a command to the printer to turn on the AMS dryer with specified parameters.
Parameters
- target_temp : int - The target drying temperature.
- duration : int - The drying duration in hours.
- target_humidity : int - The target humidity level.
- cooling_temp : int - The cooling temperature after drying (default is 45).
- rotate_tray : bool - Whether to rotate the tray during drying (default is False).
- ams_id : int - The AMS ID to control (default is 0).
- filament_type : str - The filament type string (e.g. 'ABS'). Passed to firmware for validation.
Raises ValueError before publishing if ams_id does not match a
connected AMS unit, or the unit has no dryer.
Source code in src/bpm/bambuprinter.py
def turn_on_ams_dryer(
self,
target_temp: int,
duration: int,
target_humidity: int = 0,
cooling_temp: int = 45,
rotate_tray: bool = False,
ams_id: int = 0,
filament_type: str = "",
):
"""
Sends a command to the printer to turn on the AMS dryer with specified parameters.
Parameters
----------
* target_temp : int - The target drying temperature.
* duration : int - The drying duration in hours.
* target_humidity : int - The target humidity level.
* cooling_temp : int - The cooling temperature after drying (default is 45).
* rotate_tray : bool - Whether to rotate the tray during drying (default is False).
* ams_id : int - The AMS ID to control (default is 0).
* filament_type : str - The filament type string (e.g. 'ABS'). Passed to firmware for validation.
Raises `ValueError` before publishing if `ams_id` does not match a
connected AMS unit, or the unit has no dryer.
"""
self._require_ams_dryer(ams_id)
cmd = copy.deepcopy(AMS_FILAMENT_DRYING)
cmd["print"]["ams_id"] = ams_id
cmd["print"]["mode"] = 1 # Turn on drying mode
cmd["print"]["filament"] = filament_type
cmd["print"]["temp"] = target_temp
cmd["print"]["duration"] = duration
cmd["print"]["humidity"] = target_humidity
cmd["print"]["cooling_temp"] = cooling_temp
cmd["print"]["rotate_tray"] = rotate_tray
self.client.publish(
f"device/{self.config.serial_number}/request", json.dumps(cmd)
)
logger.debug(
f"turn_on_ams_dryer - published AMS_FILAMENT_DRYING to [device/{self.config.serial_number}/request] command: [{cmd}]"
)
unload_filament
Requests the printer to unload whatever filament / spool is currently loaded.
Parameters
- ams_id : int = 0 - The AMS unit, or external holder (254/255), to unload from
(default is 0). The command is
slot_id255,target255,curr_tempandtar_temp210, as Bambu Studio sends. The A1 refuses it without the temperatures.
A refusal appears in BambuState.hms_errors as a command_error entry, as for
load_filament.
Source code in src/bpm/bambuprinter.py
def unload_filament(self, ams_id: int = 0):
"""
Requests the printer to unload whatever filament / spool is currently loaded.
Parameters
----------
* ams_id : int = 0 - The AMS unit, or external holder (254/255), to unload from
(default is 0). The command is `slot_id` 255, `target` 255, `curr_temp` and
`tar_temp` 210, as Bambu Studio sends. The A1 refuses it without the temperatures.
A refusal appears in `BambuState.hms_errors` as a `command_error` entry, as for
`load_filament`.
"""
msg = copy.deepcopy(AMS_CHANGE_FILAMENT)
msg["print"]["ams_id"] = ams_id
# Studio's defaults (DeviceManager.hpp); the A1 refuses an unload without them
msg["print"]["curr_temp"] = 210
msg["print"]["tar_temp"] = 210
self._clear_command_error("ams_change_filament")
self.client.publish(
f"device/{self.config.serial_number}/request",
json.dumps(msg),
)
logger.debug(
f"unload_filament - published AMS_CHANGE_FILAMENT to [device/{self.config.serial_number}/request] - bambu_msg: [{msg}]"
)
upload_sdcard_file
Uploads the local filesystem file to the printer and returns an updated dict of all files on the printer
Parameters
- src : str - the full path filename on the host to be uploaded to the printer
- dest : str - the full path filename on the printer to upload to
Source code in src/bpm/bambuprinter.py
def upload_sdcard_file(self, src: str, dest: str):
"""
Uploads the local filesystem file to the printer and returns an updated dict of all files on the printer
Parameters
----------
* src : str - the full path filename on the host to be uploaded to the printer
* dest : str - the full path filename on the printer to upload to
"""
logger.debug(f"upload_sdcard_file - uploading file src: [{src}] dest: [{dest}]")
with self.ftp_connection() as ftps:
ftps.upload_file(src, dest)
if src.endswith(".3mf"):
get_project_info(dest, self, local_file=src)
remote_files = self.get_sdcard_contents()
return remote_files