From 22887d4f7a3223f8af2a6e4f538b4d4d93be5f42 Mon Sep 17 00:00:00 2001 From: snacj <0xsnacj@proton.me> Date: Thu, 16 Oct 2025 13:05:47 +0200 Subject: [PATCH 1/8] Added 3 New Pid Controllers for Extruder front, middle, back, heating. --- electron/package-lock.json | 58 +++++---- .../extruder/extruder2/Extruder2Settings.tsx | 117 ++++++++++++++++++ .../extruder/extruder2/extruder2Namespace.ts | 18 ++- .../extruder/extruder2/useExtruder.ts | 83 +++++++++++++ package-lock.json | 6 + server/src/machines/extruder1/api.rs | 14 ++- server/src/machines/extruder1/emit.rs | 50 +++++++- 7 files changed, 316 insertions(+), 30 deletions(-) create mode 100644 package-lock.json diff --git a/electron/package-lock.json b/electron/package-lock.json index cad93b0f..199aaaa6 100644 --- a/electron/package-lock.json +++ b/electron/package-lock.json @@ -175,6 +175,7 @@ "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz", "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", @@ -638,6 +639,7 @@ "url": "https://opencollective.com/csstools" } ], + "peer": true, "engines": { "node": ">=18" }, @@ -660,6 +662,7 @@ "url": "https://opencollective.com/csstools" } ], + "peer": true, "engines": { "node": ">=18" } @@ -1102,6 +1105,7 @@ "integrity": "sha512-lyb1zK3YHeWUjaz7yiK0GnxSPduwASKMyiDbCtbn3spP6EEt+UWtktggWehG0icFrXAk3GwvcJ4nCrJO0N9IhQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "electron-fuses": "dist/bin.js" }, @@ -4028,6 +4032,7 @@ "resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.130.17.tgz", "integrity": "sha512-F1tyMEbX7PJclnA25hAFXhLKq4jqeODvvaogsd0/NX02Kmz3QoT9GV+rgpv2TxamU6RwjRgihSH06Dn5hM6SOw==", "license": "MIT", + "peer": true, "dependencies": { "@tanstack/history": "1.130.12", "@tanstack/react-store": "^0.7.0", @@ -4109,6 +4114,7 @@ "resolved": "https://registry.npmjs.org/@tanstack/router-core/-/router-core-1.130.17.tgz", "integrity": "sha512-0AH4s/52X2shYLifr9hR+6R6NpGsBp1M3LyjBTZm6Jlkud+nOhkkRxKxYS7l+JErRlffdxVHxMdoTnKcQ+duiQ==", "license": "MIT", + "peer": true, "dependencies": { "@tanstack/history": "1.130.12", "@tanstack/store": "^0.7.0", @@ -4342,8 +4348,7 @@ "version": "5.0.4", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -4500,6 +4505,7 @@ "integrity": "sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.10.0" } @@ -4516,6 +4522,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.16.tgz", "integrity": "sha512-WBM/nDbEZmDUORKnh5i1bTnAz6vTohUf9b8esSMu+b24+srbaxa04UbJgWx78CVfNXA20sNu0odEIluZDFdCog==", "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -4525,6 +4532,7 @@ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.7.tgz", "integrity": "sha512-i5ZzwYpqjmrKenzkoLM2Ibzt6mAsM7pxB6BCIouEVVmgiqaMj1TjaK7hnA36hbW5aZv20kx7Lw6hWzPWg0Rurw==", "devOptional": true, + "peer": true, "peerDependencies": { "@types/react": "^19.0.0" } @@ -4607,6 +4615,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.38.0.tgz", "integrity": "sha512-Zhy8HCvBUEfBECzIl1PKqF4p11+d0aUJS1GeUiuqK9WmOug8YCmC4h4bjyBvMyAMI9sbRczmrYL5lKg/YMbrcQ==", "dev": true, + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.38.0", "@typescript-eslint/types": "8.38.0", @@ -4961,6 +4970,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5408,6 +5418,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001726", "electron-to-chromium": "^1.5.173", @@ -6100,7 +6111,8 @@ "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "peer": true }, "node_modules/data-urls": { "version": "5.0.0", @@ -6373,8 +6385,7 @@ "version": "0.5.16", "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", - "dev": true, - "peer": true + "dev": true }, "node_modules/dom-helpers": { "version": "5.2.1", @@ -7014,16 +7025,6 @@ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true }, - "node_modules/encoding": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", - "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "dev": true, - "optional": true, - "dependencies": { - "iconv-lite": "^0.6.2" - } - }, "node_modules/end-of-stream": { "version": "1.4.5", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", @@ -7384,6 +7385,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.32.0.tgz", "integrity": "sha512-LSehfdpgMeWcTZkWZVIJl+tkZ2nuSkyyB9C27MZqFWXuph7DvaowgcTvKqxvpLW1JZIk8PN7hFY3Rj9LQ7m7lg==", "dev": true, + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -7444,6 +7446,7 @@ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, + "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -8750,6 +8753,7 @@ "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" } ], + "peer": true, "dependencies": { "@babel/runtime": "^7.27.6" }, @@ -8812,6 +8816,7 @@ "version": "10.1.1", "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", + "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/immer" @@ -9486,6 +9491,7 @@ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", "dev": true, + "peer": true, "dependencies": { "cssstyle": "^4.2.1", "data-urls": "^5.0.0", @@ -10051,7 +10057,6 @@ "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true, - "peer": true, "bin": { "lz-string": "bin/bin.js" } @@ -11994,6 +11999,7 @@ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -12107,7 +12113,6 @@ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, - "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -12122,7 +12127,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, - "peer": true, "engines": { "node": ">=10" }, @@ -12251,6 +12255,7 @@ "version": "19.1.1", "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz", "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -12259,6 +12264,7 @@ "version": "19.1.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz", "integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==", + "peer": true, "dependencies": { "scheduler": "^0.26.0" }, @@ -12270,6 +12276,7 @@ "version": "7.62.0", "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.62.0.tgz", "integrity": "sha512-7KWFejc98xqG/F4bAxpL41NB3o1nnvQO1RWZT3TqRZYL8RryQETGfEdVnJN2fy1crCiBLLjkRBVK05j24FxJGA==", + "peer": true, "engines": { "node": ">=18.0.0" }, @@ -12310,8 +12317,7 @@ "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true, - "peer": true + "dev": true }, "node_modules/react-lifecycles-compat": { "version": "3.0.4", @@ -13062,6 +13068,7 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.3.2.tgz", "integrity": "sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==", + "peer": true, "engines": { "node": ">=10" } @@ -13851,7 +13858,8 @@ "node_modules/tailwindcss": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", - "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==" + "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", + "peer": true }, "node_modules/tailwindcss-animate": { "version": "1.0.7", @@ -13953,7 +13961,8 @@ "node_modules/tiny-invariant": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", - "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==" + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "peer": true }, "node_modules/tiny-warning": { "version": "1.0.3", @@ -14004,6 +14013,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "peer": true, "engines": { "node": ">=12" }, @@ -14315,6 +14325,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "devOptional": true, + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -14676,6 +14687,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.6.tgz", "integrity": "sha512-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA==", "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -14797,6 +14809,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "peer": true, "engines": { "node": ">=12" }, @@ -15340,6 +15353,7 @@ "version": "3.25.76", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/electron/src/machines/extruder/extruder2/Extruder2Settings.tsx b/electron/src/machines/extruder/extruder2/Extruder2Settings.tsx index 05565227..f3289598 100644 --- a/electron/src/machines/extruder/extruder2/Extruder2Settings.tsx +++ b/electron/src/machines/extruder/extruder2/Extruder2Settings.tsx @@ -18,6 +18,9 @@ export function Extruder2SettingsPage() { setPressurePidKp, setPressurePidKi, setPressurePidKd, + setTemperaturePidKp, + setTemperaturePidKi, + setTemperaturePidKd, } = useExtruder2(); const [showAdvanced, setShowAdvanced] = useState(false); @@ -116,6 +119,120 @@ export function Extruder2SettingsPage() { /> + + + + + + + + + + + + + + + )} diff --git a/electron/src/machines/extruder/extruder2/extruder2Namespace.ts b/electron/src/machines/extruder/extruder2/extruder2Namespace.ts index 872cba5d..9b869c76 100644 --- a/electron/src/machines/extruder/extruder2/extruder2Namespace.ts +++ b/electron/src/machines/extruder/extruder2/extruder2Namespace.ts @@ -131,9 +131,21 @@ export const inverterStatusStateSchema = z.object({ */ export const pidSettingsSchema = z.object({ temperature: z.object({ - ki: z.number(), - kp: z.number(), - kd: z.number(), + front: z.object({ + ki: z.number(), + kp: z.number(), + kd: z.number(), + }), + middle: z.object({ + ki: z.number(), + kp: z.number(), + kd: z.number(), + }), + back: z.object({ + ki: z.number(), + kp: z.number(), + kd: z.number(), + }), }), pressure: z.object({ ki: z.number(), diff --git a/electron/src/machines/extruder/extruder2/useExtruder.ts b/electron/src/machines/extruder/extruder2/useExtruder.ts index c02a3013..9de809a6 100644 --- a/electron/src/machines/extruder/extruder2/useExtruder.ts +++ b/electron/src/machines/extruder/extruder2/useExtruder.ts @@ -293,6 +293,75 @@ export function useExtruder2() { ); }; + const setTemperaturePidKp = (kp: number, zone: string) => { + updateStateOptimistically( + (current) => { + current.data.pid_settings.pressure.kp = kp; + }, + () => { + const currentState = stateOptimistic.value; + if (currentState) { + const settings = produce( + currentState.data.pid_settings.pressure, + (draft) => { + draft.kp = kp; + }, + ); + requestTemperaturePidSettings({ + machine_identification_unique: machineIdentification, + data: { SetTemperaturePidSettings: settings, zone: zone }, + }); + } + }, + ); + }; + + const setTemperaturePidKi = (ki: number, zone: string) => { + updateStateOptimistically( + (current) => { + current.data.pid_settings.pressure.ki = ki; + }, + () => { + const currentState = stateOptimistic.value; + if (currentState) { + const settings = produce( + currentState.data.pid_settings.pressure, + (draft) => { + draft.ki = ki; + }, + ); + requestTemperaturePidSettings({ + machine_identification_unique: machineIdentification, + data: { SetTemperaturePidSettings: settings, zone: zone }, + }); + } + }, + ); + }; + + const setTemperaturePidKd = (kd: number, zone: string) => { + updateStateOptimistically( + (current) => { + current.data.pid_settings.pressure.kd = kd; + }, + () => { + const currentState = stateOptimistic.value; + if (currentState) { + const settings = produce( + currentState.data.pid_settings.pressure, + (draft) => { + draft.kd = kd; + }, + ); + requestTemperaturePidSettings({ + machine_identification_unique: machineIdentification, + data: { SetTemperaturePidSettings: settings, zone: zone }, + }); + } + }, + ); + }; + const resetInverter = () => { // No optimistic update needed for reset requestResetInverter({ @@ -356,6 +425,17 @@ export function useExtruder2() { }), ); + const { request: requestTemperaturePidSettings } = useMachineMutation( + z.object({ + SetTemperaturePidSettings: z.object({ + ki: z.number(), + kp: z.number(), + kd: z.number(), + }), + zone: z.string(), + }), + ); + const { request: requestResetInverter } = useMachineMutation( z.object({ ResetInverter: z.boolean() }), ); @@ -404,6 +484,9 @@ export function useExtruder2() { setPressurePidKp, setPressurePidKi, setPressurePidKd, + setTemperaturePidKp, + setTemperaturePidKi, + setTemperaturePidKd, resetInverter, }; } diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..b8f91335 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "control", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/server/src/machines/extruder1/api.rs b/server/src/machines/extruder1/api.rs index db05f7ec..cab47ed9 100644 --- a/server/src/machines/extruder1/api.rs +++ b/server/src/machines/extruder1/api.rs @@ -183,9 +183,16 @@ pub struct PidSettings { pub kd: f64, } +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)] +pub struct TemperaturePid { + pub front: PidSettings, + pub middle: PidSettings, + pub back: PidSettings, +} + #[derive(Serialize, Debug, Clone, PartialEq)] pub struct PidSettingsStates { - pub temperature: PidSettings, + pub temperature: TemperaturePid, pub pressure: PidSettings, } @@ -217,6 +224,7 @@ pub enum Mutation { // Pid Configure SetPressurePidSettings(PidSettings), + SetTemperaturePidSettings(PidSettings, String), // Reset ResetInverter(bool), @@ -292,6 +300,10 @@ impl MachineApi for ExtruderV2 { Mutation::SetPressurePidSettings(settings) => { self.configure_pressure_pid(settings); } + + Mutation::SetTemperaturePidSettings(settings, zone) => { + self.configure_temperature_pid(settings, zone); + } } Ok(()) } diff --git a/server/src/machines/extruder1/emit.rs b/server/src/machines/extruder1/emit.rs index 76d791fe..19243fcb 100644 --- a/server/src/machines/extruder1/emit.rs +++ b/server/src/machines/extruder1/emit.rs @@ -27,6 +27,8 @@ use uom::si::thermodynamic_temperature::ThermodynamicTemperature; #[cfg(not(feature = "mock-machine"))] impl ExtruderV2 { pub fn build_state_event(&mut self) -> StateEvent { + use crate::machines::extruder1::api::TemperaturePid; + StateEvent { is_default_state: !std::mem::replace(&mut self.emitted_default_state, true), rotation_state: RotationState { @@ -106,10 +108,22 @@ impl ExtruderV2 { fault_occurence: self.screw_speed_controller.inverter.status.fault_occurence, }, pid_settings: PidSettingsStates { - temperature: PidSettings { - ki: 0.0, - kp: 0.0, - kd: 0.0, + temperature: TemperaturePid { + front: PidSettings { + ki: self.temperature_controller_front.pid.get_ki(), + kp: self.temperature_controller_front.pid.get_kp(), + kd: self.temperature_controller_front.pid.get_kd(), + }, + middle: PidSettings { + ki: self.temperature_controller_middle.pid.get_ki(), + kp: self.temperature_controller_middle.pid.get_kp(), + kd: self.temperature_controller_middle.pid.get_kd(), + }, + back: PidSettings { + ki: self.temperature_controller_back.pid.get_ki(), + kp: self.temperature_controller_back.pid.get_kp(), + kd: self.temperature_controller_back.pid.get_kd(), + }, }, pressure: PidSettings { ki: self.screw_speed_controller.pid.get_ki(), @@ -279,4 +293,32 @@ impl ExtruderV2 { .configure(settings.ki, settings.kp, settings.kd); self.emit_state(); } + + pub fn configure_temperature_pid(&mut self, settings: PidSettings, zone: String) { + match zone.as_str() { + "front" => { + self.temperature_controller_front.pid.configure( + settings.ki, + settings.kp, + settings.kd, + ); + } + "middle" => { + self.temperature_controller_middle.pid.configure( + settings.ki, + settings.kp, + settings.kd, + ); + } + "back" => { + self.temperature_controller_back.pid.configure( + settings.ki, + settings.kp, + settings.kd, + ); + } + _ => tracing::warn!("Unknown zone: {}", zone), + } + self.emit_state(); + } } From b0c3a97f7f5ed3f8a99ed0661a0fb184c4330a26 Mon Sep 17 00:00:00 2001 From: snacj <0xsnacj@proton.me> Date: Fri, 17 Oct 2025 11:59:08 +0200 Subject: [PATCH 2/8] Fixed 3 New Pid Controllers for Extruder front, middle, back, heating. --- .../extruder/extruder2/Extruder2Settings.tsx | 249 ++++++++-------- .../extruder/extruder2/extruder2Namespace.ts | 3 + .../extruder/extruder2/useExtruder.ts | 270 ++++++++++++++---- server/src/machines/extruder1/api.rs | 22 +- server/src/machines/extruder1/emit.rs | 21 +- server/src/machines/extruder1/mock/api.rs | 4 + .../src/machines/extruder1/mock/mock_emit.rs | 24 +- server/src/machines/extruder1/mock/new.rs | 26 +- 8 files changed, 426 insertions(+), 193 deletions(-) diff --git a/electron/src/machines/extruder/extruder2/Extruder2Settings.tsx b/electron/src/machines/extruder/extruder2/Extruder2Settings.tsx index f3289598..0159d1d6 100644 --- a/electron/src/machines/extruder/extruder2/Extruder2Settings.tsx +++ b/electron/src/machines/extruder/extruder2/Extruder2Settings.tsx @@ -6,6 +6,7 @@ import { SelectionGroupBoolean } from "@/control/SelectionGroup"; import { EditValue } from "@/control/EditValue"; import { roundToDecimals } from "@/lib/decimal"; import { useExtruder2 } from "./useExtruder"; +import { ControlGrid } from "@/control/ControlGrid"; export function Extruder2SettingsPage() { const { @@ -18,9 +19,15 @@ export function Extruder2SettingsPage() { setPressurePidKp, setPressurePidKi, setPressurePidKd, - setTemperaturePidKp, - setTemperaturePidKi, - setTemperaturePidKd, + setTemperaturePidKpFront, + setTemperaturePidKiFront, + setTemperaturePidKdFront, + setTemperaturePidKpMiddle, + setTemperaturePidKiMiddle, + setTemperaturePidKdMiddle, + setTemperaturePidKpBack, + setTemperaturePidKiBack, + setTemperaturePidKdBack, } = useExtruder2(); const [showAdvanced, setShowAdvanced] = useState(false); @@ -119,120 +126,128 @@ export function Extruder2SettingsPage() { /> - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + )} diff --git a/electron/src/machines/extruder/extruder2/extruder2Namespace.ts b/electron/src/machines/extruder/extruder2/extruder2Namespace.ts index 9b869c76..7c3151d3 100644 --- a/electron/src/machines/extruder/extruder2/extruder2Namespace.ts +++ b/electron/src/machines/extruder/extruder2/extruder2Namespace.ts @@ -135,16 +135,19 @@ export const pidSettingsSchema = z.object({ ki: z.number(), kp: z.number(), kd: z.number(), + zone: z.string(), }), middle: z.object({ ki: z.number(), kp: z.number(), kd: z.number(), + zone: z.string(), }), back: z.object({ ki: z.number(), kp: z.number(), kd: z.number(), + zone: z.string(), }), }), pressure: z.object({ diff --git a/electron/src/machines/extruder/extruder2/useExtruder.ts b/electron/src/machines/extruder/extruder2/useExtruder.ts index 9de809a6..1e21372f 100644 --- a/electron/src/machines/extruder/extruder2/useExtruder.ts +++ b/electron/src/machines/extruder/extruder2/useExtruder.ts @@ -293,69 +293,225 @@ export function useExtruder2() { ); }; - const setTemperaturePidKp = (kp: number, zone: string) => { + const setTemperaturePidKdFront = (kd: number) => { updateStateOptimistically( (current) => { - current.data.pid_settings.pressure.kp = kp; + current.data.pid_settings.temperature.front.kd = kd; + current.data.pid_settings.temperature.front.zone = "front"; }, () => { const currentState = stateOptimistic.value; if (currentState) { const settings = produce( - currentState.data.pid_settings.pressure, - (draft) => { - draft.kp = kp; - }, - ); - requestTemperaturePidSettings({ - machine_identification_unique: machineIdentification, - data: { SetTemperaturePidSettings: settings, zone: zone }, - }); - } - }, - ); - }; - - const setTemperaturePidKi = (ki: number, zone: string) => { - updateStateOptimistically( - (current) => { - current.data.pid_settings.pressure.ki = ki; - }, - () => { - const currentState = stateOptimistic.value; - if (currentState) { - const settings = produce( - currentState.data.pid_settings.pressure, - (draft) => { - draft.ki = ki; - }, - ); - requestTemperaturePidSettings({ - machine_identification_unique: machineIdentification, - data: { SetTemperaturePidSettings: settings, zone: zone }, - }); - } - }, - ); - }; - - const setTemperaturePidKd = (kd: number, zone: string) => { - updateStateOptimistically( - (current) => { - current.data.pid_settings.pressure.kd = kd; - }, - () => { - const currentState = stateOptimistic.value; - if (currentState) { - const settings = produce( - currentState.data.pid_settings.pressure, + currentState.data.pid_settings.temperature.front, (draft) => { draft.kd = kd; + draft.zone = "front"; }, ); requestTemperaturePidSettings({ machine_identification_unique: machineIdentification, - data: { SetTemperaturePidSettings: settings, zone: zone }, + data: { SetTemperaturePidSettings: settings }, + }); + } + }, + ); + }; + + const setTemperaturePidKiFront = (ki: number) => { + updateStateOptimistically( + (current) => { + current.data.pid_settings.temperature.front.ki = ki; + current.data.pid_settings.temperature.front.zone = "front"; + }, + () => { + const currentState = stateOptimistic.value; + if (currentState) { + const settings = produce( + currentState.data.pid_settings.temperature.front, + (draft) => { + draft.ki = ki; + draft.zone = "front"; + }, + ); + requestTemperaturePidSettings({ + machine_identification_unique: machineIdentification, + data: { SetTemperaturePidSettings: settings }, + }); + } + }, + ); + }; + + const setTemperaturePidKpFront = (kp: number) => { + updateStateOptimistically( + (current) => { + current.data.pid_settings.temperature.front.kp = kp; + current.data.pid_settings.temperature.front.zone = "front"; + }, + () => { + const currentState = stateOptimistic.value; + if (currentState) { + const settings = produce( + currentState.data.pid_settings.temperature.front, + (draft) => { + draft.kp = kp; + draft.zone = "front"; + }, + ); + requestTemperaturePidSettings({ + machine_identification_unique: machineIdentification, + data: { SetTemperaturePidSettings: settings }, + }); + } + }, + ); + }; + + const setTemperaturePidKdMiddle = (kd: number) => { + updateStateOptimistically( + (current) => { + current.data.pid_settings.temperature.middle.kd = kd; + current.data.pid_settings.temperature.middle.zone = "middle"; + }, + () => { + const currentState = stateOptimistic.value; + if (currentState) { + const settings = produce( + currentState.data.pid_settings.temperature.middle, + (draft) => { + draft.kd = kd; + draft.zone = "middle"; + }, + ); + requestTemperaturePidSettings({ + machine_identification_unique: machineIdentification, + data: { SetTemperaturePidSettings: settings }, + }); + } + }, + ); + }; + + const setTemperaturePidKiMiddle = (ki: number) => { + updateStateOptimistically( + (current) => { + current.data.pid_settings.temperature.middle.ki = ki; + current.data.pid_settings.temperature.middle.zone = "middle"; + }, + () => { + const currentState = stateOptimistic.value; + if (currentState) { + const settings = produce( + currentState.data.pid_settings.temperature.middle, + (draft) => { + draft.ki = ki; + draft.zone = "middle"; + }, + ); + requestTemperaturePidSettings({ + machine_identification_unique: machineIdentification, + data: { SetTemperaturePidSettings: settings }, + }); + } + }, + ); + }; + + const setTemperaturePidKpMiddle = (kp: number) => { + updateStateOptimistically( + (current) => { + current.data.pid_settings.temperature.middle.kp = kp; + current.data.pid_settings.temperature.middle.zone = "middle"; + }, + () => { + const currentState = stateOptimistic.value; + if (currentState) { + const settings = produce( + currentState.data.pid_settings.temperature.middle, + (draft) => { + draft.kp = kp; + draft.zone = "middle"; + }, + ); + requestTemperaturePidSettings({ + machine_identification_unique: machineIdentification, + data: { SetTemperaturePidSettings: settings }, + }); + } + }, + ); + }; + + const setTemperaturePidKdBack = (kd: number) => { + updateStateOptimistically( + (current) => { + current.data.pid_settings.temperature.back.kd = kd; + current.data.pid_settings.temperature.back.zone = "back"; + }, + () => { + const currentState = stateOptimistic.value; + if (currentState) { + const settings = produce( + currentState.data.pid_settings.temperature.back, + (draft) => { + draft.kd = kd; + draft.zone = "back"; + }, + ); + requestTemperaturePidSettings({ + machine_identification_unique: machineIdentification, + data: { SetTemperaturePidSettings: settings }, + }); + } + }, + ); + }; + + const setTemperaturePidKiBack = (ki: number) => { + updateStateOptimistically( + (current) => { + current.data.pid_settings.temperature.back.ki = ki; + current.data.pid_settings.temperature.back.zone = "back"; + }, + () => { + const currentState = stateOptimistic.value; + if (currentState) { + const settings = produce( + currentState.data.pid_settings.temperature.back, + (draft) => { + draft.ki = ki; + draft.zone = "back"; + }, + ); + requestTemperaturePidSettings({ + machine_identification_unique: machineIdentification, + data: { SetTemperaturePidSettings: settings }, + }); + } + }, + ); + }; + + const setTemperaturePidKpBack = (kp: number) => { + updateStateOptimistically( + (current) => { + current.data.pid_settings.temperature.back.kp = kp; + current.data.pid_settings.temperature.back.zone = "back"; + }, + () => { + const currentState = stateOptimistic.value; + if (currentState) { + const settings = produce( + currentState.data.pid_settings.temperature.back, + (draft) => { + draft.kp = kp; + draft.zone = "back"; + }, + ); + requestTemperaturePidSettings({ + machine_identification_unique: machineIdentification, + data: { SetTemperaturePidSettings: settings }, }); } }, @@ -431,8 +587,8 @@ export function useExtruder2() { ki: z.number(), kp: z.number(), kd: z.number(), + zone: z.string(), }), - zone: z.string(), }), ); @@ -484,9 +640,15 @@ export function useExtruder2() { setPressurePidKp, setPressurePidKi, setPressurePidKd, - setTemperaturePidKp, - setTemperaturePidKi, - setTemperaturePidKd, + setTemperaturePidKpFront, + setTemperaturePidKiFront, + setTemperaturePidKdFront, + setTemperaturePidKpMiddle, + setTemperaturePidKiMiddle, + setTemperaturePidKdMiddle, + setTemperaturePidKpBack, + setTemperaturePidKiBack, + setTemperaturePidKdBack, resetInverter, }; } diff --git a/server/src/machines/extruder1/api.rs b/server/src/machines/extruder1/api.rs index cab47ed9..26a60137 100644 --- a/server/src/machines/extruder1/api.rs +++ b/server/src/machines/extruder1/api.rs @@ -183,16 +183,24 @@ pub struct PidSettings { pub kd: f64, } +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)] +pub struct TemperaturePidStates { + pub front: TemperaturePid, + pub middle: TemperaturePid, + pub back: TemperaturePid, +} + #[derive(Deserialize, Serialize, Debug, Clone, PartialEq)] pub struct TemperaturePid { - pub front: PidSettings, - pub middle: PidSettings, - pub back: PidSettings, + pub ki: f64, + pub kp: f64, + pub kd: f64, + pub zone: String, } #[derive(Serialize, Debug, Clone, PartialEq)] pub struct PidSettingsStates { - pub temperature: TemperaturePid, + pub temperature: TemperaturePidStates, pub pressure: PidSettings, } @@ -224,7 +232,7 @@ pub enum Mutation { // Pid Configure SetPressurePidSettings(PidSettings), - SetTemperaturePidSettings(PidSettings, String), + SetTemperaturePidSettings(TemperaturePid), // Reset ResetInverter(bool), @@ -301,8 +309,8 @@ impl MachineApi for ExtruderV2 { self.configure_pressure_pid(settings); } - Mutation::SetTemperaturePidSettings(settings, zone) => { - self.configure_temperature_pid(settings, zone); + Mutation::SetTemperaturePidSettings(settings) => { + self.configure_temperature_pid(settings); } } Ok(()) diff --git a/server/src/machines/extruder1/emit.rs b/server/src/machines/extruder1/emit.rs index 19243fcb..aa0754e2 100644 --- a/server/src/machines/extruder1/emit.rs +++ b/server/src/machines/extruder1/emit.rs @@ -5,7 +5,7 @@ use crate::machines::extruder1::{ api::{ ExtruderSettingsState, ExtruderV2Events, HeatingState, HeatingStates, InverterStatusState, LiveValuesEvent, ModeState, PidSettings, PidSettingsStates, PressureState, RegulationState, - RotationState, ScrewState, StateEvent, + RotationState, ScrewState, StateEvent, TemperaturePid, }, }; #[cfg(not(feature = "mock-machine"))] @@ -27,7 +27,7 @@ use uom::si::thermodynamic_temperature::ThermodynamicTemperature; #[cfg(not(feature = "mock-machine"))] impl ExtruderV2 { pub fn build_state_event(&mut self) -> StateEvent { - use crate::machines::extruder1::api::TemperaturePid; + use crate::machines::extruder1::api::{TemperaturePid, TemperaturePidStates}; StateEvent { is_default_state: !std::mem::replace(&mut self.emitted_default_state, true), @@ -108,21 +108,24 @@ impl ExtruderV2 { fault_occurence: self.screw_speed_controller.inverter.status.fault_occurence, }, pid_settings: PidSettingsStates { - temperature: TemperaturePid { - front: PidSettings { + temperature: TemperaturePidStates { + front: TemperaturePid { ki: self.temperature_controller_front.pid.get_ki(), kp: self.temperature_controller_front.pid.get_kp(), kd: self.temperature_controller_front.pid.get_kd(), + zone: String::from("front"), }, - middle: PidSettings { + middle: TemperaturePid { ki: self.temperature_controller_middle.pid.get_ki(), kp: self.temperature_controller_middle.pid.get_kp(), kd: self.temperature_controller_middle.pid.get_kd(), + zone: String::from("middle"), }, - back: PidSettings { + back: TemperaturePid { ki: self.temperature_controller_back.pid.get_ki(), kp: self.temperature_controller_back.pid.get_kp(), kd: self.temperature_controller_back.pid.get_kd(), + zone: String::from("back"), }, }, pressure: PidSettings { @@ -294,8 +297,8 @@ impl ExtruderV2 { self.emit_state(); } - pub fn configure_temperature_pid(&mut self, settings: PidSettings, zone: String) { - match zone.as_str() { + pub fn configure_temperature_pid(&mut self, settings: TemperaturePid) { + match settings.zone.as_str() { "front" => { self.temperature_controller_front.pid.configure( settings.ki, @@ -317,7 +320,7 @@ impl ExtruderV2 { settings.kd, ); } - _ => tracing::warn!("Unknown zone: {}", zone), + _ => tracing::warn!("Unknown zone: {}", settings.zone), } self.emit_state(); } diff --git a/server/src/machines/extruder1/mock/api.rs b/server/src/machines/extruder1/mock/api.rs index 5902ac6c..29c13548 100644 --- a/server/src/machines/extruder1/mock/api.rs +++ b/server/src/machines/extruder1/mock/api.rs @@ -37,6 +37,10 @@ impl MachineApi for ExtruderV2 { Mutation::SetPressurePidSettings(settings) => { self.configure_pressure_pid(settings); } + + Mutation::SetTemperaturePidSettings(settings) => { + self.configure_temperature_pid(settings); + } } Ok(()) } diff --git a/server/src/machines/extruder1/mock/mock_emit.rs b/server/src/machines/extruder1/mock/mock_emit.rs index 9d7eda81..d35ce038 100644 --- a/server/src/machines/extruder1/mock/mock_emit.rs +++ b/server/src/machines/extruder1/mock/mock_emit.rs @@ -1,6 +1,6 @@ use crate::machines::extruder1::{ ExtruderV2Mode, HeatingType, - api::{ExtruderV2Events, LiveValuesEvent, ModeState, PidSettings, StateEvent}, + api::{ExtruderV2Events, LiveValuesEvent, ModeState, PidSettings, StateEvent, TemperaturePid}, mock::ExtruderV2, }; use control_core::{ @@ -133,4 +133,26 @@ impl ExtruderV2 { self.pid_settings.pressure.kd = settings.kd; self.emit_state(); } + + pub fn configure_temperature_pid(&mut self, settings: TemperaturePid) { + match settings.zone.as_str() { + "front" => { + self.pid_settings.temperature.front.ki = settings.ki; + self.pid_settings.temperature.front.kp = settings.kp; + self.pid_settings.temperature.front.kd = settings.kd; + } + "middle" => { + self.pid_settings.temperature.middle.ki = settings.ki; + self.pid_settings.temperature.middle.kp = settings.kp; + self.pid_settings.temperature.middle.kd = settings.kd; + } + "back" => { + self.pid_settings.temperature.back.ki = settings.ki; + self.pid_settings.temperature.back.kp = settings.kp; + self.pid_settings.temperature.back.kd = settings.kd; + } + _ => tracing::warn!("Unknown zone: {}", settings.zone), + } + self.emit_state(); + } } diff --git a/server/src/machines/extruder1/mock/new.rs b/server/src/machines/extruder1/mock/new.rs index 9129d0c6..04f2e65e 100644 --- a/server/src/machines/extruder1/mock/new.rs +++ b/server/src/machines/extruder1/mock/new.rs @@ -3,7 +3,8 @@ use crate::machines::extruder1::{ api::{ ExtruderSettingsState, ExtruderV2Namespace, HeatingState, HeatingStates, InverterStatusState, ModeState, MotorStatusValues, PidSettings, PidSettingsStates, - PressureState, RegulationState, RotationState, ScrewState, + PressureState, RegulationState, RotationState, ScrewState, TemperaturePid, + TemperaturePidStates, }, mock::ExtruderV2, }; @@ -88,10 +89,25 @@ impl control_core::machines::new::MachineNewTrait for ExtruderV2 { fault_occurence: false, }, pid_settings: PidSettingsStates { - temperature: PidSettings { - ki: 0.0, - kp: 0.0, - kd: 0.0, + temperature: TemperaturePidStates { + front: TemperaturePid { + ki: 0.0, + kp: 0.0, + kd: 0.0, + zone: String::from("front"), + }, + middle: TemperaturePid { + ki: 0.0, + kp: 0.0, + kd: 0.0, + zone: String::from("front"), + }, + back: TemperaturePid { + ki: 0.0, + kp: 0.0, + kd: 0.0, + zone: String::from("front"), + }, }, pressure: PidSettings { ki: 0.0, From 253336b02ccc12c2aef224068389a0b0633432fb Mon Sep 17 00:00:00 2001 From: snacj <0xsnacj@proton.me> Date: Fri, 17 Oct 2025 12:59:07 +0200 Subject: [PATCH 3/8] Refactored frontend code of new PID settings --- .../extruder/extruder2/Extruder2Settings.tsx | 28 +-- .../extruder/extruder2/useExtruder.ts | 226 +----------------- 2 files changed, 21 insertions(+), 233 deletions(-) diff --git a/electron/src/machines/extruder/extruder2/Extruder2Settings.tsx b/electron/src/machines/extruder/extruder2/Extruder2Settings.tsx index 0159d1d6..e6652a4e 100644 --- a/electron/src/machines/extruder/extruder2/Extruder2Settings.tsx +++ b/electron/src/machines/extruder/extruder2/Extruder2Settings.tsx @@ -19,15 +19,7 @@ export function Extruder2SettingsPage() { setPressurePidKp, setPressurePidKi, setPressurePidKd, - setTemperaturePidKpFront, - setTemperaturePidKiFront, - setTemperaturePidKdFront, - setTemperaturePidKpMiddle, - setTemperaturePidKiMiddle, - setTemperaturePidKdMiddle, - setTemperaturePidKpBack, - setTemperaturePidKiBack, - setTemperaturePidKdBack, + setTemperaturePidValue, } = useExtruder2(); const [showAdvanced, setShowAdvanced] = useState(false); @@ -136,7 +128,7 @@ export function Extruder2SettingsPage() { max={100} step={0.01} renderValue={(v) => roundToDecimals(v, 2)} - onChange={setTemperaturePidKpFront} + onChange={(v) => setTemperaturePidValue("front", "kp", v)} title="Temperature PID KP" /> @@ -148,7 +140,7 @@ export function Extruder2SettingsPage() { max={100} step={0.01} renderValue={(v) => roundToDecimals(v, 2)} - onChange={setTemperaturePidKiFront} + onChange={(v) => setTemperaturePidValue("front", "ki", v)} title="Temperature PID KI" /> @@ -160,7 +152,7 @@ export function Extruder2SettingsPage() { max={100} step={0.01} renderValue={(v) => roundToDecimals(v, 2)} - onChange={setTemperaturePidKdFront} + onChange={(v) => setTemperaturePidValue("front", "kd", v)} title="Temperature PID KD" /> @@ -176,7 +168,7 @@ export function Extruder2SettingsPage() { max={100} step={0.01} renderValue={(v) => roundToDecimals(v, 2)} - onChange={setTemperaturePidKpMiddle} + onChange={(v) => setTemperaturePidValue("middle", "kp", v)} title="Temperature PID KP" /> @@ -190,7 +182,7 @@ export function Extruder2SettingsPage() { max={100} step={0.01} renderValue={(v) => roundToDecimals(v, 2)} - onChange={setTemperaturePidKiMiddle} + onChange={(v) => setTemperaturePidValue("middle", "ki", v)} title="Temperature PID KI" /> @@ -204,7 +196,7 @@ export function Extruder2SettingsPage() { max={100} step={0.01} renderValue={(v) => roundToDecimals(v, 2)} - onChange={setTemperaturePidKdMiddle} + onChange={(v) => setTemperaturePidValue("middle", "kd", v)} title="Temperature PID KD" /> @@ -218,7 +210,7 @@ export function Extruder2SettingsPage() { max={100} step={0.01} renderValue={(v) => roundToDecimals(v, 2)} - onChange={setTemperaturePidKpBack} + onChange={(v) => setTemperaturePidValue("back", "kp", v)} title="Temperature PID KP" /> @@ -230,7 +222,7 @@ export function Extruder2SettingsPage() { max={100} step={0.01} renderValue={(v) => roundToDecimals(v, 2)} - onChange={setTemperaturePidKiBack} + onChange={(v) => setTemperaturePidValue("back", "ki", v)} title="Temperature PID KI" /> @@ -242,7 +234,7 @@ export function Extruder2SettingsPage() { max={100} step={0.01} renderValue={(v) => roundToDecimals(v, 2)} - onChange={setTemperaturePidKdBack} + onChange={(v) => setTemperaturePidValue("back", "kd", v)} title="Temperature PID KD" /> diff --git a/electron/src/machines/extruder/extruder2/useExtruder.ts b/electron/src/machines/extruder/extruder2/useExtruder.ts index 1e21372f..dd575996 100644 --- a/electron/src/machines/extruder/extruder2/useExtruder.ts +++ b/electron/src/machines/extruder/extruder2/useExtruder.ts @@ -293,220 +293,24 @@ export function useExtruder2() { ); }; - const setTemperaturePidKdFront = (kd: number) => { + const setTemperaturePidValue = ( + zone: "front" | "middle" | "back", + key: "kp" | "ki" | "kd", + value: number, + ) => { updateStateOptimistically( (current) => { - current.data.pid_settings.temperature.front.kd = kd; - current.data.pid_settings.temperature.front.zone = "front"; + current.data.pid_settings.temperature[zone][key] = value; + current.data.pid_settings.temperature[zone].zone = zone; }, () => { const currentState = stateOptimistic.value; if (currentState) { const settings = produce( - currentState.data.pid_settings.temperature.front, + currentState.data.pid_settings.temperature[zone], (draft) => { - draft.kd = kd; - draft.zone = "front"; - }, - ); - requestTemperaturePidSettings({ - machine_identification_unique: machineIdentification, - data: { SetTemperaturePidSettings: settings }, - }); - } - }, - ); - }; - - const setTemperaturePidKiFront = (ki: number) => { - updateStateOptimistically( - (current) => { - current.data.pid_settings.temperature.front.ki = ki; - current.data.pid_settings.temperature.front.zone = "front"; - }, - () => { - const currentState = stateOptimistic.value; - if (currentState) { - const settings = produce( - currentState.data.pid_settings.temperature.front, - (draft) => { - draft.ki = ki; - draft.zone = "front"; - }, - ); - requestTemperaturePidSettings({ - machine_identification_unique: machineIdentification, - data: { SetTemperaturePidSettings: settings }, - }); - } - }, - ); - }; - - const setTemperaturePidKpFront = (kp: number) => { - updateStateOptimistically( - (current) => { - current.data.pid_settings.temperature.front.kp = kp; - current.data.pid_settings.temperature.front.zone = "front"; - }, - () => { - const currentState = stateOptimistic.value; - if (currentState) { - const settings = produce( - currentState.data.pid_settings.temperature.front, - (draft) => { - draft.kp = kp; - draft.zone = "front"; - }, - ); - requestTemperaturePidSettings({ - machine_identification_unique: machineIdentification, - data: { SetTemperaturePidSettings: settings }, - }); - } - }, - ); - }; - - const setTemperaturePidKdMiddle = (kd: number) => { - updateStateOptimistically( - (current) => { - current.data.pid_settings.temperature.middle.kd = kd; - current.data.pid_settings.temperature.middle.zone = "middle"; - }, - () => { - const currentState = stateOptimistic.value; - if (currentState) { - const settings = produce( - currentState.data.pid_settings.temperature.middle, - (draft) => { - draft.kd = kd; - draft.zone = "middle"; - }, - ); - requestTemperaturePidSettings({ - machine_identification_unique: machineIdentification, - data: { SetTemperaturePidSettings: settings }, - }); - } - }, - ); - }; - - const setTemperaturePidKiMiddle = (ki: number) => { - updateStateOptimistically( - (current) => { - current.data.pid_settings.temperature.middle.ki = ki; - current.data.pid_settings.temperature.middle.zone = "middle"; - }, - () => { - const currentState = stateOptimistic.value; - if (currentState) { - const settings = produce( - currentState.data.pid_settings.temperature.middle, - (draft) => { - draft.ki = ki; - draft.zone = "middle"; - }, - ); - requestTemperaturePidSettings({ - machine_identification_unique: machineIdentification, - data: { SetTemperaturePidSettings: settings }, - }); - } - }, - ); - }; - - const setTemperaturePidKpMiddle = (kp: number) => { - updateStateOptimistically( - (current) => { - current.data.pid_settings.temperature.middle.kp = kp; - current.data.pid_settings.temperature.middle.zone = "middle"; - }, - () => { - const currentState = stateOptimistic.value; - if (currentState) { - const settings = produce( - currentState.data.pid_settings.temperature.middle, - (draft) => { - draft.kp = kp; - draft.zone = "middle"; - }, - ); - requestTemperaturePidSettings({ - machine_identification_unique: machineIdentification, - data: { SetTemperaturePidSettings: settings }, - }); - } - }, - ); - }; - - const setTemperaturePidKdBack = (kd: number) => { - updateStateOptimistically( - (current) => { - current.data.pid_settings.temperature.back.kd = kd; - current.data.pid_settings.temperature.back.zone = "back"; - }, - () => { - const currentState = stateOptimistic.value; - if (currentState) { - const settings = produce( - currentState.data.pid_settings.temperature.back, - (draft) => { - draft.kd = kd; - draft.zone = "back"; - }, - ); - requestTemperaturePidSettings({ - machine_identification_unique: machineIdentification, - data: { SetTemperaturePidSettings: settings }, - }); - } - }, - ); - }; - - const setTemperaturePidKiBack = (ki: number) => { - updateStateOptimistically( - (current) => { - current.data.pid_settings.temperature.back.ki = ki; - current.data.pid_settings.temperature.back.zone = "back"; - }, - () => { - const currentState = stateOptimistic.value; - if (currentState) { - const settings = produce( - currentState.data.pid_settings.temperature.back, - (draft) => { - draft.ki = ki; - draft.zone = "back"; - }, - ); - requestTemperaturePidSettings({ - machine_identification_unique: machineIdentification, - data: { SetTemperaturePidSettings: settings }, - }); - } - }, - ); - }; - - const setTemperaturePidKpBack = (kp: number) => { - updateStateOptimistically( - (current) => { - current.data.pid_settings.temperature.back.kp = kp; - current.data.pid_settings.temperature.back.zone = "back"; - }, - () => { - const currentState = stateOptimistic.value; - if (currentState) { - const settings = produce( - currentState.data.pid_settings.temperature.back, - (draft) => { - draft.kp = kp; - draft.zone = "back"; + draft[key] = value; + draft.zone = zone; }, ); requestTemperaturePidSettings({ @@ -640,15 +444,7 @@ export function useExtruder2() { setPressurePidKp, setPressurePidKi, setPressurePidKd, - setTemperaturePidKpFront, - setTemperaturePidKiFront, - setTemperaturePidKdFront, - setTemperaturePidKpMiddle, - setTemperaturePidKiMiddle, - setTemperaturePidKdMiddle, - setTemperaturePidKpBack, - setTemperaturePidKiBack, - setTemperaturePidKdBack, + setTemperaturePidValue, resetInverter, }; } From 1931f008064a54e478895a4f5e8a9c8ed4553e26 Mon Sep 17 00:00:00 2001 From: snacj <0xsnacj@proton.me> Date: Fri, 17 Oct 2025 13:18:40 +0200 Subject: [PATCH 4/8] Changed rounding and step size to 0.001 for temperature PID --- .../extruder/extruder2/Extruder2Settings.tsx | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/electron/src/machines/extruder/extruder2/Extruder2Settings.tsx b/electron/src/machines/extruder/extruder2/Extruder2Settings.tsx index e6652a4e..786d36ef 100644 --- a/electron/src/machines/extruder/extruder2/Extruder2Settings.tsx +++ b/electron/src/machines/extruder/extruder2/Extruder2Settings.tsx @@ -126,8 +126,8 @@ export function Extruder2SettingsPage() { defaultValue={defaultState?.pid_settings.temperature.front.kp} min={0} max={100} - step={0.01} - renderValue={(v) => roundToDecimals(v, 2)} + step={0.001} + renderValue={(v) => roundToDecimals(v, 3)} onChange={(v) => setTemperaturePidValue("front", "kp", v)} title="Temperature PID KP" /> @@ -138,8 +138,8 @@ export function Extruder2SettingsPage() { defaultValue={defaultState?.pid_settings.temperature.front.ki} min={0} max={100} - step={0.01} - renderValue={(v) => roundToDecimals(v, 2)} + step={0.001} + renderValue={(v) => roundToDecimals(v, 3)} onChange={(v) => setTemperaturePidValue("front", "ki", v)} title="Temperature PID KI" /> @@ -150,8 +150,8 @@ export function Extruder2SettingsPage() { defaultValue={defaultState?.pid_settings.temperature.front.kd} min={0} max={100} - step={0.01} - renderValue={(v) => roundToDecimals(v, 2)} + step={0.001} + renderValue={(v) => roundToDecimals(v, 3)} onChange={(v) => setTemperaturePidValue("front", "kd", v)} title="Temperature PID KD" /> @@ -166,8 +166,8 @@ export function Extruder2SettingsPage() { } min={0} max={100} - step={0.01} - renderValue={(v) => roundToDecimals(v, 2)} + step={0.001} + renderValue={(v) => roundToDecimals(v, 3)} onChange={(v) => setTemperaturePidValue("middle", "kp", v)} title="Temperature PID KP" /> @@ -180,8 +180,8 @@ export function Extruder2SettingsPage() { } min={0} max={100} - step={0.01} - renderValue={(v) => roundToDecimals(v, 2)} + step={0.001} + renderValue={(v) => roundToDecimals(v, 3)} onChange={(v) => setTemperaturePidValue("middle", "ki", v)} title="Temperature PID KI" /> @@ -194,8 +194,8 @@ export function Extruder2SettingsPage() { } min={0} max={100} - step={0.01} - renderValue={(v) => roundToDecimals(v, 2)} + step={0.001} + renderValue={(v) => roundToDecimals(v, 3)} onChange={(v) => setTemperaturePidValue("middle", "kd", v)} title="Temperature PID KD" /> @@ -208,8 +208,8 @@ export function Extruder2SettingsPage() { defaultValue={defaultState?.pid_settings.temperature.back.kp} min={0} max={100} - step={0.01} - renderValue={(v) => roundToDecimals(v, 2)} + step={0.001} + renderValue={(v) => roundToDecimals(v, 3)} onChange={(v) => setTemperaturePidValue("back", "kp", v)} title="Temperature PID KP" /> @@ -220,8 +220,8 @@ export function Extruder2SettingsPage() { defaultValue={defaultState?.pid_settings.temperature.back.ki} min={0} max={100} - step={0.01} - renderValue={(v) => roundToDecimals(v, 2)} + step={0.001} + renderValue={(v) => roundToDecimals(v, 3)} onChange={(v) => setTemperaturePidValue("back", "ki", v)} title="Temperature PID KI" /> @@ -232,8 +232,8 @@ export function Extruder2SettingsPage() { defaultValue={defaultState?.pid_settings.temperature.back.kd} min={0} max={100} - step={0.01} - renderValue={(v) => roundToDecimals(v, 2)} + step={0.001} + renderValue={(v) => roundToDecimals(v, 3)} onChange={(v) => setTemperaturePidValue("back", "kd", v)} title="Temperature PID KD" /> From 7bc142dc8325973d7a884f9668a6a80042f40bdd Mon Sep 17 00:00:00 2001 From: snacj <0xsnacj@proton.me> Date: Fri, 17 Oct 2025 15:04:41 +0200 Subject: [PATCH 5/8] Fixed error in Extruder mock and removed zone from UpdateStateOptimistically --- .../src/machines/extruder/extruder2/useExtruder.ts | 2 -- electron/src/machines/laser/safeUseLaser1.ts | 14 ++++++++++++++ server/src/machines/extruder1/mock/new.rs | 4 ++-- 3 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 electron/src/machines/laser/safeUseLaser1.ts diff --git a/electron/src/machines/extruder/extruder2/useExtruder.ts b/electron/src/machines/extruder/extruder2/useExtruder.ts index dd575996..61a6bf2a 100644 --- a/electron/src/machines/extruder/extruder2/useExtruder.ts +++ b/electron/src/machines/extruder/extruder2/useExtruder.ts @@ -301,7 +301,6 @@ export function useExtruder2() { updateStateOptimistically( (current) => { current.data.pid_settings.temperature[zone][key] = value; - current.data.pid_settings.temperature[zone].zone = zone; }, () => { const currentState = stateOptimistic.value; @@ -310,7 +309,6 @@ export function useExtruder2() { currentState.data.pid_settings.temperature[zone], (draft) => { draft[key] = value; - draft.zone = zone; }, ); requestTemperaturePidSettings({ diff --git a/electron/src/machines/laser/safeUseLaser1.ts b/electron/src/machines/laser/safeUseLaser1.ts new file mode 100644 index 00000000..b4888399 --- /dev/null +++ b/electron/src/machines/laser/safeUseLaser1.ts @@ -0,0 +1,14 @@ +import { useLaser1 as useRealLaser1 } from "./laser1/useLaser1"; + +export function useSafeLaser1() { + const { state } = useRealLaser1(); + + return { + state: state ?? { + laser_state: { + in_tolerance: true, + }, + }, + }; +} + diff --git a/server/src/machines/extruder1/mock/new.rs b/server/src/machines/extruder1/mock/new.rs index 04f2e65e..6d0ebb3b 100644 --- a/server/src/machines/extruder1/mock/new.rs +++ b/server/src/machines/extruder1/mock/new.rs @@ -100,13 +100,13 @@ impl control_core::machines::new::MachineNewTrait for ExtruderV2 { ki: 0.0, kp: 0.0, kd: 0.0, - zone: String::from("front"), + zone: String::from("middle"), }, back: TemperaturePid { ki: 0.0, kp: 0.0, kd: 0.0, - zone: String::from("front"), + zone: String::from("back"), }, }, pressure: PidSettings { From 5dd2011e78f840c92c9ae61d57a1fc0b5dd9a599 Mon Sep 17 00:00:00 2001 From: snacj <0xsnacj@proton.me> Date: Fri, 17 Oct 2025 15:33:32 +0200 Subject: [PATCH 6/8] removed file --- electron/src/machines/laser/safeUseLaser1.ts | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 electron/src/machines/laser/safeUseLaser1.ts diff --git a/electron/src/machines/laser/safeUseLaser1.ts b/electron/src/machines/laser/safeUseLaser1.ts deleted file mode 100644 index b4888399..00000000 --- a/electron/src/machines/laser/safeUseLaser1.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { useLaser1 as useRealLaser1 } from "./laser1/useLaser1"; - -export function useSafeLaser1() { - const { state } = useRealLaser1(); - - return { - state: state ?? { - laser_state: { - in_tolerance: true, - }, - }, - }; -} - From e2cdb6a94c9a760369620ff83c998f4d2198e0e3 Mon Sep 17 00:00:00 2001 From: snacj <0xsnacj@proton.me> Date: Fri, 17 Oct 2025 16:53:38 +0200 Subject: [PATCH 7/8] Removed package-lock from root folder --- .../src/setup/GlobalLaserToastManager.tsx | 140 ++++++++++++++++++ package-lock.json | 6 - 2 files changed, 140 insertions(+), 6 deletions(-) create mode 100644 electron/src/setup/GlobalLaserToastManager.tsx delete mode 100644 package-lock.json diff --git a/electron/src/setup/GlobalLaserToastManager.tsx b/electron/src/setup/GlobalLaserToastManager.tsx new file mode 100644 index 00000000..bbace23b --- /dev/null +++ b/electron/src/setup/GlobalLaserToastManager.tsx @@ -0,0 +1,140 @@ +import React, { useEffect, useRef, useState } from "react"; +import { toast } from "sonner"; +import { useMainNamespace } from "@/client/mainNamespace"; +import { useLaser1Namespace } from "@/machines/laser/laser1/laser1Namespace"; +import { laser1 } from "@/machines/properties"; +import type { MachineIdentificationUnique } from "@/machines/types"; + +/** + * Global manager that toasts laser StateEvent changes (non-default) for a single laser. + * If machineIdentification is omitted it will attempt to discover the laser via the main namespace. + * + * Mount once in App.tsx so it persists across routes. + */ +export function GlobalLaserToastManager({ + machineIdentification, +}: { + machineIdentification?: MachineIdentificationUnique; +}) { + const main = useMainNamespace(); + const [discovered, setDiscovered] = useState(null); + + // Prefer explicit prop, otherwise use discovered id + const effectiveId = machineIdentification ?? discovered; + + // Attempt to discover the single laser machine from the main namespace + useEffect(() => { + if (machineIdentification) return; + const machinesEvent = (main as any)?.machines; + if (!machinesEvent) { + setDiscovered(null); + return; + } + + // Defensive shapes: try common structures + let list: any[] = []; + if (Array.isArray(machinesEvent?.data?.machines)) list = machinesEvent.data.machines; + else if (Array.isArray(machinesEvent?.data)) list = machinesEvent.data; + else if (Array.isArray(machinesEvent?.machines)) list = machinesEvent.machines; + else if (machinesEvent && typeof machinesEvent === "object") { + list = Object.values(machinesEvent).filter((v) => v && typeof v === "object"); + } + + const match = list.find((entry) => { + const mi = + entry?.machine_identification || + entry?.machine_identification_unique?.machine_identification || + entry?.device_machine_identification?.machine_identification; + if (!mi) return false; + return ( + mi.vendor === (laser1.machine_identification as any).vendor && + mi.machine === (laser1.machine_identification as any).machine + ); + }); + + if (match) { + const serial = + match?.serial ?? + match?.machine_identification_unique?.serial ?? + match?.device_machine_identification?.machine_identification_unique?.serial ?? + null; + if (serial != null && !Number.isNaN(Number(serial))) { + setDiscovered({ + machine_identification: laser1.machine_identification, + serial: Number(serial), + }); + return; + } + } + + setDiscovered(null); + }, [main, machineIdentification]); + + return effectiveId ? : null; +} + +/** + * Watches StateEvent for the laser namespace and shows toasts for non-default states. + * Component is non-visual. + */ +function LaserToastWatcher({ machineIdentification }: { machineIdentification: MachineIdentificationUnique }) { + const { state } = useLaser1Namespace(machineIdentification); + + // Deduplicate toasts by event timestamp + const lastToastTs = useRef(null); + + useEffect(() => { + if (!state) return; + + const eventTs = (state as any)?.ts ?? null; + const isDefault = !!(state as any)?.data?.is_default_state; + + // skip the default snapshot emitted on connect + if (isDefault) return; + + // dedupe identical events + if (eventTs != null && lastToastTs.current === eventTs) return; + lastToastTs.current = eventTs; + + try { + const data = (state as any).data; + const title = (state as any)?.name ?? "Laser State"; + const ls = data?.laser_state; + const parts: string[] = []; + if (ls) { + if (typeof ls.target_diameter === "number") parts.push(`target ${ls.target_diameter.toFixed(2)} mm`); + if (typeof ls.higher_tolerance === "number") parts.push(`+${ls.higher_tolerance.toFixed(2)} mm`); + if (typeof ls.lower_tolerance === "number") parts.push(`-${ls.lower_tolerance.toFixed(2)} mm`); + } + + const message = parts.length > 0 ? parts.join(" · ") : undefined; + + // Sonner toast call — adjust options if you want persistent/critical styling + toast( +
+ ${title} + ${message ? ` — ${message}` : ""} + +
, + { + duration: Infinity, + position: "top-center", + style: { background: "transparent", padding: 0, boxShadow: "none", border: "none" }, + } + ); + } catch (err) { + console.error("GlobalLaserToastManager: failed to build toast", err); + toast("Laser state changed"); + } + }, [state]); + + return null; +} diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index b8f91335..00000000 --- a/package-lock.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "control", - "lockfileVersion": 3, - "requires": true, - "packages": {} -} From 0dba0f2774526c7ddd7d6943c5edf44612e494d1 Mon Sep 17 00:00:00 2001 From: snacj <0xsnacj@proton.me> Date: Fri, 17 Oct 2025 16:54:29 +0200 Subject: [PATCH 8/8] Removed package-lock from root folder --- .../src/setup/GlobalLaserToastManager.tsx | 140 ------------------ 1 file changed, 140 deletions(-) delete mode 100644 electron/src/setup/GlobalLaserToastManager.tsx diff --git a/electron/src/setup/GlobalLaserToastManager.tsx b/electron/src/setup/GlobalLaserToastManager.tsx deleted file mode 100644 index bbace23b..00000000 --- a/electron/src/setup/GlobalLaserToastManager.tsx +++ /dev/null @@ -1,140 +0,0 @@ -import React, { useEffect, useRef, useState } from "react"; -import { toast } from "sonner"; -import { useMainNamespace } from "@/client/mainNamespace"; -import { useLaser1Namespace } from "@/machines/laser/laser1/laser1Namespace"; -import { laser1 } from "@/machines/properties"; -import type { MachineIdentificationUnique } from "@/machines/types"; - -/** - * Global manager that toasts laser StateEvent changes (non-default) for a single laser. - * If machineIdentification is omitted it will attempt to discover the laser via the main namespace. - * - * Mount once in App.tsx so it persists across routes. - */ -export function GlobalLaserToastManager({ - machineIdentification, -}: { - machineIdentification?: MachineIdentificationUnique; -}) { - const main = useMainNamespace(); - const [discovered, setDiscovered] = useState(null); - - // Prefer explicit prop, otherwise use discovered id - const effectiveId = machineIdentification ?? discovered; - - // Attempt to discover the single laser machine from the main namespace - useEffect(() => { - if (machineIdentification) return; - const machinesEvent = (main as any)?.machines; - if (!machinesEvent) { - setDiscovered(null); - return; - } - - // Defensive shapes: try common structures - let list: any[] = []; - if (Array.isArray(machinesEvent?.data?.machines)) list = machinesEvent.data.machines; - else if (Array.isArray(machinesEvent?.data)) list = machinesEvent.data; - else if (Array.isArray(machinesEvent?.machines)) list = machinesEvent.machines; - else if (machinesEvent && typeof machinesEvent === "object") { - list = Object.values(machinesEvent).filter((v) => v && typeof v === "object"); - } - - const match = list.find((entry) => { - const mi = - entry?.machine_identification || - entry?.machine_identification_unique?.machine_identification || - entry?.device_machine_identification?.machine_identification; - if (!mi) return false; - return ( - mi.vendor === (laser1.machine_identification as any).vendor && - mi.machine === (laser1.machine_identification as any).machine - ); - }); - - if (match) { - const serial = - match?.serial ?? - match?.machine_identification_unique?.serial ?? - match?.device_machine_identification?.machine_identification_unique?.serial ?? - null; - if (serial != null && !Number.isNaN(Number(serial))) { - setDiscovered({ - machine_identification: laser1.machine_identification, - serial: Number(serial), - }); - return; - } - } - - setDiscovered(null); - }, [main, machineIdentification]); - - return effectiveId ? : null; -} - -/** - * Watches StateEvent for the laser namespace and shows toasts for non-default states. - * Component is non-visual. - */ -function LaserToastWatcher({ machineIdentification }: { machineIdentification: MachineIdentificationUnique }) { - const { state } = useLaser1Namespace(machineIdentification); - - // Deduplicate toasts by event timestamp - const lastToastTs = useRef(null); - - useEffect(() => { - if (!state) return; - - const eventTs = (state as any)?.ts ?? null; - const isDefault = !!(state as any)?.data?.is_default_state; - - // skip the default snapshot emitted on connect - if (isDefault) return; - - // dedupe identical events - if (eventTs != null && lastToastTs.current === eventTs) return; - lastToastTs.current = eventTs; - - try { - const data = (state as any).data; - const title = (state as any)?.name ?? "Laser State"; - const ls = data?.laser_state; - const parts: string[] = []; - if (ls) { - if (typeof ls.target_diameter === "number") parts.push(`target ${ls.target_diameter.toFixed(2)} mm`); - if (typeof ls.higher_tolerance === "number") parts.push(`+${ls.higher_tolerance.toFixed(2)} mm`); - if (typeof ls.lower_tolerance === "number") parts.push(`-${ls.lower_tolerance.toFixed(2)} mm`); - } - - const message = parts.length > 0 ? parts.join(" · ") : undefined; - - // Sonner toast call — adjust options if you want persistent/critical styling - toast( -
- ${title} - ${message ? ` — ${message}` : ""} - -
, - { - duration: Infinity, - position: "top-center", - style: { background: "transparent", padding: 0, boxShadow: "none", border: "none" }, - } - ); - } catch (err) { - console.error("GlobalLaserToastManager: failed to build toast", err); - toast("Laser state changed"); - } - }, [state]); - - return null; -}