mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-09-30 03:01:21 +02:00
Refactoring AMS module
This commit is contained in:
@@ -310,6 +310,7 @@ async def get_printer_status(printer_id: int, db: AsyncSession = Depends(get_db)
|
||||
ams_status_main=state.ams_status_main,
|
||||
ams_status_sub=state.ams_status_sub,
|
||||
mc_print_sub_stage=state.mc_print_sub_stage,
|
||||
last_ams_update=state.last_ams_update,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -143,3 +143,5 @@ class PrinterStatus(BaseModel):
|
||||
ams_status_sub: int = 0
|
||||
# mc_print_sub_stage - filament change step indicator used by OrcaSlicer/BambuStudio
|
||||
mc_print_sub_stage: int = 0
|
||||
# Timestamp of last AMS data update (for RFID refresh detection)
|
||||
last_ams_update: float = 0.0
|
||||
|
||||
@@ -130,6 +130,8 @@ 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)
|
||||
# Timestamp of last AMS data update (for RFID refresh detection)
|
||||
last_ams_update: float = 0.0
|
||||
|
||||
|
||||
# Stage name mapping from BambuStudio DeviceManager.cpp
|
||||
@@ -796,6 +798,8 @@ class BambuMQTTClient:
|
||||
|
||||
# Store AMS data in raw_data so it's accessible via API
|
||||
self.state.raw_data["ams"] = ams_list
|
||||
# Update timestamp for RFID refresh detection (frontend can detect "new data arrived")
|
||||
self.state.last_ams_update = time.time()
|
||||
logger.debug(f"[{self.serial_number}] Stored AMS data with {len(ams_list)} units")
|
||||
|
||||
# Extract ams_extruder_map from each AMS unit's info field
|
||||
|
||||
@@ -147,6 +147,8 @@ export interface PrinterStatus {
|
||||
ams_status_sub: number;
|
||||
// mc_print_sub_stage - filament change step indicator used by OrcaSlicer/BambuStudio
|
||||
mc_print_sub_stage: number;
|
||||
// Timestamp of last AMS data update (for RFID refresh detection)
|
||||
last_ams_update: number;
|
||||
}
|
||||
|
||||
export interface PrinterCreate {
|
||||
|
||||
@@ -934,6 +934,7 @@ export function AMSSectionDual({ printerId, printerModel, status, nozzleCount }:
|
||||
// Get ams_status values from printer status
|
||||
const amsStatusMain = status?.ams_status_main ?? 0;
|
||||
const trayNow = status?.tray_now ?? 255;
|
||||
const lastAmsUpdate = status?.last_ams_update ?? 0;
|
||||
|
||||
// AMS Operations hook - manages state machine for refresh/load/unload
|
||||
const amsOps = useAmsOperations({
|
||||
@@ -941,6 +942,7 @@ export function AMSSectionDual({ printerId, printerModel, status, nozzleCount }:
|
||||
amsUnits,
|
||||
amsStatusMain,
|
||||
trayNow,
|
||||
lastAmsUpdate,
|
||||
onToast: showToast,
|
||||
});
|
||||
|
||||
@@ -967,60 +969,7 @@ export function AMSSectionDual({ printerId, printerModel, status, nozzleCount }:
|
||||
}
|
||||
}, [status?.tray_now]);
|
||||
|
||||
// Watch for AMS data updates to clear the refresh spinner
|
||||
// When the printer reports updated tray data after RFID read, clear the spinner
|
||||
const prevAmsDataRef = useRef<string>('');
|
||||
useEffect(() => {
|
||||
if (!refreshingSlotState) return;
|
||||
|
||||
// Find the refreshing slot's current data
|
||||
const { amsId, trayId, startTime } = refreshingSlotState;
|
||||
const unit = amsUnits.find(u => u.id === amsId);
|
||||
const tray = unit?.tray?.find(t => t.id === trayId);
|
||||
|
||||
if (!tray) return;
|
||||
|
||||
// Create a signature of the tray data to detect changes
|
||||
const traySignature = JSON.stringify({
|
||||
tag_uid: tray.tag_uid,
|
||||
tray_uuid: tray.tray_uuid,
|
||||
tray_id_name: tray.tray_id_name,
|
||||
tray_type: tray.tray_type,
|
||||
tray_color: tray.tray_color,
|
||||
});
|
||||
|
||||
// If we have previous data and it changed, the refresh is complete
|
||||
// Also require at least 500ms to have passed (to avoid false positives from initial render)
|
||||
const elapsed = Date.now() - startTime;
|
||||
if (prevAmsDataRef.current && prevAmsDataRef.current !== traySignature && elapsed > 500) {
|
||||
console.log(`[AMSSectionDual] RFID refresh complete for AMS ${amsId} tray ${trayId} (took ${elapsed}ms)`);
|
||||
setRefreshingSlotState(null);
|
||||
}
|
||||
|
||||
// Update the ref for next comparison
|
||||
prevAmsDataRef.current = traySignature;
|
||||
}, [refreshingSlotState, amsUnits]);
|
||||
|
||||
const loadMutation = useMutation({
|
||||
mutationFn: ({ trayId, extruderId }: { trayId: number; extruderId?: number }) =>
|
||||
api.amsLoadFilament(printerId, trayId, extruderId),
|
||||
onSuccess: (data, { trayId, extruderId }) => {
|
||||
console.log(`[AMSSectionDual] Load filament success (tray ${trayId}, extruder ${extruderId}):`, data);
|
||||
},
|
||||
onError: (error, { trayId, extruderId }) => {
|
||||
console.error(`[AMSSectionDual] Load filament error (tray ${trayId}, extruder ${extruderId}):`, error);
|
||||
},
|
||||
});
|
||||
|
||||
const unloadMutation = useMutation({
|
||||
mutationFn: () => api.amsUnloadFilament(printerId),
|
||||
onSuccess: (data) => {
|
||||
console.log(`[AMSSectionDual] Unload filament success:`, data);
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error(`[AMSSectionDual] Unload filament error:`, error);
|
||||
},
|
||||
});
|
||||
|
||||
// Handle tray selection
|
||||
const handleTraySelect = (trayId: number | null) => {
|
||||
@@ -1053,132 +1002,43 @@ export function AMSSectionDual({ printerId, printerModel, status, nozzleCount }:
|
||||
console.log(`[AMSSectionDual] handleLoad called, selectedTray: ${selectedTray}`);
|
||||
if (selectedTray !== null) {
|
||||
const extruderId = getExtruderIdForTray(selectedTray);
|
||||
console.log(`[AMSSectionDual] Calling loadMutation.mutate(tray: ${selectedTray}, extruder: ${extruderId})`);
|
||||
// Set ref synchronously FIRST (refs update immediately, before MQTT can respond)
|
||||
intendedOperationRef.current = 'load';
|
||||
// Show filament change card immediately
|
||||
setUserFilamentChange({ isLoading: true, targetTrayId: selectedTray });
|
||||
loadMutation.mutate({ trayId: selectedTray, extruderId });
|
||||
console.log(`[AMSSectionDual] Starting load via hook: tray ${selectedTray}, extruder ${extruderId}`);
|
||||
amsOps.startLoad(selectedTray, extruderId);
|
||||
}
|
||||
};
|
||||
|
||||
const handleUnload = () => {
|
||||
console.log(`[AMSSectionDual] handleUnload called, printerId: ${printerId}, trayNow: ${status?.tray_now}`);
|
||||
// Set ref synchronously FIRST (refs update immediately, before MQTT can respond)
|
||||
intendedOperationRef.current = 'unload';
|
||||
// Show filament change card immediately (no target tray for unload)
|
||||
setUserFilamentChange({ isLoading: false, targetTrayId: null });
|
||||
console.log(`[AMSSectionDual] Calling unloadMutation.mutate()`);
|
||||
unloadMutation.mutate();
|
||||
console.log(`[AMSSectionDual] handleUnload called, printerId: ${printerId}, trayNow: ${trayNow}`);
|
||||
amsOps.startUnload();
|
||||
};
|
||||
|
||||
// Callback for FilamentChangeCard to close itself
|
||||
const handleFilamentChangeComplete = () => {
|
||||
console.log(`[AMSSectionDual] FilamentChangeCard completed, closing card`);
|
||||
intendedOperationRef.current = null; // Clear the synchronous ref
|
||||
setUserFilamentChange(null);
|
||||
console.log(`[AMSSectionDual] FilamentChangeCard completed, resetting operation`);
|
||||
amsOps.reset();
|
||||
};
|
||||
|
||||
const isLoading = loadMutation.isPending || unloadMutation.isPending;
|
||||
|
||||
// Handlers for modals and actions
|
||||
const handleHumidityClick = (humidity: number, temp: number) => {
|
||||
setHumidityModal({ humidity, temp });
|
||||
};
|
||||
|
||||
const refreshMutation = useMutation({
|
||||
mutationFn: ({ amsId, trayId }: { amsId: number; trayId: number }) =>
|
||||
api.refreshAmsTray(printerId, amsId, trayId),
|
||||
onSuccess: (data, variables) => {
|
||||
console.log(`[AMSSectionDual] Tray refresh response (AMS ${variables.amsId}, Tray ${variables.trayId}):`, data);
|
||||
if (data.success) {
|
||||
showToast(data.message || 'RFID refresh started', 'success');
|
||||
} else {
|
||||
showToast(data.message || 'Failed to refresh tray', 'error');
|
||||
}
|
||||
},
|
||||
onError: (error, variables) => {
|
||||
console.error(`[AMSSectionDual] Tray refresh error (AMS ${variables.amsId}, Tray ${variables.trayId}):`, error);
|
||||
showToast('Failed to refresh tray', 'error');
|
||||
},
|
||||
});
|
||||
|
||||
const handleSlotRefresh = (amsId: number, slotId: number) => {
|
||||
// Trigger RFID re-read for the specific tray
|
||||
console.log(`[AMSSectionDual] Slot refresh triggered: AMS ${amsId}, Slot ${slotId}, printerId: ${printerId}`);
|
||||
// Reset the previous data ref so we can detect the next change
|
||||
prevAmsDataRef.current = '';
|
||||
// Show spinner immediately - will be cleared when AMS data updates from MQTT
|
||||
const startTime = Date.now();
|
||||
setRefreshingSlotState({ amsId, trayId: slotId, startTime });
|
||||
refreshMutation.mutate({ amsId, trayId: slotId });
|
||||
// Fallback timeout (15s) in case data doesn't change (e.g., same spool re-read)
|
||||
setTimeout(() => {
|
||||
setRefreshingSlotState(prev => {
|
||||
if (prev && prev.startTime === startTime) {
|
||||
console.log(`[AMSSectionDual] RFID refresh timeout for AMS ${amsId} tray ${slotId}`);
|
||||
return null;
|
||||
}
|
||||
return prev;
|
||||
});
|
||||
}, 15000);
|
||||
console.log(`[AMSSectionDual] Slot refresh triggered: AMS ${amsId}, Slot ${slotId}`);
|
||||
amsOps.startRefresh(amsId, slotId);
|
||||
};
|
||||
|
||||
const handleEyeClick = (tray: AMSTray, slotLabel: string, amsId: number) => {
|
||||
setMaterialsModal({ tray, slotLabel, amsId });
|
||||
};
|
||||
|
||||
// Determine if we're in a filament change state (from MQTT ams_status)
|
||||
// ams_status_main: 0=idle, 1=filament_change, 2=rfid_identifying, 3=assist, 4=calibration
|
||||
// mc_print_sub_stage: step indicator used by BambuStudio/OrcaSlicer for filament change progress
|
||||
const amsStatusMain = status?.ams_status_main ?? 0;
|
||||
// Show FilamentChangeCard when operation is in progress (LOADING or UNLOADING state)
|
||||
const isMqttFilamentChangeActive = amsStatusMain === 1;
|
||||
|
||||
// Auto-close card when operation completes
|
||||
// Track when we transition from filament change active to idle (ams_status_main 1 -> 0)
|
||||
const prevAmsStatusMainRef = useRef(amsStatusMain);
|
||||
// Track if we've seen ams_status_main = 1 since the user clicked load/unload
|
||||
// This prevents premature card closure on brief status glitches
|
||||
const operationStartedRef = useRef(false);
|
||||
useEffect(() => {
|
||||
const wasActive = prevAmsStatusMainRef.current === 1;
|
||||
|
||||
if (isMqttFilamentChangeActive) {
|
||||
// MQTT is now reporting filament change - operation has started
|
||||
operationStartedRef.current = true;
|
||||
// Clear user-triggered state, card will continue showing because isMqttFilamentChangeActive is true
|
||||
setUserFilamentChange(null);
|
||||
} else if (wasActive && !isMqttFilamentChangeActive && operationStartedRef.current) {
|
||||
// Transition from active (1) to idle (0), AND we've confirmed operation started
|
||||
// Close the card by clearing user state and the synchronous ref
|
||||
console.log(`[AMSSectionDual] ams_status_main transitioned 1->0, operation was started, closing card`);
|
||||
intendedOperationRef.current = null;
|
||||
operationStartedRef.current = false;
|
||||
setUserFilamentChange(null);
|
||||
}
|
||||
|
||||
// Update previous status for next comparison
|
||||
prevAmsStatusMainRef.current = amsStatusMain;
|
||||
}, [isMqttFilamentChangeActive, amsStatusMain]);
|
||||
|
||||
// Show FilamentChangeCard when either MQTT reports active ams_status OR user just clicked load/unload
|
||||
const showFilamentChangeCard = isMqttFilamentChangeActive || userFilamentChange !== null;
|
||||
const showFilamentChangeCard = amsOps.state === 'LOADING' || amsOps.state === 'UNLOADING' || isMqttFilamentChangeActive;
|
||||
|
||||
// Get the loaded tray info for wire coloring
|
||||
// Wire coloring should show the path from the currently loaded filament to the extruder
|
||||
// But ONLY if the currently displayed AMS panel is the one with the loaded filament
|
||||
const trayNow = status?.tray_now ?? 255;
|
||||
|
||||
// Determine if loading or unloading for the card display
|
||||
// Priority: 1) Synchronous ref (set immediately on click), 2) React state, 3) MQTT signals
|
||||
// The ref prevents race conditions where MQTT updates arrive before React state updates
|
||||
const amsStatusSub = status?.ams_status_sub ?? 0;
|
||||
const SUB_RETRACT = 4; // Only happens during unload
|
||||
const isFilamentLoading =
|
||||
intendedOperationRef.current === 'load' ? true :
|
||||
intendedOperationRef.current === 'unload' ? false :
|
||||
userFilamentChange !== null ? userFilamentChange.isLoading :
|
||||
!(amsStatusSub === SUB_RETRACT || trayNow === 255); // Unload if retracting or tray_now is 255
|
||||
const getLoadedTrayInfo = (): {
|
||||
leftActiveSlot: number | null;
|
||||
rightActiveSlot: number | null;
|
||||
@@ -1235,8 +1095,10 @@ export function AMSSectionDual({ printerId, printerModel, status, nozzleCount }:
|
||||
|
||||
const { leftActiveSlot, rightActiveSlot, leftFilamentColor, rightFilamentColor } = getLoadedTrayInfo();
|
||||
|
||||
// Use state-based refreshing slot for spinner visibility (minimum 1.5s display time)
|
||||
const refreshingSlot = refreshingSlotState;
|
||||
// Create refreshingSlot object from hook state for spinner visibility
|
||||
const refreshingSlot = amsOps.state === 'REFRESHING' && amsOps.context?.refreshTarget
|
||||
? { amsId: amsOps.context.refreshTarget.amsId, trayId: amsOps.context.refreshTarget.trayId }
|
||||
: null;
|
||||
|
||||
return (
|
||||
<div className="bg-bambu-dark-tertiary rounded-[10px] p-3">
|
||||
@@ -1304,33 +1166,33 @@ export function AMSSectionDual({ printerId, printerModel, status, nozzleCount }:
|
||||
<div className="flex-1" />
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
{/* Unload button: disabled if not connected, printing, mutation pending, or no filament loaded */}
|
||||
{/* Unload button: disabled if not connected, printing, operation in progress, or no filament loaded */}
|
||||
<button
|
||||
onClick={handleUnload}
|
||||
disabled={!isConnected || isPrinting || isLoading || trayNow === 255}
|
||||
disabled={!isConnected || isPrinting || amsOps.isOperationInProgress || trayNow === 255}
|
||||
className={`px-7 py-2.5 rounded-lg text-sm transition-colors border ${
|
||||
!isConnected || isPrinting || isLoading || trayNow === 255
|
||||
!isConnected || isPrinting || amsOps.isOperationInProgress || trayNow === 255
|
||||
? 'bg-bambu-gray-dark text-gray-500 border-bambu-gray-dark cursor-not-allowed'
|
||||
: 'bg-bambu-dark-secondary text-white border-bambu-dark-tertiary hover:bg-bambu-dark'
|
||||
}`}
|
||||
>
|
||||
{unloadMutation.isPending ? (
|
||||
{amsOps.state === 'UNLOADING' ? (
|
||||
<Loader2 className="w-4 h-4 animate-spin" />
|
||||
) : (
|
||||
'Unload'
|
||||
)}
|
||||
</button>
|
||||
{/* Load button: disabled if not connected, printing, mutation pending, no tray selected, or selected tray is already loaded */}
|
||||
{/* Load button: disabled if not connected, printing, operation in progress, no tray selected, or selected tray is already loaded */}
|
||||
<button
|
||||
onClick={handleLoad}
|
||||
disabled={!isConnected || isPrinting || selectedTray === null || isLoading || selectedTray === trayNow}
|
||||
disabled={!isConnected || isPrinting || selectedTray === null || amsOps.isOperationInProgress || selectedTray === trayNow}
|
||||
className={`px-7 py-2.5 rounded-lg text-sm transition-colors border ${
|
||||
!isConnected || isPrinting || selectedTray === null || isLoading || selectedTray === trayNow
|
||||
!isConnected || isPrinting || selectedTray === null || amsOps.isOperationInProgress || selectedTray === trayNow
|
||||
? 'bg-bambu-gray-dark text-gray-500 border-bambu-gray-dark cursor-not-allowed'
|
||||
: 'bg-bambu-dark-secondary text-white border-bambu-dark-tertiary hover:bg-bambu-dark'
|
||||
}`}
|
||||
>
|
||||
{loadMutation.isPending ? (
|
||||
{amsOps.state === 'LOADING' ? (
|
||||
<Loader2 className="w-4 h-4 animate-spin" />
|
||||
) : (
|
||||
'Load'
|
||||
@@ -1340,20 +1202,20 @@ export function AMSSectionDual({ printerId, printerModel, status, nozzleCount }:
|
||||
</div>
|
||||
|
||||
{/* Error messages */}
|
||||
{(loadMutation.error || unloadMutation.error) && (
|
||||
{(amsOps.loadError || amsOps.unloadError) && (
|
||||
<p className="mt-2 text-sm text-red-500 text-center">
|
||||
{(loadMutation.error || unloadMutation.error)?.message}
|
||||
{(amsOps.loadError || amsOps.unloadError)?.message}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Filament Change Progress Card - appears during load/unload operations */}
|
||||
{showFilamentChangeCard && (
|
||||
<FilamentChangeCard
|
||||
isLoading={isFilamentLoading}
|
||||
isLoading={amsOps.isLoadOperation}
|
||||
amsStatusMain={amsStatusMain}
|
||||
amsStatusSub={status?.ams_status_sub ?? 0}
|
||||
trayNow={trayNow}
|
||||
targetTrayId={userFilamentChange?.targetTrayId ?? null}
|
||||
targetTrayId={amsOps.loadTargetTrayId}
|
||||
onComplete={handleFilamentChangeComplete}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -43,6 +43,7 @@ interface UseAmsOperationsProps {
|
||||
amsUnits: AMSUnit[];
|
||||
amsStatusMain: number;
|
||||
trayNow: number;
|
||||
lastAmsUpdate: number; // Backend timestamp for detecting AMS data updates
|
||||
onToast: (message: string, type: 'success' | 'error') => void;
|
||||
}
|
||||
|
||||
@@ -82,6 +83,7 @@ export function useAmsOperations({
|
||||
amsUnits,
|
||||
amsStatusMain,
|
||||
trayNow,
|
||||
lastAmsUpdate,
|
||||
onToast,
|
||||
}: UseAmsOperationsProps): UseAmsOperationsReturn {
|
||||
const [state, setState] = useState<OperationState>('IDLE');
|
||||
@@ -89,7 +91,10 @@ export function useAmsOperations({
|
||||
|
||||
// Track previous values for transition detection
|
||||
const prevAmsStatusMainRef = useRef(amsStatusMain);
|
||||
const prevTrayDataRef = useRef<string>('');
|
||||
// Track initial tray data signature for detecting changes during refresh
|
||||
const refreshInitialDataRef = useRef<string>('');
|
||||
// Track initial lastAmsUpdate value at start of refresh (to detect when new data arrives)
|
||||
const refreshInitialAmsUpdateRef = useRef<number>(0);
|
||||
|
||||
// Timeout ref for cleanup
|
||||
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
@@ -107,19 +112,10 @@ export function useAmsOperations({
|
||||
clearOperationTimeout();
|
||||
setState('IDLE');
|
||||
setContext(null);
|
||||
prevTrayDataRef.current = '';
|
||||
refreshInitialDataRef.current = '';
|
||||
refreshInitialAmsUpdateRef.current = 0;
|
||||
}, [clearOperationTimeout]);
|
||||
|
||||
// Set up timeout for current operation
|
||||
const startOperationTimeout = useCallback((timeoutMs: number) => {
|
||||
clearOperationTimeout();
|
||||
const startTime = context?.startTime ?? Date.now();
|
||||
timeoutRef.current = setTimeout(() => {
|
||||
console.log(`[useAmsOperations] Operation timed out after ${timeoutMs}ms`);
|
||||
reset();
|
||||
}, timeoutMs);
|
||||
}, [clearOperationTimeout, reset, context?.startTime]);
|
||||
|
||||
// === Mutations ===
|
||||
|
||||
const refreshMutation = useMutation({
|
||||
@@ -173,22 +169,24 @@ export function useAmsOperations({
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`[useAmsOperations] Starting refresh: AMS ${amsId}, Tray ${trayId}`);
|
||||
|
||||
// Capture current tray data signature for change detection
|
||||
// Capture current tray data to detect changes
|
||||
const unit = amsUnits.find(u => u.id === amsId);
|
||||
const tray = unit?.tray?.find(t => t.id === trayId);
|
||||
if (tray) {
|
||||
prevTrayDataRef.current = JSON.stringify({
|
||||
refreshInitialDataRef.current = JSON.stringify({
|
||||
tag_uid: tray.tag_uid,
|
||||
tray_uuid: tray.tray_uuid,
|
||||
tray_id_name: tray.tray_id_name,
|
||||
tray_type: tray.tray_type,
|
||||
tray_color: tray.tray_color,
|
||||
});
|
||||
}
|
||||
|
||||
// Capture initial lastAmsUpdate to detect when NEW data arrives
|
||||
refreshInitialAmsUpdateRef.current = lastAmsUpdate;
|
||||
|
||||
const startTime = Date.now();
|
||||
console.log(`[useAmsOperations] Starting refresh: AMS ${amsId}, Tray ${trayId}, startTime=${startTime}, initialAmsUpdate=${lastAmsUpdate}`);
|
||||
|
||||
setState('REFRESHING');
|
||||
setContext({ refreshTarget: { amsId, trayId }, startTime });
|
||||
|
||||
@@ -199,7 +197,7 @@ export function useAmsOperations({
|
||||
}, REFRESH_TIMEOUT_MS);
|
||||
|
||||
refreshMutation.mutate({ amsId, trayId });
|
||||
}, [state, amsUnits, reset, refreshMutation]);
|
||||
}, [state, amsUnits, lastAmsUpdate, reset, refreshMutation]);
|
||||
|
||||
const startLoad = useCallback((trayId: number, extruderId?: number) => {
|
||||
if (state !== 'IDLE') {
|
||||
@@ -245,31 +243,49 @@ export function useAmsOperations({
|
||||
|
||||
// === Completion Detection ===
|
||||
|
||||
// Detect REFRESH completion via tray data change
|
||||
// Detect REFRESH completion by waiting for new AMS data to arrive
|
||||
// RFID read takes 5-10 seconds. We detect completion when:
|
||||
// 1. Data changed (new/different spool detected) - complete after minimum 1s
|
||||
// 2. lastAmsUpdate timestamp changed from initial AND elapsed > 5 seconds
|
||||
// This means a new AMS data packet arrived after the RFID read should be done
|
||||
useEffect(() => {
|
||||
if (state !== 'REFRESHING' || !context?.refreshTarget) return;
|
||||
|
||||
const { amsId, trayId } = context.refreshTarget;
|
||||
const elapsed = Date.now() - context.startTime;
|
||||
|
||||
// Get current tray data
|
||||
const unit = amsUnits.find(u => u.id === amsId);
|
||||
const tray = unit?.tray?.find(t => t.id === trayId);
|
||||
|
||||
if (!tray) return;
|
||||
|
||||
const currentSignature = JSON.stringify({
|
||||
const currentData = JSON.stringify({
|
||||
tag_uid: tray.tag_uid,
|
||||
tray_uuid: tray.tray_uuid,
|
||||
tray_id_name: tray.tray_id_name,
|
||||
tray_type: tray.tray_type,
|
||||
tray_color: tray.tray_color,
|
||||
});
|
||||
|
||||
// Require minimum 500ms to avoid false positives from initial render
|
||||
const elapsed = Date.now() - context.startTime;
|
||||
if (prevTrayDataRef.current && prevTrayDataRef.current !== currentSignature && elapsed > 500) {
|
||||
// Check if data changed (new spool detected)
|
||||
const dataChanged = refreshInitialDataRef.current && currentData !== refreshInitialDataRef.current;
|
||||
|
||||
// Check if lastAmsUpdate changed from when we started
|
||||
const amsUpdateChanged = lastAmsUpdate !== refreshInitialAmsUpdateRef.current;
|
||||
|
||||
// Primary completion: data changed (new spool detected) - complete quickly
|
||||
if (dataChanged && elapsed > 1000) {
|
||||
console.log(`[useAmsOperations] Refresh complete: data changed for AMS ${amsId} tray ${trayId} (took ${elapsed}ms)`);
|
||||
reset();
|
||||
return;
|
||||
}
|
||||
}, [state, context, amsUnits, reset]);
|
||||
|
||||
// Secondary completion: new AMS update received after minimum wait time
|
||||
// Wait 8 seconds to ensure RFID read has time to complete before considering updates
|
||||
if (amsUpdateChanged && elapsed > 8000) {
|
||||
console.log(`[useAmsOperations] Refresh complete: new AMS update after ${elapsed}ms for AMS ${amsId} tray ${trayId}`);
|
||||
reset();
|
||||
}
|
||||
}, [state, context, amsUnits, lastAmsUpdate, reset]);
|
||||
|
||||
// Detect LOAD/UNLOAD completion via ams_status_main transition 1 → 0
|
||||
useEffect(() => {
|
||||
|
||||
File diff suppressed because one or more lines are too long
+1
-1
@@ -7,7 +7,7 @@
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/img/favicon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/img/favicon-16x16.png" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/img/apple-touch-icon.png" />
|
||||
<script type="module" crossorigin src="/assets/index-CLNJVLog.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-BHvBWY5n.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-BpSfhfce.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
Reference in New Issue
Block a user