Compare commits

..

6 Commits

9 changed files with 219 additions and 23 deletions

3
.github/FUNDING.yml vendored Normal file
View 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
View 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?

View 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
View 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
View 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

View File

@ -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",
} }
} }

View File

@ -14,6 +14,9 @@ from octoprint_bambu_printer.printer.print_job import PrintJob
from pybambu import BambuClient, commands from pybambu import BambuClient, commands
import logging import logging
import logging.handlers import logging.handlers
import paho.mqtt.client as mqtt
import json
import ssl
from octoprint.util import RepeatedTimer from octoprint.util import RepeatedTimer
@ -105,6 +108,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._bambu_client: BambuClient = self._create_client_connection_async() self._bambu_client: BambuClient = self._create_client_connection_async()
@property @property
@ -193,7 +200,7 @@ class BambuVirtualPrinter:
or print_job_state == "FAILED" or print_job_state == "FAILED"
): ):
self.change_state(self._state_idle) self.change_state(self._state_idle)
elif print_job_state == "RUNNING": elif print_job_state == "RUNNING" or print_job_state == "PREPARE":
self.change_state(self._state_printing) self.change_state(self._state_printing)
elif print_job_state == "PAUSE": elif print_job_state == "PAUSE":
self.change_state(self._state_paused) self.change_state(self._state_paused)
@ -220,6 +227,48 @@ 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:
self._mqtt_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}")
# Notify that we're connected
self.sendOk()
else:
self._mqtt_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._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'))
# If this is a Bambu Lab printer message, process it
if 'print' in payload or 'info' in payload:
# Forward the message to pybambu for processing
if self._bambu_client:
self._bambu_client._process_message(msg.topic, payload)
# Trigger our update handler
if 'print' in payload:
self.new_update("event_printer_data_update")
if 'info' in payload and 'hms' in payload['info']:
self.new_update("event_hms_errors")
self._log.debug(f"MQTT message received on topic {msg.topic}")
except Exception as e:
self._log.error(f"Error processing MQTT message: {e}")
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:
@ -237,31 +286,82 @@ class BambuVirtualPrinter:
self._log.debug(msg) self._log.debug(msg)
raise ValueError(msg) raise ValueError(msg)
self._log.debug( use_local_mqtt = self._settings.get_boolean(['local_mqtt'])
f"connecting via local mqtt: {self._settings.get_boolean(['local_mqtt'])}" self._log.debug(f"connecting via local mqtt: {use_local_mqtt}")
)
# Create a BambuClient but don't let it handle the MQTT connection
bambu_client = BambuClient( bambu_client = BambuClient(
device_type=self._settings.get(["device_type"]), device_type=self._settings.get(["device_type"]),
serial=self._settings.get(["serial"]), serial=self._settings.get(["serial"]),
host=self._settings.get(["host"]), host=self._settings.get(["host"]),
username=( username="bambuocto",
"bblp"
if self._settings.get_boolean(["local_mqtt"])
else self._settings.get(["username"])
),
access_code=self._settings.get(["access_code"]), access_code=self._settings.get(["access_code"]),
local_mqtt=self._settings.get_boolean(["local_mqtt"]), local_mqtt=use_local_mqtt,
region=self._settings.get(["region"]), region=self._settings.get(["region"]),
email=self._settings.get(["email"]), email=self._settings.get(["email"]),
auth_token=self._settings.get(["auth_token"]), auth_token=self._settings.get(["auth_token"]),
) )
bambu_client.on_disconnect = self.on_disconnect(bambu_client.on_disconnect)
bambu_client.on_connect = self.on_connect(bambu_client.on_connect) # Set up our own MQTT client
bambu_client.connect(callback=self.new_update) self._mqtt_client = mqtt.Client()
self._log.info(f"bambu connection status: {bambu_client.connected}") self._mqtt_client.on_connect = self._on_mqtt_connect
self.sendOk() self._mqtt_client.on_disconnect = self._on_mqtt_disconnect
self._mqtt_client.on_message = self._on_mqtt_message
# Configure connection based on local or cloud
if use_local_mqtt:
host = self._settings.get(["host"])
port = 1883
username = "octobambu"
self._mqtt_client.username_pw_set(username)
else:
# Cloud connection settings
region = self._settings.get(["region"])
host = f"mqtt-{region}.bambulab.com"
port = 8883
username = self._settings.get(["email"])
password = self._settings.get(["auth_token"])
self._mqtt_client.username_pw_set(username, password)
self._mqtt_client.tls_set()
# Connect MQTT
try:
self._mqtt_client.connect(host, port, 60)
self._mqtt_client.loop_start()
self._log.info(f"MQTT client started with {host}:{port}")
except Exception as e:
self._log.error(f"Failed to connect to MQTT broker: {e}")
raise
# Inject our MQTT client into the BambuClient
bambu_client._mqtt_client = self._mqtt_client
bambu_client.connected = True
# Store the Bambu client
self._bambu_client = bambu_client self._bambu_client = bambu_client
self._log.info(f"Bambu connection status: {bambu_client.connected}")
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._mqtt_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,
@ -634,6 +734,9 @@ class BambuVirtualPrinter:
return False return False
def close(self): def close(self):
if self._mqtt_client and self._mqtt_connected:
self._mqtt_client.loop_stop()
self._mqtt_client.disconnect()
if self.bambu_client.connected: if self.bambu_client.connected:
self.bambu_client.disconnect() self.bambu_client.disconnect()
self.change_state(self._state_idle) self.change_state(self._state_idle)

View File

@ -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

View File

@ -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"