Merge branch 'neu'

This commit is contained in:
Manuel Weiser 2025-03-02 15:30:19 +01:00
commit 094959335a

View File

@ -14,9 +14,8 @@ from octoprint_bambu_printer.printer.print_job import PrintJob
from pybambu import BambuClient, commands from pybambu import BambuClient, commands
import logging import logging
import logging.handlers import logging.handlers
import paho.mqtt.client as mqtt
import json import json
import ssl import paho.mqtt.client as mqtt
from octoprint.util import RepeatedTimer from octoprint.util import RepeatedTimer
@ -48,6 +47,167 @@ class BambuPrinterTelemetry:
extruderCount: int = 1 extruderCount: int = 1
class BambuMqttBridgeClient:
"""
Implements compatible interface with BambuClient but uses Paho MQTT
to connect to a MQTT broker that bridges Bambu topics
"""
def __init__(self,
device_type,
serial,
host,
mqtt_port=1883,
**kwargs):
self._log = logging.getLogger("octoprint.plugins.bambu_printer.BambuMqttBridge")
self._device_type = device_type
self._serial = serial
self._host = host
self._mqtt_port = mqtt_port
self.connected = False
self._mqtt_client = mqtt.Client()
self._device_data = self._create_empty_device_data()
self._callbacks = {}
# Setup callbacks
self._mqtt_client.on_connect = self._on_connect
self._mqtt_client.on_message = self._on_message
self._mqtt_client.on_disconnect = self._on_disconnect
def _create_empty_device_data(self):
"""Creates empty device data structure compatible with BambuClient"""
from types import SimpleNamespace
# Create basic structure matching BambuClient
device = SimpleNamespace()
device.print_job = SimpleNamespace()
device.print_job.gcode_state = "IDLE"
device.temperature = SimpleNamespace()
device.temperature.nozzle_temp = AMBIENT_TEMPERATURE
device.temperature.target_nozzle_temp = 0.0
device.temperature.bed_temp = AMBIENT_TEMPERATURE
device.temperature.target_bed_temp = 0.0
device.temperature.chamber_temp = AMBIENT_TEMPERATURE
device.hms = SimpleNamespace()
device.hms.errors = {"Count": 0}
return device
def _on_connect(self, client, userdata, flags, rc):
if rc == 0:
self._log.info(f"Connected to MQTT broker at {self._host}:{self._mqtt_port}")
self.connected = True
# Subscribe to Bambu topics
topic_base = f"device/{self._device_type}/{self._serial}"
self._mqtt_client.subscribe(f"{topic_base}/report")
self._mqtt_client.subscribe(f"{topic_base}/report_hms")
if 'callback' in self._callbacks:
self._callbacks['callback']("event_printer_data_update")
if hasattr(self, 'on_connect') and callable(self.on_connect):
self.on_connect(client, userdata, flags, rc)
else:
self._log.error(f"Failed to connect to MQTT broker, return code: {rc}")
def _on_disconnect(self, client, userdata, rc):
self._log.warning(f"Disconnected from MQTT broker with code: {rc}")
self.connected = False
if hasattr(self, 'on_disconnect') and callable(self.on_disconnect):
self.on_disconnect(client, userdata, rc)
def _on_message(self, client, userdata, msg):
try:
payload = json.loads(msg.payload.decode('utf-8'))
self._log.debug(f"Received message on topic {msg.topic}: {payload}")
if msg.topic.endswith('/report'):
self._process_report_message(payload)
if 'callback' in self._callbacks:
self._callbacks['callback']("event_printer_data_update")
elif msg.topic.endswith('/report_hms'):
self._process_hms_message(payload)
if 'callback' in self._callbacks:
self._callbacks['callback']("event_hms_errors")
except json.JSONDecodeError:
self._log.error(f"Failed to decode JSON from message: {msg.payload}")
except Exception as e:
self._log.error(f"Error processing message: {str(e)}")
def _process_report_message(self, data):
"""Process printer status report messages"""
if 'print' in data and 'gcode_state' in data['print']:
self._device_data.print_job.gcode_state = data['print']['gcode_state']
if 'temperature' in data:
temp = self._device_data.temperature
temp_data = data['temperature']
if 'nozzle_temp' in temp_data:
temp.nozzle_temp = temp_data['nozzle_temp']
if 'target_nozzle_temp' in temp_data:
temp.target_nozzle_temp = temp_data['target_nozzle_temp']
if 'bed_temp' in temp_data:
temp.bed_temp = temp_data['bed_temp']
if 'target_bed_temp' in temp_data:
temp.target_bed_temp = temp_data['target_bed_temp']
if 'chamber_temp' in temp_data:
temp.chamber_temp = temp_data['chamber_temp']
def _process_hms_message(self, data):
"""Process HMS error messages"""
if 'hms' in data:
error_count = 0
hms_errors = {"Count": 0}
for error in data['hms']:
error_count += 1
hms_errors[f"{error_count}-Error"] = error['msg']
hms_errors["Count"] = error_count
self._device_data.hms.errors = hms_errors
def connect(self, callback=None):
"""Connect to MQTT broker"""
if callback:
self._callbacks['callback'] = callback
try:
self._mqtt_client.connect(self._host, self._mqtt_port)
self._mqtt_client.loop_start()
return True
except Exception as e:
self._log.error(f"Failed to connect to MQTT broker: {str(e)}")
return False
def disconnect(self):
"""Disconnect from MQTT broker"""
if self.connected:
self._mqtt_client.loop_stop()
self._mqtt_client.disconnect()
self.connected = False
def get_device(self):
"""Returns device data structure"""
return self._device_data
def publish(self, command):
"""Publishes command to device"""
if not self.connected:
return False
try:
topic_base = f"device/{self._device_type}/{self._serial}"
if 'print' in command and 'param' in command['print']:
# Assuming commands go to command topic
message = json.dumps(command)
self._mqtt_client.publish(f"{topic_base}/cmd", message)
return True
except Exception as e:
self._log.error(f"Failed to publish command: {str(e)}")
return False
# noinspection PyBroadException # noinspection PyBroadException
class BambuVirtualPrinter: class BambuVirtualPrinter:
gcode_executor = GCodeExecutor() gcode_executor = GCodeExecutor()
@ -644,107 +804,51 @@ class BambuVirtualPrinter:
if ( if (
self._settings.get(["device_type"]) == "" self._settings.get(["device_type"]) == ""
or self._settings.get(["serial"]) == "" or self._settings.get(["serial"]) == ""
or self._settings.get(["username"]) == ""
or self._settings.get(["access_code"]) == ""
): ):
msg = "invalid settings to start connection with Bambu Printer" msg = "invalid settings to start connection with Bambu Printer"
self._log.debug(msg) self._log.debug(msg)
raise ValueError(msg) raise ValueError(msg)
use_local_mqtt = self._settings.get_boolean(['local_mqtt']) # Check if we should use MQTT bridge mode
self._log.debug(f"connecting via local mqtt: {use_local_mqtt}") use_mqtt_bridge = self._settings.get_boolean(["use_mqtt_bridge"])
# Create a BambuClient but don't let it handle the MQTT connection if use_mqtt_bridge:
self._log.debug(
f"connecting via mqtt bridge: {self._settings.get(['mqtt_host'])}:{self._settings.get(['mqtt_port'])}"
)
# Create MQTT bridge client
bambu_client = BambuMqttBridgeClient(
device_type=self._settings.get(["device_type"]),
serial=self._settings.get(["serial"]),
host=self._settings.get(["mqtt_host"]),
mqtt_port=int(self._settings.get(["mqtt_port"]) or 1883)
)
else:
# Use standard BambuClient
self._log.debug(
f"connecting via local mqtt: {self._settings.get_boolean(['local_mqtt'])}"
)
bambu_client = BambuClient( bambu_client = BambuClient(
device_type=self._settings.get(["device_type"]), device_type=self._settings.get(["device_type"]),
serial=self._settings.get(["serial"]), serial=self._settings.get(["serial"]),
host=self._settings.get(["host"]), host=self._settings.get(["host"]),
username="bambuocto", username=(
"bblp"
if self._settings.get_boolean(["local_mqtt"])
else self._settings.get(["username"])
),
access_code=self._settings.get(["access_code"]), access_code=self._settings.get(["access_code"]),
local_mqtt=use_local_mqtt, local_mqtt=self._settings.get_boolean(["local_mqtt"]),
region=self._settings.get(["region"]), region=self._settings.get(["region"]),
email=self._settings.get(["email"]), email=self._settings.get(["email"]),
auth_token=self._settings.get(["auth_token"]), auth_token=self._settings.get(["auth_token"]),
) )
# Initialisiere die device-Eigenschaft manuell, ohne connect() zu benutzen bambu_client.on_disconnect = self.on_disconnect(bambu_client.on_disconnect)
# da die connect()-Methode ein Callback als Parameter erwartet bambu_client.on_connect = self.on_connect(bambu_client.on_connect)
if not hasattr(bambu_client, 'device'): bambu_client.connect(callback=self.new_update)
self._log.debug("BambuClient has no device attribute, initializing manually") self._log.info(f"bambu connection status: {bambu_client.connected}")
# Statt eine BambuDevice-Klasse direkt zu importieren oder connect() zu verwenden, self.sendOk()
# initialisieren wir die grundlegenden Attribute anders
try:
# Manuell die notwendigen Attribute erstellen
bambu_client.device = type('', (), {})() # Ein leeres Objekt erstellen
# Grundlegende Attribute hinzufügen
bambu_client.device.temperature = type('', (), {
'nozzle_temp': 21.0,
'target_nozzle_temp': 0.0,
'bed_temp': 21.0,
'target_bed_temp': 0.0,
'chamber_temp': 21.0,
})()
bambu_client.device.print_job = type('', (), {
'gcode_state': 'IDLE',
'gcode_file': '',
'mc_percent': 0,
'mc_remaining_time': 0,
})()
bambu_client.device.hms = type('', (), {
'errors': {'Count': 0},
'update_from_payload': lambda x: None
})()
self._log.debug("Created device attributes manually")
except Exception as e:
self._log.error(f"Error initializing BambuClient: {e}", exc_info=True)
# Set up our own MQTT client
self._mqtt_client = mqtt.Client()
self._mqtt_client.on_connect = self._on_mqtt_connect
self._mqtt_client.on_disconnect = self._on_mqtt_disconnect
self._mqtt_client.on_message = self._on_mqtt_message
# Configure connection based on local or cloud
if use_local_mqtt:
host = self._settings.get(["host"])
port = 1883
username = "octobambu"
self._mqtt_client.username_pw_set(username)
else:
# Cloud connection settings
region = self._settings.get(["region"])
host = f"mqtt-{region}.bambulab.com"
port = 8883
username = self._settings.get(["email"])
password = self._settings.get(["auth_token"])
self._mqtt_client.username_pw_set(username, password)
self._mqtt_client.tls_set()
# Connect MQTT
try:
self._mqtt_client.connect(host, port, 60)
self._mqtt_client.loop_start()
self._log.info(f"MQTT client started with {host}:{port}")
# Explicitly set the connection status
self._custom_connected = True
except Exception as e:
self._log.error(f"Failed to connect to MQTT broker: {e}")
raise
# Inject our MQTT client into the BambuClient without modifying 'connected'
bambu_client._mqtt_client = self._mqtt_client
# Instead of modifying bambu_client.connected, we'll use our custom property
self._custom_connected = True
# Store the Bambu client
self._bambu_client = bambu_client self._bambu_client = bambu_client
self._log.info(f"Custom connection status: {self.is_connected}") self._log.info(f"Custom connection status: {self.is_connected}")