Compare commits
	
		
			23 Commits
		
	
	
		
			0.1.6
			...
			fd9ce76275
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| fd9ce76275 | |||
| 8dafb9fa5a | |||
| 094959335a | |||
| f64fa7aea2 | |||
| fea0f0ed25 | |||
| c7c089ef68 | |||
| ba43df279d | |||
| f5e6b3d0dd | |||
| 9358533ce8 | |||
| 92e11cdbf3 | |||
| 61c9332f15 | |||
| ad08d3eb9a | |||
| 5661c11190 | |||
| 3690767ced | |||
| eb397ff7b7 | |||
| 3a615cfafe | |||
| e9c06bb4b5 | |||
| 3ccce10648 | |||
| c99eb38655 | |||
| 698f8f4151 | |||
| 7a0293bac7 | |||
| 
						 | 
					d0fd4a5434 | ||
| 
						 | 
					3c218a548d | 
							
								
								
									
										3
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					github: [jneilliii]
 | 
				
			||||||
 | 
					patreon: jneilliii
 | 
				
			||||||
 | 
					custom: ['https://www.paypal.me/jneilliii']
 | 
				
			||||||
							
								
								
									
										26
									
								
								.github/ISSUE_TEMPLATE/bug_report.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								.github/ISSUE_TEMPLATE/bug_report.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
				
			|||||||
 | 
					---
 | 
				
			||||||
 | 
					name: Bug report
 | 
				
			||||||
 | 
					about: Please make sure to check other issues, including closed ones, prior to submitting a bug report. Debug logs are required and any bug report submitted without them will be ignored and closed. 
 | 
				
			||||||
 | 
					title: "[BUG]: "
 | 
				
			||||||
 | 
					labels: ''
 | 
				
			||||||
 | 
					assignees: ''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Describe the Bug**
 | 
				
			||||||
 | 
					<!-- A clear and concise description of what the bug is. -->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Expected Behavior**
 | 
				
			||||||
 | 
					<!-- A clear and concise description of what you expected to happen. -->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Debug Logs**
 | 
				
			||||||
 | 
					<!-- If logs are not included in your bug report it will be closed. Enable debug logging for octoprint.plugins.bambu_printer in OctoPrint's logging section of settings and recreate the issue then attach octoprint.log and plugin_bambu_printer_serial.log to this bug report. -->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Screenshots**
 | 
				
			||||||
 | 
					<!-- Please share any relevant screenshots related to the issue. -->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Printer and Plugin Setting Details**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* Printer model?
 | 
				
			||||||
 | 
					* Is your printer connected to Bambu Cloud? 
 | 
				
			||||||
 | 
					* Is the  plugin configured for local access only?
 | 
				
			||||||
							
								
								
									
										20
									
								
								.github/ISSUE_TEMPLATE/feature_request.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								.github/ISSUE_TEMPLATE/feature_request.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					---
 | 
				
			||||||
 | 
					name: Feature request
 | 
				
			||||||
 | 
					about: Create a feature request for an improvement or change you'd like implemented.
 | 
				
			||||||
 | 
					title: "[FR]: "
 | 
				
			||||||
 | 
					labels: ''
 | 
				
			||||||
 | 
					assignees: ''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Is your feature request related to a problem? Please describe.**
 | 
				
			||||||
 | 
					<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Describe the solution you'd like**
 | 
				
			||||||
 | 
					<!-- A clear and concise description of what you want to happen. -->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Describe alternatives you've considered**
 | 
				
			||||||
 | 
					<!-- A clear and concise description of any alternative solutions or features you've considered. -->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Additional context**
 | 
				
			||||||
 | 
					<!-- Add any other context or screenshots about the feature request here. -->
 | 
				
			||||||
							
								
								
									
										16
									
								
								.github/stale.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								.github/stale.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					# Number of days of inactivity before an issue becomes stale
 | 
				
			||||||
 | 
					daysUntilStale: 14
 | 
				
			||||||
 | 
					# Number of days of inactivity before a stale issue is closed
 | 
				
			||||||
 | 
					daysUntilClose: 7
 | 
				
			||||||
 | 
					# Issues with these labels will never be considered stale
 | 
				
			||||||
 | 
					exemptLabels:
 | 
				
			||||||
 | 
					  - enhancement
 | 
				
			||||||
 | 
					  - bug
 | 
				
			||||||
 | 
					# Label to use when marking an issue as stale
 | 
				
			||||||
 | 
					staleLabel: stale
 | 
				
			||||||
 | 
					# Comment to post when marking an issue as stale. Set to `false` to disable
 | 
				
			||||||
 | 
					markComment: >
 | 
				
			||||||
 | 
					  This issue has been automatically marked as stale because it has not had
 | 
				
			||||||
 | 
					  activity in 14 days. It will be closed if no further activity occurs in 7 days.
 | 
				
			||||||
 | 
					# Comment to post when closing a stale issue. Set to `false` to disable
 | 
				
			||||||
 | 
					closeComment: false
 | 
				
			||||||
							
								
								
									
										27
									
								
								.github/workflows/stale.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								.github/workflows/stale.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
				
			|||||||
 | 
					name: Mark Stale Issues
 | 
				
			||||||
 | 
					on:
 | 
				
			||||||
 | 
					  workflow_dispatch:
 | 
				
			||||||
 | 
					  schedule:
 | 
				
			||||||
 | 
					  - cron: "0 0 * * *"
 | 
				
			||||||
 | 
					permissions:
 | 
				
			||||||
 | 
					  actions: write
 | 
				
			||||||
 | 
					jobs:
 | 
				
			||||||
 | 
					  stale:
 | 
				
			||||||
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
 | 
					    steps:
 | 
				
			||||||
 | 
					    - uses: actions/stale@v9
 | 
				
			||||||
 | 
					      with:
 | 
				
			||||||
 | 
					        repo-token: ${{ secrets.GITHUB_TOKEN }}
 | 
				
			||||||
 | 
					        stale-issue-message: 'This issue has been automatically marked as stale because it has not had activity in 14 days. It will be closed if no further activity occurs in 7 days'
 | 
				
			||||||
 | 
					        days-before-stale: 14
 | 
				
			||||||
 | 
					        days-before-close: 7
 | 
				
			||||||
 | 
					        stale-issue-label: 'stale'
 | 
				
			||||||
 | 
					        days-before-issue-stale: 14
 | 
				
			||||||
 | 
					        days-before-pr-stale: -1
 | 
				
			||||||
 | 
					        days-before-issue-close: 7
 | 
				
			||||||
 | 
					        days-before-pr-close: -1
 | 
				
			||||||
 | 
					        exempt-issue-labels: 'bug,enhancement'
 | 
				
			||||||
 | 
					    - uses: actions/checkout@v4
 | 
				
			||||||
 | 
					    - uses: gautamkrishnar/keepalive-workflow@v2
 | 
				
			||||||
 | 
					      with:
 | 
				
			||||||
 | 
					        use_api: true
 | 
				
			||||||
@@ -85,7 +85,7 @@ class BambuPrintPlugin(
 | 
				
			|||||||
            "serial": "",
 | 
					            "serial": "",
 | 
				
			||||||
            "host": "",
 | 
					            "host": "",
 | 
				
			||||||
            "access_code": "",
 | 
					            "access_code": "",
 | 
				
			||||||
            "username": "bblp",
 | 
					            "username": "octobambu",
 | 
				
			||||||
            "timelapse": False,
 | 
					            "timelapse": False,
 | 
				
			||||||
            "bed_leveling": True,
 | 
					            "bed_leveling": True,
 | 
				
			||||||
            "flow_cali": False,
 | 
					            "flow_cali": False,
 | 
				
			||||||
@@ -286,10 +286,10 @@ class BambuPrintPlugin(
 | 
				
			|||||||
    def get_update_information(self):
 | 
					    def get_update_information(self):
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            "bambu_printer": {
 | 
					            "bambu_printer": {
 | 
				
			||||||
                "displayName": "Bambu Printer",
 | 
					                "displayName": "Manus Bambu Printer",
 | 
				
			||||||
                "displayVersion": self._plugin_version,
 | 
					                "displayVersion": self._plugin_version,
 | 
				
			||||||
                "type": "github_release",
 | 
					                "type": "github_release",
 | 
				
			||||||
                "user": "jneilliii",
 | 
					                "user": "ManuelW",
 | 
				
			||||||
                "repo": "OctoPrint-BambuPrinter",
 | 
					                "repo": "OctoPrint-BambuPrinter",
 | 
				
			||||||
                "current": self._plugin_version,
 | 
					                "current": self._plugin_version,
 | 
				
			||||||
                "stable_branch": {
 | 
					                "stable_branch": {
 | 
				
			||||||
@@ -304,6 +304,6 @@ class BambuPrintPlugin(
 | 
				
			|||||||
                        "comittish": ["rc", "master"],
 | 
					                        "comittish": ["rc", "master"],
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                ],
 | 
					                ],
 | 
				
			||||||
                "pip": "https://github.com/jneilliii/OctoPrint-BambuPrinter/archive/{target_version}.zip",
 | 
					                "pip": "https://gitlab.fire-devils.org/3D-Druck/OctoPrint-BambuPrinter/archive/{target_version}.zip",
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,6 +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 json
 | 
				
			||||||
 | 
					import paho.mqtt.client as mqtt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from octoprint.util import RepeatedTimer
 | 
					from octoprint.util import RepeatedTimer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -45,7 +47,232 @@ class BambuPrinterTelemetry:
 | 
				
			|||||||
    extruderCount: int = 1
 | 
					    extruderCount: int = 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# noinspection PyBroadException
 | 
					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(f"octoprint_bambu_bridge_{serial}")
 | 
				
			||||||
 | 
					        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")
 | 
				
			||||||
 | 
					            self._log.info(f"Subscribed to topic: {topic_base}/#")
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            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
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # Add reconnection attempt
 | 
				
			||||||
 | 
					        if rc != 0:  # Non-zero means unexpected disconnect
 | 
				
			||||||
 | 
					            self._log.info("Attempting to reconnect to MQTT broker...")
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                self._mqtt_client.reconnect()
 | 
				
			||||||
 | 
					            except Exception as e:
 | 
				
			||||||
 | 
					                self._log.error(f"Failed to reconnect to MQTT broker: {str(e)}", exc_info=True)
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        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"""
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            # Update print state if available
 | 
				
			||||||
 | 
					            if 'print' in data:
 | 
				
			||||||
 | 
					                print_data = data['print']
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                # Update printer state
 | 
				
			||||||
 | 
					                if 'gcode_state' in print_data:
 | 
				
			||||||
 | 
					                    self._device_data.print_job.gcode_state = print_data['gcode_state']
 | 
				
			||||||
 | 
					                    self._log.debug(f"Updated printer state: {print_data['gcode_state']}")
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                # Process direct temperature values in print data
 | 
				
			||||||
 | 
					                if 'nozzle_temper' in print_data:
 | 
				
			||||||
 | 
					                    self._device_data.temperature.nozzle_temp = float(print_data['nozzle_temper'])
 | 
				
			||||||
 | 
					                if 'nozzle_target_temper' in print_data:
 | 
				
			||||||
 | 
					                    self._device_data.temperature.target_nozzle_temp = float(print_data['nozzle_target_temper'])
 | 
				
			||||||
 | 
					                if 'bed_temper' in print_data:
 | 
				
			||||||
 | 
					                    self._device_data.temperature.bed_temp = float(print_data['bed_temper'])
 | 
				
			||||||
 | 
					                if 'bed_target_temper' in print_data:
 | 
				
			||||||
 | 
					                    self._device_data.temperature.target_bed_temp = float(print_data['bed_target_temper'])
 | 
				
			||||||
 | 
					                if 'chamber_temper' in print_data:
 | 
				
			||||||
 | 
					                    self._device_data.temperature.chamber_temp = float(print_data['chamber_temper'])
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            # Process temperature section if available
 | 
				
			||||||
 | 
					            if 'temperature' in data:
 | 
				
			||||||
 | 
					                temp = self._device_data.temperature
 | 
				
			||||||
 | 
					                temp_data = data['temperature']
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                if 'nozzle_temp' in temp_data:
 | 
				
			||||||
 | 
					                    temp.nozzle_temp = float(temp_data['nozzle_temp'])
 | 
				
			||||||
 | 
					                if 'target_nozzle_temp' in temp_data:
 | 
				
			||||||
 | 
					                    temp.target_nozzle_temp = float(temp_data['target_nozzle_temp'])
 | 
				
			||||||
 | 
					                if 'bed_temp' in temp_data:
 | 
				
			||||||
 | 
					                    temp.bed_temp = float(temp_data['bed_temp'])
 | 
				
			||||||
 | 
					                if 'target_bed_temp' in temp_data:
 | 
				
			||||||
 | 
					                    temp.target_bed_temp = float(temp_data['target_bed_temp'])
 | 
				
			||||||
 | 
					                if 'chamber_temp' in temp_data:
 | 
				
			||||||
 | 
					                    temp.chamber_temp = float(temp_data['chamber_temp'])
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					            self._log.debug(f"Updated temperatures - Nozzle: {self._device_data.temperature.nozzle_temp}/" +
 | 
				
			||||||
 | 
					                          f"{self._device_data.temperature.target_nozzle_temp}, " +
 | 
				
			||||||
 | 
					                          f"Bed: {self._device_data.temperature.bed_temp}/" +
 | 
				
			||||||
 | 
					                          f"{self._device_data.temperature.target_bed_temp}, " +
 | 
				
			||||||
 | 
					                          f"Chamber: {self._device_data.temperature.chamber_temp}")
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            self._log.error(f"Error processing report message: {str(e)}", exc_info=True)
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					    def _process_hms_message(self, data):
 | 
				
			||||||
 | 
					        """Process HMS error messages"""
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            if 'hms' in data:
 | 
				
			||||||
 | 
					                error_count = 0
 | 
				
			||||||
 | 
					                hms_errors = {"Count": 0}
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                for error in data['hms']:
 | 
				
			||||||
 | 
					                    error_count += 1
 | 
				
			||||||
 | 
					                    if isinstance(error, dict) and 'msg' in error:
 | 
				
			||||||
 | 
					                        hms_errors[f"{error_count}-Error"] = error['msg']
 | 
				
			||||||
 | 
					                    else:
 | 
				
			||||||
 | 
					                        hms_errors[f"{error_count}-Error"] = str(error)
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                hms_errors["Count"] = error_count
 | 
				
			||||||
 | 
					                self._device_data.hms.errors = hms_errors
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                if error_count > 0:
 | 
				
			||||||
 | 
					                    self._log.info(f"Found {error_count} HMS errors")
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            self._log.error(f"Error processing HMS message: {str(e)}", exc_info=True)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def connect(self, callback=None):
 | 
				
			||||||
 | 
					        """Connect to MQTT broker"""
 | 
				
			||||||
 | 
					        if callback:
 | 
				
			||||||
 | 
					            self._callbacks['callback'] = callback
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            self._log.info(f"Connecting to MQTT broker {self._host}:{self._mqtt_port}")
 | 
				
			||||||
 | 
					            self._mqtt_client.connect(self._host, self._mqtt_port)
 | 
				
			||||||
 | 
					            self._mqtt_client.loop_start()
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            # Wait a bit for the connection to establish
 | 
				
			||||||
 | 
					            time.sleep(1)
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            # If not connected after waiting, try again
 | 
				
			||||||
 | 
					            if not self.connected:
 | 
				
			||||||
 | 
					                self._log.warning("Initial connection attempt failed, retrying...")
 | 
				
			||||||
 | 
					                try:
 | 
				
			||||||
 | 
					                    self._mqtt_client.reconnect()
 | 
				
			||||||
 | 
					                    time.sleep(2)  # Wait a bit longer for retry
 | 
				
			||||||
 | 
					                except Exception as e:
 | 
				
			||||||
 | 
					                    self._log.error(f"Reconnection failed: {str(e)}", exc_info=True)
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					            return self.connected
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            self._log.error(f"Failed to connect to MQTT broker: {str(e)}", exc_info=True)
 | 
				
			||||||
 | 
					            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:
 | 
				
			||||||
 | 
					            self._log.error("Cannot publish: Not connected to MQTT broker")
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            topic_base = f"device/{self._device_type}/{self._serial}"
 | 
				
			||||||
 | 
					            if 'print' in command and 'param' in command['print']:
 | 
				
			||||||
 | 
					                # Commands go to command topic
 | 
				
			||||||
 | 
					                message = json.dumps(command)
 | 
				
			||||||
 | 
					                result = self._mqtt_client.publish(f"{topic_base}/cmd", message)
 | 
				
			||||||
 | 
					                self._log.debug(f"Published command to {topic_base}/cmd: {message}")
 | 
				
			||||||
 | 
					                return result.rc == mqtt.MQTT_ERR_SUCCESS
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                self._log.warning(f"Invalid command format: {command}")
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            self._log.error(f"Failed to publish command: {str(e)}", exc_info=True)
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					        return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class BambuVirtualPrinter:
 | 
					class BambuVirtualPrinter:
 | 
				
			||||||
    gcode_executor = GCodeExecutor()
 | 
					    gcode_executor = GCodeExecutor()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -104,7 +331,10 @@ class BambuVirtualPrinter:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        self._serial_io.start()
 | 
					        self._serial_io.start()
 | 
				
			||||||
        self._printer_thread.start()
 | 
					        self._printer_thread.start()
 | 
				
			||||||
 | 
					        self._mqtt_client = None
 | 
				
			||||||
 | 
					        self._mqtt_connected = False
 | 
				
			||||||
 | 
					        self._bambu_client = None
 | 
				
			||||||
 | 
					        self._custom_connected = False
 | 
				
			||||||
        self._bambu_client: BambuClient = self._create_client_connection_async()
 | 
					        self._bambu_client: BambuClient = self._create_client_connection_async()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
@@ -127,10 +357,6 @@ class BambuVirtualPrinter:
 | 
				
			|||||||
    def current_print_job(self, value):
 | 
					    def current_print_job(self, value):
 | 
				
			||||||
        self._current_print_job = value
 | 
					        self._current_print_job = value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					 | 
				
			||||||
    def selected_file(self):
 | 
					 | 
				
			||||||
        return self._selected_project_file
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def has_selected_file(self):
 | 
					    def has_selected_file(self):
 | 
				
			||||||
        return self._selected_project_file is not None
 | 
					        return self._selected_project_file is not None
 | 
				
			||||||
@@ -165,40 +391,41 @@ class BambuVirtualPrinter:
 | 
				
			|||||||
    def project_files(self):
 | 
					    def project_files(self):
 | 
				
			||||||
        return self._project_files_view
 | 
					        return self._project_files_view
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def change_state(self, new_state: APrinterState):
 | 
					    @property
 | 
				
			||||||
        self._state_change_queue.put(new_state)
 | 
					    def is_connected(self):
 | 
				
			||||||
 | 
					        """Custom property to track connection status without modifying BambuClient directly"""
 | 
				
			||||||
 | 
					        connection_status = self._custom_connected and self._mqtt_connected
 | 
				
			||||||
 | 
					        self._log.debug(f"Connection status check: custom_connected={self._custom_connected}, mqtt_connected={self._mqtt_connected}, result={connection_status}")
 | 
				
			||||||
 | 
					        return connection_status
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def new_update(self, event_type):
 | 
					    def new_update(self, event_type):
 | 
				
			||||||
        if event_type == "event_hms_errors":
 | 
					        """Custom property to track connection status without modifying BambuClient directly"""
 | 
				
			||||||
 | 
					        if event_type == "event_printer_data_update":
 | 
				
			||||||
 | 
					            self._log.debug("Received event_printer_data_update")
 | 
				
			||||||
            self._update_hms_errors()
 | 
					            self._update_hms_errors()
 | 
				
			||||||
        elif event_type == "event_printer_data_update":
 | 
					            # Gib den aktuellen Status detaillierter aus
 | 
				
			||||||
            self._update_printer_info()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _update_printer_info(self):
 | 
					 | 
				
			||||||
            device_data = self.bambu_client.get_device()
 | 
					            device_data = self.bambu_client.get_device()
 | 
				
			||||||
            print_job_state = device_data.print_job.gcode_state
 | 
					            print_job_state = device_data.print_job.gcode_state
 | 
				
			||||||
        temperatures = device_data.temperature
 | 
					            self._log.debug(f"BambuClient printer state: {print_job_state}")
 | 
				
			||||||
 | 
					            self._log.debug(f"Current temperatures - Nozzle: {self._telemetry.temp[0]}/{self._telemetry.targetTemp[0]}, " +
 | 
				
			||||||
 | 
					                          f"Bed: {self._telemetry.bedTemp}/{self._telemetry.bedTargetTemp}, " +
 | 
				
			||||||
 | 
					                          f"Chamber: {self._telemetry.chamberTemp}")
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
        self.lastTempAt = time.monotonic()
 | 
					            # Process the printer state even if it's "unknown"
 | 
				
			||||||
        self._telemetry.temp[0] = temperatures.nozzle_temp
 | 
					            if print_job_state:
 | 
				
			||||||
        self._telemetry.targetTemp[0] = temperatures.target_nozzle_temp
 | 
					                self._process_print_state(print_job_state)
 | 
				
			||||||
        self._telemetry.bedTemp = temperatures.bed_temp
 | 
					 | 
				
			||||||
        self._telemetry.bedTargetTemp = temperatures.target_bed_temp
 | 
					 | 
				
			||||||
        self._telemetry.chamberTemp = temperatures.chamber_temp
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self._log.debug(f"Received printer state update: {print_job_state}")
 | 
					 | 
				
			||||||
        if (
 | 
					 | 
				
			||||||
            print_job_state == "IDLE"
 | 
					 | 
				
			||||||
            or print_job_state == "FINISH"
 | 
					 | 
				
			||||||
            or print_job_state == "FAILED"
 | 
					 | 
				
			||||||
        ):
 | 
					 | 
				
			||||||
            self.change_state(self._state_idle)
 | 
					 | 
				
			||||||
        elif print_job_state == "RUNNING":
 | 
					 | 
				
			||||||
            self.change_state(self._state_printing)
 | 
					 | 
				
			||||||
        elif print_job_state == "PAUSE":
 | 
					 | 
				
			||||||
            self.change_state(self._state_paused)
 | 
					 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
            self._log.warn(f"Unknown print job state: {print_job_state}")
 | 
					                self._log.debug("No printer state received, skipping state processing")
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					        elif event_type == "event_hms_errors":
 | 
				
			||||||
 | 
					            self._log.debug("Received event_hms_errors")
 | 
				
			||||||
 | 
					            bambu_printer = self.bambu_client.get_device()
 | 
				
			||||||
 | 
					            if bambu_printer.hms.errors != self._last_hms_errors:
 | 
				
			||||||
 | 
					                self._log.debug(f"HMS Error: {bambu_printer.hms.errors}")
 | 
				
			||||||
 | 
					                for n in range(1, bambu_printer.hms.errors["Count"] + 1):
 | 
				
			||||||
 | 
					                    error = bambu_printer.hms.errors[f"{n}-Error"].strip()
 | 
				
			||||||
 | 
					                    self.sendIO(f"// action:notification {error}")
 | 
				
			||||||
 | 
					                self._last_hms_errors = bambu_printer.hms.errors
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _update_hms_errors(self):
 | 
					    def _update_hms_errors(self):
 | 
				
			||||||
        bambu_printer = self.bambu_client.get_device()
 | 
					        bambu_printer = self.bambu_client.get_device()
 | 
				
			||||||
@@ -220,6 +447,390 @@ class BambuVirtualPrinter:
 | 
				
			|||||||
        self._log.debug(f"on connect called")
 | 
					        self._log.debug(f"on connect called")
 | 
				
			||||||
        return on_connect
 | 
					        return on_connect
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _on_mqtt_connect(self, client, userdata, flags, rc):
 | 
				
			||||||
 | 
					        self._log.debug(f"MQTT connected with result code: {rc}")
 | 
				
			||||||
 | 
					        if rc == 0:
 | 
				
			||||||
 | 
					            # Notify that we're connected
 | 
				
			||||||
 | 
					            self._mqtt_connected = True
 | 
				
			||||||
 | 
					            self._custom_connected = True
 | 
				
			||||||
 | 
					            # Subscribe to the relevant topics for the Bambu printer
 | 
				
			||||||
 | 
					            device_topic = f"device/{self._settings.get(['serial'])}/report"
 | 
				
			||||||
 | 
					            client.subscribe(device_topic)
 | 
				
			||||||
 | 
					            self._log.debug(f"Subscribed to topic: {device_topic}")
 | 
				
			||||||
 | 
					            self._log.info(f"MQTT connection successful. Connected: {self.is_connected}")
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            self._mqtt_connected = False
 | 
				
			||||||
 | 
					            self._custom_connected = False
 | 
				
			||||||
 | 
					            self._log.error(f"Failed to connect to MQTT broker with result code: {rc}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _on_mqtt_disconnect(self, client, userdata, rc):
 | 
				
			||||||
 | 
					        self._mqtt_connected = False
 | 
				
			||||||
 | 
					        self._custom_connected = False
 | 
				
			||||||
 | 
					        self._log.debug(f"MQTT disconnected with result code: {rc}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _on_mqtt_message(self, client, userdata, msg):
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            # Decode message and update client data
 | 
				
			||||||
 | 
					            payload = json.loads(msg.payload.decode('utf-8'))
 | 
				
			||||||
 | 
					            self._log.debug(f"MQTT message received on topic {msg.topic}: {list(payload.keys())}")
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            # Direkte Verarbeitung der Daten
 | 
				
			||||||
 | 
					            self._process_mqtt_payload(payload)
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            # Auch an Bambu Client weiterleiten
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                # Wenn der BambuClient eine eigene Verarbeitungsmethode hat, nutzen wir diese
 | 
				
			||||||
 | 
					                if hasattr(self._bambu_client, '_process_message') and callable(self._bambu_client._process_message):
 | 
				
			||||||
 | 
					                    self._bambu_client._process_message(msg.topic, payload)
 | 
				
			||||||
 | 
					                    self._log.debug("Message forwarded to pybambu via _process_message")
 | 
				
			||||||
 | 
					                elif hasattr(self._bambu_client, '_handle_mqtt_message') and callable(self._bambu_client._handle_mqtt_message):
 | 
				
			||||||
 | 
					                    self._bambu_client._handle_mqtt_message(client, userdata, msg)
 | 
				
			||||||
 | 
					                    self._log.debug("Message forwarded to pybambu via _handle_mqtt_message")
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    # Wenn keine Methode zur Verarbeitung verfügbar ist, aktualisieren wir die Datenstruktur manuell
 | 
				
			||||||
 | 
					                    self._log.debug("No message handler found in BambuClient, updating state manually")
 | 
				
			||||||
 | 
					                    self._update_bambu_client_state(payload)
 | 
				
			||||||
 | 
					            except Exception as e:
 | 
				
			||||||
 | 
					                self._log.error(f"Error forwarding to pybambu: {e}", exc_info=True)
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            self._log.error(f"Error processing MQTT message: {e}", exc_info=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _process_mqtt_payload(self, payload):
 | 
				
			||||||
 | 
					        """Zentrale Methode zur Verarbeitung von MQTT-Payloads"""
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            # Verarbeite print-Daten
 | 
				
			||||||
 | 
					            if 'print' in payload:
 | 
				
			||||||
 | 
					                print_data = payload['print']
 | 
				
			||||||
 | 
					                self._log.info(f"Processing print data with keys: {list(print_data.keys())}")
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                # Temperaturdaten direkt verarbeiten
 | 
				
			||||||
 | 
					                self._process_direct_temperature_data(print_data)
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                # Status verarbeiten
 | 
				
			||||||
 | 
					                self._process_print_state(print_data['gcode_state'])
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                # Fortschritt verarbeiten
 | 
				
			||||||
 | 
					                self._process_progress_data(print_data)
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                # Schicht-Informationen verarbeiten
 | 
				
			||||||
 | 
					                self._process_layer_data(print_data)
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                # Lüfter-Informationen verarbeiten
 | 
				
			||||||
 | 
					                self._process_fan_data(print_data)
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                # Datei-Informationen verarbeiten
 | 
				
			||||||
 | 
					                self._process_file_data(print_data)
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                # Trigger update
 | 
				
			||||||
 | 
					                self.new_update("event_printer_data_update")
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					            # Verarbeite info-Daten
 | 
				
			||||||
 | 
					            if 'info' in payload:
 | 
				
			||||||
 | 
					                info_data = payload['info']
 | 
				
			||||||
 | 
					                self._log.info(f"Processing info data with keys: {list(info_data.keys())}")
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                # HMS-Fehler verarbeiten
 | 
				
			||||||
 | 
					                self._process_hms_errors(info_data['hms'])
 | 
				
			||||||
 | 
					                self.new_update("event_hms_errors")
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            self._log.error(f"Error processing MQTT payload: {e}", exc_info=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _process_layer_data(self, print_data):
 | 
				
			||||||
 | 
					        """Verarbeitet Schicht-Informationen aus MQTT-Nachrichten"""
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            current_layer = None
 | 
				
			||||||
 | 
					            total_layers = None
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if 'layer_num' in print_data:
 | 
				
			||||||
 | 
					                current_layer = int(print_data['layer_num'])
 | 
				
			||||||
 | 
					                self._log.debug(f"Current layer: {current_layer}")
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					            if 'total_layer_num' in print_data:
 | 
				
			||||||
 | 
					                total_layers = int(print_data['total_layer_num'])
 | 
				
			||||||
 | 
					                self._log.debug(f"Total layers: {total_layers}")
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					            # Aktualisiere den PrintJob, wenn einer existiert
 | 
				
			||||||
 | 
					            if self.current_print_job is not None:
 | 
				
			||||||
 | 
					                if current_layer is not None:
 | 
				
			||||||
 | 
					                    self.current_print_job.current_layer = current_layer
 | 
				
			||||||
 | 
					                if total_layers is not None:
 | 
				
			||||||
 | 
					                    self.current_print_job.total_layers = total_layers
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					            # Aktualisiere auch die pybambu-Datenstruktur, wenn vorhanden
 | 
				
			||||||
 | 
					            if hasattr(self._bambu_client, 'device') and hasattr(self._bambu_client.device, 'print_job'):
 | 
				
			||||||
 | 
					                if current_layer is not None:
 | 
				
			||||||
 | 
					                    self._bambu_client.device.print_job.current_layer = current_layer
 | 
				
			||||||
 | 
					                if total_layers is not None:
 | 
				
			||||||
 | 
					                    self._bambu_client.device.print_job.total_layers = total_layers
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            self._log.error(f"Error processing layer data: {e}", exc_info=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _process_fan_data(self, print_data):
 | 
				
			||||||
 | 
					        """Verarbeitet Lüfterdaten aus MQTT-Nachrichten"""
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            # Verschiedene Lüfter-Typen
 | 
				
			||||||
 | 
					            fan_data = {}
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if 'heatbreak_fan_speed' in print_data:
 | 
				
			||||||
 | 
					                fan_data['heatbreak'] = int(print_data['heatbreak_fan_speed'])
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					            if 'cooling_fan_speed' in print_data:
 | 
				
			||||||
 | 
					                fan_data['cooling'] = int(print_data['cooling_fan_speed'])
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					            if 'big_fan1_speed' in print_data:
 | 
				
			||||||
 | 
					                fan_data['chamber1'] = int(print_data['big_fan1_speed'])
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					            if 'big_fan2_speed' in print_data:
 | 
				
			||||||
 | 
					                fan_data['chamber2'] = int(print_data['big_fan2_speed'])
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					            if fan_data:
 | 
				
			||||||
 | 
					                self._log.debug(f"Fan speeds: {fan_data}")
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					            # Aktualisiere die pybambu-Struktur, wenn vorhanden
 | 
				
			||||||
 | 
					            if hasattr(self._bambu_client, 'device') and hasattr(self._bambu_client.device, 'fan_speeds'):
 | 
				
			||||||
 | 
					                try:
 | 
				
			||||||
 | 
					                    for fan_type, speed in fan_data.items():
 | 
				
			||||||
 | 
					                        setattr(self._bambu_client.device.fan_speeds, fan_type, speed)
 | 
				
			||||||
 | 
					                except:
 | 
				
			||||||
 | 
					                    # Wenn fan_speeds nicht die erwarteten Attribute hat, erstellen wir sie
 | 
				
			||||||
 | 
					                    self._bambu_client.device.fan_speeds = type('', (), fan_data)()
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            self._log.error(f"Error processing fan data: {e}", exc_info=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _process_speed_data(self, print_data):
 | 
				
			||||||
 | 
					        """Verarbeitet Geschwindigkeitsdaten aus MQTT-Nachrichten"""
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            if 'spd_mag' in print_data:
 | 
				
			||||||
 | 
					                speed_magnitude = int(print_data['spd_mag'])
 | 
				
			||||||
 | 
					                self._log.debug(f"Speed magnitude: {speed_magnitude}%")
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					            if 'spd_lvl' in print_data:
 | 
				
			||||||
 | 
					                speed_level = int(print_data['spd_lvl'])
 | 
				
			||||||
 | 
					                self._log.debug(f"Speed level: {speed_level}")
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					            # Aktualisiere die pybambu-Struktur, wenn vorhanden
 | 
				
			||||||
 | 
					            if hasattr(self._bambu_client, 'device') and not hasattr(self._bambu_client.device, 'speed'):
 | 
				
			||||||
 | 
					                self._bambu_client.device.speed = type('', (), {})()
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					            if hasattr(self._bambu_client, 'device') and hasattr(self._bambu_client.device, 'speed'):
 | 
				
			||||||
 | 
					                if 'spd_mag' in print_data:
 | 
				
			||||||
 | 
					                    self._bambu_client.device.speed.magnitude = int(print_data['spd_mag'])
 | 
				
			||||||
 | 
					                if 'spd_lvl' in print_data:
 | 
				
			||||||
 | 
					                    self._bambu_client.device.speed.level = int(print_data['spd_lvl'])
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            self._log.error(f"Error processing speed data: {e}", exc_info=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _process_file_data(self, print_data):
 | 
				
			||||||
 | 
					        """Verarbeitet Dateiinformationen aus MQTT-Nachrichten"""
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            # Dateiname
 | 
				
			||||||
 | 
					            if 'gcode_file' in print_data and print_data['gcode_file']:
 | 
				
			||||||
 | 
					                filename = print_data['gcode_file']
 | 
				
			||||||
 | 
					                self._log.debug(f"Print file: {filename}")
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                # Aktualisiere den PrintJob, wenn einer existiert
 | 
				
			||||||
 | 
					                if self.current_print_job is not None:
 | 
				
			||||||
 | 
					                    self.current_print_job.gcode_file = filename
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                # Aktualisiere auch die pybambu-Datenstruktur
 | 
				
			||||||
 | 
					                if hasattr(self._bambu_client, 'device') and hasattr(self._bambu_client.device, 'print_job'):
 | 
				
			||||||
 | 
					                    self._bambu_client.device.print_job.gcode_file = filename
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Subtask Name (oft der Projektname)
 | 
				
			||||||
 | 
					            if 'subtask_name' in print_data and print_data['subtask_name']:
 | 
				
			||||||
 | 
					                subtask_name = print_data['subtask_name']
 | 
				
			||||||
 | 
					                self._log.debug(f"Subtask name: {subtask_name}")
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                # Aktualisiere den PrintJob, wenn einer existiert
 | 
				
			||||||
 | 
					                if self.current_print_job is not None:
 | 
				
			||||||
 | 
					                    self.current_print_job.subtask_name = subtask_name
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                # Aktualisiere auch die pybambu-Datenstruktur
 | 
				
			||||||
 | 
					                if hasattr(self._bambu_client, 'device') and hasattr(self._bambu_client.device, 'print_job'):
 | 
				
			||||||
 | 
					                    self._bambu_client.device.print_job.subtask_name = subtask_name
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            self._log.error(f"Error processing file data: {e}", exc_info=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _process_print_state(self, print_job_state):
 | 
				
			||||||
 | 
					        """Verarbeitet den Druckerstatus aus MQTT-Nachrichten"""
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            self._log.debug(f"Received printer state update: {print_job_state}")
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            # Erweitern der Statuserkennung - in Bambu können die Status auch kleingeschrieben sein
 | 
				
			||||||
 | 
					            # oder andere Werte haben als die, die wir erwarten
 | 
				
			||||||
 | 
					            print_job_state = print_job_state.upper() if print_job_state else "UNKNOWN"
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            # Explicitly handle "unknown" state (both upper and lowercase)
 | 
				
			||||||
 | 
					            if print_job_state in ["UNKNOWN", ""] or print_job_state.upper() == "UNKNOWN":
 | 
				
			||||||
 | 
					                self._log.debug("Detected 'unknown' printer state, trying to determine actual state")
 | 
				
			||||||
 | 
					                # Wenn wir keinen erkannten Status haben, versuchen wir ihn aus anderen Daten abzuleiten
 | 
				
			||||||
 | 
					                # Prüfe ob Druckfortschritt vorhanden ist
 | 
				
			||||||
 | 
					                if self.current_print_job and self.current_print_job.print_percentage > 0:
 | 
				
			||||||
 | 
					                    print_job_state = "RUNNING"
 | 
				
			||||||
 | 
					                    self._log.debug(f"Changed unknown state to RUNNING based on print progress from current_print_job")
 | 
				
			||||||
 | 
					                # Prüfe auf Temperaturen, die auf einen laufenden Druck hinweisen könnten
 | 
				
			||||||
 | 
					                elif self._telemetry.targetTemp[0] > 150 or self._telemetry.bedTargetTemp > 40:
 | 
				
			||||||
 | 
					                    print_job_state = "PREPARE"
 | 
				
			||||||
 | 
					                    self._log.debug(f"Changed unknown state to PREPARE based on target temperatures")
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    self._log.debug("Keeping state as IDLE since no indicators for print activity were found")
 | 
				
			||||||
 | 
					                    print_job_state = "IDLE"  # Default to IDLE if we can't determine state
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            # Status im PrintJob aktualisieren
 | 
				
			||||||
 | 
					            if self.current_print_job is None and print_job_state in ["RUNNING", "PREPARE", "PAUSE"]:
 | 
				
			||||||
 | 
					                # Wenn wir keinen PrintJob haben, aber ein Druck läuft, erstellen wir einen
 | 
				
			||||||
 | 
					                self._log.info(f"Creating new PrintJob for running print with state: {print_job_state}")
 | 
				
			||||||
 | 
					                self.current_print_job = PrintJob()
 | 
				
			||||||
 | 
					                self.current_print_job.gcode_state = print_job_state
 | 
				
			||||||
 | 
					            elif self.current_print_job is not None:
 | 
				
			||||||
 | 
					                self.current_print_job.gcode_state = print_job_state
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					            # Prüfe auf zusätzliche Indikatoren für einen aktiven Druck
 | 
				
			||||||
 | 
					            is_printing = False
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            # Check 1: Standard-Statuserkennung
 | 
				
			||||||
 | 
					            if print_job_state in ["RUNNING", "PREPARE"]:
 | 
				
			||||||
 | 
					                is_printing = True
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					            # Check 2: Druckfortschritt > 0 und < 100
 | 
				
			||||||
 | 
					            if hasattr(self._bambu_client, 'device') and hasattr(self._bambu_client.device, 'print_job'):
 | 
				
			||||||
 | 
					                if hasattr(self._bambu_client.device.print_job, 'mc_percent'):
 | 
				
			||||||
 | 
					                    progress = getattr(self._bambu_client.device.print_job, 'mc_percent', 0)
 | 
				
			||||||
 | 
					                    if progress > 0 and progress < 100:
 | 
				
			||||||
 | 
					                        is_printing = True
 | 
				
			||||||
 | 
					                        self._log.debug(f"Detected active printing based on progress: {progress}%")
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					            # Check 3: Temperaturen deuten auf aktiven Druck hin
 | 
				
			||||||
 | 
					            if self._telemetry.temp[0] > 170 and self._telemetry.bedTemp > 40:
 | 
				
			||||||
 | 
					                # Hohe aktuelle Temperaturen deuten auf einen laufenden Druck hin
 | 
				
			||||||
 | 
					                is_printing = True
 | 
				
			||||||
 | 
					                self._log.debug(f"Detected potential printing based on actual temperatures: " +
 | 
				
			||||||
 | 
					                             f"Nozzle={self._telemetry.temp[0]}, Bed={self._telemetry.bedTemp}")
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					            # Statusänderung in den Zustandsautomaten übertragen basierend auf allen Checks
 | 
				
			||||||
 | 
					            if print_job_state in ["IDLE", "FINISH", "FAILED"] and not is_printing:
 | 
				
			||||||
 | 
					                self._log.debug(f"Changing to IDLE state based on status: {print_job_state} and is_printing={is_printing}")
 | 
				
			||||||
 | 
					                self.change_state(self._state_idle)
 | 
				
			||||||
 | 
					            elif print_job_state in ["RUNNING", "PREPARE"] or is_printing:
 | 
				
			||||||
 | 
					                self._log.debug(f"Changing to PRINTING state based on status: {print_job_state} and is_printing={is_printing}")
 | 
				
			||||||
 | 
					                self.change_state(self._state_printing)
 | 
				
			||||||
 | 
					            elif print_job_state == "PAUSE":
 | 
				
			||||||
 | 
					                self._log.debug(f"Changing to PAUSED state based on status: {print_job_state}")
 | 
				
			||||||
 | 
					                self.change_state(self._state_paused)
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                self._log.warn(f"Unknown print job state: {print_job_state}")
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            # Aktualisiere auch die pybambu-Datenstruktur
 | 
				
			||||||
 | 
					            if hasattr(self._bambu_client, 'device') and hasattr(self._bambu_client.device, 'print_job'):
 | 
				
			||||||
 | 
					                self._bambu_client.device.print_job.gcode_state = print_job_state
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            self._log.error(f"Error processing print state: {e}", exc_info=True)
 | 
				
			||||||
 | 
					            # Default to a safe state in case of errors
 | 
				
			||||||
 | 
					            self.change_state(self._state_idle)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _process_direct_temperature_data(self, print_data):
 | 
				
			||||||
 | 
					        """Verarbeitet Temperaturdaten direkt aus dem print-Objekt"""
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            # Extruder Temperatur - direkt aus den Feldern
 | 
				
			||||||
 | 
					            if 'nozzle_temper' in print_data:
 | 
				
			||||||
 | 
					                self._telemetry.temp[0] = float(print_data['nozzle_temper'])
 | 
				
			||||||
 | 
					                self._log.debug(f"Updated nozzle temperature: {self._telemetry.temp[0]}")
 | 
				
			||||||
 | 
					            if 'nozzle_target_temper' in print_data:
 | 
				
			||||||
 | 
					                self._telemetry.targetTemp[0] = float(print_data['nozzle_target_temper'])
 | 
				
			||||||
 | 
					                self._log.debug(f"Updated nozzle target: {self._telemetry.targetTemp[0]}")
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					            # Bett Temperatur
 | 
				
			||||||
 | 
					            if 'bed_temper' in print_data:
 | 
				
			||||||
 | 
					                self._telemetry.bedTemp = float(print_data['bed_temper'])
 | 
				
			||||||
 | 
					                self._log.debug(f"Updated bed temperature: {self._telemetry.bedTemp}")
 | 
				
			||||||
 | 
					            if 'bed_target_temper' in print_data:
 | 
				
			||||||
 | 
					                self._telemetry.bedTargetTemp = float(print_data['bed_target_temper'])
 | 
				
			||||||
 | 
					                self._log.debug(f"Updated bed target: {self._telemetry.bedTargetTemp}")
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					            # Kammer Temperatur
 | 
				
			||||||
 | 
					            if 'chamber_temper' in print_data:
 | 
				
			||||||
 | 
					                self._telemetry.chamberTemp = float(print_data['chamber_temper'])
 | 
				
			||||||
 | 
					                self._log.debug(f"Updated chamber temperature: {self._telemetry.chamberTemp}")
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					            # Log der aktualisierten Temperaturen
 | 
				
			||||||
 | 
					            self._log.debug(f"Current temperatures - Nozzle: {self._telemetry.temp[0]}/{self._telemetry.targetTemp[0]}, " +
 | 
				
			||||||
 | 
					                          f"Bed: {self._telemetry.bedTemp}/{self._telemetry.bedTargetTemp}, " +
 | 
				
			||||||
 | 
					                          f"Chamber: {self._telemetry.chamberTemp}")
 | 
				
			||||||
 | 
					                          
 | 
				
			||||||
 | 
					            # Auch im BambuClient aktualisieren
 | 
				
			||||||
 | 
					            if hasattr(self._bambu_client, 'device') and hasattr(self._bambu_client.device, 'temperature'):
 | 
				
			||||||
 | 
					                try:
 | 
				
			||||||
 | 
					                    temp_obj = self._bambu_client.device.temperature
 | 
				
			||||||
 | 
					                    if 'nozzle_temper' in print_data:
 | 
				
			||||||
 | 
					                        temp_obj.nozzle_temp = float(print_data['nozzle_temper'])
 | 
				
			||||||
 | 
					                    if 'nozzle_target_temper' in print_data:
 | 
				
			||||||
 | 
					                        temp_obj.target_nozzle_temp = float(print_data['nozzle_target_temper'])
 | 
				
			||||||
 | 
					                    if 'bed_temper' in print_data:
 | 
				
			||||||
 | 
					                        temp_obj.bed_temp = float(print_data['bed_temper'])
 | 
				
			||||||
 | 
					                    if 'bed_target_temper' in print_data:
 | 
				
			||||||
 | 
					                        temp_obj.target_bed_temp = float(print_data['bed_target_temper'])
 | 
				
			||||||
 | 
					                    if 'chamber_temper' in print_data:
 | 
				
			||||||
 | 
					                        temp_obj.chamber_temp = float(print_data['chamber_temper'])
 | 
				
			||||||
 | 
					                except Exception as e:
 | 
				
			||||||
 | 
					                    self._log.error(f"Error updating BambuClient temperature: {e}", exc_info=True)
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            self._log.error(f"Error processing temperature data: {e}", exc_info=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _process_progress_data(self, print_data):
 | 
				
			||||||
 | 
					        """Verarbeitet Fortschrittsdaten aus MQTT-Nachrichten"""
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            progress = -1
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if 'mc_percent' in print_data:
 | 
				
			||||||
 | 
					                progress = int(print_data['mc_percent'])
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					            remaining_time = 0
 | 
				
			||||||
 | 
					            if 'mc_remaining_time' in print_data:
 | 
				
			||||||
 | 
					                remaining_time = int(print_data['mc_remaining_time'])
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					            # Aktualisiere den PrintJob, wenn einer existiert
 | 
				
			||||||
 | 
					            if self.current_print_job is not None:
 | 
				
			||||||
 | 
					                self.current_print_job.print_percentage = progress
 | 
				
			||||||
 | 
					                self.current_print_job.remaining_time = remaining_time
 | 
				
			||||||
 | 
					                self._log.debug(f"Updated print progress: {progress}%, remaining: {remaining_time}s")
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					            # Aktualisiere auch die pybambu-Datenstruktur
 | 
				
			||||||
 | 
					            if hasattr(self._bambu_client, 'device') and hasattr(self._bambu_client.device, 'print_job'):
 | 
				
			||||||
 | 
					                self._bambu_client.device.print_job.mc_percent = progress
 | 
				
			||||||
 | 
					                self._bambu_client.device.print_job.mc_remaining_time = remaining_time
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            self._log.error(f"Error processing progress data: {e}", exc_info=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _update_bambu_client_state(self, payload):
 | 
				
			||||||
 | 
					        """Aktualisiert die internen Zustände des BambuClient"""
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            if not hasattr(self._bambu_client, 'device'):
 | 
				
			||||||
 | 
					                self._log.debug("BambuClient has no device attribute, initializing")
 | 
				
			||||||
 | 
					                return
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if 'print' in payload:
 | 
				
			||||||
 | 
					                print_data = payload['print']
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                # Temperatur aktualisieren
 | 
				
			||||||
 | 
					                if 'temperature' in print_data and hasattr(self._bambu_client.device, 'temperature'):
 | 
				
			||||||
 | 
					                    temp_obj = self._bambu_client.device.temperature
 | 
				
			||||||
 | 
					                    temp_data = print_data['temperature']
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    # Direkte Zuweisung der Temperaturen
 | 
				
			||||||
 | 
					                    if 'nozzle_temp' in temp_data:
 | 
				
			||||||
 | 
					                        temp_obj.nozzle_temp = float(temp_data['nozzle_temp'])
 | 
				
			||||||
 | 
					                    if 'target_nozzle_temp' in temp_data:
 | 
				
			||||||
 | 
					                        temp_obj.target_nozzle_temp = float(temp_data['target_nozzle_temp'])
 | 
				
			||||||
 | 
					                    if 'bed_temp' in temp_data:
 | 
				
			||||||
 | 
					                        temp_obj.bed_temp = float(temp_data['bed_temp'])
 | 
				
			||||||
 | 
					                    if 'target_bed_temp' in temp_data:
 | 
				
			||||||
 | 
					                        temp_obj.target_bed_temp = float(temp_data['target_bed_temp'])
 | 
				
			||||||
 | 
					                    if 'chamber_temp' in temp_data:
 | 
				
			||||||
 | 
					                        temp_obj.chamber_temp = float(temp_data['chamber_temp'])
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            self._log.error(f"Error updating BambuClient state: {e}", exc_info=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _create_client_connection_async(self):
 | 
					    def _create_client_connection_async(self):
 | 
				
			||||||
        self._create_client_connection()
 | 
					        self._create_client_connection()
 | 
				
			||||||
        if self._bambu_client is None:
 | 
					        if self._bambu_client is None:
 | 
				
			||||||
@@ -230,16 +841,36 @@ 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)
 | 
				
			||||||
   
 | 
					   
 | 
				
			||||||
 | 
					        # Check if we should use MQTT bridge mode
 | 
				
			||||||
 | 
					        use_mqtt_bridge = self._settings.get_boolean(["use_mqtt_bridge"])
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        if use_mqtt_bridge:
 | 
				
			||||||
 | 
					            self._log.debug(
 | 
				
			||||||
 | 
					                f"connecting via mqtt bridge: {self._settings.get(['mqtt_host'])}:{self._settings.get(['mqtt_port'])}"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            self._log.debug(
 | 
				
			||||||
 | 
					                f"Creating MQTT bridge client: {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(
 | 
					            self._log.debug(
 | 
				
			||||||
                f"connecting via local mqtt: {self._settings.get_boolean(['local_mqtt'])}"
 | 
					                f"connecting via local mqtt: {self._settings.get_boolean(['local_mqtt'])}"
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					            self._log.debug(
 | 
				
			||||||
 | 
					                f"Creating standard BambuClient with 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"]),
 | 
				
			||||||
@@ -255,13 +886,59 @@ class BambuVirtualPrinter:
 | 
				
			|||||||
                email=self._settings.get(["email"]),
 | 
					                email=self._settings.get(["email"]),
 | 
				
			||||||
                auth_token=self._settings.get(["auth_token"]),
 | 
					                auth_token=self._settings.get(["auth_token"]),
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
        bambu_client.on_disconnect = self.on_disconnect(bambu_client.on_disconnect)
 | 
					        bambu_client.on_disconnect = self.on_disconnect(bambu_client.on_disconnect)
 | 
				
			||||||
        bambu_client.on_connect = self.on_connect(bambu_client.on_connect)
 | 
					        bambu_client.on_connect = self.on_connect(bambu_client.on_connect)
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # Add more robust connection retry logic
 | 
				
			||||||
 | 
					        connection_attempts = 0
 | 
				
			||||||
 | 
					        max_attempts = 3
 | 
				
			||||||
 | 
					        retry_delay = 2
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        while connection_attempts < max_attempts:
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                self._log.info(f"Connection attempt {connection_attempts + 1}/{max_attempts}...")
 | 
				
			||||||
                bambu_client.connect(callback=self.new_update)
 | 
					                bambu_client.connect(callback=self.new_update)
 | 
				
			||||||
        self._log.info(f"bambu connection status: {bambu_client.connected}")
 | 
					                
 | 
				
			||||||
 | 
					                # Wait a moment to verify connection
 | 
				
			||||||
 | 
					                time.sleep(retry_delay)
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                if bambu_client.connected:
 | 
				
			||||||
 | 
					                    self._log.info(f"Bambu connection successful: {bambu_client.connected}")
 | 
				
			||||||
 | 
					                    break
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                self._log.warning("Connection attempt failed, retrying...")
 | 
				
			||||||
 | 
					                connection_attempts += 1
 | 
				
			||||||
 | 
					                time.sleep(retry_delay)
 | 
				
			||||||
 | 
					            except Exception as e:
 | 
				
			||||||
 | 
					                self._log.error(f"Error during connection attempt {connection_attempts + 1}: {str(e)}", exc_info=True)
 | 
				
			||||||
 | 
					                connection_attempts += 1
 | 
				
			||||||
 | 
					                if connection_attempts < max_attempts:
 | 
				
			||||||
 | 
					                    time.sleep(retry_delay)
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
        self.sendOk()
 | 
					        self.sendOk()
 | 
				
			||||||
        self._bambu_client = bambu_client
 | 
					        self._bambu_client = bambu_client
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self._log.info(f"Custom connection status: {self.is_connected}")
 | 
				
			||||||
 | 
					        self.sendOk()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def publish_mqtt(self, topic, payload):
 | 
				
			||||||
 | 
					        """Publish a message to the MQTT broker"""
 | 
				
			||||||
 | 
					        if self._mqtt_client and self._mqtt_connected:
 | 
				
			||||||
 | 
					            return self._mqtt_client.publish(topic, json.dumps(payload))
 | 
				
			||||||
 | 
					        return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Override BambuClient's publish method to use our MQTT client
 | 
				
			||||||
 | 
					    def publish(self, command):
 | 
				
			||||||
 | 
					        """Publish a command using our MQTT client"""
 | 
				
			||||||
 | 
					        if not self.is_connected:
 | 
				
			||||||
 | 
					            self._log.error("Cannot publish command: MQTT not connected")
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        serial = self._settings.get(["serial"])
 | 
				
			||||||
 | 
					        topic = f"device/{serial}/request"
 | 
				
			||||||
 | 
					        return self.publish_mqtt(topic, command)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __str__(self):
 | 
					    def __str__(self):
 | 
				
			||||||
        return "BAMBU(read_timeout={read_timeout},write_timeout={write_timeout},options={options})".format(
 | 
					        return "BAMBU(read_timeout={read_timeout},write_timeout={write_timeout},options={options})".format(
 | 
				
			||||||
            read_timeout=self.timeout,
 | 
					            read_timeout=self.timeout,
 | 
				
			||||||
@@ -269,6 +946,10 @@ class BambuVirtualPrinter:
 | 
				
			|||||||
            options={
 | 
					            options={
 | 
				
			||||||
                "device_type": self._settings.get(["device_type"]),
 | 
					                "device_type": self._settings.get(["device_type"]),
 | 
				
			||||||
                "host": self._settings.get(["host"]),
 | 
					                "host": self._settings.get(["host"]),
 | 
				
			||||||
 | 
					                "local_mqtt": self._settings.get_boolean(["local_mqtt"]),
 | 
				
			||||||
 | 
					                "region": self._settings.get(["region"]),
 | 
				
			||||||
 | 
					                "email": self._settings.get(["email"]),
 | 
				
			||||||
 | 
					                "auth_token": self._settings.get(["auth_token"]),
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -284,7 +965,6 @@ class BambuVirtualPrinter:
 | 
				
			|||||||
            if self._settings.get_boolean(["simulateReset"]):
 | 
					            if self._settings.get_boolean(["simulateReset"]):
 | 
				
			||||||
                for item in self._settings.get(["resetLines"]):
 | 
					                for item in self._settings.get(["resetLines"]):
 | 
				
			||||||
                    self.sendIO(item + "\n")
 | 
					                    self.sendIO(item + "\n")
 | 
				
			||||||
 | 
					 | 
				
			||||||
            self._serial_io.reset()
 | 
					            self._serial_io.reset()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def write(self, data: bytes) -> int:
 | 
					    def write(self, data: bytes) -> int:
 | 
				
			||||||
@@ -316,6 +996,7 @@ class BambuVirtualPrinter:
 | 
				
			|||||||
        file_info = self._project_files_view.get_file_by_stem(
 | 
					        file_info = self._project_files_view.get_file_by_stem(
 | 
				
			||||||
            file_path, [".gcode", ".3mf"]
 | 
					            file_path, [".gcode", ".3mf"]
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (
 | 
					        if (
 | 
				
			||||||
            self._selected_project_file is not None
 | 
					            self._selected_project_file is not None
 | 
				
			||||||
            and file_info is not None
 | 
					            and file_info is not None
 | 
				
			||||||
@@ -415,7 +1096,6 @@ class BambuVirtualPrinter:
 | 
				
			|||||||
                self.start_continuous_temp_report(interval)
 | 
					                self.start_continuous_temp_report(interval)
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                self.stop_continuous_temp_report()
 | 
					                self.stop_continuous_temp_report()
 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.report_print_job_status()
 | 
					        self.report_print_job_status()
 | 
				
			||||||
        return True
 | 
					        return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -516,7 +1196,7 @@ class BambuVirtualPrinter:
 | 
				
			|||||||
                acc_mag = acc_mag_scaled(speed_percentage)
 | 
					                acc_mag = acc_mag_scaled(speed_percentage)
 | 
				
			||||||
                feed = feed_rate_scaled(speed_percentage)
 | 
					                feed = feed_rate_scaled(speed_percentage)
 | 
				
			||||||
                # speed_level = 1.539 * (acc_mag**2) - 0.7032 * acc_mag + 4.0834
 | 
					                # speed_level = 1.539 * (acc_mag**2) - 0.7032 * acc_mag + 4.0834
 | 
				
			||||||
                return f"M204.2 K{acc_mag:.2f}\nM220 K{feed:.2f}\nM73.2 R{speed_frac:.2f}\n" # M1002 set_gcode_claim_speed_level ${speed_level:.0f}\n
 | 
					                return f"M204.2 K{acc_mag:.2f}\nM220 K{feed:.2f}\nM73.2 R{speed_frac:.2f}\n" # M1002 set_gcode_claim_speed_level ${speed_level:.0f}\n"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            speed_command = speed_adjust(percent)
 | 
					            speed_command = speed_adjust(percent)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -533,10 +1213,10 @@ class BambuVirtualPrinter:
 | 
				
			|||||||
            return
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # post gcode to printer otherwise
 | 
					        # post gcode to printer otherwise
 | 
				
			||||||
        if self.bambu_client.connected:
 | 
					        if self.is_connected:
 | 
				
			||||||
            GCODE_COMMAND = commands.SEND_GCODE_TEMPLATE
 | 
					            GCODE_COMMAND = commands.SEND_GCODE_TEMPLATE
 | 
				
			||||||
            GCODE_COMMAND["print"]["param"] = full_command + "\n"
 | 
					            GCODE_COMMAND["print"]["param"] = full_command + "\n"
 | 
				
			||||||
            if self.bambu_client.publish(GCODE_COMMAND):
 | 
					            if self.publish(GCODE_COMMAND):
 | 
				
			||||||
                self._log.info("command sent successfully")
 | 
					                self._log.info("command sent successfully")
 | 
				
			||||||
                self.sendOk()
 | 
					                self.sendOk()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -625,20 +1305,59 @@ class BambuVirtualPrinter:
 | 
				
			|||||||
        return output
 | 
					        return output
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _processTemperatureQuery(self) -> bool:
 | 
					    def _processTemperatureQuery(self) -> bool:
 | 
				
			||||||
        # includeOk = not self._okBeforeCommandOutput
 | 
					        # Debug-Log hinzufügen, um zu prüfen, ob die Methode aufgerufen wird
 | 
				
			||||||
        if self.bambu_client.connected:
 | 
					        self._log.debug(f"Processing temperature query - connected: {self.is_connected}")
 | 
				
			||||||
 | 
					        # Aktuelle Temperaturdaten ausgeben
 | 
				
			||||||
 | 
					        self._log.debug(f"Current temperature data: Nozzle={self._telemetry.temp[0]}/{self._telemetry.targetTemp[0]}, " +
 | 
				
			||||||
 | 
					                      f"Bed={self._telemetry.bedTemp}/{self._telemetry.bedTargetTemp}")
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # Temperaturmeldung erzeugen und senden, unabhängig von Connected-Status
 | 
				
			||||||
        output = self._create_temperature_message()
 | 
					        output = self._create_temperature_message()
 | 
				
			||||||
 | 
					        self._log.debug(f"Sending temperature message: {output.strip()}")
 | 
				
			||||||
        self.sendIO(output)
 | 
					        self.sendIO(output)
 | 
				
			||||||
        return True
 | 
					        return True
 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            return False
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def close(self):
 | 
					    def close(self):
 | 
				
			||||||
        if self.bambu_client.connected:
 | 
					        """Safely close all connections."""
 | 
				
			||||||
            self.bambu_client.disconnect()
 | 
					        try:
 | 
				
			||||||
 | 
					            # Log that we're starting to close connections
 | 
				
			||||||
 | 
					            self._log.debug("Starting to close all connections...")
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if self._mqtt_client and self._mqtt_connected:
 | 
				
			||||||
 | 
					                self._log.debug("Stopping MQTT client loop and disconnecting...")
 | 
				
			||||||
 | 
					                self._mqtt_client.loop_stop()
 | 
				
			||||||
 | 
					                self._mqtt_client.disconnect()
 | 
				
			||||||
 | 
					                self._mqtt_connected = False
 | 
				
			||||||
 | 
					                self._custom_connected = False
 | 
				
			||||||
 | 
					                self._log.debug("MQTT client disconnected")
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					            # Sicherstellen, dass wir keinen AttributError bekommen, wenn wir den BambuClient trennen
 | 
				
			||||||
 | 
					            if self._bambu_client:
 | 
				
			||||||
 | 
					                self._log.debug("Disconnecting BambuClient...")
 | 
				
			||||||
 | 
					                try:
 | 
				
			||||||
 | 
					                    self._bambu_client.disconnect()
 | 
				
			||||||
 | 
					                    self._log.debug("BambuClient disconnected successfully")
 | 
				
			||||||
 | 
					                except AttributeError:
 | 
				
			||||||
 | 
					                    # BambuClient hat keinen client-Attribut oder die disconnect-Methode funktioniert nicht wie erwartet
 | 
				
			||||||
 | 
					                    self._log.warning("BambuClient disconnect failed, cleaning up manually")
 | 
				
			||||||
 | 
					                    # Manuell aufräumen
 | 
				
			||||||
 | 
					                    if hasattr(self._bambu_client, '_mqtt_client') and self._bambu_client._mqtt_client:
 | 
				
			||||||
 | 
					                        try:
 | 
				
			||||||
 | 
					                            self._log.debug("Manually stopping BambuClient's MQTT client...")
 | 
				
			||||||
 | 
					                            self._bambu_client._mqtt_client.loop_stop()
 | 
				
			||||||
 | 
					                            self._bambu_client._mqtt_client.disconnect()
 | 
				
			||||||
 | 
					                            self._log.debug("BambuClient's MQTT client manually disconnected")
 | 
				
			||||||
 | 
					                        except Exception as ex:
 | 
				
			||||||
 | 
					                            self._log.error(f"Error during manual MQTT client cleanup: {str(ex)}")
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            self._log.error(f"Error during close: {e}", exc_info=True)
 | 
				
			||||||
 | 
					        finally:
 | 
				
			||||||
 | 
					            # Immer in einen sicheren Zustand zurückkehren
 | 
				
			||||||
 | 
					            self._log.debug("Final cleanup in close() method")
 | 
				
			||||||
            self.change_state(self._state_idle)
 | 
					            self.change_state(self._state_idle)
 | 
				
			||||||
            self._serial_io.close()
 | 
					            self._serial_io.close()
 | 
				
			||||||
            self.stop()
 | 
					            self.stop()
 | 
				
			||||||
 | 
					            self._log.debug("Connection cleanup completed")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def stop(self):
 | 
					    def stop(self):
 | 
				
			||||||
        self._running = False
 | 
					        self._running = False
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
from __future__ import annotations
 | 
					from __future__ import annotations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from typing import TYPE_CHECKING, Callable
 | 
					from typing import TYPE_CHECKING, Callable, List, Optional
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if TYPE_CHECKING:
 | 
					if TYPE_CHECKING:
 | 
				
			||||||
    from octoprint_bambu_printer.printer.file_system.remote_sd_card_file_list import (
 | 
					    from octoprint_bambu_printer.printer.file_system.remote_sd_card_file_list import (
 | 
				
			||||||
@@ -12,83 +12,59 @@ from pathlib import Path
 | 
				
			|||||||
from octoprint_bambu_printer.printer.file_system.file_info import FileInfo
 | 
					from octoprint_bambu_printer.printer.file_system.file_info import FileInfo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@dataclass
 | 
					 | 
				
			||||||
class CachedFileView:
 | 
					class CachedFileView:
 | 
				
			||||||
    file_system: RemoteSDCardFileList
 | 
					    def __init__(
 | 
				
			||||||
    folder_view: dict[tuple[str, str | list[str] | None], None] = field(
 | 
					        self, file_system, on_update: Optional[Callable] = None, base_path: str = ""
 | 
				
			||||||
        default_factory=dict
 | 
					    ):
 | 
				
			||||||
    )  # dict preserves order, but set does not. We use only dict keys as storage
 | 
					        self._filters = []
 | 
				
			||||||
    on_update: Callable[[], None] | None = None
 | 
					        self._file_system = file_system
 | 
				
			||||||
 | 
					        self._base_path = base_path
 | 
				
			||||||
 | 
					        self._update_complete_callback = on_update
 | 
				
			||||||
 | 
					        self._file_info_cache = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __post_init__(self):
 | 
					    def with_filter(self, path: str, extension: str):
 | 
				
			||||||
        self._file_alias_cache: dict[str, str] = {}
 | 
					        self._filters.append({"path": path, "extension": extension})
 | 
				
			||||||
        self._file_data_cache: dict[str, FileInfo] = {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def with_filter(
 | 
					 | 
				
			||||||
        self, folder: str, extensions: str | list[str] | None = None
 | 
					 | 
				
			||||||
    ) -> "CachedFileView":
 | 
					 | 
				
			||||||
        self.folder_view[(folder, extensions)] = None
 | 
					 | 
				
			||||||
        return self
 | 
					        return self
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def list_all_views(self):
 | 
					    def update(self) -> None:
 | 
				
			||||||
        existing_files: list[str] = []
 | 
					        try:
 | 
				
			||||||
        result: list[FileInfo] = []
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        with self.file_system.get_ftps_client() as ftp:
 | 
					 | 
				
			||||||
            for filter in self.folder_view.keys():
 | 
					 | 
				
			||||||
                result.extend(self.file_system.list_files(*filter, ftp, existing_files))
 | 
					 | 
				
			||||||
        return result
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def update(self):
 | 
					 | 
				
			||||||
            file_info_list = self.list_all_views()
 | 
					            file_info_list = self.list_all_views()
 | 
				
			||||||
        self._update_file_list_cache(file_info_list)
 | 
					            self._file_info_cache = file_info_list
 | 
				
			||||||
        if self.on_update:
 | 
					 | 
				
			||||||
            self.on_update()
 | 
					 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
    def _update_file_list_cache(self, files: list[FileInfo]):
 | 
					            # Rufe Callback auf, wenn vorhanden
 | 
				
			||||||
        self._file_alias_cache = {info.dosname: info.path.as_posix() for info in files}
 | 
					            if self._update_complete_callback is not None:
 | 
				
			||||||
        self._file_data_cache = {info.path.as_posix(): info for info in files}
 | 
					                self._update_complete_callback()
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            import logging
 | 
				
			||||||
 | 
					            logging.getLogger("octoprint.plugins.bambu_printer.BambuPrinter").error(
 | 
				
			||||||
 | 
					                f"Error updating file list: {e}", exc_info=True
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_all_info(self):
 | 
					    def list_all_views(self) -> List[FileInfo]:
 | 
				
			||||||
        self.update()
 | 
					        # Verwende die Mock-Implementation von get_file_list statt FTPS
 | 
				
			||||||
        return self.get_all_cached_info()
 | 
					        try:
 | 
				
			||||||
 | 
					            return self._file_system.get_file_list(self._base_path)
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            import logging
 | 
				
			||||||
 | 
					            logging.getLogger("octoprint.plugins.bambu_printer.BambuPrinter").error(
 | 
				
			||||||
 | 
					                f"Error listing files: {e}", exc_info=True
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            return []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_all_cached_info(self):
 | 
					    def get_all_cached_info(self) -> List[FileInfo]:
 | 
				
			||||||
        return list(self._file_data_cache.values())
 | 
					        return self._file_info_cache
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_file_data(self, file_path: str | Path) -> FileInfo | None:
 | 
					    def get_file_by_stem(self, file_stem: str, extensions: list[str]) -> FileInfo | None:
 | 
				
			||||||
        file_data = self.get_file_data_cached(file_path)
 | 
					        """Get file info by file name without extension"""
 | 
				
			||||||
        if file_data is None:
 | 
					        for file_info in self._file_info_cache:
 | 
				
			||||||
            self.update()
 | 
					            for extension in extensions:
 | 
				
			||||||
            file_data = self.get_file_data_cached(file_path)
 | 
					                if file_info.file_name.lower().startswith(f"{file_stem.lower()}{extension}"):
 | 
				
			||||||
        return file_data
 | 
					                    return file_info
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_file_data_cached(self, file_path: str | Path) -> FileInfo | None:
 | 
					 | 
				
			||||||
        if isinstance(file_path, str):
 | 
					 | 
				
			||||||
            file_path = Path(file_path).as_posix().strip("/")
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            file_path = file_path.as_posix().strip("/")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if file_path not in self._file_data_cache:
 | 
					 | 
				
			||||||
            file_path = self._file_alias_cache.get(file_path, file_path)
 | 
					 | 
				
			||||||
        return self._file_data_cache.get(file_path, None)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def get_file_by_stem(self, file_stem: str, allowed_suffixes: list[str]):
 | 
					 | 
				
			||||||
        if file_stem == "":
 | 
					 | 
				
			||||||
        return None
 | 
					        return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        file_stem = Path(file_stem).with_suffix("").stem
 | 
					    def get_file_data(self, file_path: str) -> FileInfo | None:
 | 
				
			||||||
        file_data = self._get_file_by_stem_cached(file_stem, allowed_suffixes)
 | 
					        for file_info in self._file_info_cache:
 | 
				
			||||||
        if file_data is None:
 | 
					            if file_info.path.lower() == file_path.lower() or file_info.file_name.lower() == file_path.lower():
 | 
				
			||||||
            self.update()
 | 
					                return file_info
 | 
				
			||||||
            file_data = self._get_file_by_stem_cached(file_stem, allowed_suffixes)
 | 
					 | 
				
			||||||
        return file_data
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _get_file_by_stem_cached(self, file_stem: str, allowed_suffixes: list[str]):
 | 
					 | 
				
			||||||
        for file_path_str in list(self._file_data_cache.keys()) + list(self._file_alias_cache.keys()):
 | 
					 | 
				
			||||||
            file_path = Path(file_path_str)
 | 
					 | 
				
			||||||
            if file_stem == file_path.with_suffix("").stem and all(
 | 
					 | 
				
			||||||
                suffix in allowed_suffixes for suffix in file_path.suffixes
 | 
					 | 
				
			||||||
            ):
 | 
					 | 
				
			||||||
                return self.get_file_data_cached(file_path)
 | 
					 | 
				
			||||||
        return None
 | 
					        return None
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,7 @@ from __future__ import annotations
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import datetime
 | 
					import datetime
 | 
				
			||||||
from pathlib import Path
 | 
					from pathlib import Path
 | 
				
			||||||
from typing import Iterable, Iterator
 | 
					from typing import Iterable, Iterator, List
 | 
				
			||||||
import logging.handlers
 | 
					import logging.handlers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from octoprint.util import get_dos_filename
 | 
					from octoprint.util import get_dos_filename
 | 
				
			||||||
@@ -17,6 +17,7 @@ class RemoteSDCardFileList:
 | 
				
			|||||||
        self._settings = settings
 | 
					        self._settings = settings
 | 
				
			||||||
        self._selected_project_file: FileInfo | None = None
 | 
					        self._selected_project_file: FileInfo | None = None
 | 
				
			||||||
        self._logger = logging.getLogger("octoprint.plugins.bambu_printer.BambuPrinter")
 | 
					        self._logger = logging.getLogger("octoprint.plugins.bambu_printer.BambuPrinter")
 | 
				
			||||||
 | 
					        self._mock_files = []  # Lokales Cache für Mock-Dateien
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def delete_file(self, file_path: Path) -> None:
 | 
					    def delete_file(self, file_path: Path) -> None:
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
@@ -80,8 +81,56 @@ class RemoteSDCardFileList:
 | 
				
			|||||||
                self._logger.exception(e, exc_info=False)
 | 
					                self._logger.exception(e, exc_info=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_ftps_client(self):
 | 
					    def get_ftps_client(self):
 | 
				
			||||||
        host = self._settings.get(["host"])
 | 
					        """
 | 
				
			||||||
        access_code = self._settings.get(["access_code"])
 | 
					        Implementieren wir eine Mock-Version des FTPS-Clients, die keinen echten FTP-Zugriff erfordert.
 | 
				
			||||||
        return IoTFTPSClient(
 | 
					        """
 | 
				
			||||||
            f"{host}", 990, "bblp", f"{access_code}", ssl_implicit=True
 | 
					        class MockFTPSClient:
 | 
				
			||||||
        )
 | 
					            def __enter__(self):
 | 
				
			||||||
 | 
					                return self
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					            def __exit__(self, exc_type, exc_val, exc_tb):
 | 
				
			||||||
 | 
					                pass
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					            def get_file_list(self, path=""):
 | 
				
			||||||
 | 
					                """Gibt die Mock-Dateiliste zurück"""
 | 
				
			||||||
 | 
					                return self._mock_files
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					        mock_client = MockFTPSClient()
 | 
				
			||||||
 | 
					        mock_client._mock_files = self._mock_files
 | 
				
			||||||
 | 
					        return mock_client
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def is_available(self) -> bool:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Da wir kein FTP verwenden, ist dieser Service immer verfügbar
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_file_list(self, path: str) -> List[FileInfo]:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Gibt eine Liste von Dateien im angegebenen Pfad zurück.
 | 
				
			||||||
 | 
					        Da wir kein FTP verwenden, geben wir eine leere Liste oder gespeicherte Mock-Dateien zurück.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self._logger.debug(f"Listing files in path: {path}")
 | 
				
			||||||
 | 
					        return self._mock_files
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def add_mock_file(self, file_info: FileInfo):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Fügt eine Mock-Datei zur Liste hinzu (für Tests oder wenn keine FTP-Verbindung möglich ist)
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self._mock_files.append(file_info)
 | 
				
			||||||
 | 
					        self._logger.debug(f"Added mock file: {file_info.file_name}")
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def clear_mock_files(self):
 | 
				
			||||||
 | 
					        """Löscht alle gespeicherten Mock-Dateien"""
 | 
				
			||||||
 | 
					        self._mock_files = []
 | 
				
			||||||
 | 
					        self._logger.debug("Mock file list cleared")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def delete_file(self, path: str) -> bool:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Simuliert das Löschen einer Datei, entfernt sie aus der Mock-Liste
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self._logger.debug(f"Deleting file: {path}")
 | 
				
			||||||
 | 
					        before_count = len(self._mock_files)
 | 
				
			||||||
 | 
					        self._mock_files = [f for f in self._mock_files if f.path != path]
 | 
				
			||||||
 | 
					        return before_count > len(self._mock_files)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,3 +14,4 @@ OctoPrint~=1.10.2
 | 
				
			|||||||
setuptools~=70.0.0
 | 
					setuptools~=70.0.0
 | 
				
			||||||
pyserial~=3.5
 | 
					pyserial~=3.5
 | 
				
			||||||
Flask~=2.2.5
 | 
					Flask~=2.2.5
 | 
				
			||||||
 | 
					paho-mqtt~=2.1.0
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										8
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								setup.py
									
									
									
									
									
								
							@@ -14,20 +14,20 @@ plugin_package = "octoprint_bambu_printer"
 | 
				
			|||||||
plugin_name = "OctoPrint-BambuPrinter"
 | 
					plugin_name = "OctoPrint-BambuPrinter"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# The plugin's version. Can be overwritten within OctoPrint's internal data via __plugin_version__ in the plugin module
 | 
					# The plugin's version. Can be overwritten within OctoPrint's internal data via __plugin_version__ in the plugin module
 | 
				
			||||||
plugin_version = "0.1.6"
 | 
					plugin_version = "1.0.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# The plugin's description. Can be overwritten within OctoPrint's internal data via __plugin_description__ in the plugin
 | 
					# The plugin's description. Can be overwritten within OctoPrint's internal data via __plugin_description__ in the plugin
 | 
				
			||||||
# module
 | 
					# module
 | 
				
			||||||
plugin_description = """Connects OctoPrint to BambuLabs printers."""
 | 
					plugin_description = """Connects OctoPrint to BambuLabs printers."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# The plugin's author. Can be overwritten within OctoPrint's internal data via __plugin_author__ in the plugin module
 | 
					# The plugin's author. Can be overwritten within OctoPrint's internal data via __plugin_author__ in the plugin module
 | 
				
			||||||
plugin_author = "jneilliii"
 | 
					plugin_author = "ManuelW"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# The plugin's author's mail address.
 | 
					# The plugin's author's mail address.
 | 
				
			||||||
plugin_author_email = "jneilliii+github@gmail.com"
 | 
					plugin_author_email = "manuelw@example.com"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# The plugin's homepage URL. Can be overwritten within OctoPrint's internal data via __plugin_url__ in the plugin module
 | 
					# The plugin's homepage URL. Can be overwritten within OctoPrint's internal data via __plugin_url__ in the plugin module
 | 
				
			||||||
plugin_url = "https://github.com/jneilliii/OctoPrint-BambuPrinter"
 | 
					plugin_url = "https://gitlab.fire-devils.org/3D-Druck/OctoPrint-BambuPrinter"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# The plugin's license. Can be overwritten within OctoPrint's internal data via __plugin_license__ in the plugin module
 | 
					# The plugin's license. Can be overwritten within OctoPrint's internal data via __plugin_license__ in the plugin module
 | 
				
			||||||
plugin_license = "AGPLv3"
 | 
					plugin_license = "AGPLv3"
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user