Last active
October 30, 2025 19:36
-
-
Save nobodyguy/d97808cc1570a7676962c9ae609423f9 to your computer and use it in GitHub Desktop.
Small python script to update Bambulab A1 Mini or P1S in LAN mode with older firmware (without offline SD card update) to the first version that enables offline updates. Feel free to modify it for other printers and firmware versions.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """ | |
| Bambulab Printer OTA Firmware Update Script | |
| This script connects to your Bambulab printer (A1 Mini or P1S) via MQTT | |
| and initiates an OTA firmware update. | |
| Sources: | |
| - https://github.com/lunDreame/user-bambulab-firmware/issues/15 | |
| - https://m.cafe.naver.com/ca-fe/web/cafes/30884640/articles/3353 | |
| """ | |
| import paho.mqtt.client as mqtt | |
| import json | |
| import ssl | |
| import time | |
| import sys | |
| # ============================================================================ | |
| # PRINTER CONFIGURATION | |
| # ============================================================================ | |
| # Select your printer model: "A1_MINI" or "P1S" | |
| PRINTER_MODEL = "A1_MINI" # Change to "P1S" for P1 Series printers | |
| # AMS Configuration | |
| HAS_AMS = False # Set to True if your printer has an AMS unit attached | |
| # Connection Configuration | |
| PRINTER_IP = "YOUR_PRINTER_IP" # Replace with your printer's IP address | |
| MQTT_PORT = 8883 | |
| MQTT_USERNAME = "bblp" | |
| MQTT_PASSWORD = "YOUR_8_DIGIT_ACCESS_CODE" # Replace with your 8-digit access code | |
| PRINTER_SERIAL = "YOUR_PRINTER_SERIAL" # Replace with your printer's serial number | |
| # ============================================================================ | |
| # FIRMWARE CONFIGURATIONS | |
| # ============================================================================ | |
| FIRMWARE_CONFIGS = { | |
| "A1_MINI": { | |
| "name": "Bambu Lab A1 Mini", | |
| "version": "01.04.00.00", | |
| "url": "https://public-cdn.bblmw.com/upgrade/device/N1/01.04.00.00/product/1496eccbb7/ota-n1_v01.04.00.00-20241210144819.json.sig", | |
| "description": "", | |
| "ams": [] # A1 Mini typically doesn't have AMS firmware updates in the same way | |
| }, | |
| "P1S": { | |
| "name": "Bambu Lab P1S", | |
| "version": "01.07.00.00", | |
| "url": "https://public-cdn.bblmw.com/upgrade/device/C11/01.07.00.00/product/3b63468a6f/ota-p003_v01.07.00.00-20241210145014.json.sig", | |
| "description": "", | |
| "ams": [ | |
| { | |
| "dev_model_name": "BL-A001", | |
| "address": 0, | |
| "device_id": "", | |
| "firmware": [ | |
| { | |
| "version": "00.00.06.49", | |
| "force_update": False, | |
| "url": "https://public-cdn.bblmw.com/upgrade/device/BL-A001/00.00.06.49/product/ef67633af9/ams-ota_v00.00.06.49-20241021110111.json.sig", | |
| "description": "", | |
| "status": "testing" | |
| } | |
| ], | |
| "firmware_current": None | |
| } | |
| ] | |
| } | |
| } | |
| # Get current printer configuration | |
| if PRINTER_MODEL not in FIRMWARE_CONFIGS: | |
| print(f"β Error: Unknown printer model '{PRINTER_MODEL}'") | |
| print(f" Supported models: {', '.join(FIRMWARE_CONFIGS.keys())}") | |
| sys.exit(1) | |
| PRINTER_CONFIG = FIRMWARE_CONFIGS[PRINTER_MODEL] | |
| FIRMWARE_VERSION = PRINTER_CONFIG["version"] | |
| FIRMWARE_URL = PRINTER_CONFIG["url"] | |
| FIRMWARE_DESCRIPTION = PRINTER_CONFIG["description"] | |
| # Only include AMS firmware if HAS_AMS is True | |
| AMS_FIRMWARE = PRINTER_CONFIG["ams"] if HAS_AMS else [] | |
| # MQTT Topics | |
| COMMAND_TOPIC = f"device/{PRINTER_SERIAL}/request" | |
| STATUS_TOPIC = f"device/{PRINTER_SERIAL}/report" | |
| # Connection status | |
| connected = False | |
| def on_connect(client, userdata, flags, rc): | |
| """Callback when connected to MQTT broker""" | |
| global connected | |
| if rc == 0: | |
| print("β Successfully connected to printer") | |
| connected = True | |
| # Subscribe to status updates | |
| client.subscribe(STATUS_TOPIC) | |
| print(f"β Subscribed to status topic: {STATUS_TOPIC}") | |
| else: | |
| print(f"β Connection failed with code {rc}") | |
| error_messages = { | |
| 1: "Incorrect protocol version", | |
| 2: "Invalid client identifier", | |
| 3: "Server unavailable", | |
| 4: "Bad username or password", | |
| 5: "Not authorized" | |
| } | |
| print(f" Error: {error_messages.get(rc, 'Unknown error')}") | |
| connected = False | |
| def on_disconnect(client, userdata, rc): | |
| """Callback when disconnected from MQTT broker""" | |
| global connected | |
| connected = False | |
| if rc != 0: | |
| print(f"β Unexpected disconnection (code: {rc})") | |
| def on_message(client, userdata, msg): | |
| """Callback when a message is received""" | |
| try: | |
| payload = json.loads(msg.payload.decode()) | |
| print(f"\nπ© Received message on {msg.topic}:") | |
| print(json.dumps(payload, indent=2)) | |
| # Check for upgrade status | |
| if "upgrade" in payload: | |
| upgrade_status = payload["upgrade"] | |
| if "status" in upgrade_status: | |
| print(f"\nπ Update Status: {upgrade_status['status']}") | |
| if "progress" in upgrade_status: | |
| print(f"π Progress: {upgrade_status['progress']}%") | |
| except json.JSONDecodeError: | |
| print(f"π© Received non-JSON message: {msg.payload.decode()}") | |
| except Exception as e: | |
| print(f"β Error processing message: {e}") | |
| def on_publish(client, userdata, mid): | |
| """Callback when a message is published""" | |
| print(f"β Message published (ID: {mid})") | |
| def send_ota_update(client): | |
| """Send OTA firmware update command to printer""" | |
| update_command = { | |
| "user_id": "0", | |
| "upgrade": { | |
| "sequence_id": "0", | |
| "command": "upgrade_history", | |
| "src_id": 2, | |
| "firmware_optional": { | |
| "firmware": { | |
| "version": FIRMWARE_VERSION, | |
| "url": FIRMWARE_URL, | |
| "force_update": False, | |
| "description": FIRMWARE_DESCRIPTION, | |
| "status": "release" | |
| }, | |
| "ams": AMS_FIRMWARE | |
| } | |
| } | |
| } | |
| print("\nπ€ Sending OTA update command...") | |
| print(f" Printer Model: {PRINTER_CONFIG['name']}") | |
| print(f" Firmware Version: {FIRMWARE_VERSION}") | |
| print(f" Update URL: {FIRMWARE_URL}") | |
| print(f" Command: upgrade_history") | |
| if HAS_AMS and AMS_FIRMWARE: | |
| print(f" AMS Updates: {len(AMS_FIRMWARE)} device(s)") | |
| elif HAS_AMS and not AMS_FIRMWARE: | |
| print(f" AMS Updates: None available for this printer model") | |
| else: | |
| print(f" AMS Updates: Skipped (HAS_AMS=False)") | |
| result = client.publish( | |
| COMMAND_TOPIC, | |
| json.dumps(update_command), | |
| qos=1 | |
| ) | |
| if result.rc == mqtt.MQTT_ERR_SUCCESS: | |
| print("β Update command sent successfully") | |
| return True | |
| else: | |
| print(f"β Failed to send update command (error code: {result.rc})") | |
| return False | |
| def main(): | |
| """Main function to run the OTA update""" | |
| global connected | |
| # Validate configuration | |
| if PRINTER_IP == "YOUR_PRINTER_IP": | |
| print("β Error: Please update PRINTER_IP in the script with your printer's IP address") | |
| sys.exit(1) | |
| if MQTT_PASSWORD == "YOUR_8_DIGIT_ACCESS_CODE": | |
| print("β Error: Please update MQTT_PASSWORD with your 8-digit access code") | |
| sys.exit(1) | |
| if PRINTER_SERIAL == "YOUR_PRINTER_SERIAL": | |
| print("β Error: Please update PRINTER_SERIAL with your printer's serial number") | |
| sys.exit(1) | |
| print("=" * 60) | |
| print("Bambulab Printer OTA Firmware Update") | |
| print("=" * 60) | |
| print(f"Printer Model: {PRINTER_CONFIG['name']}") | |
| print(f"Printer IP: {PRINTER_IP}") | |
| print(f"Target Version: {FIRMWARE_VERSION}") | |
| print(f"AMS Present: {'Yes' if HAS_AMS else 'No'}") | |
| print("=" * 60) | |
| # Create MQTT client with MQTT v3.1.1 | |
| client = mqtt.Client( | |
| client_id="bambulab_updater", | |
| protocol=mqtt.MQTTv311 # MQTT Version 3.1.1 | |
| ) | |
| # Set username and password | |
| client.username_pw_set(MQTT_USERNAME, MQTT_PASSWORD) | |
| # Configure TLS/SSL (SSL/TLS: On, SSL Secure: Off) | |
| client.tls_set(cert_reqs=ssl.CERT_NONE) | |
| client.tls_insecure_set(True) | |
| # Set callbacks | |
| client.on_connect = on_connect | |
| client.on_disconnect = on_disconnect | |
| client.on_message = on_message | |
| client.on_publish = on_publish | |
| # Connect to printer | |
| print(f"\nπ Connecting to printer at {PRINTER_IP}:{MQTT_PORT}...") | |
| try: | |
| client.connect(PRINTER_IP, MQTT_PORT, keepalive=60) | |
| # Start network loop in background | |
| client.loop_start() | |
| # Wait for connection | |
| timeout = 10 | |
| elapsed = 0 | |
| while not connected and elapsed < timeout: | |
| time.sleep(0.5) | |
| elapsed += 0.5 | |
| if not connected: | |
| print("β Connection timeout. Please check:") | |
| print(" - Printer IP address is correct") | |
| print(" - Access code is correct") | |
| print(" - Printer is powered on and connected to network") | |
| client.loop_stop() | |
| sys.exit(1) | |
| # Send OTA update command | |
| time.sleep(1) # Brief pause after connection | |
| if send_ota_update(client): | |
| print("\nβ³ Monitoring update progress...") | |
| print(" (Press Ctrl+C to stop monitoring)\n") | |
| # Keep running to receive status updates | |
| try: | |
| while True: | |
| time.sleep(1) | |
| except KeyboardInterrupt: | |
| print("\n\nβΉ Stopped monitoring. Update continues on printer.") | |
| except Exception as e: | |
| print(f"\nβ Error: {e}") | |
| finally: | |
| client.loop_stop() | |
| client.disconnect() | |
| print("\nπ Disconnected from printer") | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment