fix(spool-assign): defer MQTT for empty AMS slot, replay on physical insert

The SpoolBuddy "weigh-then-assign" workflow tried to configure an empty AMS
  slot at assign time, but Bambu firmware silently drops ams_filament_setting
  and extrusion_cali_sel for unloaded slots — the MQTT calls completed and the
  modal closed, yet BambuStudio kept showing the slot as default-PLA forever.

  assign_spool now detects an empty target slot (fingerprint_type empty) and
  persists the SpoolAssignment without publishing MQTT, returning a new
  pending_config flag so the frontend can swap "Assigned!" for "Slot will
  configure when you insert the spool." on_ams_change watches for the slot
  to load (state == 11, which fires for 3rd-party tags too even when
  tray_type stays empty) and replays the deferred ams_filament_setting +
  extrusion_cali_sel — including the printer-kp realignment that converts
  PFUS-prefix cloud user presets to the P-prefix local-preset filament_id
  the slicer actually accepts.

  The full assign-time MQTT block was extracted into
  apply_spool_to_slot_via_mqtt so both the assign endpoint and the
  on_ams_change replay path use the same resolution logic; the helper takes
  ~270 lines of duplication out of assign_spool.
This commit is contained in:
maziggy
2026-05-04 16:02:18 +02:00
parent 8986343999
commit b42aaca521
17 changed files with 659 additions and 304 deletions
+1
View File
@@ -2346,6 +2346,7 @@ export interface SpoolAssignment {
fingerprint_type: string | null;
spool?: InventorySpool | null;
configured: boolean;
pending_config?: boolean; // Slot was empty at assign time; will configure on insert
created_at: string;
ams_label?: string | null; // User-defined friendly name for the AMS unit
}
@@ -167,27 +167,37 @@ export function AssignToAmsModal({ isOpen, onClose, spool, printerId }: AssignTo
}, [isDualNozzle, amsExtruderMap]);
// Assign spool to AMS slot — single API call, backend handles both
// DB record AND MQTT auto-configuration (same as SpoolStation).
// DB record AND MQTT auto-configuration (same as SpoolStation). When the
// target slot is currently empty, the backend persists the assignment and
// skips the MQTT publish (firmware drops it anyway); on_ams_change re-fires
// the full configuration when filament is later inserted. The response's
// `pending_config` flag distinguishes that from the immediate-apply path
// so we can adjust the success toast.
const configureMutation = useMutation({
mutationFn: async ({ amsId, trayId }: { amsId: number; trayId: number }) => {
if (!printerId) throw new Error('No printer selected');
await api.assignSpool({
return await api.assignSpool({
spool_id: spool.id,
printer_id: printerId,
ams_id: amsId,
tray_id: trayId,
});
// Slot preset mapping is now saved by the backend in assign_spool()
// after successful MQTT configuration, using the authoritative
// slicer_filament_name from the spool record.
},
onSuccess: () => {
onSuccess: (assignment) => {
setStatusType('success');
setStatusMessage(t('spoolbuddy.modal.assignSuccess', 'Assigned!'));
if (assignment?.pending_config) {
setStatusMessage(
t(
'spoolbuddy.modal.assignPendingInsert',
'Assigned. Slot will configure when you insert the spool.',
),
);
} else {
setStatusMessage(t('spoolbuddy.modal.assignSuccess', 'Assigned!'));
}
queryClient.invalidateQueries({ queryKey: ['slotPresets'] });
setTimeout(() => onClose(), 1500);
setTimeout(() => onClose(), assignment?.pending_config ? 2500 : 1500);
},
onError: (err) => {
setStatusType('error');
+1
View File
@@ -5032,6 +5032,7 @@ export default {
assign: 'Zuweisen',
assigning: 'Zuweisen...',
assignSuccess: 'Zugewiesen!',
assignPendingInsert: 'Zugewiesen. Slot wird beim Einsetzen der Spule konfiguriert.',
assignError: 'Fehler beim Zuweisen. Bitte erneut versuchen.',
noPrinterSelected: 'Drucker auswählen...',
noAmsDetected: 'Kein AMS an diesem Drucker erkannt',
+1
View File
@@ -5041,6 +5041,7 @@ export default {
assign: 'Assign',
assigning: 'Assigning...',
assignSuccess: 'Assigned!',
assignPendingInsert: 'Assigned. Slot will configure when you insert the spool.',
assignError: 'Failed to assign spool. Please try again.',
noPrinterSelected: 'Select a printer...',
noAmsDetected: 'No AMS detected on this printer',
+1
View File
@@ -5020,6 +5020,7 @@ export default {
assign: 'Assigner',
assigning: 'Attribution...',
assignSuccess: 'Assigné !',
assignPendingInsert: 'Assigné. Le slot sera configuré lors de l\'insertion de la bobine.',
assignError: 'Échec de l\'attribution de la bobine. Veuillez réessayer.',
noPrinterSelected: 'Sélectionner une imprimante...',
noAmsDetected: 'Aucun AMS détecté sur cette imprimante',
+1
View File
@@ -5019,6 +5019,7 @@ export default {
assign: 'Assegna',
assigning: 'Assegnazione...',
assignSuccess: 'Assegnato!',
assignPendingInsert: 'Assegnato. Lo slot verrà configurato all\'inserimento della bobina.',
assignError: 'Impossibile assegnare la bobina. Riprovare.',
noPrinterSelected: 'Seleziona una stampante...',
noAmsDetected: 'Nessun AMS rilevato su questa stampante',
+1
View File
@@ -5032,6 +5032,7 @@ export default {
assign: '割り当て',
assigning: '割り当て中...',
assignSuccess: '割り当て完了!',
assignPendingInsert: '割り当てました。スプールを挿入したときにスロットが設定されます。',
assignError: 'スプールの割り当てに失敗しました。再試行してください。',
noPrinterSelected: 'プリンターを選択...',
noAmsDetected: 'このプリンターにAMSが検出されません',
+1
View File
@@ -5019,6 +5019,7 @@ export default {
assign: 'Atribuir',
assigning: 'Atribuindo...',
assignSuccess: 'Atribuído!',
assignPendingInsert: 'Atribuído. O slot será configurado quando você inserir o carretel.',
assignError: 'Falha ao atribuir carretel. Tente novamente.',
noPrinterSelected: 'Selecionar uma impressora...',
noAmsDetected: 'Nenhum AMS detectado nesta impressora',
+1
View File
@@ -5019,6 +5019,7 @@ export default {
assign: '分配',
assigning: '分配中...',
assignSuccess: '已分配!',
assignPendingInsert: '已分配。插入耗材后将配置槽位。',
assignError: '分配耗材失败。请重试。',
noPrinterSelected: '选择打印机...',
noAmsDetected: '此打印机未检测到 AMS',
+1
View File
@@ -5019,6 +5019,7 @@ export default {
assign: '分配',
assigning: '分配中...',
assignSuccess: '已分配!',
assignPendingInsert: '已分配。插入耗材後將設定槽位。',
assignError: '分配耗材失敗。請重試。',
noPrinterSelected: '選擇印表機...',
noAmsDetected: '此印表機未偵測到 AMS',