Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
97c9eee8c2 | ||
|
|
8b7b91907b | ||
|
|
35864b4de7 | ||
|
|
621cb1913f | ||
|
|
3e3e3f1c0b | ||
|
|
cdf0374020 | ||
|
|
ec406889f7 | ||
|
|
b731a3bc36 | ||
|
|
27e15820f7 |
No files matched your search
@@ -0,0 +1,14 @@
|
||||
## What does this PR do / add / change?
|
||||
|
||||
<!-- Describe the purpose of this PR. What problem does it solve or what feature does it add? -->
|
||||
|
||||
## Screenshots
|
||||
|
||||
<!-- Include screenshots for any UI changes. Remove this section if not applicable. -->
|
||||
|
||||
## Checklist before opening the pr
|
||||
|
||||
- [ ] Tested locally
|
||||
- [ ] Tested on the machine (if hardware interaction is involved)
|
||||
- [ ] No format errors (`cargo fmt` / `npm run format`)
|
||||
- [ ] No build errors (`cargo build` / `npm run build`)
|
||||
@@ -8,6 +8,9 @@ on:
|
||||
branches:
|
||||
- master
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: electron
|
||||
|
||||
@@ -8,6 +8,9 @@ on:
|
||||
branches:
|
||||
- master
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
NIX_PATH: nixpkgs=channel:nixos-25.05
|
||||
|
||||
|
||||
@@ -12,6 +12,9 @@ env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUST_VERSION: "1.86"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
Generated
+5
-22
@@ -71,7 +71,7 @@
|
||||
"@types/react": "^19.1.16",
|
||||
"@types/react-dom": "^19.0.4",
|
||||
"@types/react-virtualized": "^9.22.2",
|
||||
"electron": "^37.3.1",
|
||||
"electron": "^40.6.1",
|
||||
"electron-playwright-helpers": "^1.7.1",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint-config-prettier": "^10.1.2",
|
||||
@@ -5196,15 +5196,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/electron": {
|
||||
"version": "37.10.3",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-37.10.3.tgz",
|
||||
"integrity": "sha512-3IjCGSjQmH50IbW2PFveaTzK+KwcFX9PEhE7KXb9v5IT8cLAiryAN7qezm/XzODhDRlLu0xKG1j8xWBtZ/bx/g==",
|
||||
"version": "40.6.1",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-40.6.1.tgz",
|
||||
"integrity": "sha512-u9YfoixttdauciHV9Ut9Zf3YipJoU093kR1GSYTTXTAXqhiXI0G1A0NnL/f0O2m2UULCXaXMf2W71PloR6V9pQ==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@electron/get": "^2.0.0",
|
||||
"@types/node": "^22.7.7",
|
||||
"@types/node": "^24.9.0",
|
||||
"extract-zip": "^2.0.1"
|
||||
},
|
||||
"bin": {
|
||||
@@ -5285,16 +5285,6 @@
|
||||
"global-agent": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/electron/node_modules/@types/node": {
|
||||
"version": "22.19.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.2.tgz",
|
||||
"integrity": "sha512-LPM2G3Syo1GLzXLGJAKdqoU35XvrWzGJ21/7sgZTUpbkBaOasTj8tjwn6w+hCkqaa1TfJ/w67rJSwYItlJ2mYw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/electron/node_modules/fs-extra": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
|
||||
@@ -5330,13 +5320,6 @@
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/electron/node_modules/undici-types": {
|
||||
"version": "6.21.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/electron/node_modules/universalify": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
"@types/react": "^19.1.16",
|
||||
"@types/react-dom": "^19.0.4",
|
||||
"@types/react-virtualized": "^9.22.2",
|
||||
"electron": "^37.3.1",
|
||||
"electron": "^40.6.1",
|
||||
"electron-playwright-helpers": "^1.7.1",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint-config-prettier": "^10.1.2",
|
||||
|
||||
@@ -20,6 +20,7 @@ import { TouchButton } from "../touch/TouchButton";
|
||||
import { seriesToUPlotData } from "@/lib/timeseries";
|
||||
import { ControlCard } from "@/control/ControlCard";
|
||||
import { getAllTimeSeries } from "./createChart";
|
||||
import { TargetDashOverlay } from "./TargetDashOverlay";
|
||||
|
||||
// Collects lines connected to visible series for rendering
|
||||
const getVisibleLines = (data: any, visibleSeries: boolean[]) => {
|
||||
@@ -595,10 +596,18 @@ export function BigGraph({
|
||||
|
||||
<div className="flex-1 overflow-hidden rounded-b-3xl pt-4">
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={`h-full w-full overflow-hidden transition-opacity duration-100`}
|
||||
className={`relative h-full w-full overflow-hidden transition-opacity duration-100`}
|
||||
style={{ backgroundColor: colors.background }}
|
||||
/>
|
||||
>
|
||||
<div ref={containerRef} className="h-full w-full overflow-hidden" />
|
||||
<TargetDashOverlay
|
||||
uplotRef={uplotRef}
|
||||
newData={newData}
|
||||
config={enhancedConfig}
|
||||
selectedTimeWindow={selectedTimeWindow}
|
||||
isLiveMode={isLiveMode}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,411 @@
|
||||
import React, { useEffect, useMemo, useState, useId } from "react";
|
||||
import uPlot from "uplot";
|
||||
import { BigGraphProps, GraphConfig } from "./types";
|
||||
import { getAllTimeSeries } from "./createChart";
|
||||
|
||||
type OverlayLine = {
|
||||
key: string;
|
||||
color: string;
|
||||
width: number;
|
||||
dash: number[];
|
||||
d: string;
|
||||
};
|
||||
|
||||
type ClipRect = {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
|
||||
function findFirstGe(arr: number[], value: number): number {
|
||||
let lo = 0;
|
||||
let hi = arr.length - 1;
|
||||
let ans = arr.length;
|
||||
|
||||
while (lo <= hi) {
|
||||
const mid = (lo + hi) >> 1;
|
||||
if (arr[mid] >= value) {
|
||||
ans = mid;
|
||||
hi = mid - 1;
|
||||
} else {
|
||||
lo = mid + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return ans;
|
||||
}
|
||||
|
||||
function findLastLe(arr: number[], value: number): number {
|
||||
let lo = 0;
|
||||
let hi = arr.length - 1;
|
||||
let ans = -1;
|
||||
|
||||
while (lo <= hi) {
|
||||
const mid = (lo + hi) >> 1;
|
||||
if (arr[mid] <= value) {
|
||||
ans = mid;
|
||||
lo = mid + 1;
|
||||
} else {
|
||||
hi = mid - 1;
|
||||
}
|
||||
}
|
||||
|
||||
return ans;
|
||||
}
|
||||
|
||||
function buildSteppedPath(
|
||||
u: uPlot,
|
||||
xData: number[],
|
||||
yData: Array<number | null>,
|
||||
): string {
|
||||
const xMin = u.scales.x?.min;
|
||||
const xMax = u.scales.x?.max;
|
||||
|
||||
if (xData.length < 2 || xMin === undefined || xMax === undefined) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const firstVisibleIdx = Math.max(0, findFirstGe(xData, xMin) - 1);
|
||||
const lastVisibleIdx = Math.min(
|
||||
xData.length - 1,
|
||||
findLastLe(xData, xMax) + 1,
|
||||
);
|
||||
|
||||
if (lastVisibleIdx < firstVisibleIdx) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const parts: string[] = [];
|
||||
let started = false;
|
||||
let prevX = 0;
|
||||
let prevY = 0;
|
||||
|
||||
for (let i = firstVisibleIdx; i <= lastVisibleIdx; i++) {
|
||||
const value = yData[i];
|
||||
if (value === null || value === undefined) continue;
|
||||
|
||||
const x = u.valToPos(xData[i], "x", true);
|
||||
const y = u.valToPos(value, "y", true);
|
||||
|
||||
if (!started) {
|
||||
parts.push(`M ${x} ${y}`);
|
||||
started = true;
|
||||
prevX = x;
|
||||
prevY = y;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (x !== prevX) {
|
||||
parts.push(`L ${x} ${prevY}`);
|
||||
}
|
||||
if (y !== prevY) {
|
||||
parts.push(`L ${x} ${y}`);
|
||||
}
|
||||
|
||||
prevX = x;
|
||||
prevY = y;
|
||||
}
|
||||
|
||||
return parts.join(" ");
|
||||
}
|
||||
|
||||
function buildFlatLatestValuePath(
|
||||
u: uPlot,
|
||||
yData: Array<number | null>,
|
||||
): string {
|
||||
let latestValue: number | null = null;
|
||||
|
||||
for (let i = yData.length - 1; i >= 0; i--) {
|
||||
const value = yData[i];
|
||||
if (value !== null && value !== undefined) {
|
||||
latestValue = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (latestValue === null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const y = u.valToPos(latestValue, "y", true);
|
||||
const left = u.bbox.left;
|
||||
const right = u.bbox.left + u.bbox.width;
|
||||
|
||||
return `M ${left} ${y} L ${right} ${y}`;
|
||||
}
|
||||
|
||||
function getHistoricalDashTargets(
|
||||
data: BigGraphProps["newData"],
|
||||
config: GraphConfig,
|
||||
): Array<{
|
||||
dataIndex: number;
|
||||
dash: number[];
|
||||
color: string;
|
||||
width: number;
|
||||
}> {
|
||||
const allOriginalSeries = getAllTimeSeries(data);
|
||||
const firstConfigLineDataIndex = 1 + allOriginalSeries.length;
|
||||
let visibleLineIndex = 0;
|
||||
|
||||
const targets: Array<{
|
||||
dataIndex: number;
|
||||
dash: number[];
|
||||
color: string;
|
||||
width: number;
|
||||
}> = [];
|
||||
|
||||
config.lines?.forEach((line) => {
|
||||
if (line.show === false) return;
|
||||
|
||||
const dash = line.dash ?? (line.type === "threshold" ? [5, 5] : undefined);
|
||||
const isHistoricalDashedTarget = !!line.targetSeries && !!dash?.length;
|
||||
|
||||
if (isHistoricalDashedTarget) {
|
||||
targets.push({
|
||||
dataIndex: firstConfigLineDataIndex + visibleLineIndex,
|
||||
dash: dash!,
|
||||
color: line.color,
|
||||
width: line.width ?? 1,
|
||||
});
|
||||
}
|
||||
|
||||
visibleLineIndex++;
|
||||
});
|
||||
|
||||
return targets;
|
||||
}
|
||||
|
||||
function areDashArraysEqual(a: number[], b: number[]): boolean {
|
||||
if (a.length !== b.length) return false;
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
if (a[i] !== b[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function areOverlayLinesEqual(a: OverlayLine[], b: OverlayLine[]): boolean {
|
||||
if (a.length !== b.length) return false;
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
if (
|
||||
a[i].key !== b[i].key ||
|
||||
a[i].color !== b[i].color ||
|
||||
a[i].width !== b[i].width ||
|
||||
a[i].d !== b[i].d ||
|
||||
!areDashArraysEqual(a[i].dash, b[i].dash)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function isSameClipRect(a: ClipRect | null, b: ClipRect): boolean {
|
||||
return (
|
||||
!!a &&
|
||||
a.x === b.x &&
|
||||
a.y === b.y &&
|
||||
a.width === b.width &&
|
||||
a.height === b.height
|
||||
);
|
||||
}
|
||||
|
||||
export function TargetDashOverlay({
|
||||
uplotRef,
|
||||
newData,
|
||||
config,
|
||||
selectedTimeWindow,
|
||||
isLiveMode,
|
||||
}: {
|
||||
uplotRef: React.RefObject<uPlot | null>;
|
||||
newData: BigGraphProps["newData"];
|
||||
config: GraphConfig;
|
||||
selectedTimeWindow: number | "all";
|
||||
isLiveMode: boolean;
|
||||
}) {
|
||||
const targetMeta = useMemo(
|
||||
() => getHistoricalDashTargets(newData, config),
|
||||
[newData, config],
|
||||
);
|
||||
const clipPathId = useId().replace(/:/g, "");
|
||||
|
||||
const [lines, setLines] = useState<OverlayLine[]>([]);
|
||||
const [clipRect, setClipRect] = useState<ClipRect | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (targetMeta.length === 0) {
|
||||
setLines([]);
|
||||
setClipRect(null);
|
||||
}
|
||||
}, [targetMeta]);
|
||||
|
||||
useEffect(() => {
|
||||
let rafId: number | null = null;
|
||||
let bootstrapRafId: number | null = null;
|
||||
let recalcScheduled = false;
|
||||
let hooksAttached = false;
|
||||
const removeHookFns: Array<() => void> = [];
|
||||
let resizeObserver: ResizeObserver | null = null;
|
||||
|
||||
const recalc = () => {
|
||||
recalcScheduled = false;
|
||||
const u = uplotRef.current;
|
||||
if (!u || targetMeta.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const xData = u.data[0] as number[] | undefined;
|
||||
if (!xData || xData.length < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextLines = targetMeta
|
||||
.map((meta, index) => {
|
||||
const yData = u.data[meta.dataIndex] as
|
||||
| Array<number | null>
|
||||
| undefined;
|
||||
if (!yData || yData.length < 2) return null;
|
||||
|
||||
const useFlatShortWindowPath =
|
||||
isLiveMode &&
|
||||
selectedTimeWindow !== "all" &&
|
||||
selectedTimeWindow <= 60_000;
|
||||
|
||||
const d = useFlatShortWindowPath
|
||||
? buildFlatLatestValuePath(u, yData)
|
||||
: buildSteppedPath(u, xData, yData);
|
||||
if (!d) return null;
|
||||
|
||||
return {
|
||||
key: `${meta.dataIndex}-${index}`,
|
||||
color: meta.color,
|
||||
width: meta.width,
|
||||
dash: meta.dash,
|
||||
d,
|
||||
} as OverlayLine;
|
||||
})
|
||||
.filter((line): line is OverlayLine => !!line);
|
||||
|
||||
if (nextLines.length > 0) {
|
||||
setLines((prev) =>
|
||||
areOverlayLinesEqual(prev, nextLines) ? prev : nextLines,
|
||||
);
|
||||
const nextClipRect: ClipRect = {
|
||||
x: u.bbox.left,
|
||||
y: u.bbox.top,
|
||||
width: u.bbox.width,
|
||||
height: u.bbox.height,
|
||||
};
|
||||
setClipRect((prev) =>
|
||||
isSameClipRect(prev, nextClipRect) ? prev : nextClipRect,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const scheduleRecalc = () => {
|
||||
if (recalcScheduled) return;
|
||||
recalcScheduled = true;
|
||||
rafId = window.requestAnimationFrame(recalc);
|
||||
};
|
||||
|
||||
scheduleRecalc();
|
||||
|
||||
if (targetMeta.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const addHook = (u: any, hookName: string, fn: () => void) => {
|
||||
const hooks = u.hooks?.[hookName];
|
||||
if (!Array.isArray(hooks)) return;
|
||||
hooks.push(fn);
|
||||
removeHookFns.push(() => {
|
||||
const idx = hooks.indexOf(fn);
|
||||
if (idx >= 0) hooks.splice(idx, 1);
|
||||
});
|
||||
};
|
||||
|
||||
const onWindowResize = () => scheduleRecalc();
|
||||
window.addEventListener("resize", onWindowResize);
|
||||
|
||||
const attachHooks = (): boolean => {
|
||||
if (hooksAttached) return true;
|
||||
const u = uplotRef.current as any;
|
||||
if (!u) return false;
|
||||
|
||||
addHook(u, "draw", scheduleRecalc);
|
||||
addHook(u, "setScale", scheduleRecalc);
|
||||
addHook(u, "setData", scheduleRecalc);
|
||||
|
||||
const rootElement = u.root as HTMLElement | undefined;
|
||||
if (rootElement && typeof ResizeObserver !== "undefined") {
|
||||
resizeObserver = new ResizeObserver(() => scheduleRecalc());
|
||||
resizeObserver.observe(rootElement);
|
||||
}
|
||||
|
||||
hooksAttached = true;
|
||||
scheduleRecalc();
|
||||
return true;
|
||||
};
|
||||
|
||||
if (!attachHooks()) {
|
||||
const tryAttach = () => {
|
||||
if (attachHooks()) return;
|
||||
bootstrapRafId = window.requestAnimationFrame(tryAttach);
|
||||
};
|
||||
bootstrapRafId = window.requestAnimationFrame(tryAttach);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (rafId !== null) {
|
||||
window.cancelAnimationFrame(rafId);
|
||||
}
|
||||
if (bootstrapRafId !== null) {
|
||||
window.cancelAnimationFrame(bootstrapRafId);
|
||||
}
|
||||
removeHookFns.forEach((fn) => fn());
|
||||
window.removeEventListener("resize", onWindowResize);
|
||||
resizeObserver?.disconnect();
|
||||
};
|
||||
}, [uplotRef, targetMeta, isLiveMode, selectedTimeWindow]);
|
||||
|
||||
if (lines.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<svg
|
||||
className="pointer-events-none absolute inset-0 h-full w-full"
|
||||
aria-hidden
|
||||
>
|
||||
{clipRect && (
|
||||
<defs>
|
||||
<clipPath id={clipPathId}>
|
||||
<rect
|
||||
x={clipRect.x}
|
||||
y={clipRect.y}
|
||||
width={clipRect.width}
|
||||
height={clipRect.height}
|
||||
/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
)}
|
||||
|
||||
<g clipPath={clipRect ? `url(#${clipPathId})` : undefined}>
|
||||
{lines.map((line) => {
|
||||
return (
|
||||
<path
|
||||
key={line.key}
|
||||
d={line.d}
|
||||
fill="none"
|
||||
stroke={line.color}
|
||||
strokeWidth={line.width}
|
||||
strokeLinecap="butt"
|
||||
strokeDasharray={line.dash.join(" ")}
|
||||
strokeDashoffset={0}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -12,15 +12,6 @@ import {
|
||||
import { BigGraphProps, CreateChartParams } from "./types";
|
||||
import { normalizeDataSeries } from "./animation";
|
||||
|
||||
type CustomDashedTargetLine = {
|
||||
dataIndex: number;
|
||||
dash: number[];
|
||||
color: string;
|
||||
width: number;
|
||||
};
|
||||
|
||||
const DASH_FLOW_PX_PER_SEC = 180;
|
||||
|
||||
export function createChart({
|
||||
containerRef,
|
||||
uplotRef,
|
||||
@@ -161,8 +152,6 @@ export function createChart({
|
||||
|
||||
// Build series configuration for ALL series (but control visibility)
|
||||
const seriesConfig: uPlot.Series[] = [{ label: "Time" }];
|
||||
const customDashedTargetLines: CustomDashedTargetLine[] = [];
|
||||
const dashFlowStartTime = performance.now();
|
||||
|
||||
// Add ALL data series with visibility control
|
||||
const defaultColors = ["#3b82f6", "#ef4444", "#10b981", "#f59e0b", "#8b5cf6"];
|
||||
@@ -188,35 +177,21 @@ export function createChart({
|
||||
});
|
||||
|
||||
// Add config lines
|
||||
let visibleLineIndex = 0;
|
||||
const firstConfigLineDataIndex = 1 + allOriginalSeries.length;
|
||||
config.lines?.forEach((line) => {
|
||||
if (line.show !== false) {
|
||||
const dash =
|
||||
line.dash ?? (line.type === "threshold" ? [5, 5] : undefined);
|
||||
const lineDataIndex = firstConfigLineDataIndex + visibleLineIndex;
|
||||
const isHistoricalDashedTarget = !!line.targetSeries && !!dash?.length;
|
||||
|
||||
if (isHistoricalDashedTarget) {
|
||||
customDashedTargetLines.push({
|
||||
dataIndex: lineDataIndex,
|
||||
dash: dash!,
|
||||
color: line.color,
|
||||
width: line.width ?? 1,
|
||||
});
|
||||
}
|
||||
|
||||
seriesConfig.push({
|
||||
label: line.label,
|
||||
// Historical dashed targets are rendered manually in draw hook to
|
||||
// avoid dash shimmer while the live x-axis slides.
|
||||
// Historical dashed targets are rendered in an SVG overlay.
|
||||
stroke: isHistoricalDashedTarget ? "rgba(0,0,0,0)" : line.color,
|
||||
width: line.width ?? 1,
|
||||
dash: isHistoricalDashedTarget ? undefined : dash,
|
||||
show: true,
|
||||
points: { show: false },
|
||||
});
|
||||
visibleLineIndex++;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -346,119 +321,6 @@ export function createChart({
|
||||
}
|
||||
},
|
||||
],
|
||||
draw: [
|
||||
(u) => {
|
||||
if (customDashedTargetLines.length === 0) return;
|
||||
const xData = u.data[0] as number[] | undefined;
|
||||
if (!xData || xData.length < 2) return;
|
||||
|
||||
const xMin = u.scales.x?.min;
|
||||
const xMax = u.scales.x?.max;
|
||||
if (xMin === undefined || xMax === undefined) return;
|
||||
|
||||
const ctx = u.ctx;
|
||||
const left = u.bbox.left;
|
||||
const top = u.bbox.top;
|
||||
const snap = (v: number) => Math.round(v) + 0.5;
|
||||
const findFirstGe = (arr: number[], value: number): number => {
|
||||
let lo = 0;
|
||||
let hi = arr.length - 1;
|
||||
let ans = arr.length;
|
||||
while (lo <= hi) {
|
||||
const mid = (lo + hi) >> 1;
|
||||
if (arr[mid] >= value) {
|
||||
ans = mid;
|
||||
hi = mid - 1;
|
||||
} else {
|
||||
lo = mid + 1;
|
||||
}
|
||||
}
|
||||
return ans;
|
||||
};
|
||||
const findLastLe = (arr: number[], value: number): number => {
|
||||
let lo = 0;
|
||||
let hi = arr.length - 1;
|
||||
let ans = -1;
|
||||
while (lo <= hi) {
|
||||
const mid = (lo + hi) >> 1;
|
||||
if (arr[mid] <= value) {
|
||||
ans = mid;
|
||||
lo = mid + 1;
|
||||
} else {
|
||||
hi = mid - 1;
|
||||
}
|
||||
}
|
||||
return ans;
|
||||
};
|
||||
const firstVisibleIdx = Math.max(0, findFirstGe(xData, xMin) - 1);
|
||||
const lastVisibleIdx = Math.min(
|
||||
xData.length - 1,
|
||||
findLastLe(xData, xMax) + 1,
|
||||
);
|
||||
if (lastVisibleIdx < firstVisibleIdx) return;
|
||||
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.rect(left, top, u.bbox.width, u.bbox.height);
|
||||
ctx.clip();
|
||||
ctx.lineCap = "butt";
|
||||
|
||||
customDashedTargetLines.forEach(
|
||||
({ dataIndex, dash, color, width }) => {
|
||||
const yData = u.data[dataIndex] as
|
||||
| Array<number | null>
|
||||
| undefined;
|
||||
if (!yData || yData.length < 2) return;
|
||||
|
||||
const dashPeriod = dash.reduce((acc, curr) => acc + curr, 0);
|
||||
ctx.setLineDash(dash);
|
||||
const elapsedMs = performance.now() - dashFlowStartTime;
|
||||
const dashTravelPx = (elapsedMs * DASH_FLOW_PX_PER_SEC) / 1000;
|
||||
ctx.lineDashOffset =
|
||||
dashPeriod > 0 ? -(dashTravelPx % dashPeriod) : 0;
|
||||
ctx.strokeStyle = color;
|
||||
ctx.lineWidth = width;
|
||||
ctx.beginPath();
|
||||
|
||||
let started = false;
|
||||
let prevX = 0;
|
||||
let prevY = 0;
|
||||
|
||||
for (let i = firstVisibleIdx; i <= lastVisibleIdx; i++) {
|
||||
const value = yData[i];
|
||||
if (value === null || value === undefined) continue;
|
||||
|
||||
const x = snap(u.valToPos(xData[i], "x", true));
|
||||
const y = snap(u.valToPos(value, "y", true));
|
||||
|
||||
if (!started) {
|
||||
ctx.moveTo(x, y);
|
||||
started = true;
|
||||
prevX = x;
|
||||
prevY = y;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (x !== prevX) {
|
||||
ctx.lineTo(x, prevY);
|
||||
}
|
||||
if (y !== prevY) {
|
||||
ctx.lineTo(x, y);
|
||||
}
|
||||
|
||||
prevX = x;
|
||||
prevY = y;
|
||||
}
|
||||
|
||||
if (started) {
|
||||
ctx.stroke();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
ctx.restore();
|
||||
},
|
||||
],
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
@@ -660,25 +522,12 @@ export function createChart({
|
||||
|
||||
const cleanup = attachEventHandlers(containerRef.current, handlers);
|
||||
|
||||
let dashFlowAnimationFrame: number | null = null;
|
||||
if (customDashedTargetLines.length > 0) {
|
||||
const animateDashFlow = () => {
|
||||
if (!uplotRef.current) return;
|
||||
uplotRef.current.redraw();
|
||||
dashFlowAnimationFrame = requestAnimationFrame(animateDashFlow);
|
||||
};
|
||||
dashFlowAnimationFrame = requestAnimationFrame(animateDashFlow);
|
||||
}
|
||||
|
||||
animationRefs.lastRenderedData.current = {
|
||||
timestamps: [...timestamps],
|
||||
values: [...primaryValues],
|
||||
};
|
||||
|
||||
return () => {
|
||||
if (dashFlowAnimationFrame !== null) {
|
||||
cancelAnimationFrame(dashFlowAnimationFrame);
|
||||
}
|
||||
cleanup();
|
||||
};
|
||||
}
|
||||
|
||||
File renamed without changes.
File renamed without changes.
+2
-2
@@ -12,9 +12,9 @@ import {
|
||||
useAnalogInputTestMachineNamespace,
|
||||
} from "./useAnalogInputTestMachineNamespace";
|
||||
import { toastError } from "@/components/Toast";
|
||||
import { MachineIdentificationUnique } from "../types";
|
||||
import { MachineIdentificationUnique } from "../../types";
|
||||
import { analogInputTestMachineSerialRoute } from "@/routes/routes";
|
||||
import { analogInputTestMachine } from "../properties";
|
||||
import { analogInputTestMachine } from "../../properties";
|
||||
import { useStateOptimistic } from "@/lib/useStateOptimistic";
|
||||
import { produce } from "immer";
|
||||
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+1
-1
@@ -14,7 +14,7 @@ import {
|
||||
NamespaceId,
|
||||
createNamespaceHookImplementation,
|
||||
ThrottledStoreUpdater,
|
||||
} from "../../../client/socketioStore";
|
||||
} from "../../../../client/socketioStore";
|
||||
import { MachineIdentificationUnique } from "@/machines/types";
|
||||
import {
|
||||
createTimeSeries,
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+1
-1
@@ -12,7 +12,7 @@ import { wagoAiTestMachineSerialRoute } from "@/routes/routes";
|
||||
import { produce } from "immer";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import z from "zod";
|
||||
import { wagoAiTestMachine } from "../properties";
|
||||
import { wagoAiTestMachine } from "../../properties";
|
||||
import {
|
||||
StateEvent,
|
||||
useWagoAiTestMachineNamespace,
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -1,112 +0,0 @@
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuItem,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
|
||||
import React from "react";
|
||||
|
||||
import { Icon } from "@/components/Icon";
|
||||
import { ControlCard } from "@/control/ControlCard";
|
||||
|
||||
interface MachineIdentificationUnique {
|
||||
machine_identification: {
|
||||
vendor: number;
|
||||
machine: number;
|
||||
};
|
||||
serial: number;
|
||||
}
|
||||
|
||||
interface Machine {
|
||||
name: string;
|
||||
machine_identification_unique: MachineIdentificationUnique;
|
||||
}
|
||||
|
||||
interface MachineSelectorProps {
|
||||
machines: Machine[];
|
||||
selectedMachine?: Machine | null;
|
||||
connectedMachineState?: {
|
||||
machine_identification_unique?: {
|
||||
machine_identification: {
|
||||
vendor: number;
|
||||
machine: number;
|
||||
};
|
||||
serial: number;
|
||||
} | null;
|
||||
is_available: boolean;
|
||||
};
|
||||
setConnectedMachine: (machine: MachineIdentificationUnique) => void;
|
||||
clearConnectedMachine?: () => void;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export const MachineSelector: React.FC<MachineSelectorProps> = ({
|
||||
machines,
|
||||
selectedMachine,
|
||||
connectedMachineState,
|
||||
setConnectedMachine,
|
||||
clearConnectedMachine,
|
||||
}) => {
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<button className="flex items-center gap-2 rounded border px-4 py-2 text-left">
|
||||
<Icon name="lu:Settings" className="text-xl" />
|
||||
<span>
|
||||
{selectedMachine?.name ?? "Select a Machine"}{" "}
|
||||
{selectedMachine?.machine_identification_unique.serial ?? ""}
|
||||
</span>
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start">
|
||||
<DropdownMenuLabel>Available Machines</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
{machines.map((machine) => {
|
||||
const isSelected =
|
||||
connectedMachineState?.machine_identification_unique
|
||||
?.machine_identification.machine ===
|
||||
machine.machine_identification_unique.machine_identification
|
||||
.machine;
|
||||
|
||||
return (
|
||||
<DropdownMenuItem
|
||||
key={machine.machine_identification_unique.serial}
|
||||
onClick={() =>
|
||||
setConnectedMachine(machine.machine_identification_unique)
|
||||
}
|
||||
className={`flex min-h-[48px] cursor-pointer items-center gap-2 py-2${
|
||||
isSelected ? "bg-blue-50" : ""
|
||||
}`}
|
||||
>
|
||||
{connectedMachineState?.is_available ? (
|
||||
<Icon name="lu:Power" className="text-lg text-green-600" />
|
||||
) : (
|
||||
<Icon name="lu:Power" className="text-lg text-gray-400" />
|
||||
)}
|
||||
<span>
|
||||
{machine.name} – Serial:{" "}
|
||||
{machine.machine_identification_unique.serial}
|
||||
</span>
|
||||
</DropdownMenuItem>
|
||||
);
|
||||
})}
|
||||
|
||||
{clearConnectedMachine && (
|
||||
<>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
onClick={clearConnectedMachine}
|
||||
className="flex cursor-pointer items-center gap-2 text-red-600 hover:text-red-600"
|
||||
>
|
||||
<Icon name="lu:Settings" className="text-lg" />
|
||||
<span>Clear Selection</span>
|
||||
</DropdownMenuItem>
|
||||
</>
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
};
|
||||
@@ -15,7 +15,7 @@ import { StatusBadge } from "@/control/StatusBadge";
|
||||
import { useWinder2 } from "./useWinder";
|
||||
import {
|
||||
Mode,
|
||||
OnSpoolLengthTaskCompletedAction,
|
||||
SpoolAutomaticActionMode,
|
||||
getGearRatioMultiplier,
|
||||
} from "./winder2Namespace";
|
||||
import { TensionArm } from "../TensionArm";
|
||||
@@ -29,7 +29,6 @@ import {
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { getWinder2TraverseMax } from "./winder2Config";
|
||||
import { MachineSelector } from "../MachineSelector";
|
||||
|
||||
export function Winder2ControlPage() {
|
||||
const [showResetConfirmDialog, setShowResetConfirmDialog] = useState(false);
|
||||
@@ -39,65 +38,46 @@ export function Winder2ControlPage() {
|
||||
const {
|
||||
state,
|
||||
defaultState,
|
||||
enableTraverseLaserpointer,
|
||||
tensionArmAngle,
|
||||
zeroTensionArmAngle,
|
||||
spoolRpm,
|
||||
setMode,
|
||||
pullerSpeed,
|
||||
spoolProgress,
|
||||
setPullerRegulationMode,
|
||||
setPullerTargetSpeed,
|
||||
traversePosition,
|
||||
selectedMachine,
|
||||
filteredMachines,
|
||||
isLoading,
|
||||
isDisabled,
|
||||
// setters
|
||||
setMode,
|
||||
setSpoolDirection,
|
||||
setSpoolSpeedControlMode,
|
||||
setSpoolMinMaxMinSpeed,
|
||||
setSpoolMinMaxMaxSpeed,
|
||||
setSpoolAdaptiveTensionTarget,
|
||||
setSpoolAdaptiveRadiusLearningRate,
|
||||
setSpoolAdaptiveMaxSpeedMultiplier,
|
||||
setSpoolAdaptiveAccelerationFactor,
|
||||
setSpoolAdaptiveDeaccelerationUrgencyMultiplier,
|
||||
setPullerDirection,
|
||||
setPullerGearRatio,
|
||||
setPullerSpeedControlMode,
|
||||
setPullerFixedTargetSpeed,
|
||||
setPullerAdaptiveBaseSpeed,
|
||||
setPullerAdaptiveDeviationMax,
|
||||
setPullerAdaptiveReferenceMachine,
|
||||
resetSpoolProgress,
|
||||
setTraverseLimitInner,
|
||||
setTraverseLimitOuter,
|
||||
setTraverseStepSize,
|
||||
setTraversePadding,
|
||||
gotoTraverseLimitInner,
|
||||
gotoTraverseLimitOuter,
|
||||
gotoTraverseHome,
|
||||
setTraverseLaserpointerEnabled,
|
||||
calibrateTensionArmAngle,
|
||||
setSpoolLengthTaskTargetLength,
|
||||
resetSpoolLengthTaskProgress,
|
||||
setOnSpoolLengthTaskCompletedAction,
|
||||
setSpoolAutomaticRequiredMeters,
|
||||
setSpoolAutomaticAction,
|
||||
isLoading,
|
||||
isDisabled,
|
||||
} = useWinder2();
|
||||
|
||||
// Calculate max speed based on gear ratio
|
||||
const gearRatioMultiplier = getGearRatioMultiplier(
|
||||
state?.puller_state?.gear_ratio,
|
||||
);
|
||||
const maxMotorSpeed = 50; // Maximum motor speed in m/min
|
||||
const maxPullerSpeed = maxMotorSpeed / gearRatioMultiplier;
|
||||
const maxMotorSpeed = 75; // Maximum motor speed in m/min
|
||||
const maxTargetSpeed = maxMotorSpeed / gearRatioMultiplier;
|
||||
|
||||
const handleResetProgress = () => {
|
||||
// Check if the machine is currently in Wind mode
|
||||
if (state?.mode === "Wind") {
|
||||
if (state?.mode_state?.mode === "Wind") {
|
||||
setShowResetConfirmDialog(true);
|
||||
} else {
|
||||
resetSpoolLengthTaskProgress();
|
||||
resetSpoolProgress();
|
||||
}
|
||||
};
|
||||
|
||||
const confirmResetProgress = () => {
|
||||
resetSpoolLengthTaskProgress();
|
||||
resetSpoolProgress();
|
||||
setShowResetConfirmDialog(false);
|
||||
};
|
||||
|
||||
@@ -151,7 +131,7 @@ export function Winder2ControlPage() {
|
||||
variant="outline"
|
||||
icon="lu:ArrowLeftToLine"
|
||||
onClick={gotoTraverseLimitOuter}
|
||||
disabled={isDisabled || state?.traverse_state?.can_go_out}
|
||||
disabled={isDisabled}
|
||||
isLoading={isLoading}
|
||||
>
|
||||
Go to Outer Limit
|
||||
@@ -182,7 +162,7 @@ export function Winder2ControlPage() {
|
||||
icon="lu:ArrowRightToLine"
|
||||
onClick={gotoTraverseLimitInner}
|
||||
disabled={isDisabled}
|
||||
isLoading={isLoading || state?.traverse_state?.can_go_in}
|
||||
isLoading={isLoading}
|
||||
>
|
||||
Go to Inner Limit
|
||||
</TouchButton>
|
||||
@@ -190,12 +170,12 @@ export function Winder2ControlPage() {
|
||||
</div>
|
||||
<Label label="Laserpointer">
|
||||
<SelectionGroupBoolean
|
||||
value={state?.traverse_state.laserpointer_enabled}
|
||||
value={state?.traverse_state.laserpointer}
|
||||
disabled={isLoading || isDisabled}
|
||||
loading={isLoading}
|
||||
optionFalse={{ children: "Off", icon: "lu:LightbulbOff" }}
|
||||
optionTrue={{ children: "On", icon: "lu:Lightbulb" }}
|
||||
onChange={setTraverseLaserpointerEnabled}
|
||||
onChange={enableTraverseLaserpointer}
|
||||
/>
|
||||
</Label>
|
||||
<Label label="Home">
|
||||
@@ -203,7 +183,7 @@ export function Winder2ControlPage() {
|
||||
variant="outline"
|
||||
icon="lu:House"
|
||||
onClick={() => gotoTraverseHome()}
|
||||
disabled={isDisabled || state?.traverse_state?.can_go_home}
|
||||
disabled={isDisabled}
|
||||
isLoading={isLoading}
|
||||
>
|
||||
Go to Home
|
||||
@@ -225,20 +205,20 @@ export function Winder2ControlPage() {
|
||||
<TouchButton
|
||||
variant="outline"
|
||||
icon="lu:House"
|
||||
onClick={calibrateTensionArmAngle}
|
||||
onClick={zeroTensionArmAngle}
|
||||
disabled={isDisabled}
|
||||
isLoading={isLoading}
|
||||
>
|
||||
Calibrate
|
||||
Set Zero Point
|
||||
</TouchButton>
|
||||
{!state?.tension_arm_state?.is_calibrated && (
|
||||
<StatusBadge variant="error">Not Calibrated</StatusBadge>
|
||||
{!state?.tension_arm_state?.zeroed && (
|
||||
<StatusBadge variant="error">Not Zeroed</StatusBadge>
|
||||
)}
|
||||
</ControlCard>
|
||||
|
||||
<ControlCard className="bg-red" title="Mode">
|
||||
<SelectionGroup<Mode>
|
||||
value={state?.mode}
|
||||
value={state?.mode_state.mode}
|
||||
disabled={isDisabled}
|
||||
loading={isLoading}
|
||||
onChange={setMode}
|
||||
@@ -267,7 +247,7 @@ export function Winder2ControlPage() {
|
||||
children: "Wind",
|
||||
icon: "lu:RefreshCcw",
|
||||
isActiveClassName: "bg-green-600",
|
||||
disabled: !state?.can_wind,
|
||||
disabled: !state?.mode_state?.can_wind,
|
||||
className: "h-full",
|
||||
},
|
||||
}}
|
||||
@@ -281,98 +261,38 @@ export function Winder2ControlPage() {
|
||||
timeseries={pullerSpeed}
|
||||
renderValue={(value) => roundToDecimals(value, 1)}
|
||||
/>
|
||||
<Label label="Speed Regulation">
|
||||
<Label label="Regulation">
|
||||
<SelectionGroup
|
||||
value={state?.puller_state?.speed_control_mode}
|
||||
disabled={isDisabled}
|
||||
loading={isLoading}
|
||||
value={state?.puller_state?.regulation}
|
||||
options={{
|
||||
Fixed: {
|
||||
children: "Fixed",
|
||||
icon: "lu:Crosshair",
|
||||
Speed: {
|
||||
children: "Speed",
|
||||
icon: "lu:Gauge",
|
||||
},
|
||||
Adaptive: {
|
||||
children: "Adaptive",
|
||||
icon: "lu:Brain",
|
||||
Diameter: {
|
||||
children: "Diameter",
|
||||
icon: "lu:Sun",
|
||||
disabled: true,
|
||||
},
|
||||
}}
|
||||
onChange={(value) =>
|
||||
setPullerSpeedControlMode(value)
|
||||
}
|
||||
onChange={setPullerRegulationMode}
|
||||
disabled={isDisabled}
|
||||
loading={isLoading}
|
||||
/>
|
||||
</Label>
|
||||
<Label label="Target Speed">
|
||||
<EditValue
|
||||
value={state?.puller_state?.target_speed}
|
||||
unit="m/min"
|
||||
title="Target Speed"
|
||||
defaultValue={defaultState?.puller_state?.target_speed}
|
||||
min={0}
|
||||
max={maxTargetSpeed}
|
||||
step={0.1}
|
||||
renderValue={(value) => roundToDecimals(value, 1)}
|
||||
onChange={setPullerTargetSpeed}
|
||||
/>
|
||||
</Label>
|
||||
|
||||
{state?.puller_state?.speed_control_mode ===
|
||||
"Fixed" && (
|
||||
<>
|
||||
<Label label="Target Speed">
|
||||
<EditValue
|
||||
value={state?.puller_state?.fixed_target_speed}
|
||||
title={"Target Speed"}
|
||||
unit="m/min"
|
||||
step={0.1}
|
||||
min={0}
|
||||
max={maxPullerSpeed}
|
||||
defaultValue={
|
||||
defaultState?.puller_state?.fixed_target_speed
|
||||
}
|
||||
renderValue={(value) => roundToDecimals(value, 0)}
|
||||
onChange={(value) => setPullerFixedTargetSpeed(value)}
|
||||
/>
|
||||
</Label>
|
||||
</>
|
||||
)}
|
||||
|
||||
{state?.puller_state?.speed_control_mode ===
|
||||
"Adaptive" && (
|
||||
<>
|
||||
<Label label="Base Speed">
|
||||
<EditValue
|
||||
value={state?.puller_state?.adaptive_base_speed}
|
||||
title={"Base Speed"}
|
||||
unit="m/min"
|
||||
step={0.1}
|
||||
min={0}
|
||||
max={maxPullerSpeed}
|
||||
defaultValue={
|
||||
defaultState?.puller_state?.adaptive_base_speed
|
||||
}
|
||||
renderValue={(value) => roundToDecimals(value, 1)}
|
||||
onChange={(value) => setPullerAdaptiveBaseSpeed(value)}
|
||||
/>
|
||||
</Label>
|
||||
<Label label="Max Deviation">
|
||||
<EditValue
|
||||
value={state?.puller_state?.adaptive_deviation_max}
|
||||
title={"Max Deviation"}
|
||||
unit="m/min"
|
||||
step={10}
|
||||
min={0.1}
|
||||
max={maxPullerSpeed}
|
||||
defaultValue={
|
||||
defaultState?.puller_state?.adaptive_deviation_max
|
||||
}
|
||||
renderValue={(value) => roundToDecimals(value, 1)}
|
||||
onChange={(value) => setPullerAdaptiveDeviationMax(value)}
|
||||
/>
|
||||
</Label>
|
||||
<Label label="Reference Machine">
|
||||
<MachineSelector
|
||||
machines={filteredMachines}
|
||||
selectedMachine={selectedMachine}
|
||||
connectedMachineState={state?.puller_state.adaptive_reference_machine}
|
||||
setConnectedMachine={(machine) => {
|
||||
setPullerAdaptiveReferenceMachine(machine);
|
||||
}}
|
||||
clearConnectedMachine={() =>
|
||||
{
|
||||
if (!selectedMachine) return;
|
||||
setPullerAdaptiveReferenceMachine(null);
|
||||
}}
|
||||
/>
|
||||
</Label>
|
||||
</>
|
||||
)}
|
||||
</ControlCard>
|
||||
|
||||
<ControlCard className="bg-red" title="Spool Autostop">
|
||||
@@ -385,7 +305,7 @@ export function Winder2ControlPage() {
|
||||
|
||||
<Label label="Target Length">
|
||||
<EditValue
|
||||
value={state?.spool_length_task_state.target_length}
|
||||
value={state?.spool_automatic_action_state.spool_required_meters}
|
||||
unit="m"
|
||||
title="Expected Meters"
|
||||
defaultValue={250}
|
||||
@@ -393,7 +313,7 @@ export function Winder2ControlPage() {
|
||||
max={10000}
|
||||
step={10}
|
||||
renderValue={(value) => roundToDecimals(value, 2)}
|
||||
onChange={setSpoolLengthTaskTargetLength}
|
||||
onChange={setSpoolAutomaticRequiredMeters}
|
||||
/>
|
||||
</Label>
|
||||
|
||||
@@ -407,13 +327,13 @@ export function Winder2ControlPage() {
|
||||
</TouchButton>
|
||||
|
||||
<Label label="After Target Length Reached">
|
||||
<SelectionGroup<OnSpoolLengthTaskCompletedAction>
|
||||
<SelectionGroup<SpoolAutomaticActionMode>
|
||||
value={
|
||||
state?.spool_length_task_state.on_completed_action
|
||||
state?.spool_automatic_action_state.spool_automatic_action_mode
|
||||
}
|
||||
disabled={isDisabled}
|
||||
loading={isLoading}
|
||||
onChange={setOnSpoolLengthTaskCompletedAction}
|
||||
onChange={setSpoolAutomaticAction}
|
||||
orientation="vertical"
|
||||
options={{
|
||||
Hold: {
|
||||
|
||||
@@ -19,6 +19,7 @@ export function Winder2GraphsPage() {
|
||||
traversePosition,
|
||||
tensionArmAngle,
|
||||
pullerSpeed,
|
||||
targetPullerSpeed,
|
||||
spoolProgress,
|
||||
} = useWinder2();
|
||||
|
||||
@@ -31,6 +32,8 @@ export function Winder2GraphsPage() {
|
||||
<PullerSpeedGraph
|
||||
syncHook={syncHook}
|
||||
newData={pullerSpeed}
|
||||
targetSeries={targetPullerSpeed}
|
||||
targetSpeed={state?.puller_state?.target_speed}
|
||||
unit="m/min"
|
||||
renderValue={(value) => roundToDecimals(value, 0)}
|
||||
/>
|
||||
@@ -242,16 +245,30 @@ export function SpoolProgressGraph({
|
||||
export function PullerSpeedGraph({
|
||||
syncHook,
|
||||
newData,
|
||||
targetSeries,
|
||||
targetSpeed,
|
||||
unit,
|
||||
renderValue,
|
||||
}: {
|
||||
syncHook: ReturnType<typeof useGraphSync>;
|
||||
newData: TimeSeries | null;
|
||||
targetSeries?: TimeSeries;
|
||||
targetSpeed?: number;
|
||||
unit?: Unit;
|
||||
renderValue?: (value: number) => string;
|
||||
}) {
|
||||
const lines: GraphLine[] = [];
|
||||
|
||||
if (targetSpeed !== undefined) {
|
||||
lines.push({
|
||||
type: "target",
|
||||
value: targetSpeed,
|
||||
targetSeries,
|
||||
label: "Target Speed",
|
||||
color: "#06b6d4", // Match series color
|
||||
});
|
||||
}
|
||||
|
||||
const config: GraphConfig = {
|
||||
title: "Puller Speed",
|
||||
icon: "lu:Gauge",
|
||||
|
||||
@@ -6,7 +6,7 @@ import { PresetsPage } from "@/components/preset/PresetsPage";
|
||||
import { Preset } from "@/lib/preset/preset";
|
||||
import {
|
||||
pullerStateSchema,
|
||||
spoolStateSchema,
|
||||
spoolSpeedControllerStateSchema,
|
||||
} from "./winder2Namespace";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
@@ -26,7 +26,7 @@ const winder2PresetDataSchema = z
|
||||
})
|
||||
.partial(),
|
||||
puller_state: pullerStateSchema.partial(),
|
||||
spool_speed_controller_state: spoolStateSchema.partial(),
|
||||
spool_speed_controller_state: spoolSpeedControllerStateSchema.partial(),
|
||||
})
|
||||
.partial();
|
||||
|
||||
@@ -243,7 +243,7 @@ export function Winder2PresetsPage() {
|
||||
laserpointer: s?.traverse_state?.laserpointer,
|
||||
},
|
||||
puller_state: s?.puller_state ?? {},
|
||||
spool_speed_controller_state: s?.spool_state ?? {},
|
||||
spool_speed_controller_state: s?.spool_speed_controller_state ?? {},
|
||||
});
|
||||
|
||||
return (
|
||||
|
||||
@@ -22,38 +22,28 @@ export function Winder2SettingPage() {
|
||||
const {
|
||||
state,
|
||||
defaultState,
|
||||
isLoading,
|
||||
isDisabled,
|
||||
// setters
|
||||
setMode,
|
||||
setSpoolDirection,
|
||||
setSpoolSpeedControlMode,
|
||||
setTraverseStepSize,
|
||||
setTraversePadding,
|
||||
setTraverseLimitInner,
|
||||
setTraverseLimitOuter,
|
||||
gotoTraverseHome,
|
||||
setPullerForward,
|
||||
setPullerGearRatio,
|
||||
setSpoolRegulationMode,
|
||||
setSpoolMinMaxMinSpeed,
|
||||
setSpoolMinMaxMaxSpeed,
|
||||
setSpoolForward,
|
||||
setSpoolAdaptiveTensionTarget,
|
||||
setSpoolAdaptiveRadiusLearningRate,
|
||||
setSpoolAdaptiveMaxSpeedMultiplier,
|
||||
setSpoolAdaptiveAccelerationFactor,
|
||||
setSpoolAdaptiveDeaccelerationUrgencyMultiplier,
|
||||
setPullerDirection,
|
||||
setPullerGearRatio,
|
||||
setPullerSpeedControlMode,
|
||||
setPullerFixedTargetSpeed,
|
||||
setPullerAdaptiveBaseSpeed,
|
||||
setPullerAdaptiveDeviationMax,
|
||||
setPullerAdaptiveReferenceMachine,
|
||||
setTraverseLimitInner,
|
||||
setTraverseLimitOuter,
|
||||
setTraverseStepSize,
|
||||
setTraversePadding,
|
||||
gotoTraverseLimitInner,
|
||||
gotoTraverseLimitOuter,
|
||||
gotoTraverseHome,
|
||||
setTraverseLaserpointerEnabled,
|
||||
calibrateTensionArmAngle,
|
||||
setSpoolLengthTaskTargetLength,
|
||||
resetSpoolLengthTaskProgress,
|
||||
setOnSpoolLengthTaskCompletedAction,
|
||||
isLoading,
|
||||
isDisabled,
|
||||
selectedMachine,
|
||||
filteredMachines,
|
||||
setConnectedMachine,
|
||||
disconnectMachine,
|
||||
} = useWinder2();
|
||||
|
||||
const handleXlModeChange = (enabled: boolean) => {
|
||||
@@ -139,7 +129,7 @@ export function Winder2SettingPage() {
|
||||
<ControlCard title="Spool">
|
||||
<Label label="Speed Algorithm">
|
||||
<SelectionGroup
|
||||
value={state?.spool_state?.speed_control_mode}
|
||||
value={state?.spool_speed_controller_state?.regulation_mode}
|
||||
disabled={isDisabled}
|
||||
loading={isLoading}
|
||||
options={{
|
||||
@@ -153,14 +143,14 @@ export function Winder2SettingPage() {
|
||||
},
|
||||
}}
|
||||
onChange={(value) =>
|
||||
setSpoolSpeedControlMode(value as "Adaptive" | "MinMax")
|
||||
setSpoolRegulationMode(value as "Adaptive" | "MinMax")
|
||||
}
|
||||
/>
|
||||
</Label>
|
||||
|
||||
<Label label="Rotation Direction">
|
||||
<SelectionGroupBoolean
|
||||
value={state?.spool_state?.direction == "Forward"}
|
||||
value={state?.spool_speed_controller_state?.forward}
|
||||
disabled={isDisabled}
|
||||
loading={isLoading}
|
||||
optionFalse={{
|
||||
@@ -171,23 +161,23 @@ export function Winder2SettingPage() {
|
||||
children: "Forward",
|
||||
icon: "lu:RotateCw",
|
||||
}}
|
||||
onChange={(value) => setSpoolDirection(value ? "Forward" : "Reverse")}
|
||||
onChange={(value) => setSpoolForward(value)}
|
||||
/>
|
||||
</Label>
|
||||
|
||||
{state?.spool_state?.speed_control_mode ===
|
||||
{state?.spool_speed_controller_state?.regulation_mode ===
|
||||
"MinMax" && (
|
||||
<>
|
||||
<Label label="Minimum Speed">
|
||||
<EditValue
|
||||
value={state?.spool_state?.minmax_min_speed}
|
||||
value={state?.spool_speed_controller_state?.minmax_min_speed}
|
||||
title={"Minimum Speed"}
|
||||
unit="rpm"
|
||||
step={10}
|
||||
min={0}
|
||||
max={600}
|
||||
defaultValue={
|
||||
defaultState?.spool_state?.minmax_min_speed
|
||||
defaultState?.spool_speed_controller_state?.minmax_min_speed
|
||||
}
|
||||
renderValue={(value) => roundToDecimals(value, 0)}
|
||||
onChange={(value) => setSpoolMinMaxMinSpeed(value)}
|
||||
@@ -195,14 +185,14 @@ export function Winder2SettingPage() {
|
||||
</Label>
|
||||
<Label label="Maximum Speed">
|
||||
<EditValue
|
||||
value={state?.spool_state?.minmax_max_speed}
|
||||
value={state?.spool_speed_controller_state?.minmax_max_speed}
|
||||
title={"Maximum Speed"}
|
||||
unit="rpm"
|
||||
step={10}
|
||||
min={0}
|
||||
max={600}
|
||||
defaultValue={
|
||||
defaultState?.spool_state?.minmax_max_speed
|
||||
defaultState?.spool_speed_controller_state?.minmax_max_speed
|
||||
}
|
||||
renderValue={(value) => roundToDecimals(value, 0)}
|
||||
onChange={(value) => setSpoolMinMaxMaxSpeed(value)}
|
||||
@@ -211,13 +201,13 @@ export function Winder2SettingPage() {
|
||||
</>
|
||||
)}
|
||||
|
||||
{state?.spool_state?.speed_control_mode ===
|
||||
{state?.spool_speed_controller_state?.regulation_mode ===
|
||||
"Adaptive" && (
|
||||
<div className="flex flex-row flex-wrap gap-4">
|
||||
<Label label="Tension Target">
|
||||
<EditValue
|
||||
value={
|
||||
state?.spool_state?.adaptive_tension_target
|
||||
state?.spool_speed_controller_state?.adaptive_tension_target
|
||||
}
|
||||
title={"Tension Target"}
|
||||
unit={undefined}
|
||||
@@ -225,7 +215,7 @@ export function Winder2SettingPage() {
|
||||
min={0}
|
||||
max={1}
|
||||
defaultValue={
|
||||
defaultState?.spool_state
|
||||
defaultState?.spool_speed_controller_state
|
||||
?.adaptive_tension_target
|
||||
}
|
||||
renderValue={(value) => roundToDecimals(value, 2)}
|
||||
@@ -235,7 +225,7 @@ export function Winder2SettingPage() {
|
||||
<Label label="Learning Rate">
|
||||
<EditValue
|
||||
value={
|
||||
state?.spool_state
|
||||
state?.spool_speed_controller_state
|
||||
?.adaptive_radius_learning_rate
|
||||
}
|
||||
title={"Radius Learning Rate"}
|
||||
@@ -244,7 +234,7 @@ export function Winder2SettingPage() {
|
||||
min={0}
|
||||
max={100}
|
||||
defaultValue={
|
||||
defaultState?.spool_state
|
||||
defaultState?.spool_speed_controller_state
|
||||
?.adaptive_radius_learning_rate
|
||||
}
|
||||
renderValue={(value) => roundToDecimals(value, 2)}
|
||||
@@ -256,7 +246,7 @@ export function Winder2SettingPage() {
|
||||
<Label label="Max Speed Multiplier">
|
||||
<EditValue
|
||||
value={
|
||||
state?.spool_state
|
||||
state?.spool_speed_controller_state
|
||||
?.adaptive_max_speed_multiplier
|
||||
}
|
||||
title={"Max Speed Multiplier"}
|
||||
@@ -265,7 +255,7 @@ export function Winder2SettingPage() {
|
||||
min={0.1}
|
||||
max={10}
|
||||
defaultValue={
|
||||
defaultState?.spool_state
|
||||
defaultState?.spool_speed_controller_state
|
||||
?.adaptive_max_speed_multiplier
|
||||
}
|
||||
renderValue={(value) => roundToDecimals(value, 1)}
|
||||
@@ -277,7 +267,7 @@ export function Winder2SettingPage() {
|
||||
<Label label="Acceleration Factor">
|
||||
<EditValue
|
||||
value={
|
||||
state?.spool_state
|
||||
state?.spool_speed_controller_state
|
||||
?.adaptive_acceleration_factor
|
||||
}
|
||||
title={"Acceleration Factor"}
|
||||
@@ -286,7 +276,7 @@ export function Winder2SettingPage() {
|
||||
min={0.01}
|
||||
max={100}
|
||||
defaultValue={
|
||||
defaultState?.spool_state
|
||||
defaultState?.spool_speed_controller_state
|
||||
?.adaptive_acceleration_factor
|
||||
}
|
||||
renderValue={(value) => roundToDecimals(value, 2)}
|
||||
@@ -298,7 +288,7 @@ export function Winder2SettingPage() {
|
||||
<Label label="Deaccel. Urgency">
|
||||
<EditValue
|
||||
value={
|
||||
state?.spool_state
|
||||
state?.spool_speed_controller_state
|
||||
?.adaptive_deacceleration_urgency_multiplier
|
||||
}
|
||||
title={"Deacceleration Urgency Multiplier"}
|
||||
@@ -307,7 +297,7 @@ export function Winder2SettingPage() {
|
||||
min={1}
|
||||
max={100}
|
||||
defaultValue={
|
||||
defaultState?.spool_state
|
||||
defaultState?.spool_speed_controller_state
|
||||
?.adaptive_deacceleration_urgency_multiplier
|
||||
}
|
||||
renderValue={(value) => roundToDecimals(value, 1)}
|
||||
@@ -323,7 +313,7 @@ export function Winder2SettingPage() {
|
||||
<ControlCard title="Puller">
|
||||
<Label label="Rotation Direction">
|
||||
<SelectionGroupBoolean
|
||||
value={state?.puller_state?.direction == "Forward"}
|
||||
value={state?.puller_state?.forward}
|
||||
disabled={isDisabled}
|
||||
loading={isLoading}
|
||||
optionFalse={{
|
||||
@@ -334,7 +324,7 @@ export function Winder2SettingPage() {
|
||||
children: "Forward",
|
||||
icon: "lu:RotateCw",
|
||||
}}
|
||||
onChange={(value) => setPullerDirection(value ? "Forward" : "Reverse")}
|
||||
onChange={(value) => setPullerForward(value)}
|
||||
/>
|
||||
</Label>
|
||||
<Label label="Gear Ratio">
|
||||
@@ -347,23 +337,37 @@ export function Winder2SettingPage() {
|
||||
children: "1:1",
|
||||
icon: "lu:Circle",
|
||||
},
|
||||
FiveToOne: {
|
||||
children: "5:1",
|
||||
OneToFive: {
|
||||
children: "1:5",
|
||||
icon: "lu:Cog",
|
||||
},
|
||||
TenToOne: {
|
||||
children: "10:1",
|
||||
OneToTen: {
|
||||
children: "1:10",
|
||||
icon: "lu:Cog",
|
||||
},
|
||||
}}
|
||||
onChange={(value) =>
|
||||
setPullerGearRatio(
|
||||
value as "OneToOne" | "FiveToOne" | "TenToOne",
|
||||
value as "OneToOne" | "OneToFive" | "OneToTen",
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Label>
|
||||
</ControlCard>
|
||||
<MachineSelector
|
||||
machines={filteredMachines}
|
||||
selectedMachine={selectedMachine}
|
||||
connectedMachineState={state?.connected_machine_state}
|
||||
setConnectedMachine={setConnectedMachine}
|
||||
clearConnectedMachine={() => {
|
||||
if (!selectedMachine) return;
|
||||
setConnectedMachine({
|
||||
machine_identification: { vendor: 0, machine: 0 },
|
||||
serial: 0,
|
||||
});
|
||||
disconnectMachine(selectedMachine.machine_identification_unique);
|
||||
}}
|
||||
/>
|
||||
</ControlGrid>
|
||||
</Page>
|
||||
);
|
||||
|
||||
@@ -9,20 +9,18 @@ import { VENDOR_QITECH, winder2 } from "@/machines/properties";
|
||||
import { winder2SerialRoute } from "@/routes/routes";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
SpoolSpeedControlMode,
|
||||
SpoolRegulationMode,
|
||||
StateEvent,
|
||||
useWinder2Namespace,
|
||||
modeSchema,
|
||||
Mode,
|
||||
spoolSpeedControlModeSchema,
|
||||
OnSpoolLengthTaskCompletedAction,
|
||||
spoolLengthTaskCompletedAction,
|
||||
spoolRegulationModeSchema,
|
||||
pullerRegulationSchema,
|
||||
PullerRegulation,
|
||||
SpoolAutomaticActionMode,
|
||||
spoolAutomaticActionModeSchema,
|
||||
gearRatioSchema,
|
||||
GearRatio,
|
||||
pullerSpeedControlModeSchema,
|
||||
directionSchema,
|
||||
Direction,
|
||||
PullerSpeedControlMode
|
||||
} from "./winder2Namespace";
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { produce } from "immer";
|
||||
@@ -64,9 +62,10 @@ export function useWinder2() {
|
||||
defaultState,
|
||||
traversePosition,
|
||||
pullerSpeed,
|
||||
targetPullerSpeed,
|
||||
spoolRpm,
|
||||
tensionArmAngle,
|
||||
spoolLengthTaskProgress,
|
||||
spoolProgress,
|
||||
} = useWinder2Namespace(machineIdentification);
|
||||
|
||||
// Single optimistic state for all state management
|
||||
@@ -79,76 +78,25 @@ export function useWinder2() {
|
||||
}
|
||||
}, [state]);
|
||||
|
||||
// boilerplate junk ends here..
|
||||
|
||||
// machine request functions
|
||||
// Request functions for all operations
|
||||
const { request: requestTraverseGotoLimitInner } = useMachineMutation(
|
||||
z.literal("GotoTraverseLimitInner"),
|
||||
);
|
||||
const { request: requestTraverseGotoLimitOuter } = useMachineMutation(
|
||||
z.literal("GotoTraverseLimitOuter"),
|
||||
);
|
||||
const { request: requestTraverseGotoHome } = useMachineMutation(
|
||||
z.literal("GotoTraverseHome"),
|
||||
);
|
||||
const { request: requestSetLaserpointer } = useMachineMutation(
|
||||
z.object({ EnableTraverseLaserpointer: z.boolean() }),
|
||||
);
|
||||
const { request: requestModeSet } = useMachineMutation(
|
||||
z.object({ SetMode: modeSchema }),
|
||||
);
|
||||
|
||||
// spool request functions
|
||||
const { request: requestSpoolSetDirection } = useMachineMutation(
|
||||
z.object({ SetSpoolDirection: directionSchema }),
|
||||
const { request: requestTensionArmZero } = useMachineMutation(
|
||||
z.literal("ZeroTensionArmAngle"),
|
||||
);
|
||||
const { request: requestSpoolSetSpeedControlMode } = useMachineMutation(
|
||||
z.object({ SetSpoolSpeedControlMode: spoolSpeedControlModeSchema }),
|
||||
);
|
||||
const { request: requestSpoolSetMinMaxMinSpeed } = useMachineMutation(
|
||||
z.object({ SetSpoolMinMaxMinSpeed: z.number() }),
|
||||
);
|
||||
const { request: requestSpoolSetMinMaxMaxSpeed } = useMachineMutation(
|
||||
z.object({ SetSpoolMinMaxMaxSpeed: z.number() }),
|
||||
);
|
||||
const { request: requestSpoolSetAdaptiveTensionTarget } = useMachineMutation(
|
||||
z.object({ SetSpoolAdaptiveTensionTarget: z.number() }),
|
||||
);
|
||||
const { request: requestSpoolSetAdaptiveRadiusLearningRate } = useMachineMutation(
|
||||
z.object({ SetSpoolAdaptiveRadiusLearningRate: z.number() }),
|
||||
);
|
||||
const { request: requestSpoolSetAdaptiveMaxSpeedMultiplier } = useMachineMutation(
|
||||
z.object({ SetSpoolAdaptiveMaxSpeedMultiplier: z.number() }),
|
||||
);
|
||||
const { request: requestSpoolSetAdaptiveAccelerationFactor } = useMachineMutation(
|
||||
z.object({ SetSpoolAdaptiveAccelerationFactor: z.number() }),
|
||||
);
|
||||
const { request: requestSpoolSetAdaptiveDeaccelerationUrgencyMultiplier } = useMachineMutation(
|
||||
z.object({ SetSpoolAdaptiveDeaccelerationUrgencyMultiplier: z.number() }),
|
||||
);
|
||||
|
||||
// puller request functions
|
||||
const { request: requestPullerSetDirection } = useMachineMutation(
|
||||
z.object({ SetPullerDirection: directionSchema }),
|
||||
);
|
||||
const { request: requestPullerSetGearRatio } = useMachineMutation(
|
||||
z.object({ SetPullerGearRatio: gearRatioSchema }),
|
||||
);
|
||||
const { request: requestPullerSetSpeedControlMode } = useMachineMutation(
|
||||
z.object({
|
||||
SetPullerSpeedControlMode: pullerSpeedControlModeSchema,
|
||||
}),
|
||||
);
|
||||
const { request: requestPullerSetFixedTargetSpeed } = useMachineMutation(
|
||||
z.object({
|
||||
SetPullerFixedTargetSpeed: z.number(),
|
||||
}),
|
||||
);
|
||||
const { request: requestPullerSetAdaptiveBaseSpeed } = useMachineMutation(
|
||||
z.object({
|
||||
SetPullerAdaptiveBaseSpeed: z.number(),
|
||||
}),
|
||||
);
|
||||
const { request: requestPullerSetAdaptiveDeviationMax } = useMachineMutation(
|
||||
z.object({
|
||||
SetPullerAdaptiveDeviationMax: z.number(),
|
||||
}),
|
||||
);
|
||||
const { request: requestPullerSetAdaptiveReferenceMachine } = useMachineMutation(
|
||||
z.object({
|
||||
SetPullerAdaptiveReferenceMachine: machineIdentificationUnique.nullable(),
|
||||
}),
|
||||
);
|
||||
|
||||
// traverse
|
||||
const { request: requestTraverseSetLimitInner } = useMachineMutation(
|
||||
z.object({ SetTraverseLimitInner: z.number() }),
|
||||
);
|
||||
@@ -161,33 +109,75 @@ export function useWinder2() {
|
||||
const { request: requestTraverseSetPadding } = useMachineMutation(
|
||||
z.object({ SetTraversePadding: z.number() }),
|
||||
);
|
||||
const { request: requestTraverseGotoLimitInner } = useMachineMutation(
|
||||
z.literal("GotoTraverseLimitInner"),
|
||||
const { request: requestPullerSetTargetSpeed } = useMachineMutation(
|
||||
z.object({ SetPullerTargetSpeed: z.number() }),
|
||||
);
|
||||
const { request: requestTraverseGotoLimitOuter } = useMachineMutation(
|
||||
z.literal("GotoTraverseLimitOuter"),
|
||||
const { request: requestPullerSetTargetDiameter } = useMachineMutation(
|
||||
z.object({ SetPullerTargetDiameter: z.number() }),
|
||||
);
|
||||
const { request: requestTraverseGotoHome } = useMachineMutation(
|
||||
z.literal("GotoTraverseHome"),
|
||||
const { request: requestPullerSetRegulationMode } = useMachineMutation(
|
||||
z.object({
|
||||
SetPullerRegulationMode: pullerRegulationSchema,
|
||||
}),
|
||||
);
|
||||
const { request: requestSetLaserpointerEnabled } = useMachineMutation(
|
||||
z.object({ SetTraverseLaserpointerEnabled: z.boolean() }),
|
||||
const { request: requestPullerSetForward } = useMachineMutation(
|
||||
z.object({ SetPullerForward: z.boolean() }),
|
||||
);
|
||||
const { request: requestPullerSetGearRatio } = useMachineMutation(
|
||||
z.object({ SetPullerGearRatio: gearRatioSchema }),
|
||||
);
|
||||
const { request: requestSpoolSetRegulationMode } = useMachineMutation(
|
||||
z.object({ SetSpoolRegulationMode: spoolRegulationModeSchema }),
|
||||
);
|
||||
const { request: requestSpoolSetMinMaxMinSpeed } = useMachineMutation(
|
||||
z.object({ SetSpoolMinMaxMinSpeed: z.number() }),
|
||||
);
|
||||
const { request: requestSpoolSetMinMaxMaxSpeed } = useMachineMutation(
|
||||
z.object({ SetSpoolMinMaxMaxSpeed: z.number() }),
|
||||
);
|
||||
const { request: requestSpoolSetForward } = useMachineMutation(
|
||||
z.object({ SetSpoolForward: z.boolean() }),
|
||||
);
|
||||
const { request: requestSpoolSetAdaptiveTensionTarget } = useMachineMutation(
|
||||
z.object({ SetSpoolAdaptiveTensionTarget: z.number() }),
|
||||
);
|
||||
const { request: requestSpoolSetAdaptiveRadiusLearningRate } =
|
||||
useMachineMutation(
|
||||
z.object({ SetSpoolAdaptiveRadiusLearningRate: z.number() }),
|
||||
);
|
||||
const { request: requestSpoolSetAdaptiveMaxSpeedMultiplier } =
|
||||
useMachineMutation(
|
||||
z.object({ SetSpoolAdaptiveMaxSpeedMultiplier: z.number() }),
|
||||
);
|
||||
const { request: requestSpoolSetAdaptiveAccelerationFactor } =
|
||||
useMachineMutation(
|
||||
z.object({ SetSpoolAdaptiveAccelerationFactor: z.number() }),
|
||||
);
|
||||
const { request: requestSpoolSetAdaptiveDeaccelerationUrgencyMultiplier } =
|
||||
useMachineMutation(
|
||||
z.object({ SetSpoolAdaptiveDeaccelerationUrgencyMultiplier: z.number() }),
|
||||
);
|
||||
const { request: requestSpoolAutomaticRequiredMeters } = useMachineMutation(
|
||||
z.object({ SetSpoolAutomaticRequiredMeters: z.number() }),
|
||||
);
|
||||
const { request: requestSpoolResetProgress } = useMachineMutation(
|
||||
z.literal("ResetSpoolProgress"),
|
||||
);
|
||||
|
||||
// tension arm
|
||||
const { request: requestTensionArmCalibrate } = useMachineMutation(
|
||||
z.literal("CalibrateTensionArmAngle"),
|
||||
const { request: requestSpoolAutomaticAction } = useMachineMutation(
|
||||
z.object({ SetSpoolAutomaticAction: spoolAutomaticActionModeSchema }),
|
||||
);
|
||||
|
||||
// spool length task
|
||||
const { request: requestSpoolLengthTaskSetTargetLength } = useMachineMutation(
|
||||
z.object({ SetSpoolLengthTaskTargetLength: z.number() }),
|
||||
const { request: requestConnectedMachine } = useMachineMutation(
|
||||
z.object({
|
||||
SetConnectedMachine: machineIdentificationUnique,
|
||||
}),
|
||||
);
|
||||
const { request: requestSpoolLengthTaskResetProgress } = useMachineMutation(
|
||||
z.literal("ResetSetSpoolLengthTaskProgress"),
|
||||
);
|
||||
const { request: requestSpoolLengthTaskSetOnCompletedAction } = useMachineMutation(
|
||||
z.object({ SetOnSpoolLengthTaskCompletedAction: spoolLengthTaskCompletedAction }),
|
||||
|
||||
const { request: requestDisconnectedMachine } = useMachineMutation(
|
||||
z.object({
|
||||
DisconnectMachine: machineIdentificationUnique,
|
||||
}),
|
||||
);
|
||||
|
||||
// Helper function for optimistic updates using produce
|
||||
@@ -203,245 +193,19 @@ export function useWinder2() {
|
||||
};
|
||||
|
||||
// Action functions
|
||||
const setMode = (mode: Mode) => {
|
||||
const zeroTensionArmAngle = () => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.mode = mode;
|
||||
current.data.tension_arm_state.zeroed = true;
|
||||
},
|
||||
() =>
|
||||
requestModeSet({
|
||||
requestTensionArmZero({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetMode: mode },
|
||||
data: "ZeroTensionArmAngle",
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
// spool
|
||||
const setSpoolDirection = (direction: Direction) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.spool_state.direction = direction;
|
||||
},
|
||||
() =>
|
||||
requestSpoolSetDirection({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetSpoolDirection: direction },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setSpoolSpeedControlMode = (mode: SpoolSpeedControlMode) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.spool_state.speed_control_mode = mode;
|
||||
},
|
||||
() =>
|
||||
requestSpoolSetSpeedControlMode({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetSpoolSpeedControlMode: mode },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setSpoolMinMaxMinSpeed = (speed: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.spool_state.minmax_min_speed = speed;
|
||||
},
|
||||
() =>
|
||||
requestSpoolSetMinMaxMinSpeed({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetSpoolMinMaxMinSpeed: speed },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setSpoolMinMaxMaxSpeed = (speed: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.spool_state.minmax_max_speed = speed;
|
||||
},
|
||||
() =>
|
||||
requestSpoolSetMinMaxMaxSpeed({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetSpoolMinMaxMaxSpeed: speed },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setSpoolAdaptiveTensionTarget = (value: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.spool_state.adaptive_tension_target =
|
||||
value;
|
||||
},
|
||||
() =>
|
||||
requestSpoolSetAdaptiveTensionTarget({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetSpoolAdaptiveTensionTarget: value },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setSpoolAdaptiveRadiusLearningRate = (value: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.spool_state.adaptive_radius_learning_rate =
|
||||
value;
|
||||
},
|
||||
() =>
|
||||
requestSpoolSetAdaptiveRadiusLearningRate({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetSpoolAdaptiveRadiusLearningRate: value },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setSpoolAdaptiveMaxSpeedMultiplier = (value: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.spool_state.adaptive_max_speed_multiplier =
|
||||
value;
|
||||
},
|
||||
() =>
|
||||
requestSpoolSetAdaptiveMaxSpeedMultiplier({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetSpoolAdaptiveMaxSpeedMultiplier: value },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setSpoolAdaptiveAccelerationFactor = (value: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.spool_state.adaptive_acceleration_factor =
|
||||
value;
|
||||
},
|
||||
() =>
|
||||
requestSpoolSetAdaptiveAccelerationFactor({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetSpoolAdaptiveAccelerationFactor: value },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setSpoolAdaptiveDeaccelerationUrgencyMultiplier = (value: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.spool_state.adaptive_deacceleration_urgency_multiplier =
|
||||
value;
|
||||
},
|
||||
() =>
|
||||
requestSpoolSetAdaptiveDeaccelerationUrgencyMultiplier({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetSpoolAdaptiveDeaccelerationUrgencyMultiplier: value },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
// puller
|
||||
const setPullerDirection = (direction: Direction) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.puller_state.direction = direction;
|
||||
},
|
||||
() =>
|
||||
requestPullerSetDirection({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetPullerDirection: direction },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setPullerGearRatio = (gearRatio: GearRatio) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.puller_state.gear_ratio = gearRatio;
|
||||
},
|
||||
() => {
|
||||
requestPullerSetGearRatio({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetPullerGearRatio: gearRatio },
|
||||
});
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const setPullerSpeedControlMode = (mode: PullerSpeedControlMode) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.puller_state.speed_control_mode = mode;
|
||||
},
|
||||
() =>
|
||||
requestPullerSetSpeedControlMode({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetPullerSpeedControlMode: mode },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setPullerFixedTargetSpeed = (speed: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.puller_state.fixed_target_speed = speed;
|
||||
},
|
||||
() =>
|
||||
requestPullerSetFixedTargetSpeed({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetPullerFixedTargetSpeed: speed },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setPullerAdaptiveBaseSpeed = (speed: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.puller_state.fixed_target_speed = speed;
|
||||
},
|
||||
() =>
|
||||
requestPullerSetAdaptiveBaseSpeed({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetPullerAdaptiveBaseSpeed: speed },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setPullerAdaptiveDeviationMax = (speed: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.puller_state.fixed_target_speed = speed;
|
||||
},
|
||||
() =>
|
||||
requestPullerSetAdaptiveDeviationMax({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetPullerAdaptiveDeviationMax: speed },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setPullerAdaptiveReferenceMachine = (machineUid: {
|
||||
machine_identification: {
|
||||
vendor: number;
|
||||
machine: number;
|
||||
};
|
||||
serial: number;
|
||||
} | null) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.puller_state
|
||||
.adaptive_reference_machine
|
||||
.machine_identification_unique
|
||||
= machineUid;
|
||||
},
|
||||
() =>
|
||||
requestPullerSetAdaptiveReferenceMachine({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetPullerAdaptiveReferenceMachine: machineUid },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
// traverse
|
||||
const setTraverseLimitInner = (limitInner: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
@@ -468,32 +232,6 @@ export function useWinder2() {
|
||||
);
|
||||
};
|
||||
|
||||
const setTraverseStepSize = (stepSize: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.traverse_state.step_size = stepSize;
|
||||
},
|
||||
() =>
|
||||
requestTraverseSetStepSize({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetTraverseStepSize: stepSize },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setTraversePadding = (padding: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.traverse_state.padding = padding;
|
||||
},
|
||||
() =>
|
||||
requestTraverseSetPadding({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetTraversePadding: padding },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const gotoTraverseLimitInner = () => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
@@ -533,64 +271,325 @@ export function useWinder2() {
|
||||
);
|
||||
};
|
||||
|
||||
const setTraverseLaserpointerEnabled = (enabled: boolean) => {
|
||||
const enableTraverseLaserpointer = (enabled: boolean) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.traverse_state.laserpointer_enabled = enabled;
|
||||
current.data.traverse_state.laserpointer = enabled;
|
||||
},
|
||||
() =>
|
||||
requestSetLaserpointerEnabled({
|
||||
requestSetLaserpointer({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetTraverseLaserpointerEnabled: enabled },
|
||||
data: { EnableTraverseLaserpointer: enabled },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
// tension arm
|
||||
const calibrateTensionArmAngle = () => {
|
||||
const setMode = (mode: Mode) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.tension_arm_state.is_calibrated = true;
|
||||
current.data.mode_state.mode = mode;
|
||||
},
|
||||
() =>
|
||||
requestTensionArmCalibrate({
|
||||
requestModeSet({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: "CalibrateTensionArmAngle",
|
||||
data: { SetMode: mode },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
// spool length task
|
||||
const setSpoolLengthTaskTargetLength = (target_length: number) => {
|
||||
const setTraverseStepSize = (stepSize: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.spool_length_task_state.target_length = target_length;
|
||||
current.data.traverse_state.step_size = stepSize;
|
||||
},
|
||||
() =>
|
||||
requestSpoolLengthTaskSetTargetLength({
|
||||
requestTraverseSetStepSize({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetSpoolLengthTaskTargetLength: target_length },
|
||||
data: { SetTraverseStepSize: stepSize },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const resetSpoolLengthTaskProgress = () => {
|
||||
requestSpoolLengthTaskResetProgress({
|
||||
const setTraversePadding = (padding: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.traverse_state.padding = padding;
|
||||
},
|
||||
() =>
|
||||
requestTraverseSetPadding({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetTraversePadding: padding },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setPullerTargetSpeed = (targetSpeed: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.puller_state.target_speed = targetSpeed;
|
||||
},
|
||||
() =>
|
||||
requestPullerSetTargetSpeed({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetPullerTargetSpeed: targetSpeed },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setPullerTargetDiameter = (targetDiameter: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.puller_state.target_diameter = targetDiameter;
|
||||
},
|
||||
() =>
|
||||
requestPullerSetTargetDiameter({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetPullerTargetDiameter: targetDiameter },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setPullerRegulationMode = (regulationMode: PullerRegulation) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.puller_state.regulation = regulationMode;
|
||||
},
|
||||
() =>
|
||||
requestPullerSetRegulationMode({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetPullerRegulationMode: regulationMode },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setPullerForward = (forward: boolean) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.puller_state.forward = forward;
|
||||
},
|
||||
() =>
|
||||
requestPullerSetForward({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetPullerForward: forward },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setPullerGearRatio = (gearRatio: GearRatio) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.puller_state.gear_ratio = gearRatio;
|
||||
// Reset target speed to 0 to prevent sudden speed changes
|
||||
current.data.puller_state.target_speed = 0;
|
||||
},
|
||||
async () => {
|
||||
// First set the target speed to 0
|
||||
await requestPullerSetTargetSpeed({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetPullerTargetSpeed: 0 },
|
||||
});
|
||||
// Then set the gear ratio
|
||||
await requestPullerSetGearRatio({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetPullerGearRatio: gearRatio },
|
||||
});
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const setSpoolAutomaticRequiredMeters = (meters: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.spool_automatic_action_state.spool_required_meters =
|
||||
meters;
|
||||
},
|
||||
() =>
|
||||
requestSpoolAutomaticRequiredMeters({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetSpoolAutomaticRequiredMeters: meters },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setSpoolAutomaticAction = (mode: SpoolAutomaticActionMode) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.spool_automatic_action_state.spool_automatic_action_mode =
|
||||
mode;
|
||||
},
|
||||
() =>
|
||||
requestSpoolAutomaticAction({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetSpoolAutomaticAction: mode },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const resetSpoolProgress = () => {
|
||||
requestSpoolResetProgress({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: "ResetSetSpoolLengthTaskProgress",
|
||||
data: "ResetSpoolProgress",
|
||||
});
|
||||
};
|
||||
|
||||
const setOnSpoolLengthTaskCompletedAction = (action: OnSpoolLengthTaskCompletedAction) => {
|
||||
const setSpoolRegulationMode = (mode: SpoolRegulationMode) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.spool_length_task_state.on_completed_action =
|
||||
action;
|
||||
current.data.spool_speed_controller_state.regulation_mode = mode;
|
||||
},
|
||||
() =>
|
||||
requestSpoolLengthTaskSetOnCompletedAction({
|
||||
requestSpoolSetRegulationMode({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetOnSpoolLengthTaskCompletedAction: action },
|
||||
data: { SetSpoolRegulationMode: mode },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setSpoolMinMaxMinSpeed = (speed: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.spool_speed_controller_state.minmax_min_speed = speed;
|
||||
},
|
||||
() =>
|
||||
requestSpoolSetMinMaxMinSpeed({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetSpoolMinMaxMinSpeed: speed },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setSpoolMinMaxMaxSpeed = (speed: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.spool_speed_controller_state.minmax_max_speed = speed;
|
||||
},
|
||||
() =>
|
||||
requestSpoolSetMinMaxMaxSpeed({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetSpoolMinMaxMaxSpeed: speed },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setSpoolForward = (forward: boolean) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.spool_speed_controller_state.forward = forward;
|
||||
},
|
||||
() =>
|
||||
requestSpoolSetForward({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetSpoolForward: forward },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setSpoolAdaptiveTensionTarget = (value: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.spool_speed_controller_state.adaptive_tension_target =
|
||||
value;
|
||||
},
|
||||
() =>
|
||||
requestSpoolSetAdaptiveTensionTarget({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetSpoolAdaptiveTensionTarget: value },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setSpoolAdaptiveRadiusLearningRate = (value: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.spool_speed_controller_state.adaptive_radius_learning_rate =
|
||||
value;
|
||||
},
|
||||
() =>
|
||||
requestSpoolSetAdaptiveRadiusLearningRate({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetSpoolAdaptiveRadiusLearningRate: value },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setSpoolAdaptiveMaxSpeedMultiplier = (value: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.spool_speed_controller_state.adaptive_max_speed_multiplier =
|
||||
value;
|
||||
},
|
||||
() =>
|
||||
requestSpoolSetAdaptiveMaxSpeedMultiplier({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetSpoolAdaptiveMaxSpeedMultiplier: value },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setSpoolAdaptiveAccelerationFactor = (value: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.spool_speed_controller_state.adaptive_acceleration_factor =
|
||||
value;
|
||||
},
|
||||
() =>
|
||||
requestSpoolSetAdaptiveAccelerationFactor({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetSpoolAdaptiveAccelerationFactor: value },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setSpoolAdaptiveDeaccelerationUrgencyMultiplier = (value: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.spool_speed_controller_state.adaptive_deacceleration_urgency_multiplier =
|
||||
value;
|
||||
},
|
||||
() =>
|
||||
requestSpoolSetAdaptiveDeaccelerationUrgencyMultiplier({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetSpoolAdaptiveDeaccelerationUrgencyMultiplier: value },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setConnectedMachine = (machineIdentificationUnique: {
|
||||
machine_identification: {
|
||||
vendor: number;
|
||||
machine: number;
|
||||
};
|
||||
serial: number;
|
||||
}) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.connected_machine_state.machine_identification_unique =
|
||||
machineIdentificationUnique;
|
||||
},
|
||||
() =>
|
||||
requestConnectedMachine({
|
||||
machine_identification_unique,
|
||||
data: { SetConnectedMachine: machineIdentificationUnique },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const disconnectMachine = (machineIdentificationUnique: {
|
||||
machine_identification: {
|
||||
vendor: number;
|
||||
machine: number;
|
||||
};
|
||||
serial: number;
|
||||
}) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.connected_machine_state.machine_identification_unique =
|
||||
null;
|
||||
},
|
||||
() =>
|
||||
requestDisconnectedMachine({
|
||||
machine_identification_unique,
|
||||
data: { DisconnectMachine: machineIdentificationUnique },
|
||||
}),
|
||||
);
|
||||
};
|
||||
@@ -609,7 +608,7 @@ export function useWinder2() {
|
||||
m.machine_identification_unique.machine_identification.vendor ===
|
||||
VENDOR_QITECH &&
|
||||
m.machine_identification_unique.machine_identification.machine ===
|
||||
0x0006, // id of laser machine
|
||||
0x0008,
|
||||
),
|
||||
[machines, machineIdentification],
|
||||
);
|
||||
@@ -617,7 +616,7 @@ export function useWinder2() {
|
||||
// Get selected machine by serial
|
||||
const selectedMachine = useMemo(() => {
|
||||
const serial =
|
||||
state?.data.puller_state.adaptive_reference_machine?.machine_identification_unique
|
||||
state?.data.connected_machine_state?.machine_identification_unique
|
||||
?.serial;
|
||||
|
||||
return (
|
||||
@@ -640,65 +639,44 @@ export function useWinder2() {
|
||||
// Individual live values (TimeSeries)
|
||||
traversePosition,
|
||||
pullerSpeed,
|
||||
targetPullerSpeed,
|
||||
spoolRpm,
|
||||
tensionArmAngle,
|
||||
spoolProgress: spoolLengthTaskProgress,
|
||||
spoolProgress,
|
||||
|
||||
// Loading states
|
||||
isLoading,
|
||||
isDisabled,
|
||||
|
||||
|
||||
// Action functions
|
||||
//---------------------------------------------------------
|
||||
|
||||
// machine
|
||||
enableTraverseLaserpointer,
|
||||
setMode,
|
||||
|
||||
// spool
|
||||
setSpoolDirection,
|
||||
setSpoolSpeedControlMode,
|
||||
|
||||
// spool speed minmax strategy
|
||||
zeroTensionArmAngle,
|
||||
setTraverseLimitInner,
|
||||
setTraverseLimitOuter,
|
||||
gotoTraverseLimitInner,
|
||||
gotoTraverseLimitOuter,
|
||||
gotoTraverseHome,
|
||||
resetSpoolProgress,
|
||||
setTraverseStepSize,
|
||||
setTraversePadding,
|
||||
setPullerTargetSpeed,
|
||||
setPullerTargetDiameter,
|
||||
setPullerRegulationMode,
|
||||
setPullerForward,
|
||||
setPullerGearRatio,
|
||||
setSpoolAutomaticRequiredMeters,
|
||||
setSpoolAutomaticAction,
|
||||
setSpoolRegulationMode,
|
||||
setSpoolMinMaxMinSpeed,
|
||||
setSpoolMinMaxMaxSpeed,
|
||||
|
||||
// spool speed adaptive strategy
|
||||
setSpoolForward,
|
||||
setSpoolAdaptiveTensionTarget,
|
||||
setSpoolAdaptiveRadiusLearningRate,
|
||||
setSpoolAdaptiveMaxSpeedMultiplier,
|
||||
setSpoolAdaptiveAccelerationFactor,
|
||||
setSpoolAdaptiveDeaccelerationUrgencyMultiplier,
|
||||
|
||||
// puller
|
||||
setPullerDirection,
|
||||
setPullerGearRatio,
|
||||
setPullerSpeedControlMode,
|
||||
|
||||
// puller speed fixed strategy
|
||||
setPullerFixedTargetSpeed,
|
||||
|
||||
// puller speed adaptive strategy
|
||||
setPullerAdaptiveBaseSpeed,
|
||||
setPullerAdaptiveDeviationMax,
|
||||
setPullerAdaptiveReferenceMachine,
|
||||
|
||||
// traverse
|
||||
setTraverseLimitInner,
|
||||
setTraverseLimitOuter,
|
||||
setTraverseStepSize,
|
||||
setTraversePadding,
|
||||
gotoTraverseLimitInner,
|
||||
gotoTraverseLimitOuter,
|
||||
gotoTraverseHome,
|
||||
setTraverseLaserpointerEnabled,
|
||||
|
||||
// tension arm
|
||||
calibrateTensionArmAngle,
|
||||
|
||||
// spool length task
|
||||
setSpoolLengthTaskTargetLength,
|
||||
resetSpoolLengthTaskProgress,
|
||||
setOnSpoolLengthTaskCompletedAction,
|
||||
setConnectedMachine,
|
||||
disconnectMachine,
|
||||
};
|
||||
}
|
||||
@@ -28,14 +28,38 @@ export const liveValuesEventDataSchema = z.object({
|
||||
puller_speed: z.number(),
|
||||
spool_rpm: z.number(),
|
||||
tension_arm_angle: z.number(),
|
||||
spool_length_task_progress: z.number(),
|
||||
spool_progress: z.number(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Direction enum
|
||||
* Puller regulation type enum
|
||||
*/
|
||||
export const directionSchema = z.enum(["Forward", "Reverse"]);
|
||||
export type Direction = z.infer<typeof directionSchema>;
|
||||
export const pullerRegulationSchema = z.enum(["Speed", "Diameter"]);
|
||||
export type PullerRegulation = z.infer<typeof pullerRegulationSchema>;
|
||||
|
||||
/**
|
||||
* Gear ratio enum for winding speed
|
||||
*/
|
||||
export const gearRatioSchema = z.enum(["OneToOne", "OneToFive", "OneToTen"]);
|
||||
export type GearRatio = z.infer<typeof gearRatioSchema>;
|
||||
|
||||
/**
|
||||
* Get the multiplier for a gear ratio
|
||||
*/
|
||||
export function getGearRatioMultiplier(
|
||||
gearRatio: GearRatio | undefined,
|
||||
): number {
|
||||
switch (gearRatio) {
|
||||
case "OneToOne":
|
||||
return 1.0;
|
||||
case "OneToFive":
|
||||
return 5.0;
|
||||
case "OneToTen":
|
||||
return 10.0;
|
||||
default:
|
||||
return 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Machine operation mode enum
|
||||
@@ -43,6 +67,74 @@ export type Direction = z.infer<typeof directionSchema>;
|
||||
export const modeSchema = z.enum(["Standby", "Hold", "Pull", "Wind"]);
|
||||
export type Mode = z.infer<typeof modeSchema>;
|
||||
|
||||
/**
|
||||
* Machine operation mode enum
|
||||
*/
|
||||
export const spoolAutomaticActionModeSchema = z.enum([
|
||||
"NoAction",
|
||||
"Pull",
|
||||
"Hold",
|
||||
]);
|
||||
|
||||
export type SpoolAutomaticActionMode = z.infer<
|
||||
typeof spoolAutomaticActionModeSchema
|
||||
>;
|
||||
|
||||
/**
|
||||
* Spool speed controller regulation mode enum
|
||||
*/
|
||||
export const spoolRegulationModeSchema = z.enum(["Adaptive", "MinMax"]);
|
||||
export type SpoolRegulationMode = z.infer<typeof spoolRegulationModeSchema>;
|
||||
|
||||
export const spoolAutomaticActionStateSchema = z.object({
|
||||
spool_required_meters: z.number(),
|
||||
spool_automatic_action_mode: spoolAutomaticActionModeSchema,
|
||||
});
|
||||
|
||||
export type SpoolAutomaticActionState = z.infer<
|
||||
typeof spoolAutomaticActionStateSchema
|
||||
>;
|
||||
|
||||
/**
|
||||
* Traverse state schema
|
||||
*/
|
||||
export const traverseStateSchema = z.object({
|
||||
limit_inner: z.number(),
|
||||
limit_outer: z.number(),
|
||||
position_in: z.number(),
|
||||
position_out: z.number(),
|
||||
is_going_in: z.boolean(),
|
||||
is_going_out: z.boolean(),
|
||||
is_homed: z.boolean(),
|
||||
is_going_home: z.boolean(),
|
||||
is_traversing: z.boolean(),
|
||||
laserpointer: z.boolean(),
|
||||
step_size: z.number(),
|
||||
padding: z.number(),
|
||||
can_go_in: z.boolean(),
|
||||
can_go_out: z.boolean(),
|
||||
can_go_home: z.boolean(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Puller state schema
|
||||
*/
|
||||
export const pullerStateSchema = z.object({
|
||||
regulation: pullerRegulationSchema,
|
||||
target_speed: z.number(),
|
||||
target_diameter: z.number(),
|
||||
forward: z.boolean(),
|
||||
gear_ratio: gearRatioSchema,
|
||||
});
|
||||
|
||||
/**
|
||||
* Mode state schema
|
||||
*/
|
||||
export const modeStateSchema = z.object({
|
||||
mode: modeSchema,
|
||||
can_wind: z.boolean(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Connected machine state scheme
|
||||
*/
|
||||
@@ -56,147 +148,45 @@ export const machineIdentificationUniqueSchema = z.object({
|
||||
serial: z.number(),
|
||||
});
|
||||
|
||||
export const machineCrossConnectionStateSchema = z.object({
|
||||
export const connectedMachineStateSchema = z.object({
|
||||
machine_identification_unique: machineIdentificationUniqueSchema.nullable(),
|
||||
is_available: z.boolean(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Gear ratio enum for winding speed
|
||||
*/
|
||||
export const gearRatioSchema = z.enum(["OneToOne", "FiveToOne", "TenToOne"]);
|
||||
export type GearRatio = z.infer<typeof gearRatioSchema>;
|
||||
|
||||
/**
|
||||
* Get the multiplier for a gear ratio
|
||||
*/
|
||||
export function getGearRatioMultiplier(
|
||||
gearRatio: GearRatio | undefined,
|
||||
): number {
|
||||
switch (gearRatio) {
|
||||
case "OneToOne":
|
||||
return 1.0;
|
||||
case "FiveToOne":
|
||||
return 5.0;
|
||||
case "TenToOne":
|
||||
return 10.0;
|
||||
default:
|
||||
return 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Machine operation mode enum
|
||||
*/
|
||||
export const spoolLengthTaskCompletedAction = z.enum(["NoAction", "Pull", "Hold"]);
|
||||
export type OnSpoolLengthTaskCompletedAction = z.infer<typeof spoolLengthTaskCompletedAction>;
|
||||
|
||||
/**
|
||||
* Spool speed controller regulation mode enum
|
||||
*/
|
||||
export const spoolSpeedControlModeSchema = z.enum(["Adaptive", "MinMax"]);
|
||||
export type SpoolSpeedControlMode = z.infer<typeof spoolSpeedControlModeSchema>;
|
||||
|
||||
/**
|
||||
* Spool speed controller regulation mode enum
|
||||
*/
|
||||
export const pullerSpeedControlModeSchema = z.enum(["Fixed", "Adaptive"]);
|
||||
export type PullerSpeedControlMode = z.infer<typeof pullerSpeedControlModeSchema>;
|
||||
|
||||
/**
|
||||
* Spool speed controller state schema
|
||||
*/
|
||||
export const spoolStateSchema = z.object({
|
||||
direction: directionSchema,
|
||||
speed_control_mode: spoolSpeedControlModeSchema,
|
||||
|
||||
// min max mode
|
||||
minmax_min_speed: z.number(),
|
||||
minmax_max_speed: z.number(),
|
||||
|
||||
// adaptive mode
|
||||
adaptive_tension_target: z.number(),
|
||||
adaptive_radius_learning_rate: z.number(),
|
||||
adaptive_max_speed_multiplier: z.number(),
|
||||
adaptive_acceleration_factor: z.number(),
|
||||
adaptive_deacceleration_urgency_multiplier: z.number(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Puller state schema
|
||||
*/
|
||||
export const pullerStateSchema = z.object({
|
||||
direction: directionSchema,
|
||||
gear_ratio: gearRatioSchema,
|
||||
speed_control_mode: pullerSpeedControlModeSchema,
|
||||
fixed_target_speed: z.number(),
|
||||
adaptive_base_speed: z.number(),
|
||||
adaptive_deviation_max: z.number(),
|
||||
adaptive_reference_machine: machineCrossConnectionStateSchema,
|
||||
});
|
||||
|
||||
/**
|
||||
* Traverse state schema
|
||||
*/
|
||||
export const traverseStateSchema = z.object({
|
||||
// config
|
||||
limit_inner: z.number(),
|
||||
limit_outer: z.number(),
|
||||
step_size: z.number(),
|
||||
padding: z.number(),
|
||||
|
||||
// states
|
||||
is_going_in: z.boolean(),
|
||||
is_going_out: z.boolean(),
|
||||
is_homed: z.boolean(),
|
||||
is_going_home: z.boolean(),
|
||||
is_traversing: z.boolean(),
|
||||
|
||||
// state transitions
|
||||
can_go_in: z.boolean(),
|
||||
can_go_out: z.boolean(),
|
||||
can_go_home: z.boolean(),
|
||||
|
||||
// lazeeeeeeeer
|
||||
laserpointer_enabled: z.boolean(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Tension arm state schema
|
||||
*/
|
||||
export const tensionArmStateSchema = z.object({
|
||||
is_calibrated: z.boolean(),
|
||||
zeroed: z.boolean(),
|
||||
});
|
||||
|
||||
/**
|
||||
* spool length task state schema
|
||||
* Spool speed controller state schema
|
||||
*/
|
||||
export const spoolLengthTaskStateSchema = z.object({
|
||||
target_length: z.number(),
|
||||
on_completed_action: spoolLengthTaskCompletedAction,
|
||||
export const spoolSpeedControllerStateSchema = z.object({
|
||||
regulation_mode: spoolRegulationModeSchema,
|
||||
minmax_min_speed: z.number(),
|
||||
minmax_max_speed: z.number(),
|
||||
adaptive_tension_target: z.number(),
|
||||
adaptive_radius_learning_rate: z.number(),
|
||||
adaptive_max_speed_multiplier: z.number(),
|
||||
adaptive_acceleration_factor: z.number(),
|
||||
adaptive_deacceleration_urgency_multiplier: z.number(),
|
||||
forward: z.boolean(),
|
||||
});
|
||||
|
||||
export type SpoolLengthTaskState = z.infer<typeof spoolLengthTaskStateSchema>;
|
||||
|
||||
/**
|
||||
* Consolidated state event schema (state changes only)
|
||||
*/
|
||||
export const stateEventDataSchema = z.object({
|
||||
// common
|
||||
is_default_state: z.boolean(),
|
||||
|
||||
// machine
|
||||
mode: modeSchema,
|
||||
can_wind: z.boolean(),
|
||||
|
||||
// devices
|
||||
spool_state: spoolStateSchema,
|
||||
puller_state: pullerStateSchema,
|
||||
traverse_state: traverseStateSchema,
|
||||
puller_state: pullerStateSchema,
|
||||
mode_state: modeStateSchema,
|
||||
tension_arm_state: tensionArmStateSchema,
|
||||
|
||||
// tasks
|
||||
spool_length_task_state: spoolLengthTaskStateSchema,
|
||||
spool_speed_controller_state: spoolSpeedControllerStateSchema,
|
||||
spool_automatic_action_state: spoolAutomaticActionStateSchema,
|
||||
connected_machine_state: connectedMachineStateSchema,
|
||||
});
|
||||
|
||||
// ========== Event Schemas with Wrappers ==========
|
||||
@@ -216,11 +206,12 @@ export type Winder2NamespaceStore = {
|
||||
defaultState: StateEvent | null;
|
||||
|
||||
// Time series data for live values
|
||||
spoolRpm: TimeSeries;
|
||||
pullerSpeed: TimeSeries;
|
||||
traversePosition: TimeSeries;
|
||||
tensionArmAngle: TimeSeries;
|
||||
spoolLengthTaskProgress: TimeSeries;
|
||||
pullerSpeed: TimeSeries;
|
||||
spoolRpm: TimeSeries;
|
||||
tensionArmAngle: TimeSeries;
|
||||
spoolProgress: TimeSeries;
|
||||
targetPullerSpeed: TimeSeries;
|
||||
};
|
||||
|
||||
//Store Factory and Message Handler -> no param, so default values
|
||||
@@ -251,9 +242,10 @@ export const createWinder2NamespaceStore =
|
||||
// Time series data for live values
|
||||
traversePosition,
|
||||
pullerSpeed,
|
||||
targetPullerSpeed,
|
||||
spoolRpm,
|
||||
tensionArmAngle,
|
||||
spoolLengthTaskProgress: spoolProgress,
|
||||
spoolProgress,
|
||||
};
|
||||
});
|
||||
/**
|
||||
@@ -281,21 +273,28 @@ export function winder2MessageHandler(
|
||||
};
|
||||
|
||||
try {
|
||||
if (eventName === "State")
|
||||
{
|
||||
if (eventName === "StateEvent") {
|
||||
console.log(event);
|
||||
// Parse and validate the state event
|
||||
const stateEvent = stateEventSchema.parse(event);
|
||||
const nextTargetPullerSpeed = stateEvent.data.puller_state.target_speed;
|
||||
|
||||
updateStore((state) => ({
|
||||
...state,
|
||||
state: stateEvent,
|
||||
targetPullerSpeed:
|
||||
state.targetPullerSpeed.current?.value === nextTargetPullerSpeed
|
||||
? state.targetPullerSpeed
|
||||
: addTargetPullerSpeed(state.targetPullerSpeed, {
|
||||
value: nextTargetPullerSpeed,
|
||||
timestamp: event.ts,
|
||||
}),
|
||||
// only set default state if is_default_state is true
|
||||
defaultState: stateEvent.data.is_default_state
|
||||
? stateEvent
|
||||
: state.defaultState,
|
||||
}));
|
||||
} else if (eventName === "LiveValues") {
|
||||
} else if (eventName === "LiveValuesEvent") {
|
||||
// Parse and validate the live values event
|
||||
const liveValuesEvent = liveValuesEventSchema.parse(event);
|
||||
|
||||
@@ -305,7 +304,7 @@ export function winder2MessageHandler(
|
||||
puller_speed,
|
||||
spool_rpm,
|
||||
tension_arm_angle,
|
||||
spool_length_task_progress: spool_progress,
|
||||
spool_progress,
|
||||
} = liveValuesEvent.data;
|
||||
const timestamp = liveValuesEvent.ts;
|
||||
|
||||
@@ -328,8 +327,8 @@ export function winder2MessageHandler(
|
||||
value: spool_progress,
|
||||
timestamp,
|
||||
};
|
||||
newState.spoolLengthTaskProgress = addSpoolProgress(
|
||||
state.spoolLengthTaskProgress,
|
||||
newState.spoolProgress = addSpoolProgress(
|
||||
state.spoolProgress,
|
||||
timeseriesValue,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -43,44 +43,44 @@ import { Laser1GraphsPage } from "@/machines/laser/laser1/Laser1Graph";
|
||||
import { Laser1Page } from "@/machines/laser/laser1/Laser1Page";
|
||||
import { Laser1PresetsPage } from "@/machines/laser/laser1/Laser1PresetsPage";
|
||||
|
||||
import { Mock1ControlPage } from "@/machines/mock/mock1/Mock1ControlPage";
|
||||
import { Mock1GraphPage } from "@/machines/mock/mock1/Mock1Graph";
|
||||
import { Mock1ManualPage } from "@/machines/mock/mock1/Mock1Manual";
|
||||
import { Mock1Page } from "@/machines/mock/mock1/Mock1Page";
|
||||
import { Mock1PresetsPage } from "@/machines/mock/mock1/Mock1PresetsPage";
|
||||
import { Mock1ControlPage } from "@/machines/minimal_machines/mock/mock1/Mock1ControlPage";
|
||||
import { Mock1GraphPage } from "@/machines/minimal_machines/mock/mock1/Mock1Graph";
|
||||
import { Mock1ManualPage } from "@/machines/minimal_machines/mock/mock1/Mock1Manual";
|
||||
import { Mock1Page } from "@/machines/minimal_machines/mock/mock1/Mock1Page";
|
||||
import { Mock1PresetsPage } from "@/machines/minimal_machines/mock/mock1/Mock1PresetsPage";
|
||||
|
||||
import { Aquapath1ControlPage } from "@/machines/aquapath/aquapath1/Aquapath1ControlPage";
|
||||
import { Aquapath1Page } from "@/machines/aquapath/aquapath1/Aquapath1Page";
|
||||
import { Aquapath1GraphPage } from "@/machines/aquapath/aquapath1/Aquapath1Graph";
|
||||
import { Aquapath1SettingsPage } from "@/machines/aquapath/aquapath1/Aquapath1Settings";
|
||||
|
||||
import { TestMachinePage } from "@/machines/testmachine/TestMachinePage";
|
||||
import { TestMachineControlPage } from "@/machines/testmachine/TestMachineControlPage";
|
||||
import { TestMachinePage } from "@/machines/minimal_machines/testmachine/TestMachinePage";
|
||||
import { TestMachineControlPage } from "@/machines/minimal_machines/testmachine/TestMachineControlPage";
|
||||
|
||||
import { TestMachineStepperPage } from "@/machines/testmachinestepper/TestMachineStepperPage";
|
||||
import { TestMachineStepperControlPage } from "@/machines/testmachinestepper/TestMachineStepperControlPage";
|
||||
import { AnalogInputTestMachine } from "@/machines/analoginputtestmachine/AnalogInputTestMachinePage";
|
||||
import { AnalogInputTestMachineControl } from "@/machines/analoginputtestmachine/AnalogInputTestMachineControlPage";
|
||||
import { WagoAiTestMachine } from "@/machines/wagoaitestmachine/WagoAiTestMachinePage";
|
||||
import { WagoAiTestMachineControl } from "@/machines/wagoaitestmachine/WagoAiTestMachineControlPage";
|
||||
import { TestMachineStepperPage } from "@/machines/minimal_machines/testmachinestepper/TestMachineStepperPage";
|
||||
import { TestMachineStepperControlPage } from "@/machines/minimal_machines/testmachinestepper/TestMachineStepperControlPage";
|
||||
import { AnalogInputTestMachine } from "@/machines/minimal_machines/analoginputtestmachine/AnalogInputTestMachinePage";
|
||||
import { AnalogInputTestMachineControl } from "@/machines/minimal_machines/analoginputtestmachine/AnalogInputTestMachineControlPage";
|
||||
import { WagoAiTestMachine } from "@/machines/minimal_machines/wagoaitestmachine/WagoAiTestMachinePage";
|
||||
import { WagoAiTestMachineControl } from "@/machines/minimal_machines/wagoaitestmachine/WagoAiTestMachineControlPage";
|
||||
|
||||
import { DigitalInputTestMachinePage } from "@/machines/digitalinputtestmachine/DigitalInputTestMachinePage";
|
||||
import { DigitalInputTestMachineControlPage } from "@/machines/digitalinputtestmachine/DigitalInputTestMachineControlPage";
|
||||
import { DigitalInputTestMachinePage } from "@/machines/minimal_machines/digitalinputtestmachine/DigitalInputTestMachinePage";
|
||||
import { DigitalInputTestMachineControlPage } from "@/machines/minimal_machines/digitalinputtestmachine/DigitalInputTestMachineControlPage";
|
||||
|
||||
import { IP20TestMachinePage } from "@/machines/ip20testmachine/IP20TestMachinePage";
|
||||
import { IP20TestMachineControlPage } from "@/machines/ip20testmachine/IP20TestMachineControlPage";
|
||||
import { TestMotorPage } from "@/machines/motor_test_machine/TestMotorPage";
|
||||
import { TestMotorControlPage } from "@/machines/motor_test_machine/TestMotorControlPage";
|
||||
import { IP20TestMachinePage } from "@/machines/minimal_machines/ip20testmachine/IP20TestMachinePage";
|
||||
import { IP20TestMachineControlPage } from "@/machines/minimal_machines/ip20testmachine/IP20TestMachineControlPage";
|
||||
import { TestMotorPage } from "@/machines/minimal_machines/motor_test_machine/TestMotorPage";
|
||||
import { TestMotorControlPage } from "@/machines/minimal_machines/motor_test_machine/TestMotorControlPage";
|
||||
|
||||
import { MetricsGraphsPage } from "@/metrics/MetricsGraphsPage";
|
||||
import { MetricsControlPage } from "@/metrics/MetricsControlPage";
|
||||
|
||||
import { WagoPower1Page } from "@/machines/wago_power/wago_power1/WagoPower1Page";
|
||||
import { WagoPower1ControlPage } from "@/machines/wago_power/wago_power1/WagoPower1ControlPage";
|
||||
import { WagoDoTestMachinePage } from "@/machines/wagodotestmachine/WagoDoTestMachinePage";
|
||||
import { WagoDoTestMachineControlPage } from "@/machines/wagodotestmachine/WagoDoTestMachineControlPage";
|
||||
import { Wago8chDioTestMachinePage } from "@/machines/wago8chdiotestmachine/wago8chDioTestMachinePage";
|
||||
import { Wago8chDioTestMachineControlRoute } from "@/machines/wago8chdiotestmachine/wago8chDioTestMachineControlPage";
|
||||
import { WagoDoTestMachinePage } from "@/machines/minimal_machines/wagodotestmachine/WagoDoTestMachinePage";
|
||||
import { WagoDoTestMachineControlPage } from "@/machines/minimal_machines/wagodotestmachine/WagoDoTestMachineControlPage";
|
||||
import { Wago8chDioTestMachinePage } from "@/machines/minimal_machines/wago8chdiotestmachine/wago8chDioTestMachinePage";
|
||||
import { Wago8chDioTestMachineControlRoute } from "@/machines/minimal_machines/wago8chdiotestmachine/wago8chDioTestMachineControlPage";
|
||||
|
||||
// make a route tree like this
|
||||
// _mainNavigation/machines/winder2/$serial/control
|
||||
|
||||
Generated
+111
-12
@@ -1,6 +1,21 @@
|
||||
{
|
||||
"nodes": {
|
||||
"crane": {
|
||||
"locked": {
|
||||
"lastModified": 1771796463,
|
||||
"narHash": "sha256-9bCDuUzpwJXcHMQYMS1yNuzYMmKO/CCwCexpjWOl62I=",
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"rev": "3d3de3313e263e04894f284ac18177bd26169bad",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"crane_2": {
|
||||
"locked": {
|
||||
"lastModified": 1750266157,
|
||||
"narHash": "sha256-tL42YoNg9y30u7zAqtoGDNdTyXTi8EALDeCB13FtbQA=",
|
||||
@@ -15,7 +30,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"crane_2": {
|
||||
"crane_3": {
|
||||
"locked": {
|
||||
"lastModified": 1750266157,
|
||||
"narHash": "sha256-tL42YoNg9y30u7zAqtoGDNdTyXTi8EALDeCB13FtbQA=",
|
||||
@@ -102,12 +117,51 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils_5": {
|
||||
"inputs": {
|
||||
"systems": "systems_5"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"home-manager": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1771851181,
|
||||
"narHash": "sha256-gFgE6mGUftwseV3DUENMb0k0EiHd739lZexPo5O/sdQ=",
|
||||
"owner": "nix-community",
|
||||
"repo": "home-manager",
|
||||
"rev": "9a4b494b1aa1b93d8edf167f46dc8e0c0011280c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "home-manager",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"home-manager_2": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"qitech-control",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1750973805,
|
||||
"narHash": "sha256-BZXgag7I0rnL/HMHAsBz3tQrfKAibpY2vovexl2lS+Y=",
|
||||
@@ -122,9 +176,10 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"home-manager_2": {
|
||||
"home-manager_3": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"qitech-control",
|
||||
"qitech-control",
|
||||
"nixpkgs"
|
||||
]
|
||||
@@ -143,9 +198,10 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"home-manager_3": {
|
||||
"home-manager_4": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"qitech-control",
|
||||
"qitech-control",
|
||||
"qitech-control",
|
||||
"nixpkgs"
|
||||
@@ -167,11 +223,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1750969886,
|
||||
"narHash": "sha256-zW/OFnotiz/ndPFdebpo3X0CrbVNf22n4DjN2vxlb58=",
|
||||
"lastModified": 1767313136,
|
||||
"narHash": "sha256-16KkgfdYqjaeRGBaYsNrhPRRENs0qzkQVUooNHtoy2w=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "a676066377a2fe7457369dd37c31fd2263b662f4",
|
||||
"rev": "ac62194c3917d5f474c1a844b6fd6da2db95077d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -207,6 +263,31 @@
|
||||
],
|
||||
"qitech-control": "qitech-control_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1772023797,
|
||||
"narHash": "sha256-RipiUlH5UKiofTRquREluUFOwEfNjLFEvyDol/VWDRg=",
|
||||
"owner": "qitechgmbh",
|
||||
"repo": "control",
|
||||
"rev": "3e3e3f1c0bc2b26fe0fc1e7d49b65e3fd3d7f368",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "qitechgmbh",
|
||||
"repo": "control",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"qitech-control_2": {
|
||||
"inputs": {
|
||||
"crane": "crane_3",
|
||||
"flake-utils": "flake-utils_3",
|
||||
"home-manager": "home-manager_3",
|
||||
"nixpkgs": [
|
||||
"qitech-control",
|
||||
"nixpkgs"
|
||||
],
|
||||
"qitech-control": "qitech-control_3"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1751047510,
|
||||
"narHash": "sha256-SZ+DxQvFJKJOro1AVUwWltLQO1L9TIc6AI4tnM9V4tk=",
|
||||
@@ -221,15 +302,16 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"qitech-control_2": {
|
||||
"qitech-control_3": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils_3",
|
||||
"home-manager": "home-manager_3",
|
||||
"flake-utils": "flake-utils_4",
|
||||
"home-manager": "home-manager_4",
|
||||
"nixpkgs": [
|
||||
"qitech-control",
|
||||
"qitech-control",
|
||||
"nixpkgs"
|
||||
],
|
||||
"qitech-control": "qitech-control_3",
|
||||
"qitech-control": "qitech-control_4",
|
||||
"rust-overlay": "rust-overlay_2"
|
||||
},
|
||||
"locked": {
|
||||
@@ -246,10 +328,11 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"qitech-control_3": {
|
||||
"qitech-control_4": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils_4",
|
||||
"flake-utils": "flake-utils_5",
|
||||
"nixpkgs": [
|
||||
"qitech-control",
|
||||
"qitech-control",
|
||||
"qitech-control",
|
||||
"nixpkgs"
|
||||
@@ -300,6 +383,7 @@
|
||||
"rust-overlay_2": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"qitech-control",
|
||||
"qitech-control",
|
||||
"qitech-control",
|
||||
"nixpkgs"
|
||||
@@ -378,6 +462,21 @@
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems_5": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
description = "QiTech Control";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:nixos/nixpkgs/nixos-25.05";
|
||||
nixpkgs.url = "github:nixos/nixpkgs/nixos-25.11";
|
||||
|
||||
# Crane for Rust builds with dependency caching
|
||||
crane = { url = "github:ipetkov/crane"; };
|
||||
|
||||
@@ -61,7 +61,6 @@ impl MachineAct for AquaPathV1 {
|
||||
.expect("Failed to send values");
|
||||
sender.close();
|
||||
}
|
||||
MachineMessage::ReceiveLiveValues(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,6 @@ impl MachineAct for BufferV1 {
|
||||
.expect("Failed to send values");
|
||||
sender.close();
|
||||
}
|
||||
MachineMessage::ReceiveLiveValues(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,7 +77,6 @@ impl MachineAct for ExtruderV2 {
|
||||
.expect("Failed to send values");
|
||||
sender.close();
|
||||
}
|
||||
MachineMessage::ReceiveLiveValues(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,6 @@ impl MachineAct for ExtruderV2 {
|
||||
.expect("Failed to send values");
|
||||
sender.close();
|
||||
}
|
||||
MachineMessage::ReceiveLiveValues(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,7 +80,6 @@ impl MachineAct for ExtruderV3 {
|
||||
.expect("Failed to send values");
|
||||
sender.close();
|
||||
}
|
||||
MachineMessage::ReceiveLiveValues(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,7 +48,6 @@ impl MachineAct for ExtruderV2 {
|
||||
.expect("Failed to send values");
|
||||
sender.close();
|
||||
}
|
||||
MachineMessage::ReceiveLiveValues(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
use units::angle::revolution;
|
||||
use units::f64::*;
|
||||
|
||||
use super::{Clamp, ClampedValue};
|
||||
|
||||
pub fn clamp_revolution_uom(value: Angle, min: Angle, max: Angle) -> ClampedValue<Angle>
|
||||
{
|
||||
let value = value.get::<revolution>();
|
||||
let min = min.get::<revolution>();
|
||||
let max = max.get::<revolution>();
|
||||
|
||||
// clamp
|
||||
let clamped_value = clamp_revolution(value, min, max);
|
||||
|
||||
// convert back to uom
|
||||
ClampedValue {
|
||||
value: Angle::new::<revolution>(clamped_value.value),
|
||||
clamp: clamped_value.clamp
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clamp_revolution(value: f64, min: f64, max: f64) -> ClampedValue<f64>
|
||||
{
|
||||
// normalize value from 0..1
|
||||
let value = wrap_revolution(value);
|
||||
let min = wrap_revolution(min);
|
||||
let max = wrap_revolution(max);
|
||||
|
||||
// check if in acceptable range
|
||||
if revolution_in_range(value, min, max) {
|
||||
return ClampedValue::new(value, Clamp::None);
|
||||
}
|
||||
|
||||
// calculates the distance between min and max
|
||||
let (clamp_to_min_min, clamp_to_min_max, clamp_to_max_min, clamp_to_max_max) =
|
||||
clamping_ranges(min, max);
|
||||
|
||||
// check if in min clamping range
|
||||
if revolution_in_range(value, clamp_to_min_min, clamp_to_min_max) {
|
||||
return ClampedValue::new(min, Clamp::Min);
|
||||
}
|
||||
// check if in max clamping range
|
||||
if revolution_in_range(value, clamp_to_max_min, clamp_to_max_max) {
|
||||
return ClampedValue::new(max, Clamp::Max);
|
||||
}
|
||||
|
||||
// at this point our input value should be either retured (cause in spec) or clamped to min or max
|
||||
// so this point should never be reached
|
||||
// in case it does we just clamp to min
|
||||
ClampedValue::new(min, Clamp::Min)
|
||||
//TODO: consider using unreachable!() if truly unreachable
|
||||
}
|
||||
|
||||
pub fn scale_revolution_to_range(value: f64, min: f64, max: f64) -> f64
|
||||
{
|
||||
// we calculate the distance between min and max
|
||||
let distance = revolution_distance(min, max);
|
||||
|
||||
// we scale the value to the distance
|
||||
|
||||
(value - min) / distance
|
||||
}
|
||||
|
||||
fn clamping_ranges(min: f64, max: f64) -> (f64, f64, f64, f64)
|
||||
{
|
||||
// normalize min and max
|
||||
let min = wrap_revolution(min);
|
||||
let max = wrap_revolution(max);
|
||||
|
||||
// calculates the distance between min and max (distance A in the test)
|
||||
let in_spec_distance = revolution_distance(min, max);
|
||||
|
||||
// calculate distance B and clamping distance as per the test comment
|
||||
let out_spec_distance = 1.0 - in_spec_distance;
|
||||
let clamping_distance = out_spec_distance / 2.0;
|
||||
|
||||
let clamp_to_min_min = wrap_revolution(min - clamping_distance);
|
||||
let clamp_to_min_max = min;
|
||||
let clamp_to_max_min = max;
|
||||
let clamp_to_max_max = wrap_revolution(max + clamping_distance);
|
||||
|
||||
(
|
||||
clamp_to_min_min,
|
||||
clamp_to_min_max,
|
||||
clamp_to_max_min,
|
||||
clamp_to_max_max,
|
||||
)
|
||||
}
|
||||
|
||||
fn revolution_distance(min: f64, max: f64) -> f64
|
||||
{
|
||||
// Normalize the values to ensure they're in the [0, 1) range
|
||||
let normalized_min = wrap_revolution(min);
|
||||
let normalized_max = wrap_revolution(max);
|
||||
|
||||
// Check if the range crosses zero
|
||||
if normalized_min > normalized_max {
|
||||
// For ranges that cross zero (e.g., min = 0.9, max = 0.1)
|
||||
// The distance is (1 - min) + max
|
||||
1.0 - normalized_min + normalized_max
|
||||
} else {
|
||||
// For normal ranges (e.g., min = 0.1, max = 0.3)
|
||||
// The distance is simply max - min
|
||||
normalized_max - normalized_min
|
||||
}
|
||||
}
|
||||
|
||||
fn wrap_revolution(value: f64) -> f64
|
||||
{
|
||||
debug_assert!(value.is_finite(), "value must be finite");
|
||||
|
||||
match value % 1.0
|
||||
{
|
||||
0.0 if value >= 1.0 => 1.0,
|
||||
n if n < 0.0 => n + 1.0,
|
||||
n => n,
|
||||
}
|
||||
}
|
||||
|
||||
fn revolution_in_range(value: f64, min: f64, max: f64) -> bool
|
||||
{
|
||||
debug_assert!(value.is_finite(), "value must be finite");
|
||||
debug_assert!(min.is_finite(), "min must be finite");
|
||||
debug_assert!(max.is_finite(), "max must be finite");
|
||||
|
||||
debug_assert!(value >= 0.0 && value < 1.0, "value out of [0,1)");
|
||||
debug_assert!(min >= 0.0 && min < 1.0, "min out of [0,1)");
|
||||
debug_assert!(max >= 0.0 && max < 1.0, "max out of [0,1)");
|
||||
|
||||
let wraps = min > max;
|
||||
|
||||
if wraps {
|
||||
// Range wraps across boundary (e.g., 0.9 → 0.1)
|
||||
min <= value || value <= max
|
||||
} else {
|
||||
// Standard interval
|
||||
min <= value && value <= max
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
// keep for backwards reference
|
||||
mod clamp_revolution_old;
|
||||
|
||||
mod clamp_revolution;
|
||||
pub use clamp_revolution::clamp_revolution;
|
||||
pub use clamp_revolution::clamp_revolution_uom;
|
||||
pub use clamp_revolution::scale_revolution_to_range;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Clamp
|
||||
{
|
||||
None,
|
||||
Min,
|
||||
Max,
|
||||
}
|
||||
|
||||
pub struct ClampedValue<T>
|
||||
{
|
||||
pub value: T,
|
||||
pub clamp: Clamp,
|
||||
}
|
||||
|
||||
impl<T> ClampedValue<T>
|
||||
{
|
||||
pub fn new(value: T, clamp: Clamp) -> Self
|
||||
{
|
||||
Self { value, clamp }
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
use super::LaserMachine;
|
||||
use crate::MachinesLiveValues;
|
||||
use crate::MachineAct;
|
||||
use crate::MachineMessage;
|
||||
use crate::MachineValues;
|
||||
@@ -35,26 +34,6 @@ impl MachineAct for LaserMachine {
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
// send live values to all connected machines every frame
|
||||
if now.duration_since(self.last_machine_send) > Duration::from_millis(1) {
|
||||
let values = MachinesLiveValues::Laser(self.get_live_values());
|
||||
|
||||
for machine in self.connected_machines.iter() {
|
||||
const CHANNEL_MESSAGE_CAP: usize = 5;
|
||||
|
||||
// ensure we don' flood the channel. Cap it at 5
|
||||
if machine.connection.len() <= CHANNEL_MESSAGE_CAP {
|
||||
use MachineMessage::ReceiveLiveValues;
|
||||
_ = machine
|
||||
.connection
|
||||
.try_send(ReceiveLiveValues(values.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
// reset timer
|
||||
self.last_machine_send = now;
|
||||
}
|
||||
|
||||
// more than 33ms have passed since last emit (30 "fps" target)
|
||||
if now.duration_since(self.last_measurement_emit) > Duration::from_secs_f64(1.0 / 30.0) {
|
||||
self.emit_live_values();
|
||||
@@ -81,18 +60,12 @@ impl MachineAct for LaserMachine {
|
||||
use crate::MachineApi;
|
||||
let _res = self.api_mutate(value);
|
||||
}
|
||||
MachineMessage::ConnectToMachine(machine_connection) => {
|
||||
if self.connected_machines.len() >= Self::MAX_CONNECTIONS {
|
||||
tracing::debug!("Not adding machine connection. Max capacity reached!");
|
||||
return;
|
||||
}
|
||||
|
||||
self.connected_machines.push(machine_connection);
|
||||
}
|
||||
MachineMessage::DisconnectMachine(machine_connection) => {
|
||||
self.connected_machines
|
||||
.retain(|machine| machine.ident != machine_connection.ident);
|
||||
}
|
||||
MachineMessage::ConnectToMachine(_machine_connection) =>
|
||||
/*Doesnt connect to any Machine so do nothing*/
|
||||
{}
|
||||
MachineMessage::DisconnectMachine(_machine_connection) =>
|
||||
/*Doesnt connect to any Machine so do nothing*/
|
||||
{}
|
||||
MachineMessage::RequestValues(sender) => {
|
||||
sender
|
||||
.send_blocking(MachineValues {
|
||||
@@ -106,7 +79,6 @@ impl MachineAct for LaserMachine {
|
||||
|
||||
()
|
||||
}
|
||||
MachineMessage::ReceiveLiveValues(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ use crate::{
|
||||
MACHINE_LASER_V1, VENDOR_QITECH,
|
||||
machine_identification::{MachineIdentification, MachineIdentificationUnique},
|
||||
};
|
||||
use crate::{Machine, MachineConnection, MachineMessage};
|
||||
use crate::{Machine, MachineMessage};
|
||||
use api::{LaserEvents, LaserMachineNamespace, LaserState, LiveValuesEvent, StateEvent};
|
||||
use control_core::socketio::namespace::NamespaceCacheingLogic;
|
||||
use smol::{
|
||||
@@ -53,11 +53,6 @@ pub struct LaserMachine {
|
||||
/// This way we can signal to the client that the first state emission is a default state
|
||||
emitted_default_state: bool,
|
||||
did_change_state: bool,
|
||||
|
||||
/// last time we sent live values to connected machines
|
||||
last_machine_send: Instant,
|
||||
|
||||
connected_machines: Vec<MachineConnection>,
|
||||
}
|
||||
|
||||
impl Machine for LaserMachine {
|
||||
@@ -102,8 +97,6 @@ impl LaserMachine {
|
||||
machine: MACHINE_LASER_V1,
|
||||
};
|
||||
|
||||
pub const MAX_CONNECTIONS: usize = 2;
|
||||
|
||||
pub fn get_live_values(&self) -> LiveValuesEvent {
|
||||
let diameter = self.diameter.get::<millimeter>();
|
||||
let x_diameter = self.x_diameter.map(|x| x.get::<millimeter>());
|
||||
|
||||
@@ -55,8 +55,6 @@ impl MachineNewTrait for LaserMachine {
|
||||
higher_tolerance: Length::new::<millimeter>(0.05),
|
||||
in_tolerance: true,
|
||||
did_change_state: true,
|
||||
last_machine_send: Instant::now(),
|
||||
connected_machines: Vec::new(),
|
||||
};
|
||||
|
||||
Ok(laser_machine)
|
||||
|
||||
+8
-39
@@ -17,33 +17,18 @@ use std::any::Any;
|
||||
use std::fmt::Debug;
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
pub mod analog_input_test_machine;
|
||||
pub mod aquapath1;
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
pub mod buffer1;
|
||||
pub mod digital_input_test_machine;
|
||||
pub mod extruder1;
|
||||
pub mod extruder2;
|
||||
pub mod ip20_test_machine;
|
||||
pub mod laser;
|
||||
mod live_values;
|
||||
pub mod machine_identification;
|
||||
pub mod mock;
|
||||
pub mod motor_test_machine;
|
||||
pub mod minimal_machines;
|
||||
pub mod registry;
|
||||
pub mod serial;
|
||||
pub mod test_machine;
|
||||
pub mod test_machine_stepper;
|
||||
pub mod wago_8ch_dio_test_machine;
|
||||
pub mod wago_ai_test_machine;
|
||||
pub mod wago_do_test_machine;
|
||||
pub mod wago_power;
|
||||
pub mod winder2;
|
||||
pub mod types;
|
||||
pub mod helpers;
|
||||
|
||||
pub use live_values::MachinesLiveValues;
|
||||
// pub use live_values::
|
||||
|
||||
pub const VENDOR_QITECH: u16 = 0x0001;
|
||||
pub const MACHINE_WINDER_V1: u16 = 0x0002;
|
||||
@@ -81,7 +66,6 @@ pub struct CrossConnection {
|
||||
pub enum AsyncThreadMessage {
|
||||
NoMsg,
|
||||
ConnectOneWayRequest(CrossConnection),
|
||||
ConnectTwoWayRequest(CrossConnection),
|
||||
DisconnectMachines(CrossConnection),
|
||||
}
|
||||
|
||||
@@ -314,7 +298,6 @@ pub enum MachineMessage {
|
||||
ConnectToMachine(MachineConnection),
|
||||
DisconnectMachine(MachineConnection),
|
||||
RequestValues(Sender<MachineValues>),
|
||||
ReceiveLiveValues(MachinesLiveValues),
|
||||
}
|
||||
|
||||
pub trait MachineApi {
|
||||
@@ -490,10 +473,6 @@ pub trait MachineWithChannel: Send + Debug + Sync {
|
||||
fn get_live_values(&self) -> Option<Self::LiveValues> {
|
||||
None
|
||||
}
|
||||
|
||||
fn on_receive_live_values(&mut self,live_values: MachinesLiveValues) { _ = live_values; }
|
||||
fn on_connect_to_machine(&mut self,connection: MachineConnection) { _ = connection; }
|
||||
fn on_disconnect_machine(&mut self,connection: MachineConnection) { _ = connection; }
|
||||
}
|
||||
|
||||
impl<C> MachineApi for C
|
||||
@@ -523,15 +502,9 @@ impl<C> MachineAct for C
|
||||
where
|
||||
C: MachineWithChannel,
|
||||
{
|
||||
fn act(&mut self, now: Instant)
|
||||
{
|
||||
for _ in 0..5 // Limit to 5 requests max
|
||||
{
|
||||
match self.get_machine_channel_mut().api_receiver.try_recv()
|
||||
{
|
||||
Ok(msg) => self.act_machine_message(msg),
|
||||
Err(_) => break,
|
||||
}
|
||||
fn act(&mut self, now: Instant) {
|
||||
while let Ok(msg) = self.get_machine_channel_mut().api_receiver.try_recv() {
|
||||
self.act_machine_message(msg);
|
||||
}
|
||||
|
||||
if let Err(e) = self.update(now) {
|
||||
@@ -553,11 +526,11 @@ where
|
||||
MachineMessage::HttpApiJsonRequest(value) => {
|
||||
let _ = self.api_mutate(value);
|
||||
}
|
||||
MachineMessage::ConnectToMachine(connection) => {
|
||||
self.on_connect_to_machine(connection);
|
||||
MachineMessage::ConnectToMachine(_machine_connection) => {
|
||||
todo!();
|
||||
}
|
||||
MachineMessage::DisconnectMachine(connection) => {
|
||||
self.on_disconnect_machine(connection);
|
||||
MachineMessage::DisconnectMachine(_machine_connection) => {
|
||||
todo!();
|
||||
}
|
||||
MachineMessage::RequestValues(sender) => {
|
||||
sender
|
||||
@@ -570,10 +543,6 @@ where
|
||||
.expect("Failed to send values");
|
||||
sender.close();
|
||||
}
|
||||
MachineMessage::ReceiveLiveValues(live_values) =>
|
||||
{
|
||||
self.on_receive_live_values(live_values);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
use crate::laser::api::LiveValuesEvent as LaserLiveValues;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum MachinesLiveValues {
|
||||
Laser(LaserLiveValues),
|
||||
}
|
||||
+2
-2
@@ -1,7 +1,8 @@
|
||||
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
|
||||
|
||||
use crate::{
|
||||
MachineAct, MachineMessage, MachineValues, analog_input_test_machine::AnalogInputTestMachine,
|
||||
MachineAct, MachineMessage, MachineValues,
|
||||
minimal_machines::analog_input_test_machine::AnalogInputTestMachine,
|
||||
};
|
||||
|
||||
impl MachineAct for AnalogInputTestMachine {
|
||||
@@ -27,7 +28,6 @@ impl MachineAct for AnalogInputTestMachine {
|
||||
.expect("Failed to send values");
|
||||
sender.close();
|
||||
}
|
||||
MachineMessage::ReceiveLiveValues(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ use control_core::socketio::{
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{MachineApi, analog_input_test_machine::AnalogInputTestMachine};
|
||||
use crate::{MachineApi, minimal_machines::analog_input_test_machine::AnalogInputTestMachine};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AnalogInputTestMachineNamespace {
|
||||
+1
-3
@@ -4,11 +4,9 @@ use control_core::socketio::{event::Event, namespace::NamespaceCacheingLogic};
|
||||
use ethercat_hal::io::analog_input::AnalogInput;
|
||||
use smol::channel::{Receiver, Sender};
|
||||
|
||||
use self::api::{AnalogInputTestMachineEvents, AnalogInputTestMachineNamespace, MeasurementEvent};
|
||||
use crate::{
|
||||
ANALOG_INPUT_TEST_MACHINE, AsyncThreadMessage, Machine, MachineMessage, VENDOR_QITECH,
|
||||
analog_input_test_machine::api::{
|
||||
AnalogInputTestMachineEvents, AnalogInputTestMachineNamespace, MeasurementEvent,
|
||||
},
|
||||
machine_identification::{MachineIdentification, MachineIdentificationUnique},
|
||||
};
|
||||
|
||||
+3
-3
@@ -7,10 +7,10 @@ use ethercat_hal::{
|
||||
};
|
||||
use smol::{block_on, channel::unbounded};
|
||||
|
||||
use super::{AnalogInputTestMachine, api::AnalogInputTestMachineNamespace};
|
||||
use crate::{
|
||||
MachineNewHardware, MachineNewParams, MachineNewTrait,
|
||||
analog_input_test_machine::{AnalogInputTestMachine, api::AnalogInputTestMachineNamespace},
|
||||
get_ethercat_device, validate_no_role_dublicates, validate_same_machine_identification_unique,
|
||||
MachineNewHardware, MachineNewParams, MachineNewTrait, get_ethercat_device,
|
||||
validate_no_role_dublicates, validate_same_machine_identification_unique,
|
||||
};
|
||||
|
||||
impl MachineNewTrait for AnalogInputTestMachine {
|
||||
-1
@@ -41,7 +41,6 @@ impl MachineAct for DigitalInputTestMachine {
|
||||
.expect("Failed to send values");
|
||||
sender.close();
|
||||
}
|
||||
MachineMessage::ReceiveLiveValues(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -8,7 +8,7 @@ use control_core::socketio::{
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{MachineApi, digital_input_test_machine::DigitalInputTestMachine};
|
||||
use crate::{MachineApi, minimal_machines::digital_input_test_machine::DigitalInputTestMachine};
|
||||
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
pub struct StateEvent {
|
||||
+1
-3
@@ -4,11 +4,9 @@ use control_core::socketio::namespace::NamespaceCacheingLogic;
|
||||
use ethercat_hal::io::digital_input::DigitalInput;
|
||||
use smol::channel::{Receiver, Sender};
|
||||
|
||||
use self::api::{DigitalInputTestMachineEvents, DigitalInputTestMachineNamespace, StateEvent};
|
||||
use crate::{
|
||||
AsyncThreadMessage, DIGITAL_INPUT_TEST_MACHINE, Machine, MachineMessage, VENDOR_QITECH,
|
||||
digital_input_test_machine::api::{
|
||||
DigitalInputTestMachineEvents, DigitalInputTestMachineNamespace, StateEvent,
|
||||
},
|
||||
machine_identification::{MachineIdentification, MachineIdentificationUnique},
|
||||
};
|
||||
|
||||
+3
-3
@@ -11,10 +11,10 @@ use ethercat_hal::{
|
||||
};
|
||||
use smol::{block_on, lock::RwLock};
|
||||
|
||||
use super::{DigitalInputTestMachine, api::DigitalInputTestMachineNamespace};
|
||||
use crate::{
|
||||
MachineNewHardware, MachineNewParams, MachineNewTrait,
|
||||
digital_input_test_machine::{DigitalInputTestMachine, api::DigitalInputTestMachineNamespace},
|
||||
get_ethercat_device, validate_no_role_dublicates, validate_same_machine_identification_unique,
|
||||
MachineNewHardware, MachineNewParams, MachineNewTrait, get_ethercat_device,
|
||||
validate_no_role_dublicates, validate_same_machine_identification_unique,
|
||||
};
|
||||
|
||||
impl MachineNewTrait for DigitalInputTestMachine {
|
||||
-1
@@ -53,7 +53,6 @@ impl MachineAct for IP20TestMachine {
|
||||
.expect("Failed to send values");
|
||||
sender.close();
|
||||
}
|
||||
MachineMessage::ReceiveLiveValues(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
File renamed without changes.
+4
-2
@@ -1,5 +1,7 @@
|
||||
use crate::ip20_test_machine::api::{IP20TestMachineEvents, LiveValuesEvent, StateEvent};
|
||||
use crate::machine_identification::{MachineIdentification, MachineIdentificationUnique};
|
||||
use crate::minimal_machines::ip20_test_machine::api::{
|
||||
IP20TestMachineEvents, LiveValuesEvent, StateEvent,
|
||||
};
|
||||
use crate::{AsyncThreadMessage, Machine, MachineMessage};
|
||||
use control_core::socketio::namespace::NamespaceCacheingLogic;
|
||||
use ethercat_hal::io::digital_input::DigitalInput;
|
||||
@@ -9,7 +11,7 @@ use std::time::Instant;
|
||||
pub mod act;
|
||||
pub mod api;
|
||||
pub mod new;
|
||||
use crate::ip20_test_machine::api::IP20TestMachineNamespace;
|
||||
use crate::minimal_machines::ip20_test_machine::api::IP20TestMachineNamespace;
|
||||
use crate::{IP20_TEST_MACHINE, VENDOR_QITECH};
|
||||
|
||||
#[derive(Debug)]
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
use crate::ip20_test_machine::IP20TestMachine;
|
||||
use crate::ip20_test_machine::api::IP20TestMachineNamespace;
|
||||
use crate::minimal_machines::ip20_test_machine::IP20TestMachine;
|
||||
use crate::minimal_machines::ip20_test_machine::api::IP20TestMachineNamespace;
|
||||
use smol::block_on;
|
||||
use std::time::Instant;
|
||||
|
||||
@@ -68,7 +68,6 @@ impl MachineAct for MockMachine {
|
||||
.expect("Failed to send values");
|
||||
sender.close();
|
||||
}
|
||||
MachineMessage::ReceiveLiveValues(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,10 @@
|
||||
pub mod analog_input_test_machine;
|
||||
pub mod digital_input_test_machine;
|
||||
pub mod ip20_test_machine;
|
||||
pub mod mock;
|
||||
pub mod motor_test_machine;
|
||||
pub mod test_machine;
|
||||
pub mod test_machine_stepper;
|
||||
pub mod wago_8ch_dio_test_machine;
|
||||
pub mod wago_ai_test_machine;
|
||||
pub mod wago_do_test_machine;
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -41,7 +41,6 @@ impl MachineAct for TestMachine {
|
||||
.expect("Failed to send values");
|
||||
sender.close();
|
||||
}
|
||||
MachineMessage::ReceiveLiveValues(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
File renamed without changes.
+2
-2
@@ -1,5 +1,5 @@
|
||||
use crate::machine_identification::{MachineIdentification, MachineIdentificationUnique};
|
||||
use crate::test_machine::api::{StateEvent, TestMachineEvents};
|
||||
use crate::minimal_machines::test_machine::api::{StateEvent, TestMachineEvents};
|
||||
use crate::{AsyncThreadMessage, Machine, MachineMessage};
|
||||
use control_core::socketio::namespace::NamespaceCacheingLogic;
|
||||
use ethercat_hal::io::digital_output::DigitalOutput;
|
||||
@@ -8,7 +8,7 @@ use std::time::Instant;
|
||||
pub mod act;
|
||||
pub mod api;
|
||||
pub mod new;
|
||||
use crate::test_machine::api::TestMachineNamespace;
|
||||
use crate::minimal_machines::test_machine::api::TestMachineNamespace;
|
||||
use crate::{TEST_MACHINE, VENDOR_QITECH};
|
||||
|
||||
#[derive(Debug)]
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
use crate::test_machine::TestMachine;
|
||||
use crate::test_machine::api::TestMachineNamespace;
|
||||
use crate::minimal_machines::test_machine::TestMachine;
|
||||
use crate::minimal_machines::test_machine::api::TestMachineNamespace;
|
||||
use smol::block_on;
|
||||
use std::time::Instant;
|
||||
|
||||
Loaded 100 of 155 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user