From fba4f424b8c4a2aac587cdec04c3bbe1c559c73e Mon Sep 17 00:00:00 2001 From: Joacim Breiler Date: Wed, 15 May 2024 17:29:50 +0200 Subject: [PATCH] Add base for machine calibration --- src/App.tsx | 5 + src/components/alertmessage/AlertMessage.tsx | 5 +- src/pages/calibrate/Calibrate.tsx | 299 ++++++++++++++++++ src/pages/calibrate/CalibratePin.tsx | 51 +++ src/pages/selectmode/SelectMode.tsx | 11 +- src/pages/terminal/Terminal.tsx | 1 - src/panels/configuration/Configuration.tsx | 12 +- .../spindledriver/LaserSpindle.tsx | 1 - src/panels/navigation/Navigation.tsx | 7 + .../controllerservice/ControllerService.ts | 14 +- .../controllerservice/commands/Command.ts | 11 + .../commands/GetAccessPointListCommand.ts | 6 +- .../commands/GetGpioDumpCommand.ts | 31 ++ .../commands/ListFilesCommand.ts | 2 +- src/utils/utils.ts | 10 + 15 files changed, 447 insertions(+), 19 deletions(-) create mode 100644 src/pages/calibrate/Calibrate.tsx create mode 100644 src/pages/calibrate/CalibratePin.tsx create mode 100644 src/services/controllerservice/commands/GetGpioDumpCommand.ts diff --git a/src/App.tsx b/src/App.tsx index 2c9e0d5..312f084 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -17,6 +17,7 @@ import { ControllerServiceContext } from "./context/ControllerServiceContext"; import Navigation from "./panels/navigation/Navigation"; import PageTitle from "./components/pagetitle/PageTitle"; import WiFiSettings from "./pages/wifisettings/WiFiSettings"; +import Calibrate from "./pages/calibrate/Calibrate"; const App = () => { const navigate = useNavigate(); @@ -110,6 +111,10 @@ const App = () => { path="wifi" element={} /> + } + /> diff --git a/src/components/alertmessage/AlertMessage.tsx b/src/components/alertmessage/AlertMessage.tsx index 9a2288f..ab05b66 100644 --- a/src/components/alertmessage/AlertMessage.tsx +++ b/src/components/alertmessage/AlertMessage.tsx @@ -7,10 +7,11 @@ import { Alert, Col, Row } from "react-bootstrap"; type AlertMessageProps = { children: ReactNode; + variant?: "danger" | "warning" | "info"; }; -const AlertMessage = ({ children }: AlertMessageProps) => { +const AlertMessage = ({ children, variant }: AlertMessageProps) => { return ( - + { + return ( + <> + + + + + + + + ); +}; + +const Calibrate = () => { + usePageView("Calibration"); + const controllerService = useContext(ControllerServiceContext); + const [isLoading, setIsLoading] = useState(false); + const [config, setConfig] = useState(); + const [configFile, setConfigFile] = useState(); + const [gpioStatusList, setGpioStatusList] = useState([]); + + useEffect(() => { + if (!controllerService) { + return; + } + + let timer; + setIsLoading(true); + controllerService + .connect() + .then(async () => { + await controllerService.serialPort.write(Buffer.from([0x0c])); // CTRL-L Restting echo mode + const configFilenameCommand = await controllerService.send( + new GetConfigFilenameCommand() + ); + setConfigFile(configFilenameCommand.getFilename()); + const listCommand = await controllerService.send( + new ListFilesCommand() + ); + + if ( + listCommand + .getFiles() + .map((f) => f.name) + .indexOf(configFilenameCommand.getFilename()) > -1 + ) { + const fileData = await controllerService.downloadFile( + configFilenameCommand.getFilename() + ); + setConfig(fileDataToConfig(fileData.toString())); + + await sleep(2000); + timer = setInterval(() => { + controllerService + .send(new GetGpioDumpCommand()) + .then((command) => { + setGpioStatusList(command.getStatusList()); + }) + .catch((error) => console.error(error)); + }, 500); + } + }) + .finally(() => { + setIsLoading(false); + }); + + return () => { + if (timer) { + clearInterval(timer); + } + }; + }, [ + controllerService, + setIsLoading, + setConfig, + setGpioStatusList, + setConfigFile + ]); + + return ( + <> + Calibrate + {isLoading && ( + <> + Loading configuration + + )} + + {!isLoading && !config && ( + + Could not find the config file {configFile}.
+ Go to the file browser and check + that you have a valid configuration file. +
+ )} + + {!isLoading && config && ( + <> + + This page is still a work in progress and will + eventually be used for calibrating and fine tune your + machine. + + +

Input pins

+

+ Here you can check that your input pins are triggered + correctly. Trigger them manually to make sure they are + configured properly. +

+ {config?.axes?.x && ( + + )} + {config?.axes?.y && ( + + )} + {config?.axes?.z && ( + + )} + {config?.axes?.a && ( + + )} + {config?.axes?.b && ( + + )} + {config?.axes?.c && ( + + )} + + + + + + + + + + + + + + )} + + ); +}; +export default Calibrate; diff --git a/src/pages/calibrate/CalibratePin.tsx b/src/pages/calibrate/CalibratePin.tsx new file mode 100644 index 0000000..e48536d --- /dev/null +++ b/src/pages/calibrate/CalibratePin.tsx @@ -0,0 +1,51 @@ +import React from "react"; +import { Pin, PinConfig } from "../../model/Config"; +import { GpioStatus } from "../../services/controllerservice/commands/GetGpioDumpCommand"; +import { Badge, Col, Row } from "react-bootstrap"; + +type Props = { + label: string; + pinConfig?: PinConfig; + gpioStatusList: GpioStatus[]; +}; + +const CalibratePin = ({ label, pinConfig, gpioStatusList }: Props) => { + const gpioStatus: GpioStatus | undefined = gpioStatusList.find( + (s) => s.pin === pinConfig?.pin + ); + + if (!pinConfig || pinConfig.pin === Pin.NO_PIN) { + return; + } + + return ( + + {label} + +
+ {!gpioStatus && ?} + + {((gpioStatus?.state == 1 && pinConfig?.active == "low") || + (gpioStatus?.state == 0 && + pinConfig?.active == "high")) && ( + INACTIVE + )} + {((gpioStatus?.state == 0 && pinConfig?.active == "low") || + (gpioStatus?.state == 1 && + pinConfig?.active == "high")) && ( + TRIGGERED + )} +
+ +
+ ); +}; + +export default CalibratePin; diff --git a/src/pages/selectmode/SelectMode.tsx b/src/pages/selectmode/SelectMode.tsx index bad44ef..4a16da8 100644 --- a/src/pages/selectmode/SelectMode.tsx +++ b/src/pages/selectmode/SelectMode.tsx @@ -19,6 +19,7 @@ import { Spinner } from "../../components"; import { GetStartupShowCommand } from "../../services/controllerservice/commands/GetStartupShowCommand"; import AlertMessage from "../../components/alertmessage/AlertMessage"; import LogModal from "../../components/logmodal/LogModal"; +import { CalibrateCard } from "../../components/cards/calibratecard/CalibrateCard"; const SelectMode = () => { usePageView("Home"); @@ -74,8 +75,7 @@ const SelectMode = () => { {isBootError && ( - - {" "} + There was an error during boot, likely due to an unvalid configuration.
@@ -114,6 +114,13 @@ const SelectMode = () => { /> )} + {stats?.version && ( + + navigate(Page.CALIBRATE)} + /> + + )}
diff --git a/src/pages/terminal/Terminal.tsx b/src/pages/terminal/Terminal.tsx index f782c60..effac06 100644 --- a/src/pages/terminal/Terminal.tsx +++ b/src/pages/terminal/Terminal.tsx @@ -43,7 +43,6 @@ const handleTerminalInput = ( } timer = setTimeout(() => { - console.log("Term " + buffer); buffer = buffer.replace(/MSG:ERR/g, COLOR_RED + "$&" + COLOR_GRAY); buffer = buffer.replace(/MSG:INFO/g, COLOR_GREEN + "$&" + COLOR_GRAY); buffer = buffer.replace(/MSG:WARN/g, COLOR_YELLOW + "$&" + COLOR_GRAY); diff --git a/src/panels/configuration/Configuration.tsx b/src/panels/configuration/Configuration.tsx index 2d74f2a..4821e41 100644 --- a/src/panels/configuration/Configuration.tsx +++ b/src/panels/configuration/Configuration.tsx @@ -14,7 +14,7 @@ import Editor from "../../components/editor/Editor"; import SpindleDriverGroup from "./groups/SpindleDriverGroup"; import I2CGroup from "./groups/I2CGroup"; import OLEDGroup from "./groups/OLEDGroup"; -import { deepMerge } from "../../utils/utils"; +import { deepMerge, fileDataToConfig } from "../../utils/utils"; import ControlGroup from "./groups/ControlGroup"; import ProbeGroup from "./groups/ProbeGroup"; import "./Configuration.scss"; @@ -61,21 +61,17 @@ const Configuration = ({ }; const updateValue = (value: string) => { - // Workaround for values beginning with # (should not be treated as comments) - const regexp = /^(\s*.*:[ \t]*)(#\S.*)$/gm; - const transformedValue = value.replace(regexp, '$1"$2"'); - try { - const data = jsYaml.load(transformedValue); + const data = fileDataToConfig(value); setConfig((config) => { return { ...config, ...data }; }); - onChange(transformedValue, false); + onChange(value, false); } catch (error) { - onChange(transformedValue, true); + onChange(value, true); } }; diff --git a/src/panels/configuration/spindledriver/LaserSpindle.tsx b/src/panels/configuration/spindledriver/LaserSpindle.tsx index d58a1e1..7fde1a5 100644 --- a/src/panels/configuration/spindledriver/LaserSpindle.tsx +++ b/src/panels/configuration/spindledriver/LaserSpindle.tsx @@ -21,7 +21,6 @@ const LaserSpindle = ({ updateSpindleDriverValue, usedPins }: LaserSpindleProps) => { - console.log("Value: " + config?.Laser?.tool_num); return (

Laser

diff --git a/src/panels/navigation/Navigation.tsx b/src/panels/navigation/Navigation.tsx index 6878294..acb78db 100644 --- a/src/panels/navigation/Navigation.tsx +++ b/src/panels/navigation/Navigation.tsx @@ -5,6 +5,7 @@ import { faHome, faPowerOff, faRefresh, + faSliders, faTerminal, faWifi } from "@fortawesome/free-solid-svg-icons"; @@ -74,6 +75,12 @@ const Navigation = () => { WiFi )} + {stats?.version && ( + + {" "} + Calibrate + + )}
{" "} diff --git a/src/services/controllerservice/ControllerService.ts b/src/services/controllerservice/ControllerService.ts index c483085..bbec871 100644 --- a/src/services/controllerservice/ControllerService.ts +++ b/src/services/controllerservice/ControllerService.ts @@ -188,23 +188,30 @@ export class ControllerService { while (endLineIndex >= 0) { const line = this.buffer.substring(0, endLineIndex); this.buffer = this.buffer.substring(endLineIndex + 1); - console.log("<<< " + line); if (this.commands.length) { + if (this.commands[0].debugReceive) { + console.log("<<< " + line); + } this.commands[0].appendLine(line); if (this.commands[0].state == CommandState.DONE) { this.commands = this.commands.slice(1); } + } else { + console.log("<<< " + line); } endLineIndex = this.buffer.indexOf("\n"); } }; - send = ( + send = async ( command: T, timeoutMs: number = 0 ): Promise => { - console.log("sending " + command.command); + if (command.debugSend) { + console.log("sending " + command.command); + } + this.commands.push(command); const result = new Promise((resolve, reject) => { let timer; @@ -221,6 +228,7 @@ export class ControllerService { resolve(command); }; }); + this.serialPort.write(Buffer.from((command as Command).command + "\n")); return result; }; diff --git a/src/services/controllerservice/commands/Command.ts b/src/services/controllerservice/commands/Command.ts index 4951e5b..83a602a 100644 --- a/src/services/controllerservice/commands/Command.ts +++ b/src/services/controllerservice/commands/Command.ts @@ -9,6 +9,17 @@ export class Command { command: string; response: string[]; state: CommandState; + + /** + * Set to true to debug log when the command is being sent + */ + debugSend: boolean; + + /** + * Set to true to debug log when the response data is received + */ + debugReceive: boolean; + onDone: () => Promise; constructor(command: string) { diff --git a/src/services/controllerservice/commands/GetAccessPointListCommand.ts b/src/services/controllerservice/commands/GetAccessPointListCommand.ts index 02a116b..5aaaa51 100644 --- a/src/services/controllerservice/commands/GetAccessPointListCommand.ts +++ b/src/services/controllerservice/commands/GetAccessPointListCommand.ts @@ -37,7 +37,11 @@ export class GetAccessPointListCommand extends Command { isProtected: a.IS_PROTECTED === "1" })).sort((a, b) => a.signal - b.signal); } catch (error) { - console.error(error); + console.error( + "An error occured while trying to parse accesspoint data", + this.response, + error + ); } return []; diff --git a/src/services/controllerservice/commands/GetGpioDumpCommand.ts b/src/services/controllerservice/commands/GetGpioDumpCommand.ts new file mode 100644 index 0000000..6c0e5b5 --- /dev/null +++ b/src/services/controllerservice/commands/GetGpioDumpCommand.ts @@ -0,0 +1,31 @@ +import { Command } from "./Command"; + +export type GpioStatus = { + id: number; + pin: string; + direction: "Input" | "Output"; + state: 0 | 1; +}; + +const regexp = /^(\d+) (GPIO\d+) ([IO])([01]).*$/; + +export class GetGpioDumpCommand extends Command { + constructor() { + super("$GPIO/Dump"); + } + + getStatusList(): GpioStatus[] { + return this.response + .map((line) => line.match(regexp)) + .filter((matches) => !!matches) + .map((matches) => { + return { + id: +(matches?.[1] ?? "0"), + pin: matches?.[2]?.replace("GPIO", "gpio.") ?? "NO_PIN", + direction: + (matches?.[3] ?? "I") === "I" ? "Input" : "Output", + state: (matches?.[4] ?? "0") === "0" ? 0 : 1 + }; + }); + } +} diff --git a/src/services/controllerservice/commands/ListFilesCommand.ts b/src/services/controllerservice/commands/ListFilesCommand.ts index d3c46f6..3908c37 100644 --- a/src/services/controllerservice/commands/ListFilesCommand.ts +++ b/src/services/controllerservice/commands/ListFilesCommand.ts @@ -1,5 +1,5 @@ import { Command } from "./Command"; -import { ControllerFile } from "./types"; +import { ControllerFile } from "../types"; export class ListFilesCommand extends Command { constructor() { diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 8dec6ab..b4b6549 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -1,3 +1,6 @@ +import jsYaml from "js-yaml"; +import { Config } from "../model/Config"; + export const isSafari = () => { return ( navigator.vendor && @@ -74,3 +77,10 @@ export const deepMerge = (target: Props, ...sources: Props[]): Props => { export const sleep = (milliseconds: number) => { return new Promise((resolve) => setTimeout(resolve, milliseconds)); }; + +export const fileDataToConfig = (data): Config => { + // Workaround for values beginning with # (should not be treated as comments) + const regexp = /^(\s*.*:[ \t]*)(#\S.*)$/gm; + const transformedValue = data.replace(regexp, '$1"$2"'); + return jsYaml.load(transformedValue); +};