mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-09-30 03:01:21 +02:00
Merge pull request #56 from maziggy/0.1.6b6
0.1.6b6 - Fixed bug where wrong active AMS tray was shown for multi nozzle printers - Updated CHANGELOG
This commit is contained in:
+29
-1
@@ -23,9 +23,37 @@ All notable changes to Bambuddy will be documented in this file.
|
||||
- Model change automatically restarts the virtual printer (no manual disable/enable needed)
|
||||
- Models sorted alphabetically in dropdown
|
||||
- **Pending upload delete confirmation** - Confirmation modal when discarding pending uploads in queue review mode
|
||||
- **Tasmota admin link** - Direct link to Tasmota web interface on smart plug cards:
|
||||
- Auto-login using stored credentials (when available)
|
||||
- Opens in new tab for quick access to plug settings
|
||||
- **Debug logging** - Added debug logging for printer hour counter and AMS slot mapping
|
||||
- **Demo video recorder** - Playwright-based tool for recording demo videos:
|
||||
- Automated walkthrough of all pages
|
||||
- Outputs WebM format, easy conversion to MP4
|
||||
- Located in `demo-video/` directory
|
||||
|
||||
### Fixed
|
||||
- **Camera stream reconnection** - Improved detection of stuck camera streams with automatic reconnection
|
||||
- **Camera stream reconnection** - Improved detection of stuck camera streams with automatic reconnection:
|
||||
- Periodic stall detection checks every 5 seconds
|
||||
- Automatic reconnection when stream stops receiving frames
|
||||
- New `/api/v1/printers/{id}/camera/status` endpoint for stream health monitoring
|
||||
- **Spoolman sync** - Fixed sync issues with Spoolman integration:
|
||||
- Now only matches Bambu Lab vendor filaments when syncing
|
||||
- Prevents incorrect matching with third-party filaments by color alone
|
||||
- Improved filament matching accuracy
|
||||
- **Archive card context menu** - Fixed context menu positioning issues (#46)
|
||||
- **Printer card cover image** - Fixed wrong cover image displayed for multi-plate print files
|
||||
- **Skip objects modal** - Fixed object ID markers not correctly positioned over build plate preview:
|
||||
- Now uses `bbox_all` from plate metadata for accurate coordinate mapping
|
||||
- Markers correctly position relative to the actual object bounds in the preview image
|
||||
- Works correctly for multi-plate projects
|
||||
- **Active AMS slot display (H2D)** - Fixed incorrect slot display on H2D printers with multiple AMS units:
|
||||
- Now parses `snow` field from `device.extruder.info` which contains actual AMS ID
|
||||
- Previously picked first AMS on the extruder, causing wrong display when multiple AMS connected
|
||||
- Example: Switching from B2 to C1 now correctly shows C1 instead of A1
|
||||
- **Spoolman link function** - Improved "Link to Spoolman" in AMS slot detail modal
|
||||
- **GCode viewer** - Minor improvements to GCode visualization
|
||||
- **Cover image retrieval** - Improved reliability of cover image extraction
|
||||
- **Virtual printer SSDP model codes** - Corrected model codes for slicer compatibility:
|
||||
- C11=P1P, C12=P1S (were incorrectly swapped)
|
||||
- N7=P2S (was incorrectly using C13 which is X1E)
|
||||
|
||||
@@ -144,6 +144,9 @@ class PrinterState:
|
||||
ams_mapping: list = field(default_factory=list)
|
||||
# Per-AMS extruder map: {ams_id: extruder_id} where 0=right, 1=left
|
||||
ams_extruder_map: dict = field(default_factory=dict)
|
||||
# H2D per-extruder tray_now from snow field: {extruder_id: normalized_global_tray_id}
|
||||
# snow encodes AMS ID in high byte: ams_id = snow >> 8, slot = snow & 0xFF
|
||||
h2d_extruder_snow: dict = field(default_factory=dict)
|
||||
# Timestamp of last AMS data update (for RFID refresh detection)
|
||||
last_ams_update: float = 0.0
|
||||
# Printable objects for skip object functionality: {identify_id: object_name}
|
||||
@@ -771,71 +774,73 @@ class BambuMQTTClient:
|
||||
# Clear pending target since it's stale
|
||||
self.state.pending_tray_target = None
|
||||
else:
|
||||
# No pending target - use active_extruder + ams_extruder_map to disambiguate
|
||||
# Find which AMS is connected to the active extruder
|
||||
# No pending target - use h2d_extruder_snow for accurate disambiguation
|
||||
# H2D sends snow field in device.extruder.info with AMS ID in high byte
|
||||
active_ext = self.state.active_extruder # 0=right, 1=left
|
||||
ams_map = self.state.ams_extruder_map # {ams_id: extruder_id}
|
||||
|
||||
# First, check if current tray_now is already a valid global ID
|
||||
# that matches this slot AND is connected to the active extruder
|
||||
current_tray = self.state.tray_now
|
||||
if current_tray > 3 and current_tray != 255 and (current_tray % 4) == parsed_tray_now:
|
||||
current_ams = current_tray // 4
|
||||
current_ams_extruder = ams_map.get(str(current_ams))
|
||||
if current_ams_extruder == active_ext:
|
||||
# Current tray is valid, matches slot, and on correct extruder - keep it
|
||||
logger.debug(
|
||||
f"[{self.serial_number}] H2D tray_now: keeping existing global ID {current_tray} "
|
||||
f"(slot {parsed_tray_now}, AMS {current_ams} on extruder {active_ext})"
|
||||
)
|
||||
# Don't update tray_now - it's already correct
|
||||
pass
|
||||
else:
|
||||
# Current AMS is on wrong extruder - need to find correct AMS
|
||||
active_ams_id = None
|
||||
for ams_id_str, ext_id in ams_map.items():
|
||||
if ext_id == active_ext:
|
||||
try:
|
||||
active_ams_id = int(ams_id_str)
|
||||
except ValueError:
|
||||
pass
|
||||
break
|
||||
if active_ams_id is not None:
|
||||
global_tray_id = active_ams_id * 4 + parsed_tray_now
|
||||
# Best source: use snow value from device.extruder.info if available
|
||||
snow_tray = self.state.h2d_extruder_snow.get(active_ext)
|
||||
if snow_tray is not None and snow_tray != 255:
|
||||
# snow_tray is already normalized to global ID
|
||||
# Verify the slot matches what we see in tray_now
|
||||
snow_slot = snow_tray % 4 if snow_tray < 128 else -1
|
||||
if snow_slot == parsed_tray_now:
|
||||
if self.state.tray_now != snow_tray:
|
||||
logger.info(
|
||||
f"[{self.serial_number}] H2D tray_now disambiguation: "
|
||||
f"slot {parsed_tray_now} + active_extruder {active_ext} -> AMS {active_ams_id} -> global ID {global_tray_id}"
|
||||
f"[{self.serial_number}] H2D tray_now from snow: "
|
||||
f"extruder[{active_ext}] snow={snow_tray} (slot {snow_slot})"
|
||||
)
|
||||
self.state.tray_now = global_tray_id
|
||||
else:
|
||||
logger.warning(
|
||||
f"[{self.serial_number}] H2D tray_now: no ams_extruder_map for active_extruder {active_ext}"
|
||||
)
|
||||
self.state.tray_now = parsed_tray_now
|
||||
self.state.tray_now = snow_tray
|
||||
else:
|
||||
# Slot mismatch - snow field may not have updated yet, trust snow
|
||||
logger.debug(
|
||||
f"[{self.serial_number}] H2D tray_now: ams.tray_now slot {parsed_tray_now} "
|
||||
f"!= snow slot {snow_slot}, using snow value {snow_tray}"
|
||||
)
|
||||
self.state.tray_now = snow_tray
|
||||
else:
|
||||
# No valid current tray - find an AMS connected to the active extruder
|
||||
active_ams_id = None
|
||||
# Fallback: snow not available, use ams_extruder_map (less reliable)
|
||||
ams_map = self.state.ams_extruder_map
|
||||
# Find ALL AMS units on the active extruder
|
||||
ams_on_extruder = []
|
||||
for ams_id_str, ext_id in ams_map.items():
|
||||
if ext_id == active_ext:
|
||||
try:
|
||||
active_ams_id = int(ams_id_str)
|
||||
ams_on_extruder.append(int(ams_id_str))
|
||||
except ValueError:
|
||||
pass
|
||||
break
|
||||
|
||||
if active_ams_id is not None:
|
||||
# Calculate global tray ID using the active AMS
|
||||
if len(ams_on_extruder) == 1:
|
||||
# Single AMS on this extruder - unambiguous
|
||||
active_ams_id = ams_on_extruder[0]
|
||||
global_tray_id = active_ams_id * 4 + parsed_tray_now
|
||||
logger.info(
|
||||
f"[{self.serial_number}] H2D tray_now disambiguation: "
|
||||
f"slot {parsed_tray_now} + active_extruder {active_ext} -> AMS {active_ams_id} -> global ID {global_tray_id}"
|
||||
f"[{self.serial_number}] H2D tray_now fallback: "
|
||||
f"slot {parsed_tray_now} + single AMS {active_ams_id} -> global ID {global_tray_id}"
|
||||
)
|
||||
self.state.tray_now = global_tray_id
|
||||
elif len(ams_on_extruder) > 1:
|
||||
# Multiple AMS on this extruder - keep current if valid, else use slot as-is
|
||||
current_tray = self.state.tray_now
|
||||
current_ams = current_tray // 4 if current_tray < 128 else -1
|
||||
if current_ams in ams_on_extruder and (current_tray % 4) == parsed_tray_now:
|
||||
# Current is valid and matches slot - keep it
|
||||
logger.debug(
|
||||
f"[{self.serial_number}] H2D tray_now: multiple AMS {ams_on_extruder}, "
|
||||
f"keeping current {current_tray} (matches slot {parsed_tray_now})"
|
||||
)
|
||||
else:
|
||||
# Can't disambiguate - use slot as-is (will be wrong for non-first AMS)
|
||||
logger.warning(
|
||||
f"[{self.serial_number}] H2D tray_now: multiple AMS {ams_on_extruder} on extruder {active_ext}, "
|
||||
f"no snow field, using slot {parsed_tray_now} (may be incorrect)"
|
||||
)
|
||||
self.state.tray_now = parsed_tray_now
|
||||
else:
|
||||
# Fallback: use slot as-is
|
||||
# No AMS on this extruder - use slot as-is
|
||||
logger.warning(
|
||||
f"[{self.serial_number}] H2D tray_now: no ams_extruder_map for active_extruder {active_ext}, "
|
||||
f"using slot {parsed_tray_now} as global ID (may be incorrect for multi-AMS)"
|
||||
f"[{self.serial_number}] H2D tray_now: no AMS on extruder {active_ext}, "
|
||||
f"using slot {parsed_tray_now}"
|
||||
)
|
||||
self.state.tray_now = parsed_tray_now
|
||||
else:
|
||||
@@ -1247,6 +1252,45 @@ class BambuMQTTClient:
|
||||
if not respect_local_target:
|
||||
temps["nozzle_target"] = 0.0
|
||||
temps["nozzle_heating"] = False # Direct = not heating
|
||||
# Parse H2D snow field (slot now) for accurate tray_now disambiguation
|
||||
# snow encodes AMS ID in high byte: ams_id = snow >> 8, slot = snow & 0xFF
|
||||
if has_h2d_extruder_info:
|
||||
for ext_info in extruder_info:
|
||||
ext_id = ext_info.get("id")
|
||||
snow = ext_info.get("snow")
|
||||
if ext_id is not None and snow is not None and ext_id <= 1:
|
||||
# Normalize H2D snow value to global tray ID
|
||||
ams_id = snow >> 8
|
||||
slot = snow & 0xFF
|
||||
if 0 <= ams_id <= 3:
|
||||
# Regular AMS slot
|
||||
global_tray = ams_id * 4 + (slot & 0x03)
|
||||
old_val = self.state.h2d_extruder_snow.get(ext_id)
|
||||
if old_val != global_tray:
|
||||
logger.info(
|
||||
f"[{self.serial_number}] H2D extruder[{ext_id}] snow: "
|
||||
f"raw={snow} (AMS {ams_id} slot {slot}) -> global tray {global_tray}"
|
||||
)
|
||||
self.state.h2d_extruder_snow[ext_id] = global_tray
|
||||
elif ams_id == 254 or ams_id == 255:
|
||||
# External spool or unloaded
|
||||
normalized = 254 if slot != 255 else 255
|
||||
old_val = self.state.h2d_extruder_snow.get(ext_id)
|
||||
if old_val != normalized:
|
||||
logger.info(
|
||||
f"[{self.serial_number}] H2D extruder[{ext_id}] snow: "
|
||||
f"raw={snow} -> {'external' if normalized == 254 else 'unloaded'}"
|
||||
)
|
||||
self.state.h2d_extruder_snow[ext_id] = normalized
|
||||
elif 128 <= ams_id <= 135:
|
||||
# External spool with hub mapping
|
||||
old_val = self.state.h2d_extruder_snow.get(ext_id)
|
||||
if old_val != ams_id:
|
||||
logger.info(
|
||||
f"[{self.serial_number}] H2D extruder[{ext_id}] snow: "
|
||||
f"raw={snow} -> external hub {ams_id}"
|
||||
)
|
||||
self.state.h2d_extruder_snow[ext_id] = ams_id
|
||||
# Parse bed heating state from device.bed.info.temp encoding
|
||||
# temp > 500 means encoded (target*65536+current), heating = target > 0 AND current < target
|
||||
bed_data = device.get("bed", {})
|
||||
|
||||
Reference in New Issue
Block a user