Refactoring AMS module
|
Before Width: | Height: | Size: 974 B After Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 5.6 KiB |
@@ -1,2 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" width="512" height="512" viewBox="0 0 24 24"><path d="M20.5,24c-.042,0-.086-.005-.129-.017-.267-.071-.426-.345-.354-.612,.114-.428,.243-.826,.369-1.218,.315-.98,.614-1.906,.614-3.16,0-2.766-1.029-4.801-1.927-6.265-.965-1.576-2.073-3.771-2.073-6.788,0-1.884,.772-4.482,1.294-5.646,.113-.252,.409-.365,.661-.251,.252,.113,.364,.409,.251,.661-.421,.938-1.206,3.436-1.206,5.236,0,2.766,1.029,4.801,1.927,6.265,.965,1.576,2.073,3.771,2.073,6.788,0,1.411-.337,2.456-.663,3.467-.121,.375-.244,.758-.354,1.168-.06,.224-.262,.371-.483,.371Zm-7,0c-.042,0-.086-.005-.129-.017-.267-.071-.426-.345-.354-.612,.114-.428,.243-.826,.369-1.218,.315-.98,.614-1.906,.614-3.16,0-2.766-1.029-4.801-1.927-6.265-.965-1.576-2.073-3.771-2.073-6.788,0-1.884,.772-4.482,1.294-5.646,.112-.252,.409-.365,.661-.251,.252,.113,.364,.409,.251,.661-.421,.938-1.206,3.436-1.206,5.236,0,2.766,1.029,4.801,1.927,6.265,.965,1.576,2.073,3.771,2.073,6.788,0,1.411-.337,2.456-.663,3.467-.121,.375-.244,.758-.354,1.168-.06,.224-.262,.371-.483,.371Zm-7,0c-.042,0-.086-.005-.129-.017-.267-.071-.426-.345-.354-.612,.114-.428,.243-.826,.369-1.218,.315-.98,.614-1.906,.614-3.16,0-2.766-1.029-4.801-1.927-6.265-.965-1.576-2.073-3.771-2.073-6.788,0-1.884,.772-4.482,1.294-5.646,.112-.252,.41-.365,.661-.251,.252,.113,.364,.409,.251,.661-.421,.938-1.206,3.436-1.206,5.236,0,2.766,1.029,4.801,1.927,6.265,.965,1.576,2.073,3.771,2.073,6.788,0,1.411-.337,2.456-.663,3.467-.121,.375-.244,.758-.354,1.168-.06,.224-.262,.371-.483,.371Z"/></svg>
|
||||
<svg width="16" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.4759 7.06822H13.7562L8.74263 11.2268C8.514 11.416 8.10431 11.416 7.87554 11.227C7.87545 11.2269 7.87536 11.2268 7.87528 11.2268L2.86167 7.06822H4.142C4.54877 7.06822 4.93613 6.94338 5.23267 6.71974C5.52893 6.49633 5.76004 6.14967 5.76004 5.72506V1.84316C5.76004 1.80403 5.78049 1.72911 5.88953 1.64688C5.99827 1.56488 6.16993 1.5 6.37809 1.5H10.2398C10.448 1.5 10.6196 1.56488 10.7284 1.64688C10.8374 1.72911 10.8579 1.80403 10.8579 1.84316V5.72506C10.8579 6.14967 11.089 6.49633 11.3852 6.71974C11.6818 6.94338 12.0691 7.06822 12.4759 7.06822ZM2.36979 7.09452C2.36773 7.09555 2.36658 7.096 2.36652 7.09597C2.36645 7.09594 2.36748 7.09542 2.36979 7.09452ZM14.2475 7.09456C14.2498 7.09545 14.2508 7.09596 14.2507 7.096C14.2507 7.09603 14.2495 7.09558 14.2475 7.09456Z" stroke="#6B6B6B"/>
|
||||
<path d="M3.80389 10.668C3.58699 10.7742 3.42822 10.9007 3.42822 11.0895C3.42822 11.673 4.95994 11.673 4.95994 12.2548C4.95994 12.8383 3.42822 12.8383 3.42822 13.42C3.42822 14.0035 4.95994 14.0035 4.95994 14.587C4.95994 15.1704 3.42822 15.1704 3.42822 15.7539" stroke="#6B6B6B" stroke-miterlimit="10" stroke-linecap="round"/>
|
||||
<path d="M8.63467 14.1348C8.88288 14.2477 9.07518 14.381 9.07518 14.5867C9.07518 15.1702 7.54346 15.1702 7.54346 15.7536" stroke="#6B6B6B" stroke-miterlimit="10" stroke-linecap="round"/>
|
||||
<path d="M11.893 11.4316C12.3223 11.7065 13.1899 11.8161 13.1899 12.2546C13.1899 12.838 11.6582 12.838 11.6582 13.4198C11.6582 14.0033 13.1899 14.0033 13.1899 14.5867C13.1899 15.1702 11.6582 15.1702 11.6582 15.7537" stroke="#6B6B6B" stroke-miterlimit="10" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.7 KiB |
@@ -1 +1,6 @@
|
||||
<svg id="Teamwork" enable-background="new 0 0 64 64" height="512" viewBox="0 0 64 64" width="512" xmlns="http://www.w3.org/2000/svg"><path d="m29.351 39h-15.351c-.553 0-1-.448-1-1s.447-1 1-1h15.351c.553 0 1 .448 1 1s-.448 1-1 1z"/><path d="m29.351 27h-15.351c-.553 0-1-.448-1-1s.447-1 1-1h15.351c.553 0 1 .448 1 1s-.448 1-1 1z"/><path d="m28 33h-20c-.553 0-1-.448-1-1s.447-1 1-1h20c.553 0 1 .448 1 1s-.447 1-1 1z"/><path d="m38 35.902c-1.987 0-4-1.34-4-3.902s2.013-3.902 4-3.902 4 1.34 4 3.902-2.013 3.902-4 3.902zm0-5.804c-.923 0-2 .498-2 1.902s1.077 1.902 2 1.902 2-.498 2-1.902-1.077-1.902-2-1.902z"/><path d="m40.121 30.879c-.256 0-.512-.098-.707-.293-.391-.391-.391-1.023 0-1.414l5.657-5.657c.391-.391 1.023-.391 1.414 0s.391 1.023 0 1.414l-5.657 5.657c-.195.195-.451.293-.707.293z"/><path d="m34.465 36.536c-.256 0-.512-.098-.707-.293-.391-.39-.391-1.023 0-1.414l1.414-1.415c.391-.391 1.023-.391 1.414 0 .391.39.391 1.023 0 1.414l-1.414 1.415c-.195.195-.451.293-.707.293z"/><path d="m38 51c-.553 0-1-.448-1-1v-3c0-.552.447-1 1-1s1 .448 1 1v3c0 .552-.447 1-1 1z"/><path d="m50.728 45.728c-.256 0-.512-.098-.707-.293l-2.121-2.122c-.391-.391-.391-1.024 0-1.414.391-.391 1.023-.391 1.414 0l2.121 2.122c.391.391.391 1.024 0 1.414-.196.195-.452.293-.707.293z"/><path d="m48.606 22.394c-.256 0-.512-.098-.707-.293-.391-.39-.391-1.023 0-1.414l2.121-2.122c.391-.391 1.023-.391 1.414 0 .391.39.391 1.023 0 1.414l-2.121 2.122c-.195.195-.451.293-.707.293z"/><path d="m38 18c-.553 0-1-.448-1-1v-3c0-.552.447-1 1-1s1 .448 1 1v3c0 .552-.447 1-1 1z"/><path d="m56 33h-3c-.553 0-1-.448-1-1s.447-1 1-1h3c.553 0 1 .448 1 1s-.447 1-1 1z"/><path d="m30.789 21h-10.789c-.553 0-1-.448-1-1s.447-1 1-1h10.789c.553 0 1 .448 1 1s-.447 1-1 1z"/><path d="m30.789 45h-10.789c-.553 0-1-.448-1-1s.447-1 1-1h10.789c.553 0 1 .448 1 1s-.447 1-1 1z"/><path d="m37.938 50.981c-2.931 0-5.892-.675-8.642-2.092-.491-.253-.684-.856-.431-1.347s.857-.684 1.347-.431c7.844 4.042 17.615 1.334 22.245-6.165 4.643-7.521 2.7-17.48-4.423-22.673-5.159-3.76-12.155-4.304-17.822-1.385-.49.253-1.094.061-1.347-.431-.253-.491-.061-1.094.431-1.347 6.333-3.264 14.149-2.657 19.916 1.546 7.962 5.804 10.135 16.935 4.946 25.34-3.551 5.754-9.81 8.985-16.22 8.985z"/></svg>
|
||||
<svg width="20" height="21" viewBox="0 0 20 21" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M17.4005 12.7763C17.649 12.8443 17.907 12.6982 17.9606 12.4463C18.1963 11.3405 18.1993 10.1959 17.9674 9.08583C17.7037 7.8233 17.1438 6.64152 16.3337 5.63787C15.5236 4.63422 14.4866 3.83744 13.3082 3.3132C12.1298 2.78897 10.8436 2.55228 9.55579 2.62265C8.26794 2.69302 7.01524 3.06843 5.90095 3.71795C4.78665 4.36746 3.84266 5.27248 3.14678 6.35842C2.45089 7.44436 2.02303 8.68012 1.89846 9.96387C1.78893 11.0926 1.91663 12.23 2.27134 13.3036C2.35217 13.5482 2.62452 13.6653 2.8641 13.5706V13.5706C3.10368 13.4759 3.21959 13.2053 3.14058 12.9601C2.83915 12.0245 2.73177 11.0355 2.82702 10.054C2.93731 8.91737 3.31613 7.82324 3.93226 6.86177C4.54839 5.9003 5.38418 5.09901 6.37076 4.52394C7.35733 3.94887 8.46645 3.61649 9.60669 3.55418C10.7469 3.49188 11.8857 3.70144 12.929 4.16559C13.9724 4.62974 14.8905 5.33519 15.6077 6.2238C16.3249 7.11242 16.8207 8.15875 17.0542 9.27658C17.2558 10.2419 17.2569 11.2367 17.0592 12.1995C17.0073 12.4519 17.152 12.7083 17.4005 12.7763V12.7763Z" fill="#323A3D"/>
|
||||
<path d="M15.4157 8.78647C15.593 8.71313 15.6782 8.50941 15.5948 8.33658C15.1923 7.50231 14.6033 6.76987 13.8715 6.19699C13.0484 5.5526 12.0726 5.13199 11.0389 4.97602C10.0053 4.82004 8.94883 4.93399 7.9722 5.30681C7.10396 5.63825 6.32502 6.16427 5.6943 6.84264C5.56365 6.98317 5.58494 7.20296 5.73272 7.32535V7.32535C5.88051 7.44775 6.09873 7.42629 6.23043 7.28674C6.78398 6.70015 7.46373 6.2447 8.22002 5.956C9.08471 5.62591 10.0201 5.52502 10.9353 5.66312C11.8504 5.80122 12.7144 6.17362 13.4432 6.74415C14.0806 7.24316 14.5957 7.8789 14.9515 8.60271C15.0362 8.77491 15.2383 8.85981 15.4157 8.78647V8.78647Z" fill="#323A3D"/>
|
||||
<path d="M13.7871 9.75159L10.6357 14.0831" stroke="#323A3D" stroke-width="1.04628" stroke-linecap="round"/>
|
||||
<circle cx="10.1926" cy="14.5735" r="1.32935" fill="#323A3D"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 1.9 KiB |
@@ -1,4 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 24 24">
|
||||
<path d="m22.98,10.346c-.721-.804-1.714-1.278-2.798-1.336-1.661-.081-4.16.444-5.969.983-.179-.197-.378-.371-.603-.515.164-.177.324-.352.494-.533,1.375-1.456,2.797-2.96,2.89-4.691C17.113,2.032,15.42.126,13.219.006c-1.067-.062-2.08.302-2.872,1.013-.804.721-1.278,1.715-1.336,2.798-.09,1.676.443,4.161.982,5.969-.197.179-.371.378-.515.603-.178-.164-.354-.325-.534-.495-1.455-1.374-2.96-2.795-4.69-2.888C2.054,6.892.127,8.581.006,10.781c-.059,1.06.302,2.08,1.014,2.873.721.804,1.714,1.278,2.798,1.336.125.006.255.01.389.01,1.655,0,3.907-.494,5.58-.993.178.197.378.371.603.515-.164.177-.324.352-.494.533-1.375,1.456-2.796,2.96-2.889,4.691-.119,2.222,1.574,4.127,3.775,4.247.074.004.147.006.221.006.979,0,1.914-.358,2.651-1.02.804-.721,1.278-1.714,1.336-2.798.09-1.676-.443-4.161-.982-5.969.197-.179.371-.378.515-.603.178.164.354.325.534.495,1.455,1.374,2.96,2.795,4.69,2.888.073.004.146.006.22.006,2.125,0,3.91-1.654,4.027-3.781.059-1.06-.302-2.08-1.014-2.873Zm-10.98,3.654c-1.103,0-2-.897-2-2s.897-2,2-2,2,.897,2,2-.897,2-2,2Zm-1.991-10.129c.044-.817.401-1.565,1.006-2.108.595-.533,1.36-.801,2.149-.759,1.651.09,2.921,1.524,2.832,3.195-.073,1.365-1.367,2.734-2.618,4.059-.258.274-.51.543-.751.81-.202-.043-.412-.068-.627-.068-.4,0-.78.082-1.129.224-.468-1.64-.941-3.886-.863-5.353ZM3.87,13.991c-.816-.043-1.564-.4-2.106-1.005-.533-.594-.803-1.358-.76-2.15.091-1.651,1.554-2.924,3.196-2.831,1.364.073,2.732,1.365,4.057,2.616.274.259.544.511.811.753-.043.203-.068.412-.068.627,0,.4.082.78.224,1.129-1.64.469-3.883.943-5.354.863Zm10.121,6.138c-.044.816-.401,1.565-1.006,2.107-.593.533-1.359.801-2.149.759-1.651-.09-2.921-1.524-2.832-3.195.073-1.365,1.367-2.734,2.618-4.058.258-.275.51-.544.751-.81.202.043.412.068.627.068.4,0,.78-.082,1.129-.224.468,1.64.941,3.886.863,5.353Zm5.809-4.134c-1.364-.073-2.732-1.365-4.057-2.616-.274-.259-.544-.511-.811-.753.043-.202.068-.412.068-.627,0-.4-.082-.78-.224-1.129,1.64-.469,3.886-.938,5.354-.862.816.043,1.564.4,2.106,1.005.533.594.803,1.358.76,2.15-.091,1.651-1.53,2.932-3.196,2.831Z"/>
|
||||
<svg width="19" height="18" viewBox="0 0 19 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.6444 8.72819C12.3737 7.93152 12.8358 6.88333 12.8815 5.7196C12.9537 3.86391 11.9465 2.21641 10.4205 1.38122C10.2905 1.31022 10.1557 1.24523 10.0186 1.18506C9.74417 1.07796 9.44933 1.01417 9.13884 1.00214C7.64057 0.943171 6.37816 2.1105 6.3204 3.60878C6.2867 4.48608 6.67301 5.28155 7.29879 5.80264C7.34813 5.83754 7.39507 5.87485 7.442 5.91336C7.92097 6.31169 8.25913 6.87129 8.37587 7.5043C8.67793 7.40923 9.00045 7.3635 9.335 7.37673C10.317 7.41765 11.1642 7.95077 11.6444 8.72819Z" stroke="#00AE42" stroke-miterlimit="10" stroke-linecap="round"/>
|
||||
<path d="M6.3842 10.1074C6.39743 9.77525 6.46723 9.45995 6.58276 9.16752C5.57669 8.98339 4.50443 9.12179 3.52844 9.63445C1.88455 10.4997 0.961517 12.1954 1.00123 13.9343C1.00484 14.0823 1.01567 14.2316 1.03252 14.3808C1.07705 14.6708 1.16971 14.9597 1.31412 15.234C2.01212 16.5602 3.65481 17.0705 4.98099 16.3713C5.75721 15.9621 6.25422 15.2304 6.39142 14.4265C6.39743 14.3664 6.40586 14.3062 6.41669 14.2472C6.53583 13.5564 6.91972 12.9174 7.52024 12.4926C6.79938 11.9523 6.34689 11.0774 6.3842 10.1074Z" stroke="#00AE42" stroke-miterlimit="10" stroke-linecap="round"/>
|
||||
<path d="M16.7327 11.0132C15.9902 10.545 15.1081 10.4813 14.3427 10.7641C14.2874 10.7881 14.232 10.811 14.1754 10.8327C13.4895 11.0854 12.7097 11.0601 12.0213 10.7147C11.8083 11.9254 10.8347 12.8592 9.62646 13.0313C9.94778 14.0747 10.6277 15.0134 11.6242 15.6416C13.1959 16.632 15.1262 16.5839 16.6124 15.6801C16.7388 15.6031 16.8627 15.5188 16.9831 15.4298C17.2129 15.2457 17.4163 15.0218 17.5812 14.7595C18.3815 13.4898 18.0012 11.8135 16.7327 11.0132Z" stroke="#00AE42" stroke-miterlimit="10" stroke-linecap="round"/>
|
||||
<path d="M9.20391 11.3717C9.76553 11.3717 10.2208 10.9164 10.2208 10.3548C10.2208 9.79317 9.76553 9.33789 9.20391 9.33789C8.64229 9.33789 8.18701 9.79317 8.18701 10.3548C8.18701 10.9164 8.64229 11.3717 9.20391 11.3717Z" stroke="#00AE42" stroke-miterlimit="10" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.0 KiB |
@@ -151,6 +151,7 @@ interface FilamentChangeCardProps {
|
||||
targetTrayId: number | null; // Target tray we're trying to load (null for unload)
|
||||
onComplete: () => void; // Called when operation completes
|
||||
onRetry?: () => void;
|
||||
operationInitiated: boolean; // True if we initiated the operation (hook is in LOADING/UNLOADING state)
|
||||
}
|
||||
|
||||
interface StepInfo {
|
||||
@@ -159,7 +160,7 @@ interface StepInfo {
|
||||
stepNumber: number;
|
||||
}
|
||||
|
||||
function FilamentChangeCard({ isLoading, amsStatusMain, amsStatusSub, trayNow, targetTrayId, onComplete, onRetry }: FilamentChangeCardProps) {
|
||||
function FilamentChangeCard({ isLoading, amsStatusMain, amsStatusSub, trayNow, targetTrayId, onComplete, onRetry, operationInitiated }: FilamentChangeCardProps) {
|
||||
const [isCollapsed, setIsCollapsed] = useState(false);
|
||||
const [isCompleted, setIsCompleted] = useState(false);
|
||||
const prevAmsStatusRef = useRef(amsStatusMain);
|
||||
@@ -167,31 +168,47 @@ function FilamentChangeCard({ isLoading, amsStatusMain, amsStatusSub, trayNow, t
|
||||
const completionTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
// ams_status_sub values for filament change steps
|
||||
// Observed progression: 5 -> 6 -> 2 -> 7 -> 0
|
||||
// Observed from H2D printer logs:
|
||||
// Load progression: 9 (feeding start) -> 5 (prep) -> 6 (pushing) -> 2 (heating) -> 6 (more pushing) -> 7 (purging)
|
||||
// Unload progression: 9 (start) -> 2 (heating) -> 3 (retract prep) -> 4 (retract)
|
||||
// 2: Heating nozzle
|
||||
// 3: AMS feeding filament to hub
|
||||
// 3: AMS feeding filament to hub / retract prep
|
||||
// 4: Retraction / extruder pulling filament
|
||||
// 5: Initial filament push / preparation
|
||||
// 6: Load verification / extruder pushing
|
||||
// 7: Purging
|
||||
// 9: Feeding start / operation initiated
|
||||
const SUB_HEATING = 2;
|
||||
const SUB_FEEDING = 3;
|
||||
const SUB_RETRACT = 4;
|
||||
const SUB_PUSH_PREP = 5;
|
||||
const SUB_PUSH = 6;
|
||||
const SUB_PURGE = 7;
|
||||
const SUB_FEEDING_START = 9;
|
||||
|
||||
// Log status updates for debugging
|
||||
useEffect(() => {
|
||||
console.log(`[FilamentChangeCard] Status: main=${amsStatusMain}, sub=${amsStatusSub}, trayNow=${trayNow}, isLoading=${isLoading}`);
|
||||
}, [amsStatusMain, amsStatusSub, trayNow, isLoading]);
|
||||
|
||||
// Track component mount time for minimum operation duration check
|
||||
const mountTimeRef = useRef(Date.now());
|
||||
|
||||
// Detect completion via ams_status_main transition from 1 (filament_change) to 0 (idle)
|
||||
// Also use tray_now as a secondary indicator
|
||||
useEffect(() => {
|
||||
const wasActive = prevAmsStatusRef.current === 1;
|
||||
const isNowIdle = amsStatusMain === 0;
|
||||
const trayChanged = trayNow !== prevTrayNowRef.current;
|
||||
const elapsed = Date.now() - mountTimeRef.current;
|
||||
|
||||
// Don't detect completion in the first 3 seconds - operation can't complete that fast
|
||||
// This prevents false positives from initial state or rapid updates
|
||||
if (elapsed < 3000) {
|
||||
prevAmsStatusRef.current = amsStatusMain;
|
||||
prevTrayNowRef.current = trayNow;
|
||||
return;
|
||||
}
|
||||
|
||||
// Primary completion detection: ams_status_main transitions from 1 to 0
|
||||
if (wasActive && isNowIdle) {
|
||||
@@ -239,23 +256,29 @@ function FilamentChangeCard({ isLoading, amsStatusMain, amsStatusSub, trayNow, t
|
||||
const getStepFromAmsStatusSub = (): number => {
|
||||
if (isCompleted) return 99; // All done
|
||||
|
||||
// If not in filament change mode, not started
|
||||
if (amsStatusMain !== 1) return 0;
|
||||
// If not in filament change mode yet, check if we initiated the operation
|
||||
// This handles the delay between sending the command and MQTT status updating
|
||||
if (amsStatusMain !== 1) {
|
||||
// If we initiated the operation, show step 1 as in progress
|
||||
if (operationInitiated) return 1;
|
||||
return 0; // Not started
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
// Loading sequence: Push -> Heat -> Purge (matches Bambu Studio/OrcaSlicer display)
|
||||
// Observed progression: 5 -> 6 -> 2 -> 7
|
||||
// Map sub status to steps: 5/6 -> step 1, 2 -> step 2, 7 -> step 3
|
||||
if (amsStatusSub === SUB_PUSH_PREP || amsStatusSub === SUB_PUSH || amsStatusSub === SUB_FEEDING) return 1; // Step 1: Pushing
|
||||
// Observed progression: 9 (start) -> 5 (prep) -> 6 (push) -> 2 (heat) -> 6 (push) -> 7 (purge)
|
||||
// Map sub status to steps: 9/5/6 -> step 1, 2 -> step 2, 7 -> step 3
|
||||
if (amsStatusSub === SUB_FEEDING_START || amsStatusSub === SUB_PUSH_PREP || amsStatusSub === SUB_PUSH || amsStatusSub === SUB_FEEDING) return 1; // Step 1: Pushing
|
||||
if (amsStatusSub === SUB_HEATING) return 2; // Step 2: Heating
|
||||
if (amsStatusSub === SUB_PURGE) return 3; // Step 3: Purging
|
||||
// Default to step 1 when in filament_change mode
|
||||
return 1;
|
||||
} else {
|
||||
// Unloading sequence: Heat -> Retract
|
||||
// Map sub status to steps: 2 -> step 1, 4 -> step 2
|
||||
if (amsStatusSub === SUB_HEATING) return 1; // Step 1: Heating
|
||||
if (amsStatusSub === SUB_RETRACT) return 2; // Step 2: Retracting
|
||||
// Observed progression: 9 (start) -> 2 (heat) -> 3 (retract prep) -> 4 (retract)
|
||||
// Map sub status to steps: 9/2 -> step 1, 3/4 -> step 2
|
||||
if (amsStatusSub === SUB_FEEDING_START || amsStatusSub === SUB_HEATING) return 1; // Step 1: Heating
|
||||
if (amsStatusSub === SUB_FEEDING || amsStatusSub === SUB_RETRACT) return 2; // Step 2: Retracting
|
||||
// Default to step 1 when in filament_change mode
|
||||
return 1;
|
||||
}
|
||||
@@ -899,14 +922,13 @@ export function AMSSectionDual({ printerId, printerModel, status, nozzleCount }:
|
||||
|
||||
// Distribute AMS units based on ams_extruder_map
|
||||
// Each AMS unit's info field tells us which extruder it's connected to:
|
||||
// UI layout: Left panel shows extruder 0 AMS units, Right panel shows extruder 1 AMS units
|
||||
// Note: Internal nozzle IDs are different (T0=right physical nozzle, T1=left physical nozzle)
|
||||
// Bambu convention: extruder_id 0 = RIGHT physical nozzle, extruder_id 1 = LEFT physical nozzle
|
||||
// UI layout: Left panel shows extruder 1 (left nozzle), Right panel shows extruder 0 (right nozzle)
|
||||
const leftUnits = (() => {
|
||||
if (!isDualNozzle) return amsUnits;
|
||||
if (Object.keys(amsExtruderMap).length > 0) {
|
||||
// Filter AMS units assigned to extruder 0 (left UI panel)
|
||||
// JSON keys are strings, so convert unit.id to string
|
||||
return amsUnits.filter(unit => amsExtruderMap[String(unit.id)] === 0);
|
||||
// Filter AMS units assigned to extruder 1 (LEFT physical nozzle -> left UI panel)
|
||||
return amsUnits.filter(unit => amsExtruderMap[String(unit.id)] === 1);
|
||||
}
|
||||
// Fallback: even indices go to left
|
||||
return amsUnits.filter((_, i) => i % 2 === 0);
|
||||
@@ -915,9 +937,8 @@ export function AMSSectionDual({ printerId, printerModel, status, nozzleCount }:
|
||||
const rightUnits = (() => {
|
||||
if (!isDualNozzle) return [];
|
||||
if (Object.keys(amsExtruderMap).length > 0) {
|
||||
// Filter AMS units assigned to extruder 1 (right UI panel)
|
||||
// JSON keys are strings, so convert unit.id to string
|
||||
return amsUnits.filter(unit => amsExtruderMap[String(unit.id)] === 1);
|
||||
// Filter AMS units assigned to extruder 0 (RIGHT physical nozzle -> right UI panel)
|
||||
return amsUnits.filter(unit => amsExtruderMap[String(unit.id)] === 0);
|
||||
}
|
||||
// Fallback: odd indices go to right
|
||||
return amsUnits.filter((_, i) => i % 2 === 1);
|
||||
@@ -1033,12 +1054,66 @@ export function AMSSectionDual({ printerId, printerModel, status, nozzleCount }:
|
||||
};
|
||||
|
||||
// Show FilamentChangeCard when operation is in progress (LOADING or UNLOADING state)
|
||||
// Use debounced visibility to prevent flickering when ams_status_main bounces
|
||||
const isMqttFilamentChangeActive = amsStatusMain === 1;
|
||||
const showFilamentChangeCard = amsOps.state === 'LOADING' || amsOps.state === 'UNLOADING' || isMqttFilamentChangeActive;
|
||||
const shouldShowCard = 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
|
||||
// Debounce hiding the card - keep it visible for at least 2 seconds after conditions become false
|
||||
const [showFilamentChangeCard, setShowFilamentChangeCard] = useState(false);
|
||||
const hideTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (shouldShowCard) {
|
||||
// Clear any pending hide timeout
|
||||
if (hideTimeoutRef.current) {
|
||||
clearTimeout(hideTimeoutRef.current);
|
||||
hideTimeoutRef.current = null;
|
||||
}
|
||||
setShowFilamentChangeCard(true);
|
||||
} else if (showFilamentChangeCard) {
|
||||
// Delay hiding the card by 1.5 seconds to prevent flicker
|
||||
if (!hideTimeoutRef.current) {
|
||||
hideTimeoutRef.current = setTimeout(() => {
|
||||
setShowFilamentChangeCard(false);
|
||||
hideTimeoutRef.current = null;
|
||||
}, 1500);
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (hideTimeoutRef.current) {
|
||||
clearTimeout(hideTimeoutRef.current);
|
||||
}
|
||||
};
|
||||
}, [shouldShowCard, showFilamentChangeCard]);
|
||||
|
||||
// Determine if it's a load operation for the FilamentChangeCard
|
||||
// Simple logic: use state if active, otherwise use lastOperationType
|
||||
// - LOADING state -> definitely load
|
||||
// - UNLOADING state -> definitely unload
|
||||
// - IDLE state -> use lastOperationType to remember what we initiated
|
||||
// - Fallback: if lastOperationType is null and MQTT shows activity, infer from tray_now
|
||||
// (tray_now=255 means unloading, any other value means loading)
|
||||
const isFilamentLoadOperation = (() => {
|
||||
if (amsOps.state === 'LOADING') return true;
|
||||
if (amsOps.state === 'UNLOADING') return false;
|
||||
if (amsOps.lastOperationType === 'load') return true;
|
||||
if (amsOps.lastOperationType === 'unload') return false;
|
||||
// Fallback: infer from tray_now when MQTT shows filament change active
|
||||
// If tray has a valid filament (not 255), we're loading
|
||||
return trayNow !== 255;
|
||||
})();
|
||||
|
||||
// Debug logging for card type determination
|
||||
useEffect(() => {
|
||||
if (showFilamentChangeCard) {
|
||||
console.log(`[AMSSectionDual] Card type: isFilamentLoadOperation=${isFilamentLoadOperation}, state=${amsOps.state}, lastOperationType=${amsOps.lastOperationType}, trayNow=${trayNow}`);
|
||||
}
|
||||
}, [showFilamentChangeCard, isFilamentLoadOperation, amsOps.state, amsOps.lastOperationType, trayNow]);
|
||||
|
||||
// Get the loaded tray info for wire coloring and extruder inlet
|
||||
// Wire coloring: only show path if the currently displayed AMS panel has the loaded filament
|
||||
// Extruder inlet: ALWAYS show the loaded filament color regardless of which AMS is displayed
|
||||
const getLoadedTrayInfo = (): {
|
||||
leftActiveSlot: number | null;
|
||||
rightActiveSlot: number | null;
|
||||
@@ -1060,31 +1135,33 @@ export function AMSSectionDual({ printerId, printerModel, status, nozzleCount }:
|
||||
const color = tray?.tray_color ?? null;
|
||||
|
||||
// Determine if this AMS is on left or right UI panel
|
||||
// UI layout: extruder 0 = left panel, extruder 1 = right panel
|
||||
// Bambu convention: extruder 0 = RIGHT physical nozzle, extruder 1 = LEFT physical nozzle
|
||||
// UI layout: left panel shows extruder 1 (left nozzle), right panel shows extruder 0 (right nozzle)
|
||||
const extruderId = amsExtruderMap[String(unit.id)];
|
||||
|
||||
// Check if this AMS unit is the one currently displayed in the panel
|
||||
const currentLeftUnit = leftUnits[leftAmsIndex];
|
||||
const currentRightUnit = rightUnits[rightAmsIndex];
|
||||
|
||||
if (extruderId === 0) {
|
||||
// Left UI panel (extruder 0) - leftUnits filters for amsExtruderMap === 0
|
||||
// Only show colored wiring if the currently displayed AMS unit is the one with loaded filament
|
||||
if (extruderId === 1) {
|
||||
// Left UI panel (extruder 1 = LEFT physical nozzle) - leftUnits filters for amsExtruderMap === 1
|
||||
// Wire path: only show if the currently displayed AMS unit is the one with loaded filament
|
||||
// Extruder inlet: ALWAYS show the loaded filament color
|
||||
const isDisplayed = currentLeftUnit?.id === unit.id;
|
||||
return {
|
||||
leftActiveSlot: isDisplayed ? slotIndex : null,
|
||||
leftActiveSlot: isDisplayed ? slotIndex : null, // Wire path only when AMS is displayed
|
||||
rightActiveSlot: null,
|
||||
leftFilamentColor: isDisplayed ? color : null, // Hide color if different AMS is selected
|
||||
leftFilamentColor: color, // Extruder inlet always shows loaded color
|
||||
rightFilamentColor: null
|
||||
};
|
||||
} else {
|
||||
// Right UI panel (extruder 1) - rightUnits filters for amsExtruderMap === 1
|
||||
// Right UI panel (extruder 0 = RIGHT physical nozzle) - rightUnits filters for amsExtruderMap === 0
|
||||
const isDisplayed = currentRightUnit?.id === unit.id;
|
||||
return {
|
||||
leftActiveSlot: null,
|
||||
rightActiveSlot: isDisplayed ? slotIndex : null,
|
||||
rightActiveSlot: isDisplayed ? slotIndex : null, // Wire path only when AMS is displayed
|
||||
leftFilamentColor: null,
|
||||
rightFilamentColor: isDisplayed ? color : null // Hide color if different AMS is selected
|
||||
rightFilamentColor: color // Extruder inlet always shows loaded color
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1211,12 +1288,13 @@ export function AMSSectionDual({ printerId, printerModel, status, nozzleCount }:
|
||||
{/* Filament Change Progress Card - appears during load/unload operations */}
|
||||
{showFilamentChangeCard && (
|
||||
<FilamentChangeCard
|
||||
isLoading={amsOps.isLoadOperation}
|
||||
isLoading={isFilamentLoadOperation}
|
||||
amsStatusMain={amsStatusMain}
|
||||
amsStatusSub={status?.ams_status_sub ?? 0}
|
||||
trayNow={trayNow}
|
||||
targetTrayId={amsOps.loadTargetTrayId}
|
||||
onComplete={handleFilamentChangeComplete}
|
||||
operationInitiated={amsOps.state === 'LOADING' || amsOps.state === 'UNLOADING'}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -67,6 +67,9 @@ interface UseAmsOperationsReturn {
|
||||
// For FilamentChangeCard - which type of operation
|
||||
isLoadOperation: boolean;
|
||||
loadTargetTrayId: number | null;
|
||||
// Last initiated operation type - persists after state reset
|
||||
// Used to determine card type when MQTT shows change but our state is IDLE
|
||||
lastOperationType: 'load' | 'unload' | null;
|
||||
|
||||
// Mutation error states (for UI feedback)
|
||||
loadError: Error | null;
|
||||
@@ -88,6 +91,10 @@ export function useAmsOperations({
|
||||
}: UseAmsOperationsProps): UseAmsOperationsReturn {
|
||||
const [state, setState] = useState<OperationState>('IDLE');
|
||||
const [context, setContext] = useState<OperationContext | null>(null);
|
||||
// Track the last operation type (load vs unload) - persists after state reset
|
||||
// This helps show correct card type when MQTT shows filament change but our state is IDLE
|
||||
// Using state instead of ref so changes trigger re-renders in consuming components
|
||||
const [lastOperationType, setLastOperationType] = useState<'load' | 'unload' | null>(null);
|
||||
|
||||
// Track previous values for transition detection
|
||||
const prevAmsStatusMainRef = useRef(amsStatusMain);
|
||||
@@ -210,6 +217,7 @@ export function useAmsOperations({
|
||||
const startTime = Date.now();
|
||||
setState('LOADING');
|
||||
setContext({ loadTargetTrayId: trayId, startTime });
|
||||
setLastOperationType('load'); // Remember this was a load operation
|
||||
|
||||
// Set timeout
|
||||
timeoutRef.current = setTimeout(() => {
|
||||
@@ -231,6 +239,7 @@ export function useAmsOperations({
|
||||
const startTime = Date.now();
|
||||
setState('UNLOADING');
|
||||
setContext({ startTime });
|
||||
setLastOperationType('unload'); // Remember this was an unload operation
|
||||
|
||||
// Set timeout
|
||||
timeoutRef.current = setTimeout(() => {
|
||||
@@ -306,11 +315,14 @@ export function useAmsOperations({
|
||||
}, [state, amsStatusMain, reset]);
|
||||
|
||||
// Secondary completion detection for LOAD: tray_now matches target
|
||||
// Wait at least 5 seconds to ensure the filament actually reached the nozzle
|
||||
// (tray_now can be updated before the physical load is complete)
|
||||
useEffect(() => {
|
||||
if (state !== 'LOADING' || !context?.loadTargetTrayId) return;
|
||||
|
||||
if (trayNow === context.loadTargetTrayId) {
|
||||
console.log(`[useAmsOperations] Load complete: tray_now=${trayNow} matches target`);
|
||||
const elapsed = context?.startTime ? Date.now() - context.startTime : 0;
|
||||
if (trayNow === context.loadTargetTrayId && elapsed > 5000) {
|
||||
console.log(`[useAmsOperations] Load complete: tray_now=${trayNow} matches target (${elapsed}ms elapsed)`);
|
||||
reset();
|
||||
}
|
||||
}, [state, context, trayNow, reset]);
|
||||
@@ -357,6 +369,7 @@ export function useAmsOperations({
|
||||
isRefreshingSlot,
|
||||
isLoadOperation,
|
||||
loadTargetTrayId,
|
||||
lastOperationType,
|
||||
loadError: loadMutation.error,
|
||||
unloadError: unloadMutation.error,
|
||||
refreshError: refreshMutation.error,
|
||||
|
||||
@@ -10,6 +10,11 @@ export default defineConfig({
|
||||
},
|
||||
server: {
|
||||
proxy: {
|
||||
'/api/v1/ws': {
|
||||
target: 'http://localhost:8000',
|
||||
ws: true,
|
||||
changeOrigin: true,
|
||||
},
|
||||
'/api': {
|
||||
target: 'http://localhost:8000',
|
||||
changeOrigin: true,
|
||||
|
||||