Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions bambulab/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from pathlib import Path


# TODO(alstr): Refactor this class for token/account region handling
class TokenManager:
"""
Manages API token mappings and validation.
Expand Down Expand Up @@ -247,13 +248,13 @@ def _handle_email_verification(
"""
# Send verification code to email
send_payload = {
"email": email,
"phone" if self.region == "china" else "email": email,
"type": "codeLogin"
}

try:
response = self.session.post(
f"{self.base_url}/v1/user-service/user/sendemail/code",
f"{self.base_url}/v1/user-service/user/" + "sendsmscode" if self.region == "china" else "sendemail/code",
json=send_payload,
timeout=30
)
Expand Down Expand Up @@ -378,6 +379,7 @@ def save_token(self, token: str) -> None:
# Don't fail if we can't save, just warn
print(f"Warning: Could not save token to {self.token_file}: {e}")

#TODO(alstr): Refactor region handling for saved tokens
def load_token(self) -> Optional[str]:
"""
Load saved token from file.
Expand Down
10 changes: 7 additions & 3 deletions bambulab/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@ class BambuClient:
Handles authentication, request formatting, and response parsing.
"""

BASE_URL = "https://api.bambulab.com"
#TODO(alstr): It is better to refactor all servers and session token/account in separate configuration management module
BASE_URL_GLOBAL = "https://api.bambulab.com"
BASE_URL_CHINA = "https://api.bambulab.cn"
DEFAULT_TIMEOUT = 30

def __init__(self, token: str, timeout: int = None):
def __init__(self, token: str, timeout: int = None, region: Optional[str] = "global",):
"""
Initialize the Bambu API client.

Expand All @@ -35,6 +37,8 @@ def __init__(self, token: str, timeout: int = None):
timeout: Request timeout in seconds (default: 30)
"""
self.token = token
self.region = region
self.base_url = self.BASE_URL_CHINA if region == "china" else self.BASE_URL_GLOBAL
self.timeout = timeout or self.DEFAULT_TIMEOUT
self.session = requests.Session()

Expand Down Expand Up @@ -69,7 +73,7 @@ def _request(
Raises:
BambuAPIError: If request fails
"""
url = f"{self.BASE_URL}/{endpoint.lstrip('/')}"
url = f"{self.base_url}/{endpoint.lstrip('/')}"
headers = self._get_headers()

try:
Expand Down
14 changes: 9 additions & 5 deletions bambulab/mqtt.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,17 @@ class MQTTClient:
Connects to Bambu Lab cloud MQTT broker and subscribes to device updates.
"""

BROKER = "us.mqtt.bambulab.com"
BROKER_GLOBAL = "us.mqtt.bambulab.com"
BROKER_CHINA = "cn.mqtt.bambulab.com"
PORT = 8883

def __init__(
self,
username: str,
access_token: str,
device_id: str,
on_message: Optional[Callable] = None
on_message: Optional[Callable] = None,
region: Optional[str] = "global"
):
"""
Initialize MQTT client.
Expand All @@ -56,6 +58,8 @@ def __init__(

self.username = username
self.access_token = access_token
self.region = region
self.broker = self.BROKER_CHINA if region == "china" else self.BROKER_GLOBAL
self.device_id = device_id
self.on_message_callback = on_message

Expand All @@ -68,7 +72,7 @@ def _on_connect(self, client, userdata, flags, rc, properties=None):
"""Callback when connected to broker"""
if rc == 0:
self.connected = True
logger.info(f"Connected to MQTT broker: {self.BROKER}")
logger.info(f"Connected to MQTT broker: {self.broker}")

# Subscribe to device report topic
topic = f"device/{self.device_id}/report"
Expand Down Expand Up @@ -126,8 +130,8 @@ def connect(self, blocking: bool = False):
self.client.tls_set(cert_reqs=ssl.CERT_REQUIRED, tls_version=ssl.PROTOCOL_TLS)

# Connect
logger.info(f"Connecting to {self.BROKER}:{self.PORT}...")
self.client.connect(self.BROKER, self.PORT, keepalive=60)
logger.info(f"Connecting to {self.broker}:{self.PORT}...")
self.client.connect(self.broker, self.PORT, keepalive=60)

if blocking:
self.client.loop_forever()
Expand Down
4 changes: 2 additions & 2 deletions cli_tools/camera_viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,10 +247,10 @@ def view_rtsp_stream(stream):
print()


def get_printer_info(token):
def get_printer_info(token, region='global'):
"""Get printer information from cloud API."""
try:
client = BambuClient(token)
client = BambuClient(token, region=region)
devices = client.get_devices()

if not devices:
Expand Down
2 changes: 1 addition & 1 deletion cli_tools/login.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ def main():
password = args.password

if not username:
username = input("Email: ")
username = input("Phone: " if args.region == "china" else "Email: ")

if not password:
password = getpass.getpass("Password: ")
Expand Down
8 changes: 5 additions & 3 deletions cli_tools/monitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import os
import time
from datetime import datetime
from typing import Optional

# Add parent directory to path for bambulab import
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
Expand All @@ -21,7 +22,7 @@
class PrinterMonitor:
"""Monitor printer status with formatted output"""

def __init__(self, username: str, access_token: str, device_id: str):
def __init__(self, username: str, access_token: str, device_id: str, region: Optional[str] = "global"):
self.device_id = device_id
self.message_count = 0
self.last_update = None
Expand All @@ -31,7 +32,8 @@ def __init__(self, username: str, access_token: str, device_id: str):
username=username,
access_token=access_token,
device_id=device_id,
on_message=self.on_message
on_message=self.on_message,
region=region
)

def on_message(self, device_id: str, data: dict):
Expand Down Expand Up @@ -112,7 +114,7 @@ def start(self):
print("Bambu Lab Printer Monitor")
print("=" * 80)
print(f"Device ID: {self.device_id}")
print(f"Broker: {self.client.BROKER}:{self.client.PORT}")
print(f"Broker: {self.client.broker}:{self.client.PORT}")
print()
print("Connecting to MQTT...")
print("=" * 80)
Expand Down
9 changes: 8 additions & 1 deletion cli_tools/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ def main():
print(" --firmware Show firmware info")
print(" --json Output in JSON format")
print(" --device <id> Filter by device ID")
print(" --region region (global or china), default is global")
print()
print("Examples:")
print(" python query.py AADBD2wZe_token...")
Expand All @@ -72,12 +73,18 @@ def main():
if idx + 1 < len(args):
device_filter = args[idx + 1]

region = 'global'
if '--region' in args:
idx = args.index('--region')
if idx + 1 < len(args):
region = args[idx + 1]

# Default to showing devices
show_devices = not any([show_status, show_profile, show_projects, show_firmware])

# Create API client
try:
client = BambuClient(access_token)
client = BambuClient(access_token, region=region)
except Exception as e:
print(f"Error: Failed to create API client: {e}")
sys.exit(1)
Expand Down
7 changes: 4 additions & 3 deletions servers/proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from flask import Flask, request, jsonify, Response
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
from typing import Optional

# Add parent directory to path for bambulab import
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
Expand Down Expand Up @@ -226,7 +227,7 @@ def cleanup_expired_mqtt_sessions():
print(f"[MQTT] Cleanup error: {e}")


def start_mqtt_session(device_id: str, real_token: str):
def start_mqtt_session(device_id: str, real_token: str, region: Optional[str] = "global"):
"""
Start MQTT monitoring session for a specific device.

Expand Down Expand Up @@ -257,7 +258,7 @@ def start_mqtt_session(device_id: str, real_token: str):

try:
# Get user profile for username
client = BambuClient(real_token)
client = BambuClient(real_token, region=region)
profile = client.get(f"v1/user-service/my/profile")
username = profile.get('uid') or profile.get('user_id')

Expand Down Expand Up @@ -399,7 +400,7 @@ def proxy_v1(endpoint):
limiter.limit(RATE_LIMITS["default"])(lambda: None)()

# Create client with real token
client = BambuClient(real_token)
client = BambuClient(real_token) #TODO(fix): handing region with TokenManager here

# Get request body for write operations
data = None
Expand Down