r/OpenBambu

BambuHandy alternative

Hey all,

I got tired of the official Bambu Handy app requiring a cloud account to do things that never needed to leave my house, so I built a local-only Android companion app for my P1S.

BambuMobile: It connects directly to your printer over LAN using MQTT on port 8883 and the camera stream on port 6000. No Bambu cloud. No account. No data leaving your network. Works the same way OrcaSlicer and the other community tools do.

What it does:

  • Live MJPG camera feed (pure Rust TLS implementation)
  • Print status: progress %, layers, remaining time, stage
  • Nozzle and bed temperatures with targets
  • Pause / Resume / Stop
  • Chamber light toggle
  • Speed mode (Silent / Standard / Sport / Ludicrous)
  • AMS view: per-unit humidity grade (A–E), per-slot filament type and colour
  • External spool display
  • Manual jog controls with an OrcaSlicer-style XY wheel, separate extruder column, and Z/bed column: ±1 and ±10 mm steps plus home
  • Credentials saved to app data: no browser storage

On right to repair:

You paid $700+ for a printer. The MQTT broker is on the printer. The camera stream is on the printer. None of it needs the internet. But the official app routes it through Bambu's servers anyway. Projects like OpenBambuAPI document these protocols so that community tools can exist independent of whatever Bambu decides to do with their cloud, their ToS, or their business model in the future. That's worth building for.

Disclaimer:

Yes it was vibe coded. With Claude. Please don't come whining about it. It's open source so you can go and fix it if you want. I don't write Rust.

The app works great on my P1S. If someone can test it on a P1P, X1C, or A1 that'd be excellent. Don't @ me about code quality.

Built with Tauri v2 (Rust backend) + React/TypeScript. Licensed AGPL-3.0 so you can fork it, but keep it open.

GitHub: https://github.com/joel-sgc/BambuMobile

Roadmap has multiple printer support and some kind of print file browsing. We'll see what's possible on the protocol side.

u/EnderGopo — 5 hours ago
▲ 148 r/OpenBambu+1 crossposts

Open network plugin for Orca Slicer

Finally it's here and it's working. Thanks to great work of Alexey Cluster!

So here is the source: https://github.com/ClusterM/open-bambu-networking

And I've compiled it for Orca Slicer 2.3.2 and above (tested on dev build 2.4.0): https://github.com/Lojza007/open-bambu-networking/releases

It works with Alexey P2S and mine P1S. Now you can access files on printer and time lapses too. And I hope it'll be soon included in Orca Slicer.

If you want to compile by your self you need:

- GIT: https://github.com/git-for-windows/git/releases/tag/v2.54.0.windows.1

- Visual Studio 2019 Community Edition: https://aka.ms/vs/16/release/vs_community.exe (and select Desktop development with C++)

- VCPKG: https://github.com/microsoft/vcpkg (and after clone execute bootstrap-vcpkg.bat)

And then you need to open Developer PowerShell for VS 2019 and execute:

$env:VCPKG_ROOT = "C:\path\to\vcpkg"
.\configure.ps1 -ClientType orca_slicer
cmake --build   build --config Release
cmake --install build --config Release
u/WaitAcademic6615 — 16 hours ago

Openbu going to the Google Play Store

I am working on getting Openbu into the Google Play Store. It is a replacement for Bambu Handy and Lanbu, and has been around for three months.

I need your help. Google has requirements of 20 testers before they will publish a new app to the store. Please DM me your Google Play Store account's email address, aka the Google account email address you logged into your Android device with and use with the Google Play Store. After I add it you can use this link.

The goal with getting it into the Google Play Store is to reach a wider audience. It is Open Source, and free. It will be staying free.

Screenshots of Openbu can be found here.

If you aren't interested in being test it for entry from the Google Play Store you can also just download the apk from here.

Features:

  • Auto-detects printers, and auto fills in the ip address and serial number. Hence only requires the access code.
  • Saving the printer connection settings by default
  • Bed and nozzle temperature control
  • Fan speed control
  • Allows the user to add an external RTSP stream to the dashboard by entering a RTSP URL, and with pinch to zoom
  • Supports the A1 and P1 series video stream based on JPEGs with pinch to zoom.
  • Support RTSP streams from non-P1 series internal cameras with pinch to zoom
  • Supports toggling of the chamber light
  • AMS HT, AMS, and AMS 2 Pro
    • Knows the correct number of trays per model
    • Shows temperature, humidity, filament types, and filament colors
    • Assigning filament types and filament colors per tray
  • Showing filament type and filament color of the External Spool
  • Assigning filament types and filament for the External Spool
  • Shows job status including layers, time left, estimated time, job name, and percentage of job done
  • Shows status of the nozzle and bed
  • Shows status of part fan, aux fan, and chamber fan depending on what the printer model has
  • Setting print speed
  • Pause/Resume and Stop
  • File management via File Manager which uses FTPS
  • Skip Objects support
  • Remote access via openbu-relay

Edit: 1 tester enrolled, and 19 more to go.

u/edgan — 2 hours ago

Keeping spools inside dry boxes plugged in to the printer?

I just installed my new BMCU370c with 4 dry boxes setup hoping I would be able to keep the spools in without needing to store them in a sealed bag every time I finish printing.

I sealed the upper lid with silicone but when pressing the box I do notice some air flow from the ptfe tube on the bmcu side.

I put some silica gel and I noticed it absorbed some moister in 2 days and turned pink.

Should I keep the spools in the dry boxes or will it ruin them?

u/Big_Airline1980 — 16 hours ago
▲ 88 r/OpenBambu+1 crossposts

OpenMech Swapper (OMS) 🔄

The Open-Source, Fully Mechanical Multi-Filament System for Bambu Lab A1 Series

OpenMech Swapper (OMS) is a high-performance, 100% mechanical filament switching system. It is designed to bring multi-material capabilities to the Bambu Lab A1 and A1 Mini without the need for extra motors, sensors, or complex electronics.

💡 Origin & Credits

This project is an Open Source evolutioninspired by the brilliant mechanical concept of MechAMS created by Sipers Mechatronics.
While the original idea proved that a fully 3D-printable mechanical AMS was possible, OMS aims to make this technology accessible to everyone under an open-source license, incorporating community-driven improvements for better durability and precision.

Key Features

Purely Mechanical: Driven by your toolhead position and the existing extruder motor.
GT2 Belt Drive: Upgraded from friction bands to standard 6mm GT2 timing belts for zero-slip and high-torque transmission.
Magnetic Indexing: Uses embedded Neodymium magnets (4x2mm) to create precise "detents," ensuring perfect alignment of the filament slots with zero mechanical wear.
Zero Electronics: No cables, no PCB, no extra steppers.
Non-Destructive: Quick installation that keeps your printer's warranty intact.

🛠 Improvements over the Concept

Reliability: By using GT2 belts, we eliminate the stretching issues of printed TPU bands.
Precision: The "Magnetic Snap" system ensures the indexing gear stays locked in the correct position even during high-speed printing.
Accessibility: All files (STEP/STL) are open for the community to remix and adapt to other printers.

📂 Compatibility

Hardware: Bambu Lab A1 & A1 Mini.
Software: Custom G-Code presets for Bambu Studio and Orca Slicer.

🔧 Hardware Requirements (BOM)

Main Body: Printed in PETG or PLA.
Drive System: 1x GT2 Timing Belt (6mm width).
Indexing: 4x2mm Neodymium Magnets (N52 recommended).
Path: Standard 4mm OD PTFE Tube.

🚀 Status: Prototyping

A1 Hardware almost done
A1 Mini Hardware:
Firmware initiating test.

🖼️** Víde**o

🤝 Contributing & License

We welcome all contributions! Whether it's optimizing G-Code macros or refining the mechanical tolerances.

u/Magarcan — 6 days ago

Ams lite dupe

So this place semi near me, is selling a AMS light dupe that connects to the printer. I’m a little scared that within the next couple of months they’re going to send an update that makes it so I can’t use this set up anymore. I wanna know if this is something that I should consider or if I should just not get it

u/Melodic-Bat-3716 — 4 days ago

Need help going without Bambu Slicer on p1S

Hey guys, I'm not super tech savvy when it comes to 3d printers, and I could use some help understanding my options.

I own a p1s with an AMS 2 on the latest firmware
It's connected to the internet using a Netgear wifi extender. So, technically, it's not on the same wifi as my PC I guess ?, and with no Ethernet available because too far from my PC.

I would like to use my p1S locally only because bambulab is pissing on my nerves. I just can't seem to make orcaslicer work on windows or Linux with my printer (it never finds it or just has a botched connection with no control, no camera feed...). It's been like that for a while but I decided to do something because it's a hassle.

What is currently the easiest option available to me ? Without extra hardware ? And as much as possible while keeping core functionalities, like access to the camera feed, control of the p1s fan, temp, etc, AND have my AMS system detected, working properly without loosing current spools everytime. Also, is there a simple way to check the camera while I'm home using an android or iOS app ?
Yup that's a lot 😅

I hope it still is possible and reasonable, I'd like to avoid super complex stuff with a rasperry pi, Dockers etc. It's too complex for my feeble mind 😞

reddit.com
u/Bartuc01 — 4 days ago

Hi,

to begin with: i dont know how to code. I used Claude and Gemini for everything. I wanted to create a small program that allows me to stream my P2S and A1 Mini Video Streams and Stats at the same time.

The P2S is working fine via rtsps://{PRINTER_IP}:322/streaming/live/1 But i can´t get the A1 mini video stream working...

As i found out via https://github.com/Doridian/OpenBambuAPI/blob/main/video.md , the A1 mini doesnt work via a RTSP Server and uses another port.

I also used go2rtc and ha-bambulab as inspiration for the AI, but the stream still doesnt work.

from tkinter import ttk, messagebox
import cv2
import threading
import time
import json
import os
import socket
import ssl
import struct
import subprocess
import platform
import numpy as np
import paho.mqtt.client as mqtt
from PIL import Image, ImageTk

# --- Konfigurations-Handling ---
CONFIG_FILE = "bambu_config.json"

def load_config():
    default_config = {
        "P2S": {"ip": "192.168.178.98", "password": "Code", "serial": "SN1"},
        "A1 Mini": {"ip": "192.168.178.82", "password": "Code", "serial": "SN2"}
    }
    if os.path.exists(CONFIG_FILE):
        try:
            with open(CONFIG_FILE, "r") as f:
                return json.load(f)
        except Exception: return default_config
    return default_config

class StreamBox:
    def __init__(self, parent, title, config_ref, column):
        self.title_text = title
        self.config_ref = config_ref
        
        self.frame = tk.Frame(parent, bg="#1e1e1e", bd=2, relief="groove", padx=10, pady=10)
        self.frame.grid(row=0, column=column, padx=20, pady=20, sticky="n")
        
        self.header_label = tk.Label(self.frame, text=title, fg="#00FF00", bg="#1e1e1e", font=("Arial", 14, "bold"))
        self.header_label.pack(pady=5)
        
        self.image_label = tk.Label(self.frame, bg="black", width=640, height=480, text="Warte auf Signal...", fg="white")
        self.image_label.pack(pady=5)
        
        self.status_lbl = tk.Label(self.frame, text="Offline", fg="gray", bg="#1e1e1e", font=("Arial", 11, "bold"))
        self.status_lbl.pack()
        
        self.progress = ttk.Progressbar(self.frame, orient="horizontal", length=600, mode="determinate")
        self.progress.pack(pady=10)
        
        self.running = False
        self.latest_frame = None
        self.update_gui_loop()

    def start(self):
        self.running = True
        # Starte MQTT und Video in separaten Threads
        threading.Thread(target=self._run_mqtt, daemon=True).start()
        threading.Thread(target=self._run_video_logic, daemon=True).start()

    def _run_video_logic(self):
        if "A1" in self.title_text:
            self._run_a1_socket_video()
        else:
            self._run_standard_rtsp_video()

    def _run_a1_socket_video(self):
        """A1 Mini Spezial: Port 6000 TCP/TLS JPEG Stream (webcamd Logik)"""
        conf = self.config_ref.get(self.title_text, {})
        ip, pw = conf.get("ip"), conf.get("password")
        
        while self.running:
            try:
                # Socket Erstellung
                sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                sock.settimeout(10)
                
                # SSL Kontext (Bambu nutzt oft TLS v1.2)
                context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
                context.check_hostname = False
                context.verify_mode = ssl.CERT_NONE
                
                try:
                    conn = context.wrap_socket(sock, server_hostname=ip)
                    conn.connect((ip, 6000))
                except:
                    # Fallback auf Plain TCP (falls SSL fehlschlägt)
                    sock.close()
                    conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                    conn.settimeout(10)
                    conn.connect((ip, 6000))
                
                with conn:
                    # Authentifizierungs-Paket (webcamd Spec: 80 Bytes)
                    # Magic (4), Command (4), Flags (4), Sequence (4), User (32), Pass (32)
                    header = struct.pack("<IIII", 0x40, 0x3000, 0, 0)
                    username = b"bblp".ljust(32, b'\x00')
                    password = pw.encode('ascii').ljust(32, b'\x00')
                    conn.sendall(header + username + password)
                    
                    buffer = b""
                    while self.running:
                        # 1. Header einlesen (16 Bytes)
                        while len(buffer) < 16:
                            chunk = conn.recv(16384)
                            if not chunk: break
                            buffer += chunk
                        
                        if not buffer: break
                        
                        # Payload Größe aus den ersten 4 Bytes lesen
                        frame_size = struct.unpack("<I", buffer[:4])[0]
                        buffer = buffer[16:] # Header abschneiden
                        
                        # 2. JPEG Daten einlesen
                        while len(buffer) < frame_size:
                            chunk = conn.recv(16384)
                            if not chunk: break
                            buffer += chunk
                        
                        jpeg_data = buffer[:frame_size]
                        buffer = buffer[frame_size:] # Rest behalten
                        
                        # 3. Bild verarbeiten
                        if jpeg_data.startswith(b'\xff\xd8'):
                            nparr = np.frombuffer(jpeg_data, np.uint8)
                            frame = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
                            if frame is not None:
                                frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
                                self.latest_frame = cv2.resize(frame, (640, 480))
            except Exception as e:
                print(f"[{self.title_text}] Video Error: {e}")
                time.sleep(5)

    def _run_standard_rtsp_video(self):
        """P2S: Klassischer RTSPS Stream"""
        conf = self.config_ref.get(self.title_text, {})
        url = f"rtsps://bblp:{conf.get('password')}@{conf.get('ip')}:322/streaming/live/1"
        os.environ["OPENCV_FFMPEG_CAPTURE_OPTIONS"] = "rtsp_transport;tcp|tls_verify;0"
        
        while self.running:
            cap = cv2.VideoCapture(url, cv2.CAP_FFMPEG)
            while self.running:
                ret, frame = cap.read()
                if not ret: break
                frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
                self.latest_frame = cv2.resize(frame, (640, 480))
            cap.release()
            time.sleep(5)

    def _run_mqtt(self):
        """Telemetrie Daten (Status & Fortschritt)"""
        conf = self.config_ref.get(self.title_text, {})
        ip, pw, sn = conf.get('ip'), conf.get('password'), conf.get('serial')
        
        client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
        client.tls_set(cert_reqs=mqtt.ssl.CERT_NONE)
        client.username_pw_set("bblp", pw)
        
        def on_msg(c, u, m):
            try:
                data = json.loads(m.payload.decode())
                p = data.get('print')
                if p:
                    if "gcode_state" in p:
                        st = p["gcode_state"]
                        self.status_lbl.config(text=f"Status: {st}", fg="#00FF00" if st == "RUNNING" else "orange")
                    if "mc_percent" in p:
                        self.progress['value'] = p['mc_percent']
            except: pass

        client.on_message = on_msg
        
        while self.running:
            try:
                client.connect(ip, 8883, 60)
                client.subscribe(f"device/{sn}/report")
                
                # Watchdog: Weckt die Kamera alle 10s auf
                def trigger():
                    while self.running and client.is_connected():
                        # pushall aktiviert Telemetrie und Video-Server am Drucker
                        client.publish(f"device/{sn}/request", '{"print":{"command":"pushall","sequence_id":"1"}}')
                        time.sleep(10)
                
                threading.Thread(target=trigger, daemon=True).start()
                client.loop_forever()
            except:
                self.status_lbl.config(text="MQTT Connect...", fg="red")
                time.sleep(5)

    def update_gui_loop(self):
        if self.running and self.latest_frame is not None:
            img = ImageTk.PhotoImage(Image.fromarray(self.latest_frame))
            self.image_label.img = img
            self.image_label.config(image=img, text="")
        self.image_label.after(30, self.update_gui_loop)

class App:
    def __init__(self, root):
        self.root = root
        self.root.title("Bambu Hybrid Dashboard (A1 Special)")
        self.root.state('zoomed')
        self.root.configure(bg="#121212")
        self.config = load_config()

        self.sidebar = tk.Frame(root, bg="#1a1a1a", width=380, bd=2, relief="raised")
        self.sidebar.pack(side="right", fill="y")
        self.sidebar.pack_propagate(False)

        self.main_container = tk.Frame(root, bg="#121212")
        self.main_container.pack(side="left", fill="both", expand=True)
        
        self.ping_labels = {}
        self.setup_sidebar()
        
        self.boxes = [
            StreamBox(self.main_container, "P2S", self.config, 0),
            StreamBox(self.main_container, "A1 Mini", self.config, 1)
        ]

    def setup_sidebar(self):
        tk.Label(self.sidebar, text="DRUCKER-SETUP", fg="white", bg="#1a1a1a", font=("Arial", 14, "bold")).pack(pady=20)
        self.entries = {}
        for name in ["P2S", "A1 Mini"]:
            f = tk.LabelFrame(self.sidebar, text=f" {name} ", bg="#1a1a1a", fg="orange", padx=10, pady=10)
            f.pack(fill="x", padx=15, pady=10)
            
            p_lbl = tk.Label(f, text="Ping: ?", bg="#1a1a1a", fg="#888")
            p_lbl.grid(row=0, column=0, columnspan=2, sticky="w")
            self.ping_labels[name] = p_lbl
            
            res = []
            for i, (l, k) in enumerate([("IP:", "ip"), ("Code:", "password"), ("S/N:", "serial")]):
                tk.Label(f, text=l, bg="#1a1a1a", fg="white").grid(row=i+1, column=0, sticky="w")
                e = tk.Entry(f, width=22, bg="#2a2a2a", fg="white", insertbackground="white", bd=0)
                e.insert(0, self.config[name].get(k, ""))
                e.grid(row=i+1, column=1, pady=3, padx=5)
                res.append(e)
            self.entries[name] = res
        
        tk.Button(self.sidebar, text="SPEICHERN", bg="#28a745", fg="white", command=self.save_settings, height=2).pack(fill="x", padx=30, pady=10)
        tk.Button(self.sidebar, text="SYSTEM START", bg="#007BFF", fg="white", command=self.start_all, height=2).pack(fill="x", padx=30, pady=5)

    def silent_ping(self):
        param = '-n' if platform.system().lower() == 'windows' else '-c'
        for name in ["P2S", "A1 Mini"]:
            ip = self.entries[name][0].get()
            if not ip: continue
            try:
                res = subprocess.run(
                    ['ping', param, '1', '-w', '800', ip], 
                    stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
                    creationflags=0x08000000 if platform.system().lower() == 'windows' else 0
                )
                status = "ONLINE" if res.returncode == 0 else "OFFLINE"
                self.ping_labels[name].config(text=f"Ping: {status}", fg="#00FF00" if status == "ONLINE" else "red")
            except: pass

    def save_settings(self):
        for name, w in self.entries.items():
            self.config[name] = {"ip": w[0].get(), "password": w[1].get(), "serial": w[2].get()}
        with open(CONFIG_FILE, "w") as f: json.dump(self.config, f, indent=4)
        messagebox.showinfo("Erfolg", "Konfiguration gespeichert!")

    def start_all(self):
        self.silent_ping()
        for box in self.boxes: box.start()

if __name__ == "__main__":
    root = tk.Tk()
    app = App(root)
    root.mainloop()```
u/Scared-Cockroach-176 — 11 days ago