Add default winder to gluetex device with dummy data

This commit is contained in:
Christian committed 2025-11-15 13:56:32 +01:00
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,
};
}
+50
View File
@@ -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,
+17
View File
@@ -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>
);
}