Add default winder to gluetex device with dummy data
This commit is contained in:
1 parent
7dd3d50653
commit
b080414bf2
11 files changed
+2074
No files matched your search
@@ -0,0 +1,376 @@
|
||||
import { ControlCard } from "@/control/ControlCard";
|
||||
import { Page } from "@/components/Page";
|
||||
import React, { useState } from "react";
|
||||
import { ControlGrid } from "@/control/ControlGrid";
|
||||
import { TimeSeriesValueNumeric } from "@/control/TimeSeriesValue";
|
||||
import { TraverseBar } from "../TraverseBar";
|
||||
import {
|
||||
SelectionGroup,
|
||||
SelectionGroupBoolean,
|
||||
} from "@/control/SelectionGroup";
|
||||
import { EditValue } from "@/control/EditValue";
|
||||
import { Label } from "@/control/Label";
|
||||
import { TouchButton } from "@/components/touch/TouchButton";
|
||||
import { StatusBadge } from "@/control/StatusBadge";
|
||||
import { useGluetex } from "./useGluetex";
|
||||
import {
|
||||
Mode,
|
||||
SpoolAutomaticActionMode,
|
||||
getGearRatioMultiplier,
|
||||
} from "./gluetexNamespace";
|
||||
import { TensionArm } from "../TensionArm";
|
||||
import { roundDegreesToDecimals, roundToDecimals } from "@/lib/decimal";
|
||||
import { Spool } from "../Spool";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { getGluetexTraverseMax } from "./gluetexConfig";
|
||||
|
||||
export function GluetexControlPage() {
|
||||
const [showResetConfirmDialog, setShowResetConfirmDialog] = useState(false);
|
||||
const traverseMax = getGluetexTraverseMax();
|
||||
|
||||
// use optimistic state
|
||||
const {
|
||||
state,
|
||||
defaultState,
|
||||
enableTraverseLaserpointer,
|
||||
tensionArmAngle,
|
||||
zeroTensionArmAngle,
|
||||
spoolRpm,
|
||||
setMode,
|
||||
pullerSpeed,
|
||||
spoolProgress,
|
||||
setPullerRegulationMode,
|
||||
setPullerTargetSpeed,
|
||||
traversePosition,
|
||||
resetSpoolProgress,
|
||||
setTraverseLimitInner,
|
||||
setTraverseLimitOuter,
|
||||
gotoTraverseLimitInner,
|
||||
gotoTraverseLimitOuter,
|
||||
gotoTraverseHome,
|
||||
setSpoolAutomaticRequiredMeters,
|
||||
setSpoolAutomaticAction,
|
||||
isLoading,
|
||||
isDisabled,
|
||||
} = useGluetex();
|
||||
|
||||
// Calculate max speed based on gear ratio
|
||||
const gearRatioMultiplier = getGearRatioMultiplier(
|
||||
state?.puller_state?.gear_ratio,
|
||||
);
|
||||
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_state?.mode === "Wind") {
|
||||
setShowResetConfirmDialog(true);
|
||||
} else {
|
||||
resetSpoolProgress();
|
||||
}
|
||||
};
|
||||
|
||||
const confirmResetProgress = () => {
|
||||
resetSpoolProgress();
|
||||
setShowResetConfirmDialog(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<ControlGrid>
|
||||
<ControlCard title="Spool">
|
||||
<Spool rpm={spoolRpm.current?.value} />
|
||||
<TimeSeriesValueNumeric
|
||||
label="Spool Speed"
|
||||
unit="rpm"
|
||||
timeseries={spoolRpm}
|
||||
renderValue={(value) => roundToDecimals(value, 0)}
|
||||
/>
|
||||
</ControlCard>
|
||||
|
||||
<ControlCard className="bg-red" height={2} title="Traverse">
|
||||
<TimeSeriesValueNumeric
|
||||
label="Position"
|
||||
unit="mm"
|
||||
timeseries={traversePosition}
|
||||
renderValue={(value) => roundToDecimals(value, 1)}
|
||||
/>
|
||||
{state?.traverse_state && (
|
||||
<TraverseBar
|
||||
inside={0}
|
||||
outside={traverseMax}
|
||||
min={state?.traverse_state.limit_inner}
|
||||
max={state?.traverse_state.limit_outer}
|
||||
current={traversePosition.current?.value ?? 0}
|
||||
/>
|
||||
)}
|
||||
<div className="flex flex-row flex-wrap gap-4">
|
||||
<Label label="Outer Limit">
|
||||
<EditValue
|
||||
value={state?.traverse_state?.limit_outer}
|
||||
unit="mm"
|
||||
title="Outer Limit"
|
||||
defaultValue={defaultState?.traverse_state?.limit_outer}
|
||||
// Traverse limit validation: Outer limit must be at least 0.9mm greater than inner limit
|
||||
// We use 1mm buffer to ensure the backend validation (which requires >0.9mm) will pass
|
||||
// Formula: min_outer = inner_limit + 1mm
|
||||
min={Math.max(0, (state?.traverse_state?.limit_inner ?? 0) + 1)}
|
||||
minLabel="IN"
|
||||
maxLabel="OUT"
|
||||
max={traverseMax}
|
||||
renderValue={(value) => roundToDecimals(value, 0)}
|
||||
onChange={setTraverseLimitOuter}
|
||||
/>
|
||||
<TouchButton
|
||||
variant="outline"
|
||||
icon="lu:ArrowLeftToLine"
|
||||
onClick={gotoTraverseLimitOuter}
|
||||
disabled={isDisabled}
|
||||
isLoading={isLoading}
|
||||
>
|
||||
Go to Outer Limit
|
||||
</TouchButton>
|
||||
</Label>
|
||||
<Label label="Inner Limit">
|
||||
<EditValue
|
||||
value={state?.traverse_state?.limit_inner}
|
||||
unit="mm"
|
||||
title="Inner Limit"
|
||||
min={0}
|
||||
// Traverse limit validation: Inner limit must be at least 0.9mm smaller than outer limit
|
||||
// We use 1mm buffer to ensure the backend validation (which requires outer > inner + 0.9mm) will pass
|
||||
// Formula: max_inner = outer_limit - 1mm
|
||||
max={Math.min(
|
||||
traverseMax,
|
||||
(state?.traverse_state?.limit_outer ?? traverseMax) - 1,
|
||||
)}
|
||||
defaultValue={defaultState?.traverse_state?.limit_inner}
|
||||
minLabel="IN"
|
||||
maxLabel="OUT"
|
||||
renderValue={(value) => roundToDecimals(value, 0)}
|
||||
inverted
|
||||
onChange={setTraverseLimitInner}
|
||||
/>
|
||||
<TouchButton
|
||||
variant="outline"
|
||||
icon="lu:ArrowRightToLine"
|
||||
onClick={gotoTraverseLimitInner}
|
||||
disabled={isDisabled}
|
||||
isLoading={isLoading}
|
||||
>
|
||||
Go to Inner Limit
|
||||
</TouchButton>
|
||||
</Label>
|
||||
</div>
|
||||
<Label label="Laserpointer">
|
||||
<SelectionGroupBoolean
|
||||
value={state?.traverse_state.laserpointer}
|
||||
disabled={isLoading || isDisabled}
|
||||
loading={isLoading}
|
||||
optionFalse={{ children: "Off", icon: "lu:LightbulbOff" }}
|
||||
optionTrue={{ children: "On", icon: "lu:Lightbulb" }}
|
||||
onChange={enableTraverseLaserpointer}
|
||||
/>
|
||||
</Label>
|
||||
<Label label="Home">
|
||||
<TouchButton
|
||||
variant="outline"
|
||||
icon="lu:House"
|
||||
onClick={() => gotoTraverseHome()}
|
||||
disabled={isDisabled}
|
||||
isLoading={isLoading}
|
||||
>
|
||||
Go to Home
|
||||
</TouchButton>
|
||||
{state?.traverse_state?.is_homed !== true ? (
|
||||
<StatusBadge variant={"error"}>{"Not Homed"}</StatusBadge>
|
||||
) : null}
|
||||
</Label>
|
||||
</ControlCard>
|
||||
|
||||
<ControlCard title="Tension Arm">
|
||||
<TensionArm degrees={tensionArmAngle.current?.value} />
|
||||
<TimeSeriesValueNumeric
|
||||
label="Tension Arm"
|
||||
unit="deg"
|
||||
timeseries={tensionArmAngle}
|
||||
renderValue={(value) => roundDegreesToDecimals(value, 0)}
|
||||
/>
|
||||
<TouchButton
|
||||
variant="outline"
|
||||
icon="lu:House"
|
||||
onClick={zeroTensionArmAngle}
|
||||
disabled={isDisabled}
|
||||
isLoading={isLoading}
|
||||
>
|
||||
Set Zero Point
|
||||
</TouchButton>
|
||||
{!state?.tension_arm_state?.zeroed && (
|
||||
<StatusBadge variant="error">Not Zeroed</StatusBadge>
|
||||
)}
|
||||
</ControlCard>
|
||||
|
||||
<ControlCard className="bg-red" title="Mode">
|
||||
<SelectionGroup<Mode>
|
||||
value={state?.mode_state.mode}
|
||||
disabled={isDisabled}
|
||||
loading={isLoading}
|
||||
onChange={setMode}
|
||||
orientation="vertical"
|
||||
className="grid h-full grid-cols-2 gap-2"
|
||||
options={{
|
||||
Standby: {
|
||||
children: "Standby",
|
||||
icon: "lu:Power",
|
||||
isActiveClassName: "bg-green-600",
|
||||
className: "h-full",
|
||||
},
|
||||
Hold: {
|
||||
children: "Hold",
|
||||
icon: "lu:CirclePause",
|
||||
isActiveClassName: "bg-green-600",
|
||||
className: "h-full",
|
||||
},
|
||||
Pull: {
|
||||
children: "Pull",
|
||||
icon: "lu:ChevronsLeft",
|
||||
isActiveClassName: "bg-green-600",
|
||||
className: "h-full",
|
||||
},
|
||||
Wind: {
|
||||
children: "Wind",
|
||||
icon: "lu:RefreshCcw",
|
||||
isActiveClassName: "bg-green-600",
|
||||
disabled: !state?.mode_state?.can_wind,
|
||||
className: "h-full",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</ControlCard>
|
||||
|
||||
<ControlCard className="bg-red" title="Puller">
|
||||
<TimeSeriesValueNumeric
|
||||
label="Speed"
|
||||
unit="m/min"
|
||||
timeseries={pullerSpeed}
|
||||
renderValue={(value) => roundToDecimals(value, 1)}
|
||||
/>
|
||||
<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>
|
||||
</ControlCard>
|
||||
|
||||
<ControlCard className="bg-red" title="Spool Autostop">
|
||||
<TimeSeriesValueNumeric
|
||||
label="Pulled Distance"
|
||||
renderValue={(value) => roundToDecimals(value, 2)}
|
||||
unit="m"
|
||||
timeseries={spoolProgress}
|
||||
/>
|
||||
|
||||
<Label label="Target Length">
|
||||
<EditValue
|
||||
value={state?.spool_automatic_action_state.spool_required_meters}
|
||||
unit="m"
|
||||
title="Expected Meters"
|
||||
defaultValue={250}
|
||||
min={10}
|
||||
max={10000}
|
||||
step={10}
|
||||
renderValue={(value) => roundToDecimals(value, 2)}
|
||||
onChange={setSpoolAutomaticRequiredMeters}
|
||||
/>
|
||||
</Label>
|
||||
|
||||
<TouchButton
|
||||
variant="outline"
|
||||
onClick={handleResetProgress}
|
||||
disabled={isDisabled}
|
||||
isLoading={isLoading || state?.traverse_state?.is_going_out}
|
||||
>
|
||||
Reset Progress
|
||||
</TouchButton>
|
||||
|
||||
<Label label="After Target Length Reached">
|
||||
<SelectionGroup<SpoolAutomaticActionMode>
|
||||
value={
|
||||
state?.spool_automatic_action_state.spool_automatic_action_mode
|
||||
}
|
||||
disabled={isDisabled}
|
||||
loading={isLoading}
|
||||
onChange={setSpoolAutomaticAction}
|
||||
orientation="vertical"
|
||||
options={{
|
||||
Hold: {
|
||||
children: "Hold",
|
||||
icon: "lu:CirclePause",
|
||||
className: "h-full",
|
||||
},
|
||||
Pull: {
|
||||
children: "Pull",
|
||||
icon: "lu:ChevronsLeft",
|
||||
className: "h-full",
|
||||
},
|
||||
|
||||
NoAction: {
|
||||
children: "No Action",
|
||||
icon: "lu:RefreshCcw",
|
||||
className: "h-full",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Label>
|
||||
</ControlCard>
|
||||
</ControlGrid>
|
||||
|
||||
{/* Reset Progress Confirmation Dialog */}
|
||||
<Dialog
|
||||
open={showResetConfirmDialog}
|
||||
onOpenChange={setShowResetConfirmDialog}
|
||||
>
|
||||
<DialogContent className="max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Reset Spool Progress?</DialogTitle>
|
||||
<DialogDescription>
|
||||
The machine is currently in Wind mode. Are you sure you want to
|
||||
reset the spool progress?
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="mt-4 flex flex-col gap-2">
|
||||
<TouchButton
|
||||
variant="destructive"
|
||||
onClick={confirmResetProgress}
|
||||
disabled={isLoading}
|
||||
isLoading={isLoading}
|
||||
>
|
||||
Yes, Reset Progress
|
||||
</TouchButton>
|
||||
<TouchButton
|
||||
variant="outline"
|
||||
onClick={() => setShowResetConfirmDialog(false)}
|
||||
>
|
||||
Cancel
|
||||
</TouchButton>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
import { Page } from "@/components/Page";
|
||||
import {
|
||||
AutoSyncedBigGraph,
|
||||
SyncedFloatingControlPanel,
|
||||
useGraphSync,
|
||||
type GraphConfig,
|
||||
type GraphLine,
|
||||
} from "@/components/graph";
|
||||
import React from "react";
|
||||
import { useGluetex } from "./useGluetex";
|
||||
import { roundDegreesToDecimals, roundToDecimals } from "@/lib/decimal";
|
||||
import { TimeSeries } from "@/lib/timeseries";
|
||||
import { Unit } from "@/control/units";
|
||||
|
||||
export function GluetexGraphsPage() {
|
||||
const {
|
||||
state,
|
||||
spoolRpm,
|
||||
traversePosition,
|
||||
tensionArmAngle,
|
||||
pullerSpeed,
|
||||
spoolProgress,
|
||||
} = useGluetex();
|
||||
|
||||
const syncHook = useGraphSync("gluetex-group");
|
||||
|
||||
return (
|
||||
<Page className="pb-27">
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="grid gap-4">
|
||||
<PullerSpeedGraph
|
||||
syncHook={syncHook}
|
||||
newData={pullerSpeed}
|
||||
targetSpeed={state?.puller_state?.target_speed}
|
||||
unit="m/min"
|
||||
renderValue={(value) => roundToDecimals(value, 0)}
|
||||
/>
|
||||
|
||||
<TensionArmAngleGraph
|
||||
syncHook={syncHook}
|
||||
newData={tensionArmAngle}
|
||||
unit="deg"
|
||||
renderValue={(value) => roundDegreesToDecimals(value, 0)}
|
||||
/>
|
||||
|
||||
<SpoolRpmGraph
|
||||
syncHook={syncHook}
|
||||
newData={spoolRpm}
|
||||
unit="rpm"
|
||||
renderValue={(value) => roundToDecimals(value, 0)}
|
||||
/>
|
||||
|
||||
<TraversePositionGraph
|
||||
syncHook={syncHook}
|
||||
newData={traversePosition}
|
||||
limitInner={state?.traverse_state?.limit_inner}
|
||||
limitOuter={state?.traverse_state?.limit_outer}
|
||||
unit="mm"
|
||||
renderValue={(value) => roundToDecimals(value, 1)}
|
||||
/>
|
||||
|
||||
<SpoolProgressGraph
|
||||
syncHook={syncHook}
|
||||
newData={spoolProgress}
|
||||
unit="m"
|
||||
renderValue={(value) => roundToDecimals(value, 2)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<SyncedFloatingControlPanel controlProps={syncHook.controlProps} />
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
||||
export function SpoolRpmGraph({
|
||||
syncHook,
|
||||
newData,
|
||||
unit,
|
||||
renderValue,
|
||||
}: {
|
||||
syncHook: ReturnType<typeof useGraphSync>;
|
||||
newData: TimeSeries | null;
|
||||
unit?: Unit;
|
||||
renderValue?: (value: number) => string;
|
||||
}) {
|
||||
const config: GraphConfig = {
|
||||
title: "Spool Speed",
|
||||
icon: "lu:RotateCcw",
|
||||
colors: {
|
||||
primary: "#10b981",
|
||||
grid: "#e2e8f0",
|
||||
background: "#ffffff",
|
||||
},
|
||||
exportFilename: "spool_rpm_data",
|
||||
};
|
||||
|
||||
return (
|
||||
<AutoSyncedBigGraph
|
||||
syncHook={syncHook}
|
||||
newData={{
|
||||
newData,
|
||||
color: "#10b981",
|
||||
}}
|
||||
unit={unit}
|
||||
renderValue={renderValue}
|
||||
config={config}
|
||||
graphId="spool-rpm"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function TraversePositionGraph({
|
||||
syncHook,
|
||||
newData,
|
||||
limitInner,
|
||||
limitOuter,
|
||||
unit,
|
||||
renderValue,
|
||||
}: {
|
||||
syncHook: ReturnType<typeof useGraphSync>;
|
||||
newData: TimeSeries | null;
|
||||
limitInner?: number;
|
||||
limitOuter?: number;
|
||||
unit?: Unit;
|
||||
renderValue?: (value: number) => string;
|
||||
}) {
|
||||
const lines: GraphLine[] = [];
|
||||
|
||||
if (limitInner !== undefined) {
|
||||
lines.push({
|
||||
type: "threshold",
|
||||
value: limitInner,
|
||||
color: "#8b5cf6",
|
||||
dash: [5, 5],
|
||||
});
|
||||
}
|
||||
|
||||
if (limitOuter !== undefined) {
|
||||
lines.push({
|
||||
type: "threshold",
|
||||
value: limitOuter,
|
||||
color: "#8b5cf6",
|
||||
dash: [5, 5],
|
||||
});
|
||||
}
|
||||
|
||||
const config: GraphConfig = {
|
||||
title: "Traverse Position",
|
||||
icon: "lu:Move",
|
||||
colors: {
|
||||
primary: "#8b5cf6",
|
||||
},
|
||||
exportFilename: "traverse_position_data",
|
||||
};
|
||||
|
||||
return (
|
||||
<AutoSyncedBigGraph
|
||||
syncHook={syncHook}
|
||||
newData={{
|
||||
newData,
|
||||
color: "#8b5cf6",
|
||||
lines,
|
||||
}}
|
||||
unit={unit}
|
||||
renderValue={renderValue}
|
||||
config={config}
|
||||
graphId="traverse-position"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function TensionArmAngleGraph({
|
||||
syncHook,
|
||||
newData,
|
||||
unit,
|
||||
renderValue,
|
||||
}: {
|
||||
syncHook: ReturnType<typeof useGraphSync>;
|
||||
newData: TimeSeries | null;
|
||||
unit?: Unit;
|
||||
renderValue?: (value: number) => string;
|
||||
}) {
|
||||
const config: GraphConfig = {
|
||||
title: "Tension Arm Angle",
|
||||
icon: "lu:RotateCw",
|
||||
colors: {
|
||||
primary: "#f59e0b",
|
||||
},
|
||||
exportFilename: "tension_arm_angle_data",
|
||||
};
|
||||
|
||||
return (
|
||||
<AutoSyncedBigGraph
|
||||
syncHook={syncHook}
|
||||
newData={{
|
||||
newData,
|
||||
color: "#f59e0b",
|
||||
}}
|
||||
unit={unit}
|
||||
renderValue={renderValue}
|
||||
config={config}
|
||||
graphId="tension-arm-angle"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function SpoolProgressGraph({
|
||||
syncHook,
|
||||
newData,
|
||||
unit,
|
||||
renderValue,
|
||||
}: {
|
||||
syncHook: ReturnType<typeof useGraphSync>;
|
||||
newData: TimeSeries | null;
|
||||
unit?: Unit;
|
||||
renderValue?: (value: number) => string;
|
||||
}) {
|
||||
const config: GraphConfig = {
|
||||
title: "Spool Progress",
|
||||
icon: "lu:RotateCw",
|
||||
colors: {
|
||||
primary: "#f59e0b",
|
||||
},
|
||||
exportFilename: "spool_progress",
|
||||
};
|
||||
|
||||
return (
|
||||
<AutoSyncedBigGraph
|
||||
syncHook={syncHook}
|
||||
newData={{
|
||||
newData,
|
||||
color: "#f59e0b",
|
||||
}}
|
||||
unit={unit}
|
||||
renderValue={renderValue}
|
||||
config={config}
|
||||
graphId="spool-progress"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function PullerSpeedGraph({
|
||||
syncHook,
|
||||
newData,
|
||||
targetSpeed,
|
||||
unit,
|
||||
renderValue,
|
||||
}: {
|
||||
syncHook: ReturnType<typeof useGraphSync>;
|
||||
newData: TimeSeries | null;
|
||||
targetSpeed?: number;
|
||||
unit?: Unit;
|
||||
renderValue?: (value: number) => string;
|
||||
}) {
|
||||
const lines: GraphLine[] = [];
|
||||
|
||||
if (targetSpeed !== undefined) {
|
||||
lines.push({
|
||||
type: "target",
|
||||
value: targetSpeed,
|
||||
label: "Target Speed",
|
||||
color: "#06b6d4", // Match series color
|
||||
});
|
||||
}
|
||||
|
||||
const config: GraphConfig = {
|
||||
title: "Puller Speed",
|
||||
icon: "lu:Gauge",
|
||||
colors: {
|
||||
primary: "#06b6d4",
|
||||
},
|
||||
exportFilename: "puller_speed_data",
|
||||
};
|
||||
|
||||
return (
|
||||
<AutoSyncedBigGraph
|
||||
syncHook={syncHook}
|
||||
newData={{
|
||||
newData,
|
||||
color: "#06b6d4",
|
||||
lines,
|
||||
}}
|
||||
unit={unit}
|
||||
renderValue={renderValue}
|
||||
config={config}
|
||||
graphId="puller-speed"
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Page } from "@/components/Page";
|
||||
import { MarkdownWithToc } from "@/components/MarkdownWithToc";
|
||||
import React from "react";
|
||||
import winderManualContent from "@/assets/markdown/winder/manual.md?raw";
|
||||
|
||||
export function GluetexManualPage() {
|
||||
return (
|
||||
<Page>
|
||||
<MarkdownWithToc markdownContent={winderManualContent} />
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { Topbar } from "@/components/Topbar";
|
||||
import React from "react";
|
||||
|
||||
export function GluetexPage() {
|
||||
return (
|
||||
<Topbar
|
||||
pathname={`/_sidebar/machines/gluetex`}
|
||||
items={[
|
||||
{
|
||||
link: "control",
|
||||
activeLink: "control",
|
||||
title: "Control",
|
||||
icon: "lu:CirclePlay",
|
||||
},
|
||||
{
|
||||
link: "graphs",
|
||||
activeLink: "graphs",
|
||||
title: "Graphs",
|
||||
icon: "lu:ChartSpline",
|
||||
},
|
||||
{
|
||||
link: "settings",
|
||||
activeLink: "settings",
|
||||
title: "Config",
|
||||
icon: "lu:Settings",
|
||||
},
|
||||
{
|
||||
link: "manual",
|
||||
activeLink: "manual",
|
||||
title: "Manual",
|
||||
icon: "lu:BookOpen",
|
||||
},
|
||||
{
|
||||
link: "presets",
|
||||
activeLink: "presets",
|
||||
title: "Presets",
|
||||
icon: "lu:Save",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import React from "react";
|
||||
import { Page } from "@/components/Page";
|
||||
|
||||
export function GluetexPresetsPage() {
|
||||
return (
|
||||
<Page>
|
||||
<div className="p-8">
|
||||
<h1 className="mb-4 text-2xl font-bold">Gluetex Presets</h1>
|
||||
<p className="text-gray-600">
|
||||
Preset functionality is not available in this test version. This is a
|
||||
hardcoded frontend for testing purposes only.
|
||||
</p>
|
||||
</div>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,374 @@
|
||||
import { Page } from "@/components/Page";
|
||||
import { ControlCard } from "@/control/ControlCard";
|
||||
import { ControlGrid } from "@/control/ControlGrid";
|
||||
import { EditValue } from "@/control/EditValue";
|
||||
import React, { useState } from "react";
|
||||
import { useGluetex } from "./useGluetex";
|
||||
import { roundToDecimals } from "@/lib/decimal";
|
||||
import { Label } from "@/control/Label";
|
||||
import { SelectionGroupBoolean } from "@/control/SelectionGroup";
|
||||
import { SelectionGroup } from "@/control/SelectionGroup";
|
||||
import { MachineSelector } from "@/components/MachineConnectionDropdown";
|
||||
import {
|
||||
getGluetexXLMode,
|
||||
setGluetexXLMode,
|
||||
GLUETEX_TRAVERSE_MAX_STANDARD,
|
||||
GLUETEX_TRAVERSE_MAX_XL,
|
||||
} from "./gluetexConfig";
|
||||
|
||||
export function GluetexSettingPage() {
|
||||
const [xlMode, setXlMode] = useState(getGluetexXLMode());
|
||||
|
||||
const {
|
||||
state,
|
||||
defaultState,
|
||||
setTraverseStepSize,
|
||||
setTraversePadding,
|
||||
setTraverseLimitInner,
|
||||
setTraverseLimitOuter,
|
||||
gotoTraverseHome,
|
||||
setPullerForward,
|
||||
setPullerGearRatio,
|
||||
setSpoolRegulationMode,
|
||||
setSpoolMinMaxMinSpeed,
|
||||
setSpoolMinMaxMaxSpeed,
|
||||
setSpoolForward,
|
||||
setSpoolAdaptiveTensionTarget,
|
||||
setSpoolAdaptiveRadiusLearningRate,
|
||||
setSpoolAdaptiveMaxSpeedMultiplier,
|
||||
setSpoolAdaptiveAccelerationFactor,
|
||||
setSpoolAdaptiveDeaccelerationUrgencyMultiplier,
|
||||
isLoading,
|
||||
isDisabled,
|
||||
selectedMachine,
|
||||
filteredMachines,
|
||||
setConnectedMachine,
|
||||
disconnectMachine,
|
||||
} = useGluetex();
|
||||
|
||||
const handleXlModeChange = (enabled: boolean) => {
|
||||
setGluetexXLMode(enabled);
|
||||
setXlMode(enabled);
|
||||
|
||||
// When switching from XL to normal mode, reset traverse limits to default values
|
||||
if (!enabled && defaultState) {
|
||||
// Only reset if current values exceed the standard max
|
||||
const currentOuter = state?.traverse_state?.limit_outer ?? 0;
|
||||
const currentInner = state?.traverse_state?.limit_inner ?? 0;
|
||||
const defaultOuter = defaultState.traverse_state?.limit_outer;
|
||||
const defaultInner = defaultState.traverse_state?.limit_inner;
|
||||
|
||||
if (
|
||||
currentOuter > GLUETEX_TRAVERSE_MAX_STANDARD &&
|
||||
defaultOuter !== undefined
|
||||
) {
|
||||
setTraverseLimitOuter(defaultOuter);
|
||||
setTraverseLimitInner(defaultInner);
|
||||
}
|
||||
if (
|
||||
currentInner > GLUETEX_TRAVERSE_MAX_STANDARD &&
|
||||
defaultInner !== undefined
|
||||
) {
|
||||
setTraverseLimitOuter(defaultOuter);
|
||||
setTraverseLimitInner(defaultInner);
|
||||
}
|
||||
|
||||
// Home the traverse when switching from XL to normal mode
|
||||
gotoTraverseHome();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<ControlGrid>
|
||||
<ControlCard title="Traverse">
|
||||
<Label label="Traverse Size">
|
||||
<SelectionGroupBoolean
|
||||
value={xlMode}
|
||||
disabled={isDisabled}
|
||||
loading={isLoading}
|
||||
optionFalse={{
|
||||
children: `Standard (${GLUETEX_TRAVERSE_MAX_STANDARD}mm)`,
|
||||
icon: "lu:Settings",
|
||||
}}
|
||||
optionTrue={{
|
||||
children: `XL (${GLUETEX_TRAVERSE_MAX_XL}mm)`,
|
||||
icon: "lu:Maximize2",
|
||||
}}
|
||||
onChange={handleXlModeChange}
|
||||
/>
|
||||
</Label>
|
||||
<Label label="Step Size">
|
||||
<EditValue
|
||||
value={state?.traverse_state?.step_size}
|
||||
title={"Step Size"}
|
||||
unit="mm"
|
||||
step={0.05}
|
||||
min={0.1}
|
||||
max={75}
|
||||
defaultValue={defaultState?.traverse_state?.step_size}
|
||||
renderValue={(value) => roundToDecimals(value, 2)}
|
||||
onChange={(value) => setTraverseStepSize(value)}
|
||||
/>
|
||||
</Label>
|
||||
<Label label="Padding">
|
||||
<EditValue
|
||||
value={state?.traverse_state?.padding}
|
||||
title={"Padding"}
|
||||
unit="mm"
|
||||
step={0.01}
|
||||
min={0}
|
||||
max={5}
|
||||
defaultValue={defaultState?.traverse_state?.padding}
|
||||
renderValue={(value) => roundToDecimals(value, 2)}
|
||||
onChange={(value) => setTraversePadding(value)}
|
||||
/>
|
||||
</Label>
|
||||
</ControlCard>
|
||||
|
||||
<ControlCard title="Spool">
|
||||
<Label label="Speed Algorithm">
|
||||
<SelectionGroup
|
||||
value={state?.spool_speed_controller_state?.regulation_mode}
|
||||
disabled={isDisabled}
|
||||
loading={isLoading}
|
||||
options={{
|
||||
MinMax: {
|
||||
children: "Min/Max",
|
||||
icon: "lu:ArrowUpDown",
|
||||
},
|
||||
Adaptive: {
|
||||
children: "Adaptive",
|
||||
icon: "lu:Brain",
|
||||
},
|
||||
}}
|
||||
onChange={(value) =>
|
||||
setSpoolRegulationMode(value as "Adaptive" | "MinMax")
|
||||
}
|
||||
/>
|
||||
</Label>
|
||||
|
||||
<Label label="Rotation Direction">
|
||||
<SelectionGroupBoolean
|
||||
value={state?.spool_speed_controller_state?.forward}
|
||||
disabled={isDisabled}
|
||||
loading={isLoading}
|
||||
optionFalse={{
|
||||
children: "Reverse",
|
||||
icon: "lu:RotateCcw",
|
||||
}}
|
||||
optionTrue={{
|
||||
children: "Forward",
|
||||
icon: "lu:RotateCw",
|
||||
}}
|
||||
onChange={(value) => setSpoolForward(value)}
|
||||
/>
|
||||
</Label>
|
||||
|
||||
{state?.spool_speed_controller_state?.regulation_mode ===
|
||||
"MinMax" && (
|
||||
<>
|
||||
<Label label="Minimum Speed">
|
||||
<EditValue
|
||||
value={state?.spool_speed_controller_state?.minmax_min_speed}
|
||||
title={"Minimum Speed"}
|
||||
unit="rpm"
|
||||
step={10}
|
||||
min={0}
|
||||
max={600}
|
||||
defaultValue={
|
||||
defaultState?.spool_speed_controller_state?.minmax_min_speed
|
||||
}
|
||||
renderValue={(value) => roundToDecimals(value, 0)}
|
||||
onChange={(value) => setSpoolMinMaxMinSpeed(value)}
|
||||
/>
|
||||
</Label>
|
||||
<Label label="Maximum Speed">
|
||||
<EditValue
|
||||
value={state?.spool_speed_controller_state?.minmax_max_speed}
|
||||
title={"Maximum Speed"}
|
||||
unit="rpm"
|
||||
step={10}
|
||||
min={0}
|
||||
max={600}
|
||||
defaultValue={
|
||||
defaultState?.spool_speed_controller_state?.minmax_max_speed
|
||||
}
|
||||
renderValue={(value) => roundToDecimals(value, 0)}
|
||||
onChange={(value) => setSpoolMinMaxMaxSpeed(value)}
|
||||
/>
|
||||
</Label>
|
||||
</>
|
||||
)}
|
||||
|
||||
{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_speed_controller_state?.adaptive_tension_target
|
||||
}
|
||||
title={"Tension Target"}
|
||||
unit={undefined}
|
||||
step={0.01}
|
||||
min={0}
|
||||
max={1}
|
||||
defaultValue={
|
||||
defaultState?.spool_speed_controller_state
|
||||
?.adaptive_tension_target
|
||||
}
|
||||
renderValue={(value) => roundToDecimals(value, 2)}
|
||||
onChange={(value) => setSpoolAdaptiveTensionTarget(value)}
|
||||
/>
|
||||
</Label>
|
||||
<Label label="Learning Rate">
|
||||
<EditValue
|
||||
value={
|
||||
state?.spool_speed_controller_state
|
||||
?.adaptive_radius_learning_rate
|
||||
}
|
||||
title={"Radius Learning Rate"}
|
||||
unit={undefined}
|
||||
step={0.001}
|
||||
min={0}
|
||||
max={100}
|
||||
defaultValue={
|
||||
defaultState?.spool_speed_controller_state
|
||||
?.adaptive_radius_learning_rate
|
||||
}
|
||||
renderValue={(value) => roundToDecimals(value, 2)}
|
||||
onChange={(value) =>
|
||||
setSpoolAdaptiveRadiusLearningRate(value)
|
||||
}
|
||||
/>
|
||||
</Label>
|
||||
<Label label="Max Speed Multiplier">
|
||||
<EditValue
|
||||
value={
|
||||
state?.spool_speed_controller_state
|
||||
?.adaptive_max_speed_multiplier
|
||||
}
|
||||
title={"Max Speed Multiplier"}
|
||||
unit={undefined}
|
||||
step={0.1}
|
||||
min={0.1}
|
||||
max={10}
|
||||
defaultValue={
|
||||
defaultState?.spool_speed_controller_state
|
||||
?.adaptive_max_speed_multiplier
|
||||
}
|
||||
renderValue={(value) => roundToDecimals(value, 1)}
|
||||
onChange={(value) =>
|
||||
setSpoolAdaptiveMaxSpeedMultiplier(value)
|
||||
}
|
||||
/>
|
||||
</Label>
|
||||
<Label label="Acceleration Factor">
|
||||
<EditValue
|
||||
value={
|
||||
state?.spool_speed_controller_state
|
||||
?.adaptive_acceleration_factor
|
||||
}
|
||||
title={"Acceleration Factor"}
|
||||
unit={undefined}
|
||||
step={0.01}
|
||||
min={0.01}
|
||||
max={100}
|
||||
defaultValue={
|
||||
defaultState?.spool_speed_controller_state
|
||||
?.adaptive_acceleration_factor
|
||||
}
|
||||
renderValue={(value) => roundToDecimals(value, 2)}
|
||||
onChange={(value) =>
|
||||
setSpoolAdaptiveAccelerationFactor(value)
|
||||
}
|
||||
/>
|
||||
</Label>
|
||||
<Label label="Deaccel. Urgency">
|
||||
<EditValue
|
||||
value={
|
||||
state?.spool_speed_controller_state
|
||||
?.adaptive_deacceleration_urgency_multiplier
|
||||
}
|
||||
title={"Deacceleration Urgency Multiplier"}
|
||||
unit={undefined}
|
||||
step={0.5}
|
||||
min={1}
|
||||
max={100}
|
||||
defaultValue={
|
||||
defaultState?.spool_speed_controller_state
|
||||
?.adaptive_deacceleration_urgency_multiplier
|
||||
}
|
||||
renderValue={(value) => roundToDecimals(value, 1)}
|
||||
onChange={(value) =>
|
||||
setSpoolAdaptiveDeaccelerationUrgencyMultiplier(value)
|
||||
}
|
||||
/>
|
||||
</Label>
|
||||
</div>
|
||||
)}
|
||||
</ControlCard>
|
||||
|
||||
<ControlCard title="Puller">
|
||||
<Label label="Rotation Direction">
|
||||
<SelectionGroupBoolean
|
||||
value={state?.puller_state?.forward}
|
||||
disabled={isDisabled}
|
||||
loading={isLoading}
|
||||
optionFalse={{
|
||||
children: "Reverse",
|
||||
icon: "lu:RotateCcw",
|
||||
}}
|
||||
optionTrue={{
|
||||
children: "Forward",
|
||||
icon: "lu:RotateCw",
|
||||
}}
|
||||
onChange={(value) => setPullerForward(value)}
|
||||
/>
|
||||
</Label>
|
||||
<Label label="Gear Ratio">
|
||||
<SelectionGroup
|
||||
value={state?.puller_state?.gear_ratio}
|
||||
disabled={isDisabled}
|
||||
loading={isLoading}
|
||||
options={{
|
||||
OneToOne: {
|
||||
children: "1:1",
|
||||
icon: "lu:Circle",
|
||||
},
|
||||
OneToFive: {
|
||||
children: "1:5",
|
||||
icon: "lu:Cog",
|
||||
},
|
||||
OneToTen: {
|
||||
children: "1:10",
|
||||
icon: "lu:Cog",
|
||||
},
|
||||
}}
|
||||
onChange={(value) =>
|
||||
setPullerGearRatio(
|
||||
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>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Configuration helper for Gluetex machine settings
|
||||
*/
|
||||
|
||||
const GLUETEX_XL_MODE_KEY = "gluetex_xl_mode";
|
||||
|
||||
export const GLUETEX_TRAVERSE_MAX_STANDARD = 180; // mm
|
||||
export const GLUETEX_TRAVERSE_MAX_XL = 385; // mm
|
||||
|
||||
/**
|
||||
* Get the XL mode setting from localStorage
|
||||
* @returns true if XL mode is enabled, false otherwise
|
||||
*/
|
||||
export function getGluetexXLMode(): boolean {
|
||||
const stored = localStorage.getItem(GLUETEX_XL_MODE_KEY);
|
||||
return stored === "true";
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the XL mode setting in localStorage
|
||||
* @param enabled - true to enable XL mode, false to disable
|
||||
*/
|
||||
export function setGluetexXLMode(enabled: boolean): void {
|
||||
localStorage.setItem(GLUETEX_XL_MODE_KEY, enabled.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the maximum traverse limit based on XL mode
|
||||
* @returns the maximum traverse limit in mm
|
||||
*/
|
||||
export function getGluetexTraverseMax(): number {
|
||||
return getGluetexXLMode()
|
||||
? GLUETEX_TRAVERSE_MAX_XL
|
||||
: GLUETEX_TRAVERSE_MAX_STANDARD;
|
||||
}
|
||||
@@ -0,0 +1,368 @@
|
||||
/**
|
||||
* @file gluetexNamespace.ts
|
||||
* @description TypeScript implementation of Gluetex namespace with hardcoded test data (no backend).
|
||||
*/
|
||||
|
||||
import { useMemo, useState, useEffect } from "react";
|
||||
import {
|
||||
createTimeSeries,
|
||||
TimeSeries,
|
||||
} from "@/lib/timeseries";
|
||||
import { MachineIdentificationUnique } from "@/machines/types";
|
||||
|
||||
// ========== Type Definitions ==========
|
||||
|
||||
export type PullerRegulation = "Speed" | "Diameter";
|
||||
export type GearRatio = "OneToOne" | "OneToFive" | "OneToTen";
|
||||
export type Mode = "Standby" | "Hold" | "Pull" | "Wind";
|
||||
export type SpoolAutomaticActionMode = "NoAction" | "Pull" | "Hold";
|
||||
export type SpoolRegulationMode = "Adaptive" | "MinMax";
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
export type TraverseState = {
|
||||
limit_inner: number;
|
||||
limit_outer: number;
|
||||
position_in: number;
|
||||
position_out: number;
|
||||
is_going_in: boolean;
|
||||
is_going_out: boolean;
|
||||
is_homed: boolean;
|
||||
is_going_home: boolean;
|
||||
is_traversing: boolean;
|
||||
laserpointer: boolean;
|
||||
step_size: number;
|
||||
padding: number;
|
||||
can_go_in: boolean;
|
||||
can_go_out: boolean;
|
||||
can_go_home: boolean;
|
||||
};
|
||||
|
||||
export type PullerState = {
|
||||
regulation: PullerRegulation;
|
||||
target_speed: number;
|
||||
target_diameter: number;
|
||||
forward: boolean;
|
||||
gear_ratio: GearRatio;
|
||||
};
|
||||
|
||||
export type ModeState = {
|
||||
mode: Mode;
|
||||
can_wind: boolean;
|
||||
};
|
||||
|
||||
export type TensionArmState = {
|
||||
zeroed: boolean;
|
||||
};
|
||||
|
||||
export type SpoolSpeedControllerState = {
|
||||
regulation_mode: SpoolRegulationMode;
|
||||
minmax_min_speed: number;
|
||||
minmax_max_speed: number;
|
||||
adaptive_tension_target: number;
|
||||
adaptive_radius_learning_rate: number;
|
||||
adaptive_max_speed_multiplier: number;
|
||||
adaptive_acceleration_factor: number;
|
||||
adaptive_deacceleration_urgency_multiplier: number;
|
||||
forward: boolean;
|
||||
};
|
||||
|
||||
export type SpoolAutomaticActionState = {
|
||||
spool_required_meters: number;
|
||||
spool_automatic_action_mode: SpoolAutomaticActionMode;
|
||||
};
|
||||
|
||||
export type ConnectedMachineState = {
|
||||
machine_identification_unique: {
|
||||
machine_identification: {
|
||||
vendor: number;
|
||||
machine: number;
|
||||
};
|
||||
serial: number;
|
||||
} | null;
|
||||
is_available: boolean;
|
||||
};
|
||||
|
||||
export type StateEvent = {
|
||||
is_default_state: boolean;
|
||||
traverse_state: TraverseState;
|
||||
puller_state: PullerState;
|
||||
mode_state: ModeState;
|
||||
tension_arm_state: TensionArmState;
|
||||
spool_speed_controller_state: SpoolSpeedControllerState;
|
||||
spool_automatic_action_state: SpoolAutomaticActionState;
|
||||
connected_machine_state: ConnectedMachineState;
|
||||
};
|
||||
|
||||
export type GluetexNamespaceStore = {
|
||||
state: StateEvent | null;
|
||||
defaultState: StateEvent | null;
|
||||
traversePosition: TimeSeries;
|
||||
pullerSpeed: TimeSeries;
|
||||
spoolRpm: TimeSeries;
|
||||
tensionArmAngle: TimeSeries;
|
||||
spoolProgress: TimeSeries;
|
||||
};
|
||||
|
||||
// ========== Hardcoded Test Data ==========
|
||||
|
||||
const HARDCODED_STATE: StateEvent = {
|
||||
is_default_state: false,
|
||||
traverse_state: {
|
||||
limit_inner: 20,
|
||||
limit_outer: 160,
|
||||
position_in: 0,
|
||||
position_out: 180,
|
||||
is_going_in: false,
|
||||
is_going_out: false,
|
||||
is_homed: true,
|
||||
is_going_home: false,
|
||||
is_traversing: false,
|
||||
laserpointer: false,
|
||||
step_size: 1.5,
|
||||
padding: 5,
|
||||
can_go_in: true,
|
||||
can_go_out: true,
|
||||
can_go_home: true,
|
||||
},
|
||||
puller_state: {
|
||||
regulation: "Speed",
|
||||
target_speed: 15.0,
|
||||
target_diameter: 50.0,
|
||||
forward: true,
|
||||
gear_ratio: "OneToOne",
|
||||
},
|
||||
mode_state: {
|
||||
mode: "Standby",
|
||||
can_wind: true,
|
||||
},
|
||||
tension_arm_state: {
|
||||
zeroed: true,
|
||||
},
|
||||
spool_speed_controller_state: {
|
||||
regulation_mode: "Adaptive",
|
||||
minmax_min_speed: 5.0,
|
||||
minmax_max_speed: 50.0,
|
||||
adaptive_tension_target: 35.0,
|
||||
adaptive_radius_learning_rate: 0.1,
|
||||
adaptive_max_speed_multiplier: 2.0,
|
||||
adaptive_acceleration_factor: 0.5,
|
||||
adaptive_deacceleration_urgency_multiplier: 1.5,
|
||||
forward: true,
|
||||
},
|
||||
spool_automatic_action_state: {
|
||||
spool_required_meters: 100.0,
|
||||
spool_automatic_action_mode: "NoAction",
|
||||
},
|
||||
connected_machine_state: {
|
||||
machine_identification_unique: null,
|
||||
is_available: false,
|
||||
},
|
||||
};
|
||||
|
||||
const DEFAULT_STATE: StateEvent = {
|
||||
is_default_state: true,
|
||||
traverse_state: {
|
||||
limit_inner: 10,
|
||||
limit_outer: 170,
|
||||
position_in: 0,
|
||||
position_out: 180,
|
||||
is_going_in: false,
|
||||
is_going_out: false,
|
||||
is_homed: false,
|
||||
is_going_home: false,
|
||||
is_traversing: false,
|
||||
laserpointer: false,
|
||||
step_size: 1.0,
|
||||
padding: 3,
|
||||
can_go_in: true,
|
||||
can_go_out: true,
|
||||
can_go_home: true,
|
||||
},
|
||||
puller_state: {
|
||||
regulation: "Speed",
|
||||
target_speed: 10.0,
|
||||
target_diameter: 40.0,
|
||||
forward: true,
|
||||
gear_ratio: "OneToOne",
|
||||
},
|
||||
mode_state: {
|
||||
mode: "Standby",
|
||||
can_wind: true,
|
||||
},
|
||||
tension_arm_state: {
|
||||
zeroed: false,
|
||||
},
|
||||
spool_speed_controller_state: {
|
||||
regulation_mode: "Adaptive",
|
||||
minmax_min_speed: 5.0,
|
||||
minmax_max_speed: 40.0,
|
||||
adaptive_tension_target: 30.0,
|
||||
adaptive_radius_learning_rate: 0.1,
|
||||
adaptive_max_speed_multiplier: 2.0,
|
||||
adaptive_acceleration_factor: 0.5,
|
||||
adaptive_deacceleration_urgency_multiplier: 1.5,
|
||||
forward: true,
|
||||
},
|
||||
spool_automatic_action_state: {
|
||||
spool_required_meters: 50.0,
|
||||
spool_automatic_action_mode: "NoAction",
|
||||
},
|
||||
connected_machine_state: {
|
||||
machine_identification_unique: null,
|
||||
is_available: false,
|
||||
},
|
||||
};
|
||||
|
||||
// Constants for time durations
|
||||
const TWENTY_MILLISECOND = 20;
|
||||
const ONE_SECOND = 1000;
|
||||
const FIVE_SECOND = 5 * ONE_SECOND;
|
||||
const ONE_HOUR = 60 * 60 * ONE_SECOND;
|
||||
|
||||
/**
|
||||
* Hook for Gluetex namespace with hardcoded test data
|
||||
* This simulates the backend behavior with fake data for testing
|
||||
*/
|
||||
export function useGluetexNamespace(
|
||||
_machine_identification_unique: MachineIdentificationUnique,
|
||||
): GluetexNamespaceStore {
|
||||
const [state, setState] = useState<StateEvent>(HARDCODED_STATE);
|
||||
|
||||
// Create time series with simulated data
|
||||
const traversePosition = useMemo(() => {
|
||||
const { initialTimeSeries, insert } = createTimeSeries(
|
||||
TWENTY_MILLISECOND,
|
||||
ONE_SECOND,
|
||||
FIVE_SECOND,
|
||||
ONE_HOUR,
|
||||
);
|
||||
let series = initialTimeSeries;
|
||||
|
||||
// Add some initial simulated values
|
||||
const now = Date.now();
|
||||
for (let i = 0; i < 50; i++) {
|
||||
const timestamp = now - (50 - i) * 100;
|
||||
const value = 90 + Math.sin(i / 5) * 30; // Oscillating between 60 and 120
|
||||
series = insert(series, { value, timestamp });
|
||||
}
|
||||
|
||||
return series;
|
||||
}, []);
|
||||
|
||||
const pullerSpeed = useMemo(() => {
|
||||
const { initialTimeSeries, insert } = createTimeSeries(
|
||||
TWENTY_MILLISECOND,
|
||||
ONE_SECOND,
|
||||
FIVE_SECOND,
|
||||
ONE_HOUR,
|
||||
);
|
||||
let series = initialTimeSeries;
|
||||
|
||||
const now = Date.now();
|
||||
for (let i = 0; i < 50; i++) {
|
||||
const timestamp = now - (50 - i) * 100;
|
||||
const value = 14.5 + Math.random() * 1; // Around 15 m/min with variation
|
||||
series = insert(series, { value, timestamp });
|
||||
}
|
||||
|
||||
return series;
|
||||
}, []);
|
||||
|
||||
const spoolRpm = useMemo(() => {
|
||||
const { initialTimeSeries, insert } = createTimeSeries(
|
||||
TWENTY_MILLISECOND,
|
||||
ONE_SECOND,
|
||||
FIVE_SECOND,
|
||||
ONE_HOUR,
|
||||
);
|
||||
let series = initialTimeSeries;
|
||||
|
||||
const now = Date.now();
|
||||
for (let i = 0; i < 50; i++) {
|
||||
const timestamp = now - (50 - i) * 100;
|
||||
const value = 25 + Math.random() * 5; // Around 25-30 rpm
|
||||
series = insert(series, { value, timestamp });
|
||||
}
|
||||
|
||||
return series;
|
||||
}, []);
|
||||
|
||||
const tensionArmAngle = useMemo(() => {
|
||||
const { initialTimeSeries, insert } = createTimeSeries(
|
||||
TWENTY_MILLISECOND,
|
||||
ONE_SECOND,
|
||||
FIVE_SECOND,
|
||||
ONE_HOUR,
|
||||
);
|
||||
let series = initialTimeSeries;
|
||||
|
||||
const now = Date.now();
|
||||
for (let i = 0; i < 50; i++) {
|
||||
const timestamp = now - (50 - i) * 100;
|
||||
const value = 35 + Math.sin(i / 3) * 10; // Oscillating around 35 degrees
|
||||
series = insert(series, { value, timestamp });
|
||||
}
|
||||
|
||||
return series;
|
||||
}, []);
|
||||
|
||||
const spoolProgress = useMemo(() => {
|
||||
const { initialTimeSeries, insert } = createTimeSeries(
|
||||
TWENTY_MILLISECOND,
|
||||
ONE_SECOND,
|
||||
FIVE_SECOND,
|
||||
ONE_HOUR,
|
||||
);
|
||||
let series = initialTimeSeries;
|
||||
|
||||
const now = Date.now();
|
||||
for (let i = 0; i < 50; i++) {
|
||||
const timestamp = now - (50 - i) * 100;
|
||||
const value = i * 0.5; // Gradually increasing progress
|
||||
series = insert(series, { value, timestamp });
|
||||
}
|
||||
|
||||
return series;
|
||||
}, []);
|
||||
|
||||
// Simulate live data updates
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
// Update traverse position to simulate movement
|
||||
const currentPos = state.traverse_state.position_in;
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
traverse_state: {
|
||||
...prev.traverse_state,
|
||||
position_in: (currentPos + 0.5) % 180, // Slowly moving
|
||||
},
|
||||
}));
|
||||
}, 1000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [state.traverse_state.position_in]);
|
||||
|
||||
return {
|
||||
state,
|
||||
defaultState: DEFAULT_STATE,
|
||||
traversePosition,
|
||||
pullerSpeed,
|
||||
spoolRpm,
|
||||
tensionArmAngle,
|
||||
spoolProgress,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,494 @@
|
||||
/**
|
||||
* @file useGluetex.ts
|
||||
* @description Hook for Gluetex machine with hardcoded test data (no backend integration).
|
||||
*/
|
||||
|
||||
import { useMemo, useState } from "react";
|
||||
import { MachineIdentificationUnique } from "@/machines/types";
|
||||
import {
|
||||
SpoolRegulationMode,
|
||||
StateEvent,
|
||||
useGluetexNamespace,
|
||||
Mode,
|
||||
PullerRegulation,
|
||||
SpoolAutomaticActionMode,
|
||||
GearRatio,
|
||||
} from "./gluetexNamespace";
|
||||
|
||||
export function useGluetex() {
|
||||
// For testing, we use a dummy machine identification
|
||||
const machineIdentification: MachineIdentificationUnique = useMemo(
|
||||
() => ({
|
||||
machine_identification: {
|
||||
vendor: 1,
|
||||
machine: 3,
|
||||
},
|
||||
serial: 999,
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
// Get state and live values from namespace (hardcoded)
|
||||
const {
|
||||
state: namespaceState,
|
||||
defaultState: namespaceDefaultState,
|
||||
traversePosition,
|
||||
pullerSpeed,
|
||||
spoolRpm,
|
||||
tensionArmAngle,
|
||||
spoolProgress,
|
||||
} = useGluetexNamespace(machineIdentification);
|
||||
|
||||
// Use local state for testing changes
|
||||
const [localState, setLocalState] = useState<StateEvent | null>(
|
||||
namespaceState,
|
||||
);
|
||||
|
||||
// Mock action functions - these update local state instead of calling backend
|
||||
const zeroTensionArmAngle = () => {
|
||||
console.log("Mock: Zero tension arm angle");
|
||||
setLocalState((prev) => {
|
||||
if (!prev) return prev;
|
||||
return {
|
||||
...prev,
|
||||
tension_arm_state: { ...prev.tension_arm_state, zeroed: true },
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const setTraverseLimitInner = (limitInner: number) => {
|
||||
console.log("Mock: Set traverse limit inner:", limitInner);
|
||||
setLocalState((prev) => {
|
||||
if (!prev) return prev;
|
||||
return {
|
||||
...prev,
|
||||
traverse_state: { ...prev.traverse_state, limit_inner: limitInner },
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const setTraverseLimitOuter = (limitOuter: number) => {
|
||||
console.log("Mock: Set traverse limit outer:", limitOuter);
|
||||
setLocalState((prev) => {
|
||||
if (!prev) return prev;
|
||||
return {
|
||||
...prev,
|
||||
traverse_state: { ...prev.traverse_state, limit_outer: limitOuter },
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const gotoTraverseLimitInner = () => {
|
||||
console.log("Mock: Go to traverse limit inner");
|
||||
setLocalState((prev) => {
|
||||
if (!prev) return prev;
|
||||
return {
|
||||
...prev,
|
||||
traverse_state: { ...prev.traverse_state, is_going_in: true },
|
||||
};
|
||||
});
|
||||
setTimeout(() => {
|
||||
setLocalState((prev) => {
|
||||
if (!prev) return prev;
|
||||
return {
|
||||
...prev,
|
||||
traverse_state: { ...prev.traverse_state, is_going_in: false },
|
||||
};
|
||||
});
|
||||
}, 2000);
|
||||
};
|
||||
|
||||
const gotoTraverseLimitOuter = () => {
|
||||
console.log("Mock: Go to traverse limit outer");
|
||||
setLocalState((prev) => {
|
||||
if (!prev) return prev;
|
||||
return {
|
||||
...prev,
|
||||
traverse_state: { ...prev.traverse_state, is_going_out: true },
|
||||
};
|
||||
});
|
||||
setTimeout(() => {
|
||||
setLocalState((prev) => {
|
||||
if (!prev) return prev;
|
||||
return {
|
||||
...prev,
|
||||
traverse_state: { ...prev.traverse_state, is_going_out: false },
|
||||
};
|
||||
});
|
||||
}, 2000);
|
||||
};
|
||||
|
||||
const gotoTraverseHome = () => {
|
||||
console.log("Mock: Go to traverse home");
|
||||
setLocalState((prev) => {
|
||||
if (!prev) return prev;
|
||||
return {
|
||||
...prev,
|
||||
traverse_state: { ...prev.traverse_state, is_going_home: true },
|
||||
};
|
||||
});
|
||||
setTimeout(() => {
|
||||
setLocalState((prev) => {
|
||||
if (!prev) return prev;
|
||||
return {
|
||||
...prev,
|
||||
traverse_state: {
|
||||
...prev.traverse_state,
|
||||
is_going_home: false,
|
||||
is_homed: true,
|
||||
},
|
||||
};
|
||||
});
|
||||
}, 2000);
|
||||
};
|
||||
|
||||
const enableTraverseLaserpointer = (enabled: boolean) => {
|
||||
console.log("Mock: Enable traverse laserpointer:", enabled);
|
||||
setLocalState((prev) => {
|
||||
if (!prev) return prev;
|
||||
return {
|
||||
...prev,
|
||||
traverse_state: { ...prev.traverse_state, laserpointer: enabled },
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const setMode = (mode: Mode) => {
|
||||
console.log("Mock: Set mode:", mode);
|
||||
setLocalState((prev) => {
|
||||
if (!prev) return prev;
|
||||
return {
|
||||
...prev,
|
||||
mode_state: { ...prev.mode_state, mode },
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const setTraverseStepSize = (stepSize: number) => {
|
||||
console.log("Mock: Set traverse step size:", stepSize);
|
||||
setLocalState((prev) => {
|
||||
if (!prev) return prev;
|
||||
return {
|
||||
...prev,
|
||||
traverse_state: { ...prev.traverse_state, step_size: stepSize },
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const setTraversePadding = (padding: number) => {
|
||||
console.log("Mock: Set traverse padding:", padding);
|
||||
setLocalState((prev) => {
|
||||
if (!prev) return prev;
|
||||
return {
|
||||
...prev,
|
||||
traverse_state: { ...prev.traverse_state, padding },
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const setPullerTargetSpeed = (targetSpeed: number) => {
|
||||
console.log("Mock: Set puller target speed:", targetSpeed);
|
||||
setLocalState((prev) => {
|
||||
if (!prev) return prev;
|
||||
return {
|
||||
...prev,
|
||||
puller_state: { ...prev.puller_state, target_speed: targetSpeed },
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const setPullerTargetDiameter = (targetDiameter: number) => {
|
||||
console.log("Mock: Set puller target diameter:", targetDiameter);
|
||||
setLocalState((prev) => {
|
||||
if (!prev) return prev;
|
||||
return {
|
||||
...prev,
|
||||
puller_state: { ...prev.puller_state, target_diameter: targetDiameter },
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const setPullerRegulationMode = (regulationMode: PullerRegulation) => {
|
||||
console.log("Mock: Set puller regulation mode:", regulationMode);
|
||||
setLocalState((prev) => {
|
||||
if (!prev) return prev;
|
||||
return {
|
||||
...prev,
|
||||
puller_state: { ...prev.puller_state, regulation: regulationMode },
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const setPullerForward = (forward: boolean) => {
|
||||
console.log("Mock: Set puller forward:", forward);
|
||||
setLocalState((prev) => {
|
||||
if (!prev) return prev;
|
||||
return {
|
||||
...prev,
|
||||
puller_state: { ...prev.puller_state, forward },
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const setPullerGearRatio = (gearRatio: GearRatio) => {
|
||||
console.log("Mock: Set puller gear ratio:", gearRatio);
|
||||
setLocalState((prev) => {
|
||||
if (!prev) return prev;
|
||||
return {
|
||||
...prev,
|
||||
puller_state: {
|
||||
...prev.puller_state,
|
||||
gear_ratio: gearRatio,
|
||||
target_speed: 0,
|
||||
},
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const setSpoolAutomaticRequiredMeters = (meters: number) => {
|
||||
console.log("Mock: Set spool automatic required meters:", meters);
|
||||
setLocalState((prev) => {
|
||||
if (!prev) return prev;
|
||||
return {
|
||||
...prev,
|
||||
spool_automatic_action_state: {
|
||||
...prev.spool_automatic_action_state,
|
||||
spool_required_meters: meters,
|
||||
},
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const setSpoolAutomaticAction = (mode: SpoolAutomaticActionMode) => {
|
||||
console.log("Mock: Set spool automatic action:", mode);
|
||||
setLocalState((prev) => {
|
||||
if (!prev) return prev;
|
||||
return {
|
||||
...prev,
|
||||
spool_automatic_action_state: {
|
||||
...prev.spool_automatic_action_state,
|
||||
spool_automatic_action_mode: mode,
|
||||
},
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const resetSpoolProgress = () => {
|
||||
console.log("Mock: Reset spool progress");
|
||||
};
|
||||
|
||||
const setSpoolRegulationMode = (mode: SpoolRegulationMode) => {
|
||||
console.log("Mock: Set spool regulation mode:", mode);
|
||||
setLocalState((prev) => {
|
||||
if (!prev) return prev;
|
||||
return {
|
||||
...prev,
|
||||
spool_speed_controller_state: {
|
||||
...prev.spool_speed_controller_state,
|
||||
regulation_mode: mode,
|
||||
},
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const setSpoolMinMaxMinSpeed = (speed: number) => {
|
||||
console.log("Mock: Set spool min max min speed:", speed);
|
||||
setLocalState((prev) => {
|
||||
if (!prev) return prev;
|
||||
return {
|
||||
...prev,
|
||||
spool_speed_controller_state: {
|
||||
...prev.spool_speed_controller_state,
|
||||
minmax_min_speed: speed,
|
||||
},
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const setSpoolMinMaxMaxSpeed = (speed: number) => {
|
||||
console.log("Mock: Set spool min max max speed:", speed);
|
||||
setLocalState((prev) => {
|
||||
if (!prev) return prev;
|
||||
return {
|
||||
...prev,
|
||||
spool_speed_controller_state: {
|
||||
...prev.spool_speed_controller_state,
|
||||
minmax_max_speed: speed,
|
||||
},
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const setSpoolForward = (forward: boolean) => {
|
||||
console.log("Mock: Set spool forward:", forward);
|
||||
setLocalState((prev) => {
|
||||
if (!prev) return prev;
|
||||
return {
|
||||
...prev,
|
||||
spool_speed_controller_state: {
|
||||
...prev.spool_speed_controller_state,
|
||||
forward,
|
||||
},
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const setSpoolAdaptiveTensionTarget = (value: number) => {
|
||||
console.log("Mock: Set spool adaptive tension target:", value);
|
||||
setLocalState((prev) => {
|
||||
if (!prev) return prev;
|
||||
return {
|
||||
...prev,
|
||||
spool_speed_controller_state: {
|
||||
...prev.spool_speed_controller_state,
|
||||
adaptive_tension_target: value,
|
||||
},
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const setSpoolAdaptiveRadiusLearningRate = (value: number) => {
|
||||
console.log("Mock: Set spool adaptive radius learning rate:", value);
|
||||
setLocalState((prev) => {
|
||||
if (!prev) return prev;
|
||||
return {
|
||||
...prev,
|
||||
spool_speed_controller_state: {
|
||||
...prev.spool_speed_controller_state,
|
||||
adaptive_radius_learning_rate: value,
|
||||
},
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const setSpoolAdaptiveMaxSpeedMultiplier = (value: number) => {
|
||||
console.log("Mock: Set spool adaptive max speed multiplier:", value);
|
||||
setLocalState((prev) => {
|
||||
if (!prev) return prev;
|
||||
return {
|
||||
...prev,
|
||||
spool_speed_controller_state: {
|
||||
...prev.spool_speed_controller_state,
|
||||
adaptive_max_speed_multiplier: value,
|
||||
},
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const setSpoolAdaptiveAccelerationFactor = (value: number) => {
|
||||
console.log("Mock: Set spool adaptive acceleration factor:", value);
|
||||
setLocalState((prev) => {
|
||||
if (!prev) return prev;
|
||||
return {
|
||||
...prev,
|
||||
spool_speed_controller_state: {
|
||||
...prev.spool_speed_controller_state,
|
||||
adaptive_acceleration_factor: value,
|
||||
},
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const setSpoolAdaptiveDeaccelerationUrgencyMultiplier = (value: number) => {
|
||||
console.log(
|
||||
"Mock: Set spool adaptive deacceleration urgency multiplier:",
|
||||
value,
|
||||
);
|
||||
setLocalState((prev) => {
|
||||
if (!prev) return prev;
|
||||
return {
|
||||
...prev,
|
||||
spool_speed_controller_state: {
|
||||
...prev.spool_speed_controller_state,
|
||||
adaptive_deacceleration_urgency_multiplier: value,
|
||||
},
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const setConnectedMachine = (machineIdentificationUnique: any) => {
|
||||
console.log("Mock: Set connected machine:", machineIdentificationUnique);
|
||||
setLocalState((prev) => {
|
||||
if (!prev) return prev;
|
||||
return {
|
||||
...prev,
|
||||
connected_machine_state: {
|
||||
...prev.connected_machine_state,
|
||||
machine_identification_unique: machineIdentificationUnique,
|
||||
},
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const disconnectMachine = (machineIdentificationUnique: any) => {
|
||||
console.log("Mock: Disconnect machine:", machineIdentificationUnique);
|
||||
setLocalState((prev) => {
|
||||
if (!prev) return prev;
|
||||
return {
|
||||
...prev,
|
||||
connected_machine_state: {
|
||||
...prev.connected_machine_state,
|
||||
machine_identification_unique: null,
|
||||
},
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
// Mock loading states
|
||||
const isLoading = false;
|
||||
const isDisabled = false;
|
||||
|
||||
// Mock filtered machines
|
||||
const filteredMachines: any[] = [];
|
||||
const selectedMachine = null;
|
||||
|
||||
return {
|
||||
// State (use local state if available, fallback to namespace state)
|
||||
state: localState || namespaceState,
|
||||
filteredMachines,
|
||||
selectedMachine,
|
||||
defaultState: namespaceDefaultState,
|
||||
|
||||
// Live values (TimeSeries)
|
||||
traversePosition,
|
||||
pullerSpeed,
|
||||
spoolRpm,
|
||||
tensionArmAngle,
|
||||
spoolProgress,
|
||||
|
||||
// Loading states
|
||||
isLoading,
|
||||
isDisabled,
|
||||
|
||||
// Action functions (all mocked)
|
||||
enableTraverseLaserpointer,
|
||||
setMode,
|
||||
zeroTensionArmAngle,
|
||||
setTraverseLimitInner,
|
||||
setTraverseLimitOuter,
|
||||
gotoTraverseLimitInner,
|
||||
gotoTraverseLimitOuter,
|
||||
gotoTraverseHome,
|
||||
resetSpoolProgress,
|
||||
setTraverseStepSize,
|
||||
setTraversePadding,
|
||||
setPullerTargetSpeed,
|
||||
setPullerTargetDiameter,
|
||||
setPullerRegulationMode,
|
||||
setPullerForward,
|
||||
setPullerGearRatio,
|
||||
setSpoolAutomaticRequiredMeters,
|
||||
setSpoolAutomaticAction,
|
||||
setSpoolRegulationMode,
|
||||
setSpoolMinMaxMinSpeed,
|
||||
setSpoolMinMaxMaxSpeed,
|
||||
setSpoolForward,
|
||||
setSpoolAdaptiveTensionTarget,
|
||||
setSpoolAdaptiveRadiusLearningRate,
|
||||
setSpoolAdaptiveMaxSpeedMultiplier,
|
||||
setSpoolAdaptiveAccelerationFactor,
|
||||
setSpoolAdaptiveDeaccelerationUrgencyMultiplier,
|
||||
setConnectedMachine,
|
||||
disconnectMachine,
|
||||
};
|
||||
}
|
||||
@@ -10,6 +10,12 @@ import { Winder2ControlPage } from "@/machines/winder/winder2/Winder2ControlPage
|
||||
import { Winder2ManualPage } from "@/machines/winder/winder2/Winder2Manual";
|
||||
import { Winder2SettingPage } from "@/machines/winder/winder2/Winder2Settings";
|
||||
import { Winder2GraphsPage } from "@/machines/winder/winder2/Winder2Graphs";
|
||||
import { GluetexPage } from "@/machines/winder/gluetex/GluetexPage";
|
||||
import { GluetexControlPage } from "@/machines/winder/gluetex/GluetexControlPage";
|
||||
import { GluetexManualPage } from "@/machines/winder/gluetex/GluetexManual";
|
||||
import { GluetexSettingPage } from "@/machines/winder/gluetex/GluetexSettings";
|
||||
import { GluetexGraphsPage } from "@/machines/winder/gluetex/GluetexGraphs";
|
||||
import { GluetexPresetsPage } from "@/machines/winder/gluetex/GluetexPresetsPage";
|
||||
import { Extruder2Page } from "@/machines/extruder/extruder2/Extruder2Page";
|
||||
import { Extruder2ControlPage } from "@/machines/extruder/extruder2/Extruder2ControlPage";
|
||||
import { Extruder2SettingsPage } from "@/machines/extruder/extruder2/Extruder2Settings";
|
||||
@@ -132,6 +138,42 @@ export const winder2PresetsRoute = createRoute({
|
||||
component: () => <Winder2PresetsPage />,
|
||||
});
|
||||
|
||||
export const gluetexRoute = createRoute({
|
||||
getParentRoute: () => machinesRoute,
|
||||
path: "gluetex",
|
||||
component: () => <GluetexPage />,
|
||||
});
|
||||
|
||||
export const gluetexControlRoute = createRoute({
|
||||
getParentRoute: () => gluetexRoute,
|
||||
path: "control",
|
||||
component: () => <GluetexControlPage />,
|
||||
});
|
||||
|
||||
export const gluetexManualRoute = createRoute({
|
||||
getParentRoute: () => gluetexRoute,
|
||||
path: "manual",
|
||||
component: () => <GluetexManualPage />,
|
||||
});
|
||||
|
||||
export const gluetexSettingsRoute = createRoute({
|
||||
getParentRoute: () => gluetexRoute,
|
||||
path: "settings",
|
||||
component: () => <GluetexSettingPage />,
|
||||
});
|
||||
|
||||
export const gluetexGraphsRoute = createRoute({
|
||||
getParentRoute: () => gluetexRoute,
|
||||
path: "graphs",
|
||||
component: () => <GluetexGraphsPage />,
|
||||
});
|
||||
|
||||
export const gluetexPresetsRoute = createRoute({
|
||||
getParentRoute: () => gluetexRoute,
|
||||
path: "presets",
|
||||
component: () => <GluetexPresetsPage />,
|
||||
});
|
||||
|
||||
export const laser1SerialRoute = createRoute({
|
||||
getParentRoute: () => machinesRoute,
|
||||
path: "laser1/$serial",
|
||||
@@ -327,6 +369,14 @@ export const rootTree = RootRoute.addChildren([
|
||||
winder2PresetsRoute,
|
||||
]),
|
||||
|
||||
gluetexRoute.addChildren([
|
||||
gluetexControlRoute,
|
||||
gluetexManualRoute,
|
||||
gluetexSettingsRoute,
|
||||
gluetexGraphsRoute,
|
||||
gluetexPresetsRoute,
|
||||
]),
|
||||
|
||||
extruder2Route.addChildren([
|
||||
extruder2ControlRoute,
|
||||
extruder2SettingsRoute,
|
||||
|
||||
@@ -15,6 +15,8 @@ import {
|
||||
} from "@/machines/properties";
|
||||
import { IconText } from "@/components/IconText";
|
||||
import { MachinesEventData, useMainNamespace } from "@/client/mainNamespace";
|
||||
import { TouchButton } from "@/components/touch/TouchButton";
|
||||
import { useNavigate } from "@tanstack/react-router";
|
||||
|
||||
export const columns: ColumnDef<
|
||||
NonNullable<MachinesEventData>["machines"][number]
|
||||
@@ -76,6 +78,7 @@ export const columns: ColumnDef<
|
||||
|
||||
export function MachinesPage() {
|
||||
const { machines } = useMainNamespace();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const data = useMemo(() => {
|
||||
return machines?.data?.machines || [];
|
||||
@@ -93,6 +96,20 @@ export function MachinesPage() {
|
||||
<RefreshIndicator ts={machines?.ts} />
|
||||
</SectionTitle>
|
||||
<MyTable table={table} key={data.toString()} />
|
||||
|
||||
<div className="mt-8 border-t border-gray-200 p-4">
|
||||
<h3 className="mb-2 text-lg font-semibold">Test Machines</h3>
|
||||
<p className="mb-4 text-sm text-gray-600">
|
||||
Hardcoded frontend machines for testing (no backend required)
|
||||
</p>
|
||||
<TouchButton
|
||||
onClick={() => navigate({ to: "/_sidebar/machines/gluetex/control" })}
|
||||
icon="lu:TestTube"
|
||||
variant="outline"
|
||||
>
|
||||
Open Gluetex Test Winder
|
||||
</TouchButton>
|
||||
</div>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user