|
@@ -7,15 +7,15 @@ This was discovered when K-profile requests with qos=0 took 20-30 seconds,
|
|
|
but with qos=1 they respond instantly.
|
|
but with qos=1 they respond instantly.
|
|
|
"""
|
|
"""
|
|
|
|
|
|
|
|
-import json
|
|
|
|
|
-import ssl
|
|
|
|
|
import asyncio
|
|
import asyncio
|
|
|
|
|
+import json
|
|
|
import logging
|
|
import logging
|
|
|
|
|
+import ssl
|
|
|
import time
|
|
import time
|
|
|
from collections import deque
|
|
from collections import deque
|
|
|
-from datetime import datetime
|
|
|
|
|
-from typing import Callable
|
|
|
|
|
|
|
+from collections.abc import Callable
|
|
|
from dataclasses import dataclass, field
|
|
from dataclasses import dataclass, field
|
|
|
|
|
+from datetime import datetime
|
|
|
|
|
|
|
|
import paho.mqtt.client as mqtt
|
|
import paho.mqtt.client as mqtt
|
|
|
|
|
|
|
@@ -25,6 +25,7 @@ logger = logging.getLogger(__name__)
|
|
|
@dataclass
|
|
@dataclass
|
|
|
class MQTTLogEntry:
|
|
class MQTTLogEntry:
|
|
|
"""Log entry for MQTT message debugging."""
|
|
"""Log entry for MQTT message debugging."""
|
|
|
|
|
+
|
|
|
timestamp: str
|
|
timestamp: str
|
|
|
topic: str
|
|
topic: str
|
|
|
direction: str # "in" or "out"
|
|
direction: str # "in" or "out"
|
|
@@ -34,6 +35,7 @@ class MQTTLogEntry:
|
|
|
@dataclass
|
|
@dataclass
|
|
|
class HMSError:
|
|
class HMSError:
|
|
|
"""Health Management System error from printer."""
|
|
"""Health Management System error from printer."""
|
|
|
|
|
+
|
|
|
code: str
|
|
code: str
|
|
|
attr: int # Attribute value for constructing wiki URL
|
|
attr: int # Attribute value for constructing wiki URL
|
|
|
module: int
|
|
module: int
|
|
@@ -44,6 +46,7 @@ class HMSError:
|
|
|
@dataclass
|
|
@dataclass
|
|
|
class KProfile:
|
|
class KProfile:
|
|
|
"""Pressure advance (K) calibration profile from printer."""
|
|
"""Pressure advance (K) calibration profile from printer."""
|
|
|
|
|
+
|
|
|
slot_id: int
|
|
slot_id: int
|
|
|
extruder_id: int
|
|
extruder_id: int
|
|
|
nozzle_id: str
|
|
nozzle_id: str
|
|
@@ -60,6 +63,7 @@ class KProfile:
|
|
|
@dataclass
|
|
@dataclass
|
|
|
class NozzleInfo:
|
|
class NozzleInfo:
|
|
|
"""Nozzle hardware configuration."""
|
|
"""Nozzle hardware configuration."""
|
|
|
|
|
+
|
|
|
nozzle_type: str = "" # "stainless_steel" or "hardened_steel"
|
|
nozzle_type: str = "" # "stainless_steel" or "hardened_steel"
|
|
|
nozzle_diameter: str = "" # e.g., "0.4"
|
|
nozzle_diameter: str = "" # e.g., "0.4"
|
|
|
|
|
|
|
@@ -67,6 +71,7 @@ class NozzleInfo:
|
|
|
@dataclass
|
|
@dataclass
|
|
|
class PrintOptions:
|
|
class PrintOptions:
|
|
|
"""AI detection and print options from xcam data."""
|
|
"""AI detection and print options from xcam data."""
|
|
|
|
|
+
|
|
|
# Core AI detectors
|
|
# Core AI detectors
|
|
|
spaghetti_detector: bool = False
|
|
spaghetti_detector: bool = False
|
|
|
print_halt: bool = False
|
|
print_halt: bool = False
|
|
@@ -131,7 +136,7 @@ class PrinterState:
|
|
|
# Main status: 0=idle, 1=filament_change, 2=rfid_identifying, 3=assist, 4=calibration, etc.
|
|
# Main status: 0=idle, 1=filament_change, 2=rfid_identifying, 3=assist, 4=calibration, etc.
|
|
|
ams_status: int = 0
|
|
ams_status: int = 0
|
|
|
ams_status_main: int = 0 # (ams_status >> 8) & 0xFF
|
|
ams_status_main: int = 0 # (ams_status >> 8) & 0xFF
|
|
|
- ams_status_sub: int = 0 # ams_status & 0xFF
|
|
|
|
|
|
|
+ ams_status_sub: int = 0 # ams_status & 0xFF
|
|
|
# mc_print_sub_stage - filament change step indicator from print.mc_print_sub_stage
|
|
# mc_print_sub_stage - filament change step indicator from print.mc_print_sub_stage
|
|
|
# Used by OrcaSlicer/BambuStudio to track progress during filament load/unload
|
|
# Used by OrcaSlicer/BambuStudio to track progress during filament load/unload
|
|
|
mc_print_sub_stage: int = 0
|
|
mc_print_sub_stage: int = 0
|
|
@@ -338,17 +343,19 @@ class BambuMQTTClient:
|
|
|
self._last_message_time = time.time()
|
|
self._last_message_time = time.time()
|
|
|
self.state.connected = True
|
|
self.state.connected = True
|
|
|
# TEMP: Dump full payload once to find extruder state field
|
|
# TEMP: Dump full payload once to find extruder state field
|
|
|
- if not hasattr(self, '_payload_dumped'):
|
|
|
|
|
|
|
+ if not hasattr(self, "_payload_dumped"):
|
|
|
self._payload_dumped = True
|
|
self._payload_dumped = True
|
|
|
logger.info(f"[{self.serial_number}] FULL MQTT PAYLOAD DUMP:\n{json.dumps(payload, indent=2)}")
|
|
logger.info(f"[{self.serial_number}] FULL MQTT PAYLOAD DUMP:\n{json.dumps(payload, indent=2)}")
|
|
|
# Log message if logging is enabled
|
|
# Log message if logging is enabled
|
|
|
if self._logging_enabled:
|
|
if self._logging_enabled:
|
|
|
- self._message_log.append(MQTTLogEntry(
|
|
|
|
|
- timestamp=datetime.now().isoformat(),
|
|
|
|
|
- topic=msg.topic,
|
|
|
|
|
- direction="in",
|
|
|
|
|
- payload=payload,
|
|
|
|
|
- ))
|
|
|
|
|
|
|
+ self._message_log.append(
|
|
|
|
|
+ MQTTLogEntry(
|
|
|
|
|
+ timestamp=datetime.now().isoformat(),
|
|
|
|
|
+ topic=msg.topic,
|
|
|
|
|
+ direction="in",
|
|
|
|
|
+ payload=payload,
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
self._process_message(payload)
|
|
self._process_message(payload)
|
|
|
except json.JSONDecodeError:
|
|
except json.JSONDecodeError:
|
|
|
pass
|
|
pass
|
|
@@ -416,7 +423,7 @@ class BambuMQTTClient:
|
|
|
vt_tray = print_data["vt_tray"]
|
|
vt_tray = print_data["vt_tray"]
|
|
|
self.state.raw_data["vt_tray"] = vt_tray
|
|
self.state.raw_data["vt_tray"] = vt_tray
|
|
|
# Log vt_tray to investigate per-extruder data for H2D
|
|
# Log vt_tray to investigate per-extruder data for H2D
|
|
|
- if not hasattr(self, '_vt_tray_logged') or not self._vt_tray_logged:
|
|
|
|
|
|
|
+ if not hasattr(self, "_vt_tray_logged") or not self._vt_tray_logged:
|
|
|
logger.info(f"[{self.serial_number}] vt_tray data: {vt_tray}")
|
|
logger.info(f"[{self.serial_number}] vt_tray data: {vt_tray}")
|
|
|
self._vt_tray_logged = True
|
|
self._vt_tray_logged = True
|
|
|
|
|
|
|
@@ -492,9 +499,7 @@ class BambuMQTTClient:
|
|
|
if elapsed > self._xcam_hold_time:
|
|
if elapsed > self._xcam_hold_time:
|
|
|
# Hold timer expired - accept incoming and clear hold
|
|
# Hold timer expired - accept incoming and clear hold
|
|
|
del self._xcam_hold_start[module_name]
|
|
del self._xcam_hold_start[module_name]
|
|
|
- logger.debug(
|
|
|
|
|
- f"[{self.serial_number}] Hold expired for {module_name}, accepting {incoming_value}"
|
|
|
|
|
- )
|
|
|
|
|
|
|
+ logger.debug(f"[{self.serial_number}] Hold expired for {module_name}, accepting {incoming_value}")
|
|
|
return True
|
|
return True
|
|
|
|
|
|
|
|
# Within hold period - ignore incoming data
|
|
# Within hold period - ignore incoming data
|
|
@@ -531,7 +536,9 @@ class BambuMQTTClient:
|
|
|
if should_accept_value("spaghetti_detector", cfg_spaghetti):
|
|
if should_accept_value("spaghetti_detector", cfg_spaghetti):
|
|
|
old_value = self.state.print_options.spaghetti_detector
|
|
old_value = self.state.print_options.spaghetti_detector
|
|
|
if cfg_spaghetti != old_value:
|
|
if cfg_spaghetti != old_value:
|
|
|
- logger.info(f"[{self.serial_number}] spaghetti_detector changed (from cfg): {old_value} -> {cfg_spaghetti}")
|
|
|
|
|
|
|
+ logger.info(
|
|
|
|
|
+ f"[{self.serial_number}] spaghetti_detector changed (from cfg): {old_value} -> {cfg_spaghetti}"
|
|
|
|
|
+ )
|
|
|
self.state.print_options.spaghetti_detector = cfg_spaghetti
|
|
self.state.print_options.spaghetti_detector = cfg_spaghetti
|
|
|
|
|
|
|
|
# Check hold timer for sensitivity before accepting
|
|
# Check hold timer for sensitivity before accepting
|
|
@@ -564,19 +571,25 @@ class BambuMQTTClient:
|
|
|
cfg_pileup, cfg_pileup_sens = decode_detector(8)
|
|
cfg_pileup, cfg_pileup_sens = decode_detector(8)
|
|
|
if should_accept_value("pileup_detector", cfg_pileup):
|
|
if should_accept_value("pileup_detector", cfg_pileup):
|
|
|
if cfg_pileup != self.state.print_options.pileup_detector:
|
|
if cfg_pileup != self.state.print_options.pileup_detector:
|
|
|
- logger.info(f"[{self.serial_number}] pileup_detector changed (from cfg): {self.state.print_options.pileup_detector} -> {cfg_pileup}")
|
|
|
|
|
|
|
+ logger.info(
|
|
|
|
|
+ f"[{self.serial_number}] pileup_detector changed (from cfg): {self.state.print_options.pileup_detector} -> {cfg_pileup}"
|
|
|
|
|
+ )
|
|
|
self.state.print_options.pileup_detector = cfg_pileup
|
|
self.state.print_options.pileup_detector = cfg_pileup
|
|
|
# Pileup sensitivity with hold timer
|
|
# Pileup sensitivity with hold timer
|
|
|
if "pileup_sensitivity" not in self._xcam_hold_start:
|
|
if "pileup_sensitivity" not in self._xcam_hold_start:
|
|
|
if cfg_pileup_sens != self.state.print_options.pileup_sensitivity:
|
|
if cfg_pileup_sens != self.state.print_options.pileup_sensitivity:
|
|
|
- logger.info(f"[{self.serial_number}] pileup_sensitivity changed (from cfg): {self.state.print_options.pileup_sensitivity} -> {cfg_pileup_sens}")
|
|
|
|
|
|
|
+ logger.info(
|
|
|
|
|
+ f"[{self.serial_number}] pileup_sensitivity changed (from cfg): {self.state.print_options.pileup_sensitivity} -> {cfg_pileup_sens}"
|
|
|
|
|
+ )
|
|
|
self.state.print_options.pileup_sensitivity = cfg_pileup_sens
|
|
self.state.print_options.pileup_sensitivity = cfg_pileup_sens
|
|
|
else:
|
|
else:
|
|
|
hold_start = self._xcam_hold_start["pileup_sensitivity"]
|
|
hold_start = self._xcam_hold_start["pileup_sensitivity"]
|
|
|
elapsed = current_time - hold_start
|
|
elapsed = current_time - hold_start
|
|
|
if elapsed > self._xcam_hold_time:
|
|
if elapsed > self._xcam_hold_time:
|
|
|
if cfg_pileup_sens != self.state.print_options.pileup_sensitivity:
|
|
if cfg_pileup_sens != self.state.print_options.pileup_sensitivity:
|
|
|
- logger.info(f"[{self.serial_number}] pileup_sensitivity synced (from cfg after hold): {self.state.print_options.pileup_sensitivity} -> {cfg_pileup_sens}")
|
|
|
|
|
|
|
+ logger.info(
|
|
|
|
|
+ f"[{self.serial_number}] pileup_sensitivity synced (from cfg after hold): {self.state.print_options.pileup_sensitivity} -> {cfg_pileup_sens}"
|
|
|
|
|
+ )
|
|
|
self.state.print_options.pileup_sensitivity = cfg_pileup_sens
|
|
self.state.print_options.pileup_sensitivity = cfg_pileup_sens
|
|
|
del self._xcam_hold_start["pileup_sensitivity"]
|
|
del self._xcam_hold_start["pileup_sensitivity"]
|
|
|
|
|
|
|
@@ -584,19 +597,25 @@ class BambuMQTTClient:
|
|
|
cfg_clump, cfg_clump_sens = decode_detector(11)
|
|
cfg_clump, cfg_clump_sens = decode_detector(11)
|
|
|
if should_accept_value("clump_detector", cfg_clump):
|
|
if should_accept_value("clump_detector", cfg_clump):
|
|
|
if cfg_clump != self.state.print_options.nozzle_clumping_detector:
|
|
if cfg_clump != self.state.print_options.nozzle_clumping_detector:
|
|
|
- logger.info(f"[{self.serial_number}] nozzle_clumping_detector changed (from cfg): {self.state.print_options.nozzle_clumping_detector} -> {cfg_clump}")
|
|
|
|
|
|
|
+ logger.info(
|
|
|
|
|
+ f"[{self.serial_number}] nozzle_clumping_detector changed (from cfg): {self.state.print_options.nozzle_clumping_detector} -> {cfg_clump}"
|
|
|
|
|
+ )
|
|
|
self.state.print_options.nozzle_clumping_detector = cfg_clump
|
|
self.state.print_options.nozzle_clumping_detector = cfg_clump
|
|
|
# Clump sensitivity with hold timer
|
|
# Clump sensitivity with hold timer
|
|
|
if "nozzle_clumping_sensitivity" not in self._xcam_hold_start:
|
|
if "nozzle_clumping_sensitivity" not in self._xcam_hold_start:
|
|
|
if cfg_clump_sens != self.state.print_options.nozzle_clumping_sensitivity:
|
|
if cfg_clump_sens != self.state.print_options.nozzle_clumping_sensitivity:
|
|
|
- logger.info(f"[{self.serial_number}] nozzle_clumping_sensitivity changed (from cfg): {self.state.print_options.nozzle_clumping_sensitivity} -> {cfg_clump_sens}")
|
|
|
|
|
|
|
+ logger.info(
|
|
|
|
|
+ f"[{self.serial_number}] nozzle_clumping_sensitivity changed (from cfg): {self.state.print_options.nozzle_clumping_sensitivity} -> {cfg_clump_sens}"
|
|
|
|
|
+ )
|
|
|
self.state.print_options.nozzle_clumping_sensitivity = cfg_clump_sens
|
|
self.state.print_options.nozzle_clumping_sensitivity = cfg_clump_sens
|
|
|
else:
|
|
else:
|
|
|
hold_start = self._xcam_hold_start["nozzle_clumping_sensitivity"]
|
|
hold_start = self._xcam_hold_start["nozzle_clumping_sensitivity"]
|
|
|
elapsed = current_time - hold_start
|
|
elapsed = current_time - hold_start
|
|
|
if elapsed > self._xcam_hold_time:
|
|
if elapsed > self._xcam_hold_time:
|
|
|
if cfg_clump_sens != self.state.print_options.nozzle_clumping_sensitivity:
|
|
if cfg_clump_sens != self.state.print_options.nozzle_clumping_sensitivity:
|
|
|
- logger.info(f"[{self.serial_number}] nozzle_clumping_sensitivity synced (from cfg after hold): {self.state.print_options.nozzle_clumping_sensitivity} -> {cfg_clump_sens}")
|
|
|
|
|
|
|
+ logger.info(
|
|
|
|
|
+ f"[{self.serial_number}] nozzle_clumping_sensitivity synced (from cfg after hold): {self.state.print_options.nozzle_clumping_sensitivity} -> {cfg_clump_sens}"
|
|
|
|
|
+ )
|
|
|
self.state.print_options.nozzle_clumping_sensitivity = cfg_clump_sens
|
|
self.state.print_options.nozzle_clumping_sensitivity = cfg_clump_sens
|
|
|
del self._xcam_hold_start["nozzle_clumping_sensitivity"]
|
|
del self._xcam_hold_start["nozzle_clumping_sensitivity"]
|
|
|
|
|
|
|
@@ -604,19 +623,25 @@ class BambuMQTTClient:
|
|
|
cfg_airprint, cfg_airprint_sens = decode_detector(14)
|
|
cfg_airprint, cfg_airprint_sens = decode_detector(14)
|
|
|
if should_accept_value("airprint_detector", cfg_airprint):
|
|
if should_accept_value("airprint_detector", cfg_airprint):
|
|
|
if cfg_airprint != self.state.print_options.airprint_detector:
|
|
if cfg_airprint != self.state.print_options.airprint_detector:
|
|
|
- logger.info(f"[{self.serial_number}] airprint_detector changed (from cfg): {self.state.print_options.airprint_detector} -> {cfg_airprint}")
|
|
|
|
|
|
|
+ logger.info(
|
|
|
|
|
+ f"[{self.serial_number}] airprint_detector changed (from cfg): {self.state.print_options.airprint_detector} -> {cfg_airprint}"
|
|
|
|
|
+ )
|
|
|
self.state.print_options.airprint_detector = cfg_airprint
|
|
self.state.print_options.airprint_detector = cfg_airprint
|
|
|
# Airprint sensitivity with hold timer
|
|
# Airprint sensitivity with hold timer
|
|
|
if "airprint_sensitivity" not in self._xcam_hold_start:
|
|
if "airprint_sensitivity" not in self._xcam_hold_start:
|
|
|
if cfg_airprint_sens != self.state.print_options.airprint_sensitivity:
|
|
if cfg_airprint_sens != self.state.print_options.airprint_sensitivity:
|
|
|
- logger.info(f"[{self.serial_number}] airprint_sensitivity changed (from cfg): {self.state.print_options.airprint_sensitivity} -> {cfg_airprint_sens}")
|
|
|
|
|
|
|
+ logger.info(
|
|
|
|
|
+ f"[{self.serial_number}] airprint_sensitivity changed (from cfg): {self.state.print_options.airprint_sensitivity} -> {cfg_airprint_sens}"
|
|
|
|
|
+ )
|
|
|
self.state.print_options.airprint_sensitivity = cfg_airprint_sens
|
|
self.state.print_options.airprint_sensitivity = cfg_airprint_sens
|
|
|
else:
|
|
else:
|
|
|
hold_start = self._xcam_hold_start["airprint_sensitivity"]
|
|
hold_start = self._xcam_hold_start["airprint_sensitivity"]
|
|
|
elapsed = current_time - hold_start
|
|
elapsed = current_time - hold_start
|
|
|
if elapsed > self._xcam_hold_time:
|
|
if elapsed > self._xcam_hold_time:
|
|
|
if cfg_airprint_sens != self.state.print_options.airprint_sensitivity:
|
|
if cfg_airprint_sens != self.state.print_options.airprint_sensitivity:
|
|
|
- logger.info(f"[{self.serial_number}] airprint_sensitivity synced (from cfg after hold): {self.state.print_options.airprint_sensitivity} -> {cfg_airprint_sens}")
|
|
|
|
|
|
|
+ logger.info(
|
|
|
|
|
+ f"[{self.serial_number}] airprint_sensitivity synced (from cfg after hold): {self.state.print_options.airprint_sensitivity} -> {cfg_airprint_sens}"
|
|
|
|
|
+ )
|
|
|
self.state.print_options.airprint_sensitivity = cfg_airprint_sens
|
|
self.state.print_options.airprint_sensitivity = cfg_airprint_sens
|
|
|
del self._xcam_hold_start["airprint_sensitivity"]
|
|
del self._xcam_hold_start["airprint_sensitivity"]
|
|
|
|
|
|
|
@@ -782,7 +807,7 @@ class BambuMQTTClient:
|
|
|
# Extract ams_extruder_map from each AMS unit's info field
|
|
# Extract ams_extruder_map from each AMS unit's info field
|
|
|
# According to OpenBambuAPI: info field bit 8 indicates which extruder (0=right, 1=left)
|
|
# According to OpenBambuAPI: info field bit 8 indicates which extruder (0=right, 1=left)
|
|
|
# Log AMS unit fields once to discover available fields
|
|
# Log AMS unit fields once to discover available fields
|
|
|
- if not hasattr(self, '_ams_fields_logged') and ams_list:
|
|
|
|
|
|
|
+ if not hasattr(self, "_ams_fields_logged") and ams_list:
|
|
|
first_unit = ams_list[0]
|
|
first_unit = ams_list[0]
|
|
|
logger.info(f"[{self.serial_number}] AMS unit fields: {sorted(first_unit.keys())}")
|
|
logger.info(f"[{self.serial_number}] AMS unit fields: {sorted(first_unit.keys())}")
|
|
|
for ams_unit in ams_list:
|
|
for ams_unit in ams_list:
|
|
@@ -804,7 +829,9 @@ class BambuMQTTClient:
|
|
|
bit8 = (info_val >> 8) & 0x1
|
|
bit8 = (info_val >> 8) & 0x1
|
|
|
extruder_id = 1 - bit8 # 0=right, 1=left
|
|
extruder_id = 1 - bit8 # 0=right, 1=left
|
|
|
ams_extruder_map[str(ams_id)] = extruder_id
|
|
ams_extruder_map[str(ams_id)] = extruder_id
|
|
|
- logger.debug(f"[{self.serial_number}] AMS {ams_id} info={info_val} (bit8={bit8}) -> extruder {extruder_id}")
|
|
|
|
|
|
|
+ logger.debug(
|
|
|
|
|
+ f"[{self.serial_number}] AMS {ams_id} info={info_val} (bit8={bit8}) -> extruder {extruder_id}"
|
|
|
|
|
+ )
|
|
|
except (ValueError, TypeError):
|
|
except (ValueError, TypeError):
|
|
|
pass
|
|
pass
|
|
|
if ams_extruder_map:
|
|
if ams_extruder_map:
|
|
@@ -879,8 +906,8 @@ class BambuMQTTClient:
|
|
|
# Temperature data
|
|
# Temperature data
|
|
|
temps = {}
|
|
temps = {}
|
|
|
# Log all fields for debugging dual-nozzle temperature discovery (only once)
|
|
# Log all fields for debugging dual-nozzle temperature discovery (only once)
|
|
|
- if "bed_temper" in data and not hasattr(self, '_temp_fields_logged'):
|
|
|
|
|
- temp_fields = {k: v for k, v in data.items() if 'temp' in k.lower() or 'chamber' in k.lower()}
|
|
|
|
|
|
|
+ if "bed_temper" in data and not hasattr(self, "_temp_fields_logged"):
|
|
|
|
|
+ temp_fields = {k: v for k, v in data.items() if "temp" in k.lower() or "chamber" in k.lower()}
|
|
|
logger.info(f"[{self.serial_number}] Temperature-related fields: {temp_fields}")
|
|
logger.info(f"[{self.serial_number}] Temperature-related fields: {temp_fields}")
|
|
|
# Log ALL keys in print data for H2D temperature discovery
|
|
# Log ALL keys in print data for H2D temperature discovery
|
|
|
all_keys = sorted(data.keys())
|
|
all_keys = sorted(data.keys())
|
|
@@ -888,13 +915,17 @@ class BambuMQTTClient:
|
|
|
self._temp_fields_logged = True
|
|
self._temp_fields_logged = True
|
|
|
|
|
|
|
|
# Log vir_slot data (once) - this may contain per-extruder slot mapping for H2D
|
|
# Log vir_slot data (once) - this may contain per-extruder slot mapping for H2D
|
|
|
- if "vir_slot" in data and not hasattr(self, '_vir_slot_logged'):
|
|
|
|
|
|
|
+ if "vir_slot" in data and not hasattr(self, "_vir_slot_logged"):
|
|
|
logger.info(f"[{self.serial_number}] vir_slot data: {data['vir_slot']}")
|
|
logger.info(f"[{self.serial_number}] vir_slot data: {data['vir_slot']}")
|
|
|
self._vir_slot_logged = True
|
|
self._vir_slot_logged = True
|
|
|
|
|
|
|
|
# Log nozzle hardware info fields (once)
|
|
# Log nozzle hardware info fields (once)
|
|
|
- nozzle_fields = {k: v for k, v in data.items() if 'nozzle' in k.lower() or 'hw' in k.lower() or 'extruder' in k.lower() or 'upgrade' in k.lower()}
|
|
|
|
|
- if nozzle_fields and not hasattr(self, '_nozzle_fields_logged'):
|
|
|
|
|
|
|
+ nozzle_fields = {
|
|
|
|
|
+ k: v
|
|
|
|
|
+ for k, v in data.items()
|
|
|
|
|
+ if "nozzle" in k.lower() or "hw" in k.lower() or "extruder" in k.lower() or "upgrade" in k.lower()
|
|
|
|
|
+ }
|
|
|
|
|
+ if nozzle_fields and not hasattr(self, "_nozzle_fields_logged"):
|
|
|
logger.info(f"[{self.serial_number}] Nozzle/hardware fields in MQTT data: {nozzle_fields}")
|
|
logger.info(f"[{self.serial_number}] Nozzle/hardware fields in MQTT data: {nozzle_fields}")
|
|
|
self._nozzle_fields_logged = True
|
|
self._nozzle_fields_logged = True
|
|
|
# Parse active extruder from device.extruder.state bit 8
|
|
# Parse active extruder from device.extruder.state bit 8
|
|
@@ -907,7 +938,9 @@ class BambuMQTTClient:
|
|
|
# Extract bit 8 for extruder position
|
|
# Extract bit 8 for extruder position
|
|
|
new_extruder = (state_val >> 8) & 0x1
|
|
new_extruder = (state_val >> 8) & 0x1
|
|
|
if new_extruder != self.state.active_extruder:
|
|
if new_extruder != self.state.active_extruder:
|
|
|
- logger.info(f"[{self.serial_number}] ACTIVE EXTRUDER CHANGED (state bit 8): {self.state.active_extruder} -> {new_extruder} (0=right, 1=left) [state={state_val}]")
|
|
|
|
|
|
|
+ logger.info(
|
|
|
|
|
+ f"[{self.serial_number}] ACTIVE EXTRUDER CHANGED (state bit 8): {self.state.active_extruder} -> {new_extruder} (0=right, 1=left) [state={state_val}]"
|
|
|
|
|
+ )
|
|
|
self.state.active_extruder = new_extruder
|
|
self.state.active_extruder = new_extruder
|
|
|
|
|
|
|
|
# Log device.extruder structure for active extruder
|
|
# Log device.extruder structure for active extruder
|
|
@@ -920,7 +953,9 @@ class BambuMQTTClient:
|
|
|
state_val = ext_data["state"]
|
|
state_val = ext_data["state"]
|
|
|
# Extract bits 12-14 (3 bits) for switch state
|
|
# Extract bits 12-14 (3 bits) for switch state
|
|
|
switch_state = (state_val >> 12) & 0x7
|
|
switch_state = (state_val >> 12) & 0x7
|
|
|
- logger.info(f"[{self.serial_number}] device.extruder.state={state_val} (switch_state bits 12-14: {switch_state})")
|
|
|
|
|
|
|
+ logger.info(
|
|
|
|
|
+ f"[{self.serial_number}] device.extruder.state={state_val} (switch_state bits 12-14: {switch_state})"
|
|
|
|
|
+ )
|
|
|
# Log 'cur' field if present (might indicate current/active extruder)
|
|
# Log 'cur' field if present (might indicate current/active extruder)
|
|
|
if "cur" in ext_data:
|
|
if "cur" in ext_data:
|
|
|
logger.info(f"[{self.serial_number}] device.extruder.cur: {ext_data['cur']}")
|
|
logger.info(f"[{self.serial_number}] device.extruder.cur: {ext_data['cur']}")
|
|
@@ -930,11 +965,11 @@ class BambuMQTTClient:
|
|
|
temps["bed_target"] = float(data["bed_target_temper"])
|
|
temps["bed_target"] = float(data["bed_target_temper"])
|
|
|
# Check if this is H2D (has device.extruder.info with 2 extruders)
|
|
# Check if this is H2D (has device.extruder.info with 2 extruders)
|
|
|
has_h2d_extruder_info = (
|
|
has_h2d_extruder_info = (
|
|
|
- "device" in data and
|
|
|
|
|
- isinstance(data.get("device"), dict) and
|
|
|
|
|
- "extruder" in data["device"] and
|
|
|
|
|
- isinstance(data["device"]["extruder"].get("info"), list) and
|
|
|
|
|
- len(data["device"]["extruder"]["info"]) >= 2
|
|
|
|
|
|
|
+ "device" in data
|
|
|
|
|
+ and isinstance(data.get("device"), dict)
|
|
|
|
|
+ and "extruder" in data["device"]
|
|
|
|
|
+ and isinstance(data["device"]["extruder"].get("info"), list)
|
|
|
|
|
+ and len(data["device"]["extruder"]["info"]) >= 2
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
# Standard nozzle fields: these are for the RIGHT/default nozzle on H2D
|
|
# Standard nozzle fields: these are for the RIGHT/default nozzle on H2D
|
|
@@ -1002,7 +1037,9 @@ class BambuMQTTClient:
|
|
|
if chamber_val > 500:
|
|
if chamber_val > 500:
|
|
|
mqtt_target = int(chamber_val) // 65536
|
|
mqtt_target = int(chamber_val) // 65536
|
|
|
current = int(chamber_val) % 65536
|
|
current = int(chamber_val) % 65536
|
|
|
- logger.debug(f"[{self.serial_number}] chamber_temper decoded: mqtt_target={mqtt_target}, current={current}, respect_local={respect_local}")
|
|
|
|
|
|
|
+ logger.debug(
|
|
|
|
|
+ f"[{self.serial_number}] chamber_temper decoded: mqtt_target={mqtt_target}, current={current}, respect_local={respect_local}"
|
|
|
|
|
+ )
|
|
|
if -50 < current < 100:
|
|
if -50 < current < 100:
|
|
|
temps["chamber"] = float(current)
|
|
temps["chamber"] = float(current)
|
|
|
# Store decoded target for later use, but DON'T set chamber_heating here!
|
|
# Store decoded target for later use, but DON'T set chamber_heating here!
|
|
@@ -1035,7 +1072,9 @@ class BambuMQTTClient:
|
|
|
# Store decoded target as fallback (may be overridden by ctc.info.target)
|
|
# Store decoded target as fallback (may be overridden by ctc.info.target)
|
|
|
if "_chamber_decoded_target" not in temps:
|
|
if "_chamber_decoded_target" not in temps:
|
|
|
temps["_chamber_decoded_target"] = float(target)
|
|
temps["_chamber_decoded_target"] = float(target)
|
|
|
- logger.debug(f"[{self.serial_number}] info.temp encoded: {info_temp} -> current={current}, decoded_target={target}")
|
|
|
|
|
|
|
+ logger.debug(
|
|
|
|
|
+ f"[{self.serial_number}] info.temp encoded: {info_temp} -> current={current}, decoded_target={target}"
|
|
|
|
|
+ )
|
|
|
elif -50 < info_temp < 100:
|
|
elif -50 < info_temp < 100:
|
|
|
# Valid direct temperature - heater is OFF
|
|
# Valid direct temperature - heater is OFF
|
|
|
temps["chamber"] = float(info_temp)
|
|
temps["chamber"] = float(info_temp)
|
|
@@ -1118,7 +1157,9 @@ class BambuMQTTClient:
|
|
|
if "modeCur" in airduct_data:
|
|
if "modeCur" in airduct_data:
|
|
|
new_mode = airduct_data["modeCur"]
|
|
new_mode = airduct_data["modeCur"]
|
|
|
if new_mode != self.state.airduct_mode:
|
|
if new_mode != self.state.airduct_mode:
|
|
|
- logger.info(f"[{self.serial_number}] airduct_mode changed: {self.state.airduct_mode} -> {new_mode}")
|
|
|
|
|
|
|
+ logger.info(
|
|
|
|
|
+ f"[{self.serial_number}] airduct_mode changed: {self.state.airduct_mode} -> {new_mode}"
|
|
|
|
|
+ )
|
|
|
self.state.airduct_mode = new_mode
|
|
self.state.airduct_mode = new_mode
|
|
|
# Parse chamber temp - may be encoded as (target*65536+current) when > 500
|
|
# Parse chamber temp - may be encoded as (target*65536+current) when > 500
|
|
|
# Check if we recently set the target locally (within 5 seconds)
|
|
# Check if we recently set the target locally (within 5 seconds)
|
|
@@ -1134,12 +1175,16 @@ class BambuMQTTClient:
|
|
|
explicit_target = None
|
|
explicit_target = None
|
|
|
if "target" in ctc_info:
|
|
if "target" in ctc_info:
|
|
|
target_val = ctc_info["target"]
|
|
target_val = ctc_info["target"]
|
|
|
- logger.debug(f"[{self.serial_number}] ctc_info.target explicit value: {target_val}, respect_local={respect_local_target}")
|
|
|
|
|
|
|
+ logger.debug(
|
|
|
|
|
+ f"[{self.serial_number}] ctc_info.target explicit value: {target_val}, respect_local={respect_local_target}"
|
|
|
|
|
+ )
|
|
|
# Filter out invalid values (valid chamber target is 0-60°C)
|
|
# Filter out invalid values (valid chamber target is 0-60°C)
|
|
|
if 0 <= target_val <= 60 and not respect_local_target:
|
|
if 0 <= target_val <= 60 and not respect_local_target:
|
|
|
explicit_target = float(target_val)
|
|
explicit_target = float(target_val)
|
|
|
temps["chamber_target"] = explicit_target # Override any previous value
|
|
temps["chamber_target"] = explicit_target # Override any previous value
|
|
|
- logger.debug(f"[{self.serial_number}] Setting chamber_target from ctc_info.target: {explicit_target}")
|
|
|
|
|
|
|
+ logger.debug(
|
|
|
|
|
+ f"[{self.serial_number}] Setting chamber_target from ctc_info.target: {explicit_target}"
|
|
|
|
|
+ )
|
|
|
|
|
|
|
|
# Parse chamber temp from ctc.info.temp - may be encoded
|
|
# Parse chamber temp from ctc.info.temp - may be encoded
|
|
|
if "temp" in ctc_info and "chamber" not in temps:
|
|
if "temp" in ctc_info and "chamber" not in temps:
|
|
@@ -1150,7 +1195,9 @@ class BambuMQTTClient:
|
|
|
decoded_target = temp_val // 65536
|
|
decoded_target = temp_val // 65536
|
|
|
current = temp_val % 65536
|
|
current = temp_val % 65536
|
|
|
temps["chamber"] = float(current)
|
|
temps["chamber"] = float(current)
|
|
|
- logger.debug(f"[{self.serial_number}] ctc_info.temp decoded: target={decoded_target}, current={current}, explicit_target={explicit_target}")
|
|
|
|
|
|
|
+ logger.debug(
|
|
|
|
|
+ f"[{self.serial_number}] ctc_info.temp decoded: target={decoded_target}, current={current}, explicit_target={explicit_target}"
|
|
|
|
|
+ )
|
|
|
|
|
|
|
|
# Determine which target to use for heating state:
|
|
# Determine which target to use for heating state:
|
|
|
# Priority: local target > explicit target > decoded target
|
|
# Priority: local target > explicit target > decoded target
|
|
@@ -1198,11 +1245,15 @@ class BambuMQTTClient:
|
|
|
target = self.state.temperatures.get("chamber_target", 0)
|
|
target = self.state.temperatures.get("chamber_target", 0)
|
|
|
|
|
|
|
|
self.state.temperatures["chamber_heating"] = target > 0 and current < target
|
|
self.state.temperatures["chamber_heating"] = target > 0 and current < target
|
|
|
- logger.debug(f"[{self.serial_number}] Chamber heating calculated: target={target}, current={current}, heating={self.state.temperatures['chamber_heating']}, respect_local={respect_local}")
|
|
|
|
|
|
|
+ logger.debug(
|
|
|
|
|
+ f"[{self.serial_number}] Chamber heating calculated: target={target}, current={current}, heating={self.state.temperatures['chamber_heating']}, respect_local={respect_local}"
|
|
|
|
|
+ )
|
|
|
|
|
|
|
|
# Debug: log chamber value if it was updated
|
|
# Debug: log chamber value if it was updated
|
|
|
if "chamber" in temps:
|
|
if "chamber" in temps:
|
|
|
- logger.debug(f"[{self.serial_number}] Chamber temp updated to: {self.state.temperatures.get('chamber')}, target: {self.state.temperatures.get('chamber_target')}, heating: {self.state.temperatures.get('chamber_heating')}")
|
|
|
|
|
|
|
+ logger.debug(
|
|
|
|
|
+ f"[{self.serial_number}] Chamber temp updated to: {self.state.temperatures.get('chamber')}, target: {self.state.temperatures.get('chamber_target')}, heating: {self.state.temperatures.get('chamber_heating')}"
|
|
|
|
|
+ )
|
|
|
|
|
|
|
|
# Parse HMS (Health Management System) errors
|
|
# Parse HMS (Health Management System) errors
|
|
|
if "hms" in data:
|
|
if "hms" in data:
|
|
@@ -1224,12 +1275,14 @@ class BambuMQTTClient:
|
|
|
severity = (attr >> 8) & 0xF
|
|
severity = (attr >> 8) & 0xF
|
|
|
# Module is in attr byte 3 (bits 24-31)
|
|
# Module is in attr byte 3 (bits 24-31)
|
|
|
module = (attr >> 24) & 0xFF
|
|
module = (attr >> 24) & 0xFF
|
|
|
- self.state.hms_errors.append(HMSError(
|
|
|
|
|
- code=f"0x{code:x}" if code else "0x0",
|
|
|
|
|
- attr=attr,
|
|
|
|
|
- module=module,
|
|
|
|
|
- severity=severity if severity > 0 else 2,
|
|
|
|
|
- ))
|
|
|
|
|
|
|
+ self.state.hms_errors.append(
|
|
|
|
|
+ HMSError(
|
|
|
|
|
+ code=f"0x{code:x}" if code else "0x0",
|
|
|
|
|
+ attr=attr,
|
|
|
|
|
+ module=module,
|
|
|
|
|
+ severity=severity if severity > 0 else 2,
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
|
|
|
# Parse SD card status
|
|
# Parse SD card status
|
|
|
if "sdcard" in data:
|
|
if "sdcard" in data:
|
|
@@ -1244,7 +1297,9 @@ class BambuMQTTClient:
|
|
|
home_flag = home_flag & 0xFFFFFFFF
|
|
home_flag = home_flag & 0xFFFFFFFF
|
|
|
store_to_sdcard = bool((home_flag >> 11) & 1)
|
|
store_to_sdcard = bool((home_flag >> 11) & 1)
|
|
|
if store_to_sdcard != self.state.store_to_sdcard:
|
|
if store_to_sdcard != self.state.store_to_sdcard:
|
|
|
- logger.info(f"[{self.serial_number}] store_to_sdcard changed: {self.state.store_to_sdcard} -> {store_to_sdcard}")
|
|
|
|
|
|
|
+ logger.info(
|
|
|
|
|
+ f"[{self.serial_number}] store_to_sdcard changed: {self.state.store_to_sdcard} -> {store_to_sdcard}"
|
|
|
|
|
+ )
|
|
|
self.state.store_to_sdcard = store_to_sdcard
|
|
self.state.store_to_sdcard = store_to_sdcard
|
|
|
|
|
|
|
|
# Parse timelapse status (recording active during print)
|
|
# Parse timelapse status (recording active during print)
|
|
@@ -1294,7 +1349,9 @@ class BambuMQTTClient:
|
|
|
if isinstance(light, dict) and light.get("node") == "chamber_light":
|
|
if isinstance(light, dict) and light.get("node") == "chamber_light":
|
|
|
new_light_state = light.get("mode") == "on"
|
|
new_light_state = light.get("mode") == "on"
|
|
|
if new_light_state != self.state.chamber_light:
|
|
if new_light_state != self.state.chamber_light:
|
|
|
- logger.info(f"[{self.serial_number}] chamber_light changed: {self.state.chamber_light} -> {new_light_state}")
|
|
|
|
|
|
|
+ logger.info(
|
|
|
|
|
+ f"[{self.serial_number}] chamber_light changed: {self.state.chamber_light} -> {new_light_state}"
|
|
|
|
|
+ )
|
|
|
self.state.chamber_light = new_light_state
|
|
self.state.chamber_light = new_light_state
|
|
|
break
|
|
break
|
|
|
|
|
|
|
@@ -1403,12 +1460,16 @@ class BambuMQTTClient:
|
|
|
f"[{self.serial_number}] PRINT START detected - file: {current_file}, "
|
|
f"[{self.serial_number}] PRINT START detected - file: {current_file}, "
|
|
|
f"subtask: {self.state.subtask_name}, is_new: {is_new_print}, is_file_change: {is_file_change}"
|
|
f"subtask: {self.state.subtask_name}, is_new: {is_new_print}, is_file_change: {is_file_change}"
|
|
|
)
|
|
)
|
|
|
- self.on_print_start({
|
|
|
|
|
- "filename": current_file,
|
|
|
|
|
- "subtask_name": self.state.subtask_name,
|
|
|
|
|
- "remaining_time": self.state.remaining_time * 60 if self.state.remaining_time > 0 else None, # Convert minutes to seconds
|
|
|
|
|
- "raw_data": data,
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ self.on_print_start(
|
|
|
|
|
+ {
|
|
|
|
|
+ "filename": current_file,
|
|
|
|
|
+ "subtask_name": self.state.subtask_name,
|
|
|
|
|
+ "remaining_time": self.state.remaining_time * 60
|
|
|
|
|
+ if self.state.remaining_time > 0
|
|
|
|
|
+ else None, # Convert minutes to seconds
|
|
|
|
|
+ "raw_data": data,
|
|
|
|
|
+ }
|
|
|
|
|
+ )
|
|
|
|
|
|
|
|
# Detect print completion (FINISH = success, FAILED = error, IDLE = aborted)
|
|
# Detect print completion (FINISH = success, FAILED = error, IDLE = aborted)
|
|
|
# Use _was_running flag in addition to _previous_gcode_state for more robust detection
|
|
# Use _was_running flag in addition to _previous_gcode_state for more robust detection
|
|
@@ -1448,13 +1509,25 @@ class BambuMQTTClient:
|
|
|
self._completion_triggered = True
|
|
self._completion_triggered = True
|
|
|
self._was_running = False
|
|
self._was_running = False
|
|
|
self._timelapse_during_print = False # Reset for next print
|
|
self._timelapse_during_print = False # Reset for next print
|
|
|
- self.on_print_complete({
|
|
|
|
|
- "status": status,
|
|
|
|
|
- "filename": self._previous_gcode_file or current_file,
|
|
|
|
|
- "subtask_name": self.state.subtask_name,
|
|
|
|
|
- "raw_data": data,
|
|
|
|
|
- "timelapse_was_active": timelapse_was_active,
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ # Include HMS errors for failure reason detection
|
|
|
|
|
+ hms_errors_data = (
|
|
|
|
|
+ [
|
|
|
|
|
+ {"code": e.code, "attr": e.attr, "module": e.module, "severity": e.severity}
|
|
|
|
|
+ for e in self.state.hms_errors
|
|
|
|
|
+ ]
|
|
|
|
|
+ if self.state.hms_errors
|
|
|
|
|
+ else []
|
|
|
|
|
+ )
|
|
|
|
|
+ self.on_print_complete(
|
|
|
|
|
+ {
|
|
|
|
|
+ "status": status,
|
|
|
|
|
+ "filename": self._previous_gcode_file or current_file,
|
|
|
|
|
+ "subtask_name": self.state.subtask_name,
|
|
|
|
|
+ "raw_data": data,
|
|
|
|
|
+ "timelapse_was_active": timelapse_was_active,
|
|
|
|
|
+ "hms_errors": hms_errors_data,
|
|
|
|
|
+ }
|
|
|
|
|
+ )
|
|
|
|
|
|
|
|
self._previous_gcode_state = self.state.state
|
|
self._previous_gcode_state = self.state.state
|
|
|
if current_file:
|
|
if current_file:
|
|
@@ -1493,7 +1566,7 @@ class BambuMQTTClient:
|
|
|
"system": {
|
|
"system": {
|
|
|
"sequence_id": str(self._sequence_id),
|
|
"sequence_id": str(self._sequence_id),
|
|
|
"command": "get_accessories",
|
|
"command": "get_accessories",
|
|
|
- "accessory_type": "none"
|
|
|
|
|
|
|
+ "accessory_type": "none",
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
logger.debug(f"[{self.serial_number}] Requesting accessories info")
|
|
logger.debug(f"[{self.serial_number}] Requesting accessories info")
|
|
@@ -1579,23 +1652,14 @@ class BambuMQTTClient:
|
|
|
def stop_print(self) -> bool:
|
|
def stop_print(self) -> bool:
|
|
|
"""Stop the current print job."""
|
|
"""Stop the current print job."""
|
|
|
if self._client and self.state.connected:
|
|
if self._client and self.state.connected:
|
|
|
- command = {
|
|
|
|
|
- "print": {
|
|
|
|
|
- "command": "stop",
|
|
|
|
|
- "sequence_id": "0"
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ command = {"print": {"command": "stop", "sequence_id": "0"}}
|
|
|
self._client.publish(self.topic_publish, json.dumps(command), qos=1)
|
|
self._client.publish(self.topic_publish, json.dumps(command), qos=1)
|
|
|
logger.info(f"[{self.serial_number}] Sent stop print command")
|
|
logger.info(f"[{self.serial_number}] Sent stop print command")
|
|
|
return True
|
|
return True
|
|
|
return False
|
|
return False
|
|
|
|
|
|
|
|
def set_xcam_option(
|
|
def set_xcam_option(
|
|
|
- self,
|
|
|
|
|
- module_name: str,
|
|
|
|
|
- enabled: bool,
|
|
|
|
|
- print_halt: bool = True,
|
|
|
|
|
- sensitivity: str = "medium"
|
|
|
|
|
|
|
+ self, module_name: str, enabled: bool, print_halt: bool = True, sensitivity: str = "medium"
|
|
|
) -> bool:
|
|
) -> bool:
|
|
|
"""Set an xcam (AI detection) option on the printer.
|
|
"""Set an xcam (AI detection) option on the printer.
|
|
|
|
|
|
|
@@ -1813,12 +1877,14 @@ class BambuMQTTClient:
|
|
|
if self._client and self.state.connected:
|
|
if self._client and self.state.connected:
|
|
|
# Log outgoing message if logging is enabled
|
|
# Log outgoing message if logging is enabled
|
|
|
if self._logging_enabled:
|
|
if self._logging_enabled:
|
|
|
- self._message_log.append(MQTTLogEntry(
|
|
|
|
|
- timestamp=datetime.now().isoformat(),
|
|
|
|
|
- topic=self.topic_publish,
|
|
|
|
|
- direction="out",
|
|
|
|
|
- payload=command,
|
|
|
|
|
- ))
|
|
|
|
|
|
|
+ self._message_log.append(
|
|
|
|
|
+ MQTTLogEntry(
|
|
|
|
|
+ timestamp=datetime.now().isoformat(),
|
|
|
|
|
+ topic=self.topic_publish,
|
|
|
|
|
+ direction="out",
|
|
|
|
|
+ payload=command,
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
self._client.publish(self.topic_publish, json.dumps(command), qos=1)
|
|
self._client.publish(self.topic_publish, json.dumps(command), qos=1)
|
|
|
|
|
|
|
|
def enable_logging(self, enabled: bool = True):
|
|
def enable_logging(self, enabled: bool = True):
|
|
@@ -1844,18 +1910,22 @@ class BambuMQTTClient:
|
|
|
response_nozzle = data.get("nozzle_diameter")
|
|
response_nozzle = data.get("nozzle_diameter")
|
|
|
response_seq_id = data.get("sequence_id", "?")
|
|
response_seq_id = data.get("sequence_id", "?")
|
|
|
filaments = data.get("filaments", [])
|
|
filaments = data.get("filaments", [])
|
|
|
- expected_nozzle = getattr(self, '_expected_kprofile_nozzle', None)
|
|
|
|
|
|
|
+ expected_nozzle = getattr(self, "_expected_kprofile_nozzle", None)
|
|
|
has_pending_request = self._pending_kprofile_response is not None
|
|
has_pending_request = self._pending_kprofile_response is not None
|
|
|
|
|
|
|
|
# Log all incoming responses when we have a pending request (for debugging)
|
|
# Log all incoming responses when we have a pending request (for debugging)
|
|
|
if has_pending_request:
|
|
if has_pending_request:
|
|
|
- logger.info(f"[{self.serial_number}] K-profile response: nozzle={response_nozzle}, {len(filaments)} profiles, expected={expected_nozzle}")
|
|
|
|
|
|
|
+ logger.info(
|
|
|
|
|
+ f"[{self.serial_number}] K-profile response: nozzle={response_nozzle}, {len(filaments)} profiles, expected={expected_nozzle}"
|
|
|
|
|
+ )
|
|
|
|
|
|
|
|
# If we have a pending request, only accept responses with matching nozzle_diameter
|
|
# If we have a pending request, only accept responses with matching nozzle_diameter
|
|
|
# The printer broadcasts 0.4mm profiles constantly - we need to wait for the actual response
|
|
# The printer broadcasts 0.4mm profiles constantly - we need to wait for the actual response
|
|
|
if has_pending_request and expected_nozzle and response_nozzle != expected_nozzle:
|
|
if has_pending_request and expected_nozzle and response_nozzle != expected_nozzle:
|
|
|
# Ignore this broadcast, keep waiting for matching response
|
|
# Ignore this broadcast, keep waiting for matching response
|
|
|
- logger.debug(f"[{self.serial_number}] Ignoring broadcast: got nozzle={response_nozzle}, waiting for {expected_nozzle}")
|
|
|
|
|
|
|
+ logger.debug(
|
|
|
|
|
+ f"[{self.serial_number}] Ignoring broadcast: got nozzle={response_nozzle}, waiting for {expected_nozzle}"
|
|
|
|
|
+ )
|
|
|
return
|
|
return
|
|
|
|
|
|
|
|
# If no pending request, this is just a broadcast - update state silently and return early
|
|
# If no pending request, this is just a broadcast - update state silently and return early
|
|
@@ -1866,19 +1936,21 @@ class BambuMQTTClient:
|
|
|
if isinstance(f, dict):
|
|
if isinstance(f, dict):
|
|
|
try:
|
|
try:
|
|
|
cali_idx = f.get("cali_idx", 0)
|
|
cali_idx = f.get("cali_idx", 0)
|
|
|
- profiles.append(KProfile(
|
|
|
|
|
- slot_id=cali_idx,
|
|
|
|
|
- extruder_id=int(f.get("extruder_id", 0)),
|
|
|
|
|
- nozzle_id=str(f.get("nozzle_id", "")),
|
|
|
|
|
- nozzle_diameter=str(f.get("nozzle_diameter", "0.4")),
|
|
|
|
|
- filament_id=str(f.get("filament_id", "")),
|
|
|
|
|
- name=str(f.get("name", "")),
|
|
|
|
|
- k_value=str(f.get("k_value", "0.000000")),
|
|
|
|
|
- n_coef=str(f.get("n_coef", "0.000000")),
|
|
|
|
|
- ams_id=int(f.get("ams_id", 0)),
|
|
|
|
|
- tray_id=int(f.get("tray_id", -1)),
|
|
|
|
|
- setting_id=f.get("setting_id"),
|
|
|
|
|
- ))
|
|
|
|
|
|
|
+ profiles.append(
|
|
|
|
|
+ KProfile(
|
|
|
|
|
+ slot_id=cali_idx,
|
|
|
|
|
+ extruder_id=int(f.get("extruder_id", 0)),
|
|
|
|
|
+ nozzle_id=str(f.get("nozzle_id", "")),
|
|
|
|
|
+ nozzle_diameter=str(f.get("nozzle_diameter", "0.4")),
|
|
|
|
|
+ filament_id=str(f.get("filament_id", "")),
|
|
|
|
|
+ name=str(f.get("name", "")),
|
|
|
|
|
+ k_value=str(f.get("k_value", "0.000000")),
|
|
|
|
|
+ n_coef=str(f.get("n_coef", "0.000000")),
|
|
|
|
|
+ ams_id=int(f.get("ams_id", 0)),
|
|
|
|
|
+ tray_id=int(f.get("tray_id", -1)),
|
|
|
|
|
+ setting_id=f.get("setting_id"),
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
except (ValueError, TypeError):
|
|
except (ValueError, TypeError):
|
|
|
pass
|
|
pass
|
|
|
self.state.kprofiles = profiles
|
|
self.state.kprofiles = profiles
|
|
@@ -1891,19 +1963,21 @@ class BambuMQTTClient:
|
|
|
try:
|
|
try:
|
|
|
# cali_idx is the actual slot/calibration index from the printer
|
|
# cali_idx is the actual slot/calibration index from the printer
|
|
|
cali_idx = f.get("cali_idx", i)
|
|
cali_idx = f.get("cali_idx", i)
|
|
|
- profiles.append(KProfile(
|
|
|
|
|
- slot_id=cali_idx,
|
|
|
|
|
- extruder_id=int(f.get("extruder_id", 0)),
|
|
|
|
|
- nozzle_id=str(f.get("nozzle_id", "")),
|
|
|
|
|
- nozzle_diameter=str(f.get("nozzle_diameter", "0.4")),
|
|
|
|
|
- filament_id=str(f.get("filament_id", "")),
|
|
|
|
|
- name=str(f.get("name", "")),
|
|
|
|
|
- k_value=str(f.get("k_value", "0.000000")),
|
|
|
|
|
- n_coef=str(f.get("n_coef", "0.000000")),
|
|
|
|
|
- ams_id=int(f.get("ams_id", 0)),
|
|
|
|
|
- tray_id=int(f.get("tray_id", -1)),
|
|
|
|
|
- setting_id=f.get("setting_id"),
|
|
|
|
|
- ))
|
|
|
|
|
|
|
+ profiles.append(
|
|
|
|
|
+ KProfile(
|
|
|
|
|
+ slot_id=cali_idx,
|
|
|
|
|
+ extruder_id=int(f.get("extruder_id", 0)),
|
|
|
|
|
+ nozzle_id=str(f.get("nozzle_id", "")),
|
|
|
|
|
+ nozzle_diameter=str(f.get("nozzle_diameter", "0.4")),
|
|
|
|
|
+ filament_id=str(f.get("filament_id", "")),
|
|
|
|
|
+ name=str(f.get("name", "")),
|
|
|
|
|
+ k_value=str(f.get("k_value", "0.000000")),
|
|
|
|
|
+ n_coef=str(f.get("n_coef", "0.000000")),
|
|
|
|
|
+ ams_id=int(f.get("ams_id", 0)),
|
|
|
|
|
+ tray_id=int(f.get("tray_id", -1)),
|
|
|
|
|
+ setting_id=f.get("setting_id"),
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
except (ValueError, TypeError) as e:
|
|
except (ValueError, TypeError) as e:
|
|
|
logger.warning(f"Failed to parse K-profile: {e}")
|
|
logger.warning(f"Failed to parse K-profile: {e}")
|
|
|
|
|
|
|
@@ -1920,7 +1994,9 @@ class BambuMQTTClient:
|
|
|
# Fallback for when loop is not available
|
|
# Fallback for when loop is not available
|
|
|
self._pending_kprofile_response.set()
|
|
self._pending_kprofile_response.set()
|
|
|
|
|
|
|
|
- async def get_kprofiles(self, nozzle_diameter: str = "0.4", timeout: float = 5.0, max_retries: int = 3) -> list[KProfile]:
|
|
|
|
|
|
|
+ async def get_kprofiles(
|
|
|
|
|
+ self, nozzle_diameter: str = "0.4", timeout: float = 5.0, max_retries: int = 3
|
|
|
|
|
+ ) -> list[KProfile]:
|
|
|
"""Request K-profiles from the printer with retry logic.
|
|
"""Request K-profiles from the printer with retry logic.
|
|
|
|
|
|
|
|
Bambu printers sometimes ignore the first K-profile request, so we
|
|
Bambu printers sometimes ignore the first K-profile request, so we
|
|
@@ -1962,7 +2038,9 @@ class BambuMQTTClient:
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- logger.info(f"[{self.serial_number}] Requesting K-profiles for nozzle_diameter={nozzle_diameter} (attempt {attempt + 1}/{max_retries})")
|
|
|
|
|
|
|
+ logger.info(
|
|
|
|
|
+ f"[{self.serial_number}] Requesting K-profiles for nozzle_diameter={nozzle_diameter} (attempt {attempt + 1}/{max_retries})"
|
|
|
|
|
+ )
|
|
|
logger.debug(f"[{self.serial_number}] K-profile request JSON: {json.dumps(command)}")
|
|
logger.debug(f"[{self.serial_number}] K-profile request JSON: {json.dumps(command)}")
|
|
|
self._client.publish(self.topic_publish, json.dumps(command), qos=1)
|
|
self._client.publish(self.topic_publish, json.dumps(command), qos=1)
|
|
|
|
|
|
|
@@ -1970,10 +2048,14 @@ class BambuMQTTClient:
|
|
|
try:
|
|
try:
|
|
|
await asyncio.wait_for(self._pending_kprofile_response.wait(), timeout=timeout)
|
|
await asyncio.wait_for(self._pending_kprofile_response.wait(), timeout=timeout)
|
|
|
profiles = self._kprofile_response_data or []
|
|
profiles = self._kprofile_response_data or []
|
|
|
- logger.info(f"[{self.serial_number}] Got {len(profiles)} K-profiles for nozzle={nozzle_diameter} on attempt {attempt + 1}")
|
|
|
|
|
|
|
+ logger.info(
|
|
|
|
|
+ f"[{self.serial_number}] Got {len(profiles)} K-profiles for nozzle={nozzle_diameter} on attempt {attempt + 1}"
|
|
|
|
|
+ )
|
|
|
return profiles
|
|
return profiles
|
|
|
- except asyncio.TimeoutError:
|
|
|
|
|
- logger.warning(f"[{self.serial_number}] Timeout on K-profiles request attempt {attempt + 1}/{max_retries}")
|
|
|
|
|
|
|
+ except TimeoutError:
|
|
|
|
|
+ logger.warning(
|
|
|
|
|
+ f"[{self.serial_number}] Timeout on K-profiles request attempt {attempt + 1}/{max_retries}"
|
|
|
|
|
+ )
|
|
|
if attempt < max_retries - 1:
|
|
if attempt < max_retries - 1:
|
|
|
# Brief delay before retry
|
|
# Brief delay before retry
|
|
|
await asyncio.sleep(0.5)
|
|
await asyncio.sleep(0.5)
|
|
@@ -2029,6 +2111,7 @@ class BambuMQTTClient:
|
|
|
# Generate a setting_id for new profiles (required by printer)
|
|
# Generate a setting_id for new profiles (required by printer)
|
|
|
# Format: "PF" + 17 random digits
|
|
# Format: "PF" + 17 random digits
|
|
|
import random
|
|
import random
|
|
|
|
|
+
|
|
|
if not setting_id and slot_id == 0:
|
|
if not setting_id and slot_id == 0:
|
|
|
setting_id = f"PF{random.randint(10000000000000000, 99999999999999999)}"
|
|
setting_id = f"PF{random.randint(10000000000000000, 99999999999999999)}"
|
|
|
|
|
|
|
@@ -2056,7 +2139,9 @@ class BambuMQTTClient:
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
command_json = json.dumps(command)
|
|
command_json = json.dumps(command)
|
|
|
- logger.info(f"[{self.serial_number}] Setting K-profile: {name} = {k_value} (cali_idx={effective_cali_idx}, new={slot_id==0})")
|
|
|
|
|
|
|
+ logger.info(
|
|
|
|
|
+ f"[{self.serial_number}] Setting K-profile: {name} = {k_value} (cali_idx={effective_cali_idx}, new={slot_id==0})"
|
|
|
|
|
+ )
|
|
|
logger.info(f"[{self.serial_number}] K-profile SET command: {command_json}")
|
|
logger.info(f"[{self.serial_number}] K-profile SET command: {command_json}")
|
|
|
self._client.publish(self.topic_publish, command_json, qos=1)
|
|
self._client.publish(self.topic_publish, command_json, qos=1)
|
|
|
return True
|
|
return True
|
|
@@ -2081,6 +2166,7 @@ class BambuMQTTClient:
|
|
|
return False
|
|
return False
|
|
|
|
|
|
|
|
import random
|
|
import random
|
|
|
|
|
+
|
|
|
self._sequence_id += 1
|
|
self._sequence_id += 1
|
|
|
|
|
|
|
|
filament_entries = []
|
|
filament_entries = []
|
|
@@ -2097,19 +2183,21 @@ class BambuMQTTClient:
|
|
|
if not setting_id and slot_id == 0:
|
|
if not setting_id and slot_id == 0:
|
|
|
setting_id = f"PF{random.randint(10000000000000000, 99999999999999999)}"
|
|
setting_id = f"PF{random.randint(10000000000000000, 99999999999999999)}"
|
|
|
|
|
|
|
|
- filament_entries.append({
|
|
|
|
|
- "ams_id": 0,
|
|
|
|
|
- "cali_idx": effective_cali_idx,
|
|
|
|
|
- "extruder_id": p.get("extruder_id", 0),
|
|
|
|
|
- "filament_id": p.get("filament_id", ""),
|
|
|
|
|
- "k_value": p.get("k_value", "0.020000"),
|
|
|
|
|
- "n_coef": "0.000000",
|
|
|
|
|
- "name": p.get("name", ""),
|
|
|
|
|
- "nozzle_diameter": nozzle_diameter,
|
|
|
|
|
- "nozzle_id": p.get("nozzle_id", f"HS00-{nozzle_diameter}"),
|
|
|
|
|
- "setting_id": setting_id if setting_id else "",
|
|
|
|
|
- "tray_id": -1,
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ filament_entries.append(
|
|
|
|
|
+ {
|
|
|
|
|
+ "ams_id": 0,
|
|
|
|
|
+ "cali_idx": effective_cali_idx,
|
|
|
|
|
+ "extruder_id": p.get("extruder_id", 0),
|
|
|
|
|
+ "filament_id": p.get("filament_id", ""),
|
|
|
|
|
+ "k_value": p.get("k_value", "0.020000"),
|
|
|
|
|
+ "n_coef": "0.000000",
|
|
|
|
|
+ "name": p.get("name", ""),
|
|
|
|
|
+ "nozzle_diameter": nozzle_diameter,
|
|
|
|
|
+ "nozzle_id": p.get("nozzle_id", f"HS00-{nozzle_diameter}"),
|
|
|
|
|
+ "setting_id": setting_id if setting_id else "",
|
|
|
|
|
+ "tray_id": -1,
|
|
|
|
|
+ }
|
|
|
|
|
+ )
|
|
|
|
|
|
|
|
command = {
|
|
command = {
|
|
|
"print": {
|
|
"print": {
|
|
@@ -2188,7 +2276,9 @@ class BambuMQTTClient:
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
command_json = json.dumps(command)
|
|
command_json = json.dumps(command)
|
|
|
- logger.info(f"[{self.serial_number}] Deleting K-profile: cali_idx={cali_idx}, filament={filament_id}, setting_id={setting_id}, dual={is_dual_nozzle}")
|
|
|
|
|
|
|
+ logger.info(
|
|
|
|
|
+ f"[{self.serial_number}] Deleting K-profile: cali_idx={cali_idx}, filament={filament_id}, setting_id={setting_id}, dual={is_dual_nozzle}"
|
|
|
|
|
+ )
|
|
|
logger.info(f"[{self.serial_number}] K-profile DELETE command: {command_json}")
|
|
logger.info(f"[{self.serial_number}] K-profile DELETE command: {command_json}")
|
|
|
# Use QoS 1 for reliable delivery (at least once)
|
|
# Use QoS 1 for reliable delivery (at least once)
|
|
|
self._client.publish(self.topic_publish, command_json, qos=1)
|
|
self._client.publish(self.topic_publish, command_json, qos=1)
|
|
@@ -2204,12 +2294,7 @@ class BambuMQTTClient:
|
|
|
logger.warning(f"[{self.serial_number}] Cannot pause print: not connected")
|
|
logger.warning(f"[{self.serial_number}] Cannot pause print: not connected")
|
|
|
return False
|
|
return False
|
|
|
|
|
|
|
|
- command = {
|
|
|
|
|
- "print": {
|
|
|
|
|
- "command": "pause",
|
|
|
|
|
- "sequence_id": "0"
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ command = {"print": {"command": "pause", "sequence_id": "0"}}
|
|
|
self._client.publish(self.topic_publish, json.dumps(command), qos=1)
|
|
self._client.publish(self.topic_publish, json.dumps(command), qos=1)
|
|
|
logger.info(f"[{self.serial_number}] Sent pause print command")
|
|
logger.info(f"[{self.serial_number}] Sent pause print command")
|
|
|
return True
|
|
return True
|
|
@@ -2220,12 +2305,7 @@ class BambuMQTTClient:
|
|
|
logger.warning(f"[{self.serial_number}] Cannot resume print: not connected")
|
|
logger.warning(f"[{self.serial_number}] Cannot resume print: not connected")
|
|
|
return False
|
|
return False
|
|
|
|
|
|
|
|
- command = {
|
|
|
|
|
- "print": {
|
|
|
|
|
- "command": "resume",
|
|
|
|
|
- "sequence_id": "0"
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ command = {"print": {"command": "resume", "sequence_id": "0"}}
|
|
|
self._client.publish(self.topic_publish, json.dumps(command), qos=1)
|
|
self._client.publish(self.topic_publish, json.dumps(command), qos=1)
|
|
|
logger.info(f"[{self.serial_number}] Sent resume print command")
|
|
logger.info(f"[{self.serial_number}] Sent resume print command")
|
|
|
return True
|
|
return True
|
|
@@ -2246,13 +2326,7 @@ class BambuMQTTClient:
|
|
|
return False
|
|
return False
|
|
|
|
|
|
|
|
self._sequence_id += 1
|
|
self._sequence_id += 1
|
|
|
- command = {
|
|
|
|
|
- "print": {
|
|
|
|
|
- "command": "gcode_line",
|
|
|
|
|
- "param": gcode,
|
|
|
|
|
- "sequence_id": str(self._sequence_id)
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ command = {"print": {"command": "gcode_line", "param": gcode, "sequence_id": str(self._sequence_id)}}
|
|
|
# Use QoS 1 for reliable delivery (at least once)
|
|
# Use QoS 1 for reliable delivery (at least once)
|
|
|
self._client.publish(self.topic_publish, json.dumps(command), qos=1)
|
|
self._client.publish(self.topic_publish, json.dumps(command), qos=1)
|
|
|
logger.debug(f"[{self.serial_number}] Sent G-code: {gcode[:50]}...")
|
|
logger.debug(f"[{self.serial_number}] Sent G-code: {gcode[:50]}...")
|
|
@@ -2308,7 +2382,9 @@ class BambuMQTTClient:
|
|
|
# Update heating state immediately based on new target
|
|
# Update heating state immediately based on new target
|
|
|
current_temp = self.state.temperatures.get("chamber", 0)
|
|
current_temp = self.state.temperatures.get("chamber", 0)
|
|
|
self.state.temperatures["chamber_heating"] = target > 0 and current_temp < target
|
|
self.state.temperatures["chamber_heating"] = target > 0 and current_temp < target
|
|
|
- logger.info(f"[{self.serial_number}] Tracking chamber target locally: {target}°C (heating={self.state.temperatures['chamber_heating']})")
|
|
|
|
|
|
|
+ logger.info(
|
|
|
|
|
+ f"[{self.serial_number}] Tracking chamber target locally: {target}°C (heating={self.state.temperatures['chamber_heating']})"
|
|
|
|
|
+ )
|
|
|
return result
|
|
return result
|
|
|
|
|
|
|
|
def set_print_speed(self, mode: int) -> bool:
|
|
def set_print_speed(self, mode: int) -> bool:
|
|
@@ -2328,13 +2404,7 @@ class BambuMQTTClient:
|
|
|
logger.warning(f"[{self.serial_number}] Invalid speed mode: {mode}")
|
|
logger.warning(f"[{self.serial_number}] Invalid speed mode: {mode}")
|
|
|
return False
|
|
return False
|
|
|
|
|
|
|
|
- command = {
|
|
|
|
|
- "print": {
|
|
|
|
|
- "command": "print_speed",
|
|
|
|
|
- "param": str(mode),
|
|
|
|
|
- "sequence_id": "0"
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ command = {"print": {"command": "print_speed", "param": str(mode), "sequence_id": "0"}}
|
|
|
self._client.publish(self.topic_publish, json.dumps(command), qos=1)
|
|
self._client.publish(self.topic_publish, json.dumps(command), qos=1)
|
|
|
logger.info(f"[{self.serial_number}] Set print speed mode to {mode}")
|
|
logger.info(f"[{self.serial_number}] Set print speed mode to {mode}")
|
|
|
return True
|
|
return True
|
|
@@ -2387,12 +2457,7 @@ class BambuMQTTClient:
|
|
|
self._sequence_id += 1
|
|
self._sequence_id += 1
|
|
|
mode_id = 0 if mode == "cooling" else 1
|
|
mode_id = 0 if mode == "cooling" else 1
|
|
|
command = {
|
|
command = {
|
|
|
- "print": {
|
|
|
|
|
- "command": "set_airduct",
|
|
|
|
|
- "modeId": mode_id,
|
|
|
|
|
- "sequence_id": str(self._sequence_id),
|
|
|
|
|
- "submode": -1
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ "print": {"command": "set_airduct", "modeId": mode_id, "sequence_id": str(self._sequence_id), "submode": -1}
|
|
|
}
|
|
}
|
|
|
# Use QoS 1 for reliable delivery
|
|
# Use QoS 1 for reliable delivery
|
|
|
self._client.publish(self.topic_publish, json.dumps(command), qos=1)
|
|
self._client.publish(self.topic_publish, json.dumps(command), qos=1)
|
|
@@ -2425,7 +2490,7 @@ class BambuMQTTClient:
|
|
|
"led_off_time": 500,
|
|
"led_off_time": 500,
|
|
|
"loop_times": 0,
|
|
"loop_times": 0,
|
|
|
"interval_time": 0,
|
|
"interval_time": 0,
|
|
|
- "sequence_id": str(self._sequence_id)
|
|
|
|
|
|
|
+ "sequence_id": str(self._sequence_id),
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
self._client.publish(self.topic_publish, json.dumps(command), qos=1)
|
|
self._client.publish(self.topic_publish, json.dumps(command), qos=1)
|
|
@@ -2455,11 +2520,7 @@ class BambuMQTTClient:
|
|
|
# extruder_index: 0 = RIGHT, 1 = LEFT
|
|
# extruder_index: 0 = RIGHT, 1 = LEFT
|
|
|
self._sequence_id += 1
|
|
self._sequence_id += 1
|
|
|
command = {
|
|
command = {
|
|
|
- "print": {
|
|
|
|
|
- "command": "select_extruder",
|
|
|
|
|
- "extruder_index": extruder,
|
|
|
|
|
- "sequence_id": str(self._sequence_id)
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ "print": {"command": "select_extruder", "extruder_index": extruder, "sequence_id": str(self._sequence_id)}
|
|
|
}
|
|
}
|
|
|
self._client.publish(self.topic_publish, json.dumps(command), qos=1)
|
|
self._client.publish(self.topic_publish, json.dumps(command), qos=1)
|
|
|
logger.info(f"[{self.serial_number}] Sent select_extruder command: extruder_index={extruder} (0=right, 1=left)")
|
|
logger.info(f"[{self.serial_number}] Sent select_extruder command: extruder_index={extruder} (0=right, 1=left)")
|
|
@@ -2551,7 +2612,7 @@ class BambuMQTTClient:
|
|
|
"slot_id": slot_id,
|
|
"slot_id": slot_id,
|
|
|
"target": tray_id,
|
|
"target": tray_id,
|
|
|
"curr_temp": -1,
|
|
"curr_temp": -1,
|
|
|
- "tar_temp": -1
|
|
|
|
|
|
|
+ "tar_temp": -1,
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -2604,9 +2665,9 @@ class BambuMQTTClient:
|
|
|
"sequence_id": str(self._sequence_id),
|
|
"sequence_id": str(self._sequence_id),
|
|
|
"ams_id": ams_id,
|
|
"ams_id": ams_id,
|
|
|
"slot_id": 255, # 255 = unload marker
|
|
"slot_id": 255, # 255 = unload marker
|
|
|
- "target": 255, # 255 = unload destination
|
|
|
|
|
|
|
+ "target": 255, # 255 = unload destination
|
|
|
"curr_temp": nozzle_temp,
|
|
"curr_temp": nozzle_temp,
|
|
|
- "tar_temp": nozzle_temp
|
|
|
|
|
|
|
+ "tar_temp": nozzle_temp,
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -2639,13 +2700,7 @@ class BambuMQTTClient:
|
|
|
logger.warning(f"[{self.serial_number}] Invalid AMS action: {action}")
|
|
logger.warning(f"[{self.serial_number}] Invalid AMS action: {action}")
|
|
|
return False
|
|
return False
|
|
|
|
|
|
|
|
- command = {
|
|
|
|
|
- "print": {
|
|
|
|
|
- "command": "ams_control",
|
|
|
|
|
- "param": action,
|
|
|
|
|
- "sequence_id": "0"
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ command = {"print": {"command": "ams_control", "param": action, "sequence_id": "0"}}
|
|
|
self._client.publish(self.topic_publish, json.dumps(command), qos=1)
|
|
self._client.publish(self.topic_publish, json.dumps(command), qos=1)
|
|
|
logger.info(f"[{self.serial_number}] AMS control: {action}")
|
|
logger.info(f"[{self.serial_number}] AMS control: {action}")
|
|
|
return True
|
|
return True
|
|
@@ -2682,14 +2737,7 @@ class BambuMQTTClient:
|
|
|
|
|
|
|
|
# Use ams_get_rfid command to trigger RFID re-read
|
|
# Use ams_get_rfid command to trigger RFID re-read
|
|
|
# This command is used by Bambu Studio to re-read the RFID tag
|
|
# This command is used by Bambu Studio to re-read the RFID tag
|
|
|
- command = {
|
|
|
|
|
- "print": {
|
|
|
|
|
- "command": "ams_get_rfid",
|
|
|
|
|
- "ams_id": ams_id,
|
|
|
|
|
- "slot_id": tray_id,
|
|
|
|
|
- "sequence_id": "0"
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ command = {"print": {"command": "ams_get_rfid", "ams_id": ams_id, "slot_id": tray_id, "sequence_id": "0"}}
|
|
|
self._client.publish(self.topic_publish, json.dumps(command), qos=1)
|
|
self._client.publish(self.topic_publish, json.dumps(command), qos=1)
|
|
|
logger.info(f"[{self.serial_number}] Triggering RFID re-read: AMS {ams_id}, slot {tray_id}")
|
|
logger.info(f"[{self.serial_number}] Triggering RFID re-read: AMS {ams_id}, slot {tray_id}")
|
|
|
return True, f"Refreshing AMS {ams_id} tray {tray_id}"
|
|
return True, f"Refreshing AMS {ams_id} tray {tray_id}"
|
|
@@ -2738,7 +2786,7 @@ class BambuMQTTClient:
|
|
|
"nozzle_temp_min": nozzle_temp_min,
|
|
"nozzle_temp_min": nozzle_temp_min,
|
|
|
"nozzle_temp_max": nozzle_temp_max,
|
|
"nozzle_temp_max": nozzle_temp_max,
|
|
|
"k": k,
|
|
"k": k,
|
|
|
- "sequence_id": "0"
|
|
|
|
|
|
|
+ "sequence_id": "0",
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -2761,19 +2809,10 @@ class BambuMQTTClient:
|
|
|
logger.warning(f"[{self.serial_number}] Cannot set timelapse: not connected")
|
|
logger.warning(f"[{self.serial_number}] Cannot set timelapse: not connected")
|
|
|
return False
|
|
return False
|
|
|
|
|
|
|
|
- command = {
|
|
|
|
|
- "pushing": {
|
|
|
|
|
- "command": "pushall",
|
|
|
|
|
- "sequence_id": "0"
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ command = {"pushing": {"command": "pushall", "sequence_id": "0"}}
|
|
|
# First send the timelapse setting
|
|
# First send the timelapse setting
|
|
|
timelapse_cmd = {
|
|
timelapse_cmd = {
|
|
|
- "print": {
|
|
|
|
|
- "command": "gcode_line",
|
|
|
|
|
- "param": f"M981 S{1 if enable else 0} P20000",
|
|
|
|
|
- "sequence_id": "0"
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ "print": {"command": "gcode_line", "param": f"M981 S{1 if enable else 0} P20000", "sequence_id": "0"}
|
|
|
}
|
|
}
|
|
|
self._client.publish(self.topic_publish, json.dumps(timelapse_cmd), qos=1)
|
|
self._client.publish(self.topic_publish, json.dumps(timelapse_cmd), qos=1)
|
|
|
# Request status update
|
|
# Request status update
|
|
@@ -2795,20 +2834,11 @@ class BambuMQTTClient:
|
|
|
return False
|
|
return False
|
|
|
|
|
|
|
|
command = {
|
|
command = {
|
|
|
- "xcam": {
|
|
|
|
|
- "command": "ipcam_record_set",
|
|
|
|
|
- "control": "enable" if enable else "disable",
|
|
|
|
|
- "sequence_id": "0"
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ "xcam": {"command": "ipcam_record_set", "control": "enable" if enable else "disable", "sequence_id": "0"}
|
|
|
}
|
|
}
|
|
|
self._client.publish(self.topic_publish, json.dumps(command), qos=1)
|
|
self._client.publish(self.topic_publish, json.dumps(command), qos=1)
|
|
|
# Request status update
|
|
# Request status update
|
|
|
- pushall = {
|
|
|
|
|
- "pushing": {
|
|
|
|
|
- "command": "pushall",
|
|
|
|
|
- "sequence_id": "0"
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ pushall = {"pushing": {"command": "pushall", "sequence_id": "0"}}
|
|
|
self._client.publish(self.topic_publish, json.dumps(pushall), qos=1)
|
|
self._client.publish(self.topic_publish, json.dumps(pushall), qos=1)
|
|
|
logger.info(f"[{self.serial_number}] Set liveview {'enabled' if enable else 'disabled'}")
|
|
logger.info(f"[{self.serial_number}] Set liveview {'enabled' if enable else 'disabled'}")
|
|
|
return True
|
|
return True
|