Compare commits
48
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
97ec252a8d | ||
|
|
c5d96922a3 | ||
|
|
468dc921be | ||
|
|
858a145d6f | ||
|
|
9b0100f894 | ||
|
|
52b7da434b | ||
|
|
51d57bc977 | ||
|
|
a13292e568 | ||
|
|
7b96398221 | ||
|
|
6b139a886c | ||
|
|
558f769c08 | ||
|
|
a43114319e | ||
|
|
f0a2b40bf9 | ||
|
|
87363375cf | ||
|
|
59eb1693cb | ||
|
|
0b5e2d25c9 | ||
|
|
1f900a5984 | ||
|
|
bace6ab78f | ||
|
|
87eaf2616e | ||
|
|
97fa5e6543 | ||
|
|
aec5018029 | ||
|
|
18063f3dcd | ||
|
|
51338ce622 | ||
|
|
3484363bfa | ||
|
|
aafe78d6fd | ||
|
|
8c0f3256ad | ||
|
|
3b15517fe0 | ||
|
|
202c9d12db | ||
|
|
74693e80c4 | ||
|
|
9ceeeb82b7 | ||
|
|
05a4909914 | ||
|
|
348576a21c | ||
|
|
d37760bcd1 | ||
|
|
e73f6508e2 | ||
|
|
08994de8bf | ||
|
|
17024717bf | ||
|
|
1019398b34 | ||
|
|
2de135f6bd | ||
|
|
9c55b07634 | ||
|
|
f9c80096a9 | ||
|
|
1907f284f4 | ||
|
|
51e3f1b597 | ||
|
|
22f3b226d9 | ||
|
|
2cd7626197 | ||
|
|
1d8ba63577 | ||
|
|
36f0844f82 | ||
|
|
f64a8e6646 | ||
|
|
4ae448b792 |
No files matched your search
@@ -92,7 +92,7 @@ pub struct MachineCrossConnection<
|
||||
> {
|
||||
machine_manager: Weak<RwLock<MachineManager>>,
|
||||
machine_identification_unique: MachineIdentificationUnique,
|
||||
connected_machine: Weak<Mutex<MachineSlot<T>>>,
|
||||
pub connected_machine: Weak<Mutex<MachineSlot<T>>>,
|
||||
_make_compiler_happy: std::marker::PhantomData<F>, // This marks tells the typechecker, that we
|
||||
// need F in our type arguemnts. We are ok with
|
||||
// it being used only in a fake field. Without
|
||||
|
||||
@@ -5,15 +5,37 @@ import React from "react";
|
||||
import { useBuffer1 } from "./useBuffer1";
|
||||
import { ControlCard } from "@/control/ControlCard";
|
||||
import { SelectionGroup } from "@/control/SelectionGroup";
|
||||
import { EditValue } from "@/control/EditValue";
|
||||
import { TimeSeriesValueNumeric } from "@/control/TimeSeriesValue";
|
||||
import { roundToDecimals } from "@/lib/decimal";
|
||||
import { Label } from "@/control/Label";
|
||||
import { TouchButton } from "@/components/touch/TouchButton";
|
||||
import { StatusBadge } from "@/control/StatusBadge";
|
||||
|
||||
export function Buffer1ControlPage() {
|
||||
const { state, setBufferMode } = useBuffer1();
|
||||
const {
|
||||
state,
|
||||
defaultState,
|
||||
setBufferMode,
|
||||
setCurrentInputSpeed,
|
||||
pullerSpeed,
|
||||
setPullerRegulationMode,
|
||||
setPullerTargetSpeed,
|
||||
liftPosition,
|
||||
gotoLiftHome,
|
||||
isLoading,
|
||||
isDisabled,
|
||||
} = useBuffer1();
|
||||
|
||||
const current_input_speed =
|
||||
state?.current_input_speed_state.current_input_speed ?? 0.0;
|
||||
const default_speed = 0.0;
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<ControlGrid>
|
||||
<ControlCard className="bg-red" title="Mode">
|
||||
<SelectionGroup<"Standby" | "FillingBuffer" | "EmptyingBuffer">
|
||||
<SelectionGroup<"Standby" | "Hold" | "Filling" | "Emptying">
|
||||
value={state?.mode_state.mode}
|
||||
orientation="vertical"
|
||||
className="grid h-full grid-cols-2 gap-2"
|
||||
@@ -24,13 +46,19 @@ export function Buffer1ControlPage() {
|
||||
isActiveClassName: "bg-green-600",
|
||||
className: "h-full",
|
||||
},
|
||||
FillingBuffer: {
|
||||
children: "FillingBuffer",
|
||||
Hold: {
|
||||
children: "Hold",
|
||||
icon: "lu:Pause",
|
||||
isActiveClassName: "bg-green-600",
|
||||
className: "h-full",
|
||||
},
|
||||
Filling: {
|
||||
children: "Filling",
|
||||
icon: "lu:Flame",
|
||||
isActiveClassName: "bg-green-600",
|
||||
className: "h-full",
|
||||
},
|
||||
EmptyingBuffer: {
|
||||
Emptying: {
|
||||
children: "EmptyingBuffer",
|
||||
icon: "lu:ArrowBigLeftDash",
|
||||
isActiveClassName: "bg-green-600",
|
||||
@@ -40,6 +68,83 @@ export function Buffer1ControlPage() {
|
||||
onChange={setBufferMode}
|
||||
/>
|
||||
</ControlCard>
|
||||
<ControlCard title="Set Current Input Speed - (For Debugging)">
|
||||
<EditValue
|
||||
title="Input Speed"
|
||||
unit="m/min"
|
||||
value={current_input_speed}
|
||||
defaultValue={default_speed}
|
||||
min={0.0}
|
||||
max={60.0}
|
||||
step={0.1}
|
||||
renderValue={(value) => roundToDecimals(value, 1)}
|
||||
onChange={setCurrentInputSpeed}
|
||||
/>
|
||||
</ControlCard>
|
||||
|
||||
<ControlCard className="bg-red" height={2} title="Lift">
|
||||
<TimeSeriesValueNumeric
|
||||
label="Position"
|
||||
unit="mm"
|
||||
timeseries={liftPosition}
|
||||
renderValue={(value) => roundToDecimals(value, 1)}
|
||||
/>
|
||||
<Label label="Home">
|
||||
<TouchButton
|
||||
variant="outline"
|
||||
icon="lu:House"
|
||||
onClick={() => gotoLiftHome()}
|
||||
disabled={isDisabled}
|
||||
isLoading={isLoading || state?.lift_state?.is_going_home}
|
||||
>
|
||||
Go to Home
|
||||
</TouchButton>
|
||||
{state?.lift_state?.is_homed !== true ? (
|
||||
<StatusBadge variant={"error"}>{"Not Homed"}</StatusBadge>
|
||||
) : null}
|
||||
</Label>
|
||||
</ControlCard>
|
||||
|
||||
<ControlCard className="bg-red" title="Puller">
|
||||
<TimeSeriesValueNumeric
|
||||
label="Speed"
|
||||
unit="m/min"
|
||||
timeseries={pullerSpeed}
|
||||
renderValue={(value) => roundToDecimals(value, 1)}
|
||||
/>
|
||||
<Label label="Regulation">
|
||||
<SelectionGroup
|
||||
value={state?.puller_state?.regulation}
|
||||
options={{
|
||||
Speed: {
|
||||
children: "Speed",
|
||||
icon: "lu:Gauge",
|
||||
},
|
||||
Diameter: {
|
||||
children: "Diameter",
|
||||
icon: "lu:Sun",
|
||||
disabled: true,
|
||||
},
|
||||
}}
|
||||
onChange={setPullerRegulationMode}
|
||||
disabled={isDisabled}
|
||||
loading={isLoading}
|
||||
/>
|
||||
</Label>
|
||||
<Label label="Target Speed">
|
||||
<EditValue
|
||||
value={state?.puller_state?.target_speed}
|
||||
unit="m/min"
|
||||
title="Target Speed"
|
||||
defaultValue={defaultState?.puller_state?.target_speed}
|
||||
min={0}
|
||||
max={75}
|
||||
step={0.1}
|
||||
renderValue={(value) => roundToDecimals(value, 1)}
|
||||
onChange={setPullerTargetSpeed}
|
||||
/>
|
||||
</Label>
|
||||
</ControlCard>
|
||||
</ControlGrid>
|
||||
</Page>
|
||||
);
|
||||
|
||||
@@ -16,23 +16,55 @@ import {
|
||||
ThrottledStoreUpdater,
|
||||
} from "../../../client/socketioStore";
|
||||
import { MachineIdentificationUnique } from "@/machines/types";
|
||||
import {
|
||||
createTimeSeries,
|
||||
TimeSeries,
|
||||
TimeSeriesValue,
|
||||
} from "@/lib/timeseries";
|
||||
|
||||
// ========== Event Schema Definitions ==========
|
||||
|
||||
/**
|
||||
* Machine operation mode enum
|
||||
*/
|
||||
export const modeSchema = z.enum([
|
||||
"Standby",
|
||||
"FillingBuffer",
|
||||
"EmptyingBuffer",
|
||||
]);
|
||||
export const modeSchema = z.enum(["Standby", "Hold", "Filling", "Emptying"]);
|
||||
export type Mode = z.infer<typeof modeSchema>;
|
||||
|
||||
/**
|
||||
* Consolidated live values event schema (30FPS data)
|
||||
*/
|
||||
export const liveValuesEventDataSchema = z.object({});
|
||||
export const liveValuesEventDataSchema = z.object({
|
||||
lift_position: z.number().nullable(),
|
||||
puller_speed: z.number(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Puller regulation type enum
|
||||
*/
|
||||
export const pullerRegulationSchema = z.enum(["Speed", "Diameter"]);
|
||||
export type PullerRegulation = z.infer<typeof pullerRegulationSchema>;
|
||||
|
||||
/**
|
||||
* Puller state event schmema
|
||||
*/
|
||||
export const pullerStateSchema = z.object({
|
||||
regulation: pullerRegulationSchema,
|
||||
target_speed: z.number(),
|
||||
target_diameter: z.number(),
|
||||
forward: z.boolean(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Lift state schema
|
||||
*/
|
||||
export const liftStateSchema = z.object({
|
||||
is_going_up: z.boolean(),
|
||||
is_going_down: z.boolean(),
|
||||
is_homed: z.boolean(),
|
||||
is_going_home: z.boolean(),
|
||||
is_buffering: z.boolean(),
|
||||
step_size: z.number(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Mode state event schema
|
||||
@@ -41,6 +73,10 @@ export const modeStateSchema = z.object({
|
||||
mode: modeSchema,
|
||||
});
|
||||
|
||||
export const currentInputSpeedSchema = z.object({
|
||||
current_input_speed: z.number(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Connected machine state scheme
|
||||
*/
|
||||
@@ -65,7 +101,10 @@ export const connectedMachineStateSchema = z.object({
|
||||
|
||||
export const stateEventDataSchema = z.object({
|
||||
mode_state: modeStateSchema,
|
||||
lift_state: liftStateSchema,
|
||||
puller_state: pullerStateSchema,
|
||||
connected_machine_state: connectedMachineStateSchema,
|
||||
current_input_speed_state: currentInputSpeedSchema,
|
||||
});
|
||||
|
||||
// ========== Event Schemas with Wrappers ==========
|
||||
@@ -80,8 +119,24 @@ export type StateEvent = z.infer<typeof stateEventSchema>;
|
||||
export type Buffer1NamespaceStore = {
|
||||
// State events (latest only)
|
||||
state: StateEvent | null;
|
||||
defaultState: StateEvent | null;
|
||||
|
||||
// Time series data for live values
|
||||
liftPosition: TimeSeries;
|
||||
pullerSpeed: TimeSeries;
|
||||
};
|
||||
|
||||
// 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;
|
||||
|
||||
const { initialTimeSeries: pullerSpeed, insert: addPullerSpeed } =
|
||||
createTimeSeries(TWENTY_MILLISECOND, ONE_SECOND, FIVE_SECOND, ONE_HOUR);
|
||||
const { initialTimeSeries: liftPosition, insert: addLiftPosition } =
|
||||
createTimeSeries(TWENTY_MILLISECOND, ONE_SECOND, FIVE_SECOND, ONE_HOUR);
|
||||
|
||||
/**
|
||||
* Creates a message handler for Buffer1 namespace events with validation and appropriate caching strategies
|
||||
* @param store The store to update when messages are received
|
||||
@@ -110,12 +165,41 @@ export function buffer1MessageHandler(
|
||||
...state,
|
||||
state: stateEvent,
|
||||
}));
|
||||
console.log(stateEvent);
|
||||
} else if (eventName === "LiveValuesEvent") {
|
||||
// Parse and validate the live values event
|
||||
const liveValuesEvent = liveValuesEventSchema.parse(event);
|
||||
const timestamp = event.ts;
|
||||
updateStore((state) => ({
|
||||
...state,
|
||||
}));
|
||||
|
||||
// Extract values and add to time series
|
||||
const { lift_position, puller_speed } = liveValuesEvent.data;
|
||||
|
||||
const timestamp = liveValuesEvent.ts;
|
||||
updateStore((state) => {
|
||||
const newState = { ...state };
|
||||
// Add lift position if not null
|
||||
if (lift_position !== null) {
|
||||
const timeseriesValue: TimeSeriesValue = {
|
||||
value: lift_position,
|
||||
timestamp,
|
||||
};
|
||||
newState.liftPosition = addLiftPosition(
|
||||
state.liftPosition,
|
||||
timeseriesValue,
|
||||
);
|
||||
}
|
||||
|
||||
// Add puller speed
|
||||
const pullerSpeedValue: TimeSeriesValue = {
|
||||
value: puller_speed,
|
||||
timestamp,
|
||||
};
|
||||
newState.pullerSpeed = addPullerSpeed(
|
||||
state.pullerSpeed,
|
||||
pullerSpeedValue,
|
||||
);
|
||||
|
||||
return newState;
|
||||
});
|
||||
} else {
|
||||
handleUnhandledEventError(eventName);
|
||||
}
|
||||
@@ -134,7 +218,13 @@ export const createBuffer1NamespaceStore =
|
||||
(): StoreApi<Buffer1NamespaceStore> =>
|
||||
create<Buffer1NamespaceStore>(() => {
|
||||
return {
|
||||
// State event from server
|
||||
state: null,
|
||||
defaultState: null,
|
||||
|
||||
// Time series data for live values
|
||||
liftPosition,
|
||||
pullerSpeed,
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -8,7 +8,13 @@ import {
|
||||
import { buffer1SerialRoute } from "@/routes/routes";
|
||||
import { z } from "zod";
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { StateEvent, Mode, useBuffer1Namespace } from "./buffer1Namespace";
|
||||
import {
|
||||
StateEvent,
|
||||
Mode,
|
||||
useBuffer1Namespace,
|
||||
pullerRegulationSchema,
|
||||
PullerRegulation,
|
||||
} from "./buffer1Namespace";
|
||||
import { useStateOptimistic } from "@/lib/useStateOptimistic";
|
||||
import { produce } from "immer";
|
||||
import { useMachines } from "@/client/useMachines";
|
||||
@@ -45,7 +51,8 @@ export function useBuffer1() {
|
||||
const machine_identification_unique = machineIdentification;
|
||||
|
||||
// Get consolidated state and live values from namespace
|
||||
const { state } = useBuffer1Namespace(machineIdentification);
|
||||
const { state, defaultState, liftPosition, pullerSpeed } =
|
||||
useBuffer1Namespace(machineIdentification);
|
||||
|
||||
// Single optimistic state for all state management
|
||||
const stateOptimistic = useStateOptimistic<StateEvent>();
|
||||
@@ -122,10 +129,102 @@ export function useBuffer1() {
|
||||
);
|
||||
};
|
||||
|
||||
const setCurrentInputSpeed = (current_input_speed: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.current_input_speed_state.current_input_speed =
|
||||
current_input_speed;
|
||||
},
|
||||
() =>
|
||||
requestSetCurrentInputSpeed({
|
||||
machine_identification_unique,
|
||||
data: { SetCurrentInputSpeed: current_input_speed },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setPullerTargetSpeed = (targetSpeed: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.puller_state.target_speed = targetSpeed;
|
||||
},
|
||||
() =>
|
||||
requestPullerSetTargetSpeed({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetPullerTargetSpeed: targetSpeed },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setPullerTargetDiameter = (targetDiameter: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.puller_state.target_diameter = targetDiameter;
|
||||
},
|
||||
() =>
|
||||
requestPullerSetTargetDiameter({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetPullerTargetDiameter: targetDiameter },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setPullerRegulationMode = (regulationMode: PullerRegulation) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.puller_state.regulation = regulationMode;
|
||||
},
|
||||
() =>
|
||||
requestPullerSetRegulationMode({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetPullerRegulationMode: regulationMode },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setPullerForward = (forward: boolean) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.puller_state.forward = forward;
|
||||
},
|
||||
() =>
|
||||
requestPullerSetForward({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetPullerForward: forward },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const gotoLiftHome = () => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.lift_state.is_going_home = true;
|
||||
},
|
||||
() =>
|
||||
requestLiftGotoHome({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: "GotoLiftHome",
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setLiftStepSize = (stepSize: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.lift_state.step_size = stepSize;
|
||||
},
|
||||
() =>
|
||||
requestLiftSetStepSize({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetLiftStepSize: stepSize },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
// Mutation hooks
|
||||
const { request: requestBufferMode } = useMachineMutation(
|
||||
z.object({
|
||||
SetBufferMode: z.enum(["Standby", "FillingBuffer", "EmptyingBuffer"]),
|
||||
SetBufferMode: z.enum(["Standby", "Hold", "Filling", "Emptying"]),
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -141,6 +240,38 @@ export function useBuffer1() {
|
||||
}),
|
||||
);
|
||||
|
||||
const { request: requestSetCurrentInputSpeed } = useMachineMutation(
|
||||
z.object({
|
||||
SetCurrentInputSpeed: z.number(),
|
||||
}),
|
||||
);
|
||||
|
||||
const { request: requestPullerSetTargetSpeed } = useMachineMutation(
|
||||
z.object({ SetPullerTargetSpeed: z.number() }),
|
||||
);
|
||||
const { request: requestPullerSetTargetDiameter } = useMachineMutation(
|
||||
z.object({ SetPullerTargetDiameter: z.number() }),
|
||||
);
|
||||
const { request: requestPullerSetRegulationMode } = useMachineMutation(
|
||||
z.object({
|
||||
SetPullerRegulationMode: pullerRegulationSchema,
|
||||
}),
|
||||
);
|
||||
const { request: requestPullerSetForward } = useMachineMutation(
|
||||
z.object({ SetPullerForward: z.boolean() }),
|
||||
);
|
||||
|
||||
const { request: requestLiftGotoHome } = useMachineMutation(
|
||||
z.literal("GotoLiftHome"),
|
||||
);
|
||||
const { request: requestLiftSetStepSize } = useMachineMutation(
|
||||
z.object({ SetLiftStepSize: z.number() }),
|
||||
);
|
||||
|
||||
// Calculate loading states
|
||||
const isLoading = stateOptimistic.isOptimistic;
|
||||
const isDisabled = !stateOptimistic.isInitialized;
|
||||
|
||||
// General Helper functions
|
||||
const machines = useMachines();
|
||||
// Filter machines for the correct type
|
||||
@@ -176,11 +307,27 @@ export function useBuffer1() {
|
||||
filteredMachines,
|
||||
selectedMachine,
|
||||
|
||||
// Default state for initial values
|
||||
defaultState: defaultState?.data,
|
||||
|
||||
// Individual live values (TimeSeries)
|
||||
liftPosition,
|
||||
pullerSpeed,
|
||||
|
||||
// Loading states
|
||||
isLoading,
|
||||
isDisabled,
|
||||
|
||||
// Action functions (verb-first)
|
||||
setBufferMode,
|
||||
gotoLiftHome,
|
||||
setLiftStepSize,
|
||||
setConnectedMachine,
|
||||
disconnectMachine,
|
||||
setCurrentInputSpeed,
|
||||
setPullerTargetSpeed,
|
||||
setPullerTargetDiameter,
|
||||
setPullerRegulationMode,
|
||||
setPullerForward,
|
||||
};
|
||||
}
|
||||
@@ -192,12 +192,6 @@ export const buffer1ControlRoute = createRoute({
|
||||
component: () => <Buffer1ControlPage />,
|
||||
});
|
||||
|
||||
export const mock1PresetsRoute = createRoute({
|
||||
getParentRoute: () => mock1SerialRoute,
|
||||
path: "presets",
|
||||
component: () => <Mock1PresetsPage />,
|
||||
});
|
||||
|
||||
export const aquapath1SerialRoute = createRoute({
|
||||
getParentRoute: () => machinesRoute,
|
||||
path: "aquapath1/$serial",
|
||||
@@ -222,6 +216,12 @@ export const buffer1SettingsRoute = createRoute({
|
||||
component: () => <Buffer1SettingsPage />,
|
||||
});
|
||||
|
||||
export const mock1PresetsRoute = createRoute({
|
||||
getParentRoute: () => mock1SerialRoute,
|
||||
path: "presets",
|
||||
component: () => <Mock1PresetsPage />,
|
||||
});
|
||||
|
||||
export const setupRoute = createRoute({
|
||||
getParentRoute: () => sidebarRoute,
|
||||
path: "setup",
|
||||
@@ -342,7 +342,10 @@ export const rootTree = RootRoute.addChildren([
|
||||
mock1PresetsRoute,
|
||||
]),
|
||||
|
||||
buffer1SerialRoute.addChildren([buffer1ControlRoute]),
|
||||
buffer1SerialRoute.addChildren([
|
||||
buffer1ControlRoute,
|
||||
buffer1SettingsRoute,
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
]);
|
||||
@@ -2,12 +2,22 @@ use std::time::{Duration, Instant};
|
||||
|
||||
use super::BufferV1;
|
||||
use control_core::machines::new::MachineAct;
|
||||
|
||||
impl MachineAct for BufferV1 {
|
||||
fn act(&mut self, now: Instant) {
|
||||
// sync the lift speed
|
||||
self.sync_lift_speed(now);
|
||||
|
||||
// sync the puller speed
|
||||
self.sync_puller_speed(now);
|
||||
|
||||
// check if lift state changed
|
||||
if self.buffer_lift_controller.did_change_state() {
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
// if last measurement is older than 1 second, emit a new measurement
|
||||
if now.duration_since(self.last_measurement_emit) > Duration::from_secs_f64(1.0 / 30.0) {
|
||||
// Emit live values at 30 FPS
|
||||
if now.duration_since(self.last_measurement_emit) > Duration::from_secs_f64(1.0 / 60.0) {
|
||||
// Emit live values at 60 FPS
|
||||
self.emit_live_values();
|
||||
|
||||
self.last_measurement_emit = now;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use std::{sync::Arc, time::Duration};
|
||||
|
||||
use crate::machines::buffer1::puller_speed_controller::PullerRegulationMode;
|
||||
|
||||
use super::{BufferV1, BufferV1Mode};
|
||||
use control_core::{
|
||||
machines::{
|
||||
@@ -20,7 +22,12 @@ use smol::lock::Mutex;
|
||||
use tracing::instrument;
|
||||
|
||||
#[derive(Serialize, Debug, Clone, Default)]
|
||||
pub struct LiveValuesEvent {}
|
||||
pub struct LiveValuesEvent {
|
||||
/// lift position in mm
|
||||
pub lift_position: Option<f64>,
|
||||
/// puller speed in m/min
|
||||
pub puller_speed: f64,
|
||||
}
|
||||
|
||||
impl LiveValuesEvent {
|
||||
pub fn build(&self) -> Event<Self> {
|
||||
@@ -32,8 +39,14 @@ impl LiveValuesEvent {
|
||||
pub struct StateEvent {
|
||||
/// mode state
|
||||
pub mode_state: ModeState,
|
||||
/// lift state
|
||||
pub lift_state: LiftState,
|
||||
/// puller state
|
||||
pub puller_state: PullerState,
|
||||
/// connected machine state
|
||||
pub connected_machine_state: MachineCrossConnectionState,
|
||||
/// CurrentInputSpeed
|
||||
pub current_input_speed_state: CurrentInputSpeedState,
|
||||
}
|
||||
|
||||
impl StateEvent {
|
||||
@@ -55,8 +68,9 @@ pub struct ModeState {
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Mode {
|
||||
Standby,
|
||||
FillingBuffer,
|
||||
EmptyingBuffer,
|
||||
Hold,
|
||||
Filling,
|
||||
Emptying,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
@@ -66,6 +80,54 @@ pub struct ConnectedMachineState {
|
||||
pub is_available: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct PullerState {
|
||||
/// regulation type
|
||||
pub regulation: PullerRegulationMode,
|
||||
/// target speed in m/min
|
||||
pub target_speed: f64,
|
||||
/// target diameter in mm
|
||||
pub target_diameter: f64,
|
||||
/// forward rotation direction
|
||||
pub forward: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct LiftState {
|
||||
/// min position in mm
|
||||
pub limit_top: f64,
|
||||
/// max position in mm
|
||||
pub limit_bottom: f64,
|
||||
/// position in mm
|
||||
pub position_in: f64,
|
||||
/// position out in mm
|
||||
pub position_out: f64,
|
||||
/// is going to position in
|
||||
pub is_going_up: bool,
|
||||
/// is going to position out
|
||||
pub is_going_down: bool,
|
||||
/// if is homed
|
||||
pub is_homed: bool,
|
||||
/// if is homing
|
||||
pub is_going_home: bool,
|
||||
/// if is buffering
|
||||
pub is_buffering: bool,
|
||||
/// step size in mm
|
||||
pub step_size: f64,
|
||||
/// padding in mm
|
||||
pub padding: f64,
|
||||
/// can go top (to top limit)
|
||||
pub can_go_top: bool,
|
||||
/// can go bottom (to bottom limit)
|
||||
pub can_go_bottom: bool,
|
||||
/// can home
|
||||
pub can_go_home: bool,
|
||||
}
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct CurrentInputSpeedState {
|
||||
pub current_input_speed: f64,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
enum Mutation {
|
||||
// Mode
|
||||
@@ -76,6 +138,21 @@ enum Mutation {
|
||||
|
||||
// Disconnect Machine
|
||||
DisconnectMachine(MachineIdentificationUnique),
|
||||
|
||||
// Set current input speed
|
||||
SetCurrentInputSpeed(f64),
|
||||
|
||||
// Lift
|
||||
/// Step size in mm for traverse movement
|
||||
SetLiftStepSize(f64),
|
||||
GotoLiftHome,
|
||||
|
||||
// Puller
|
||||
/// on = speed, off = stop
|
||||
SetPullerRegulationMode(PullerRegulationMode),
|
||||
SetPullerTargetSpeed(f64),
|
||||
SetPullerTargetDiameter(f64),
|
||||
SetPullerForward(bool),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -124,6 +201,13 @@ impl MachineApi for BufferV1 {
|
||||
Mutation::DisconnectMachine(machine_identification_unique) => {
|
||||
self.disconnect_winder(machine_identification_unique);
|
||||
}
|
||||
Mutation::SetCurrentInputSpeed(speed) => self.set_current_input_speed(speed),
|
||||
Mutation::SetPullerRegulationMode(regulation) => self.puller_set_regulation(regulation),
|
||||
Mutation::SetPullerTargetSpeed(value) => self.puller_set_target_speed(value),
|
||||
Mutation::SetPullerTargetDiameter(_) => todo!(),
|
||||
Mutation::SetPullerForward(value) => self.puller_set_forward(value),
|
||||
Mutation::SetLiftStepSize(step_size) => self.lift_set_step_size(step_size),
|
||||
Mutation::GotoLiftHome => self.lift_goto_home(),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -0,0 +1,543 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use control_core::{
|
||||
controllers::first_degree_motion::linear_acceleration_speed_controller::LinearAccelerationLimitingController,
|
||||
converters::linear_step_converter::LinearStepConverter,
|
||||
uom_extensions::velocity::meter_per_minute,
|
||||
};
|
||||
use ethercat_hal::io::{
|
||||
digital_input::DigitalInput, stepper_velocity_el70x1::StepperVelocityEL70x1,
|
||||
};
|
||||
use tracing::info;
|
||||
use uom::{
|
||||
ConstZero,
|
||||
si::{
|
||||
acceleration::centimeter_per_second_squared,
|
||||
f64::{Acceleration, Length, Velocity},
|
||||
length::millimeter,
|
||||
velocity::millimeter_per_second,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BufferLiftController {
|
||||
/// Whether the speed controller is enabled or not
|
||||
enabled: bool,
|
||||
position: Length,
|
||||
limit_top: Length,
|
||||
limit_bottom: Length,
|
||||
step_size: Length,
|
||||
padding: Length,
|
||||
/// Stepper driver. Controls buffer stepper motor
|
||||
pub stepper_driver: StepperVelocityEL70x1,
|
||||
// Step Converter
|
||||
pub fullstep_converter: LinearStepConverter,
|
||||
pub microstep_converter: LinearStepConverter,
|
||||
|
||||
/// Linear acceleration controller to dampen speed change
|
||||
acceleration_controller: LinearAccelerationLimitingController,
|
||||
|
||||
/// Forward rotation direction. If false, applies negative sign to speed
|
||||
forward: bool,
|
||||
state: State,
|
||||
|
||||
/// Fixed constants
|
||||
spool_amount: u8,
|
||||
|
||||
/// Variables
|
||||
current_input_speed: Velocity,
|
||||
target_output_speed: Velocity,
|
||||
lift_speed: Velocity,
|
||||
|
||||
// A sticky flag if the [`State`] changed (not the sub states)
|
||||
// Needed to send state updates to the UI
|
||||
did_change_state: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum State {
|
||||
/// Initial state
|
||||
NotHomed,
|
||||
|
||||
/// Doing nothing
|
||||
/// Already homed
|
||||
Idle,
|
||||
|
||||
/// Going to upper limit
|
||||
///
|
||||
/// After reaching the upper limit, the state will change to [`State::Idle`]
|
||||
GoingUp,
|
||||
|
||||
/// Going to lower limit
|
||||
///
|
||||
/// After reaching the lower limit, the state will change to [`State::Idle`]
|
||||
GoingDown,
|
||||
|
||||
/// Homing in progress
|
||||
///
|
||||
/// After homing is done, the state will change to [`State::Idle`]
|
||||
Homing(HomingState),
|
||||
|
||||
/// Move at target speed to Buffer
|
||||
Buffering(BufferingState),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum BufferingState {
|
||||
GoingUp,
|
||||
Filling,
|
||||
Emptying,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum HomingState {
|
||||
/// In this state the lift is not moving but checks if the endstop si triggered
|
||||
/// If the endstop is triggered we go into [`HomingState::EscapeEndstop`]
|
||||
/// If the endstop is not triggered we go into [`HomingState::FindEndstop`]
|
||||
Initialize,
|
||||
|
||||
/// In this state the lift is moving out away from the endstop until it's not triggered anymore
|
||||
/// The it goes into [`HomingState::FindEnstopFineDistancing`]
|
||||
EscapeEndstop,
|
||||
|
||||
/// Moving out away from the endstop
|
||||
/// Then Transition into [`HomingState::FindEndtopFine`]
|
||||
FindEnstopFineDistancing,
|
||||
|
||||
/// In this state the lift is fast until it reaches the endstop
|
||||
FindEndstopCoarse,
|
||||
|
||||
/// In this state the lift is moving slowly until it reaches the endstop
|
||||
FindEndStopFine,
|
||||
|
||||
/// In this state we check if th current position is actually 0.0, if not we redo the homing routine
|
||||
Validate(Instant),
|
||||
}
|
||||
|
||||
impl BufferLiftController {
|
||||
pub fn new(
|
||||
driver: StepperVelocityEL70x1,
|
||||
limit_top: Length,
|
||||
limit_bot: Length,
|
||||
microsteps: u8,
|
||||
) -> Self {
|
||||
Self {
|
||||
enabled: false,
|
||||
position: Length::ZERO,
|
||||
limit_top,
|
||||
limit_bottom: limit_bot,
|
||||
step_size: Length::new::<millimeter>(1.75), // Default padding
|
||||
padding: Length::new::<millimeter>(0.9), // Default padding
|
||||
did_change_state: false,
|
||||
stepper_driver: driver,
|
||||
fullstep_converter: LinearStepConverter::from_diameter(
|
||||
200,
|
||||
Length::new::<millimeter>(32.22),
|
||||
),
|
||||
microstep_converter: LinearStepConverter::from_diameter(
|
||||
200 * microsteps as i16,
|
||||
Length::new::<millimeter>(32.22),
|
||||
),
|
||||
spool_amount: 13,
|
||||
state: State::NotHomed,
|
||||
forward: true,
|
||||
acceleration_controller: LinearAccelerationLimitingController::new_simple(
|
||||
Acceleration::new::<centimeter_per_second_squared>(1.0),
|
||||
Velocity::new::<millimeter_per_second>(5.0),
|
||||
),
|
||||
current_input_speed: Velocity::ZERO,
|
||||
target_output_speed: Velocity::ZERO,
|
||||
lift_speed: Velocity::ZERO,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BufferLiftController {
|
||||
/// Calculate the speed of the buffer lift from current input speed
|
||||
///
|
||||
/// Formula: input_speed / ( 2 * spool_amount - 1 )
|
||||
pub fn calculate_buffer_lift_speed(&self) -> Velocity {
|
||||
let lift_speed = Velocity::new::<millimeter_per_second>(
|
||||
(self.current_input_speed.get::<millimeter_per_second>()
|
||||
- self.target_output_speed.get::<millimeter_per_second>())
|
||||
/ (2.0 * self.spool_amount as f64 - 1.0),
|
||||
);
|
||||
lift_speed
|
||||
}
|
||||
|
||||
pub fn update_speed(
|
||||
&mut self,
|
||||
stepper_driver: &mut StepperVelocityEL70x1,
|
||||
lift_end_stop: &DigitalInput,
|
||||
t: Instant,
|
||||
) -> Velocity {
|
||||
let speed = match self.enabled {
|
||||
true => self.get_speed(stepper_driver, lift_end_stop),
|
||||
false => Velocity::ZERO,
|
||||
};
|
||||
|
||||
// Stepper is installed in reverse direction so we change to sign
|
||||
let speed = speed;
|
||||
|
||||
self.acceleration_controller.update(speed, t)
|
||||
}
|
||||
}
|
||||
|
||||
/// Getter & Setter
|
||||
impl BufferLiftController {
|
||||
pub fn set_enabled(&mut self, enabled: bool) {
|
||||
self.enabled = enabled;
|
||||
self.stepper_driver.set_enabled(enabled);
|
||||
}
|
||||
|
||||
pub fn set_limit_top(&mut self, limit: Length) {
|
||||
self.limit_top = limit;
|
||||
}
|
||||
|
||||
pub fn set_limit_bottom(&mut self, limit: Length) {
|
||||
self.limit_bottom = limit;
|
||||
}
|
||||
|
||||
pub fn set_step_size(&mut self, step_size: Length) {
|
||||
self.step_size = step_size;
|
||||
}
|
||||
|
||||
pub fn set_padding(&mut self, padding: Length) {
|
||||
self.padding = padding;
|
||||
}
|
||||
|
||||
pub fn get_limit_top(&self) -> Length {
|
||||
self.limit_top
|
||||
}
|
||||
|
||||
pub fn get_limit_bottom(&self) -> Length {
|
||||
self.limit_bottom
|
||||
}
|
||||
|
||||
pub fn get_step_size(&self) -> Length {
|
||||
self.step_size
|
||||
}
|
||||
|
||||
pub fn get_padding(&self) -> Length {
|
||||
self.padding
|
||||
}
|
||||
|
||||
pub fn set_forward(&mut self, forward: bool) {
|
||||
self.forward = forward;
|
||||
}
|
||||
|
||||
pub fn get_current_position(&self) -> Option<Length> {
|
||||
match self.is_homed() {
|
||||
true => Some(self.position),
|
||||
false => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn did_change_state(&mut self) -> bool {
|
||||
let did_change = self.did_change_state;
|
||||
// Reset the flag
|
||||
self.did_change_state = false;
|
||||
did_change
|
||||
}
|
||||
|
||||
pub fn set_current_input_speed(&mut self, speed: f64) {
|
||||
self.current_input_speed = Velocity::new::<meter_per_minute>(speed);
|
||||
}
|
||||
pub fn set_target_output_speed(&mut self, speed: f64) {
|
||||
self.target_output_speed = Velocity::new::<meter_per_minute>(speed);
|
||||
}
|
||||
pub fn get_current_input_speed(&self) -> Velocity {
|
||||
self.current_input_speed
|
||||
}
|
||||
pub fn get_target_output_speed(&self) -> Velocity {
|
||||
self.target_output_speed
|
||||
}
|
||||
pub fn get_lift_speed(&self) -> Velocity {
|
||||
self.lift_speed
|
||||
}
|
||||
}
|
||||
|
||||
/// State management
|
||||
impl BufferLiftController {
|
||||
pub fn goto_limit_top(&mut self) {
|
||||
self.state = State::GoingUp
|
||||
}
|
||||
|
||||
pub fn goto_limit_bottom(&mut self) {
|
||||
self.state = State::GoingDown
|
||||
}
|
||||
|
||||
pub fn goto_home(&mut self) {
|
||||
self.state = State::Homing(HomingState::Initialize);
|
||||
}
|
||||
|
||||
pub fn start_buffering(&mut self) {
|
||||
self.state = State::Buffering(BufferingState::GoingUp);
|
||||
}
|
||||
|
||||
pub fn is_going_up(&self) -> bool {
|
||||
// [`State::GoingUp`]
|
||||
matches!(self.state, State::GoingUp)
|
||||
}
|
||||
|
||||
pub fn is_going_down(&self) -> bool {
|
||||
// [`State::GoingDown`]
|
||||
matches!(self.state, State::GoingDown)
|
||||
}
|
||||
|
||||
pub fn is_homed(&self) -> bool {
|
||||
// if not [`State::NotHomed`], then it is homed
|
||||
!matches!(self.state, State::NotHomed)
|
||||
}
|
||||
|
||||
pub fn is_filling(&self) -> bool {
|
||||
// [`State::Filling`]
|
||||
matches!(self.state, State::Buffering(BufferingState::Filling))
|
||||
}
|
||||
|
||||
pub fn is_emptying(&self) -> bool {
|
||||
// [`State::Emptying`]
|
||||
matches!(self.state, State::Buffering(BufferingState::Emptying))
|
||||
}
|
||||
|
||||
pub fn is_going_home(&self) -> bool {
|
||||
// [`State::Homing`]
|
||||
matches!(self.state, State::Homing(_))
|
||||
}
|
||||
|
||||
pub fn is_buffering(&self) -> bool {
|
||||
// [`State::Buffering`]
|
||||
matches!(self.state, State::Buffering(_))
|
||||
}
|
||||
}
|
||||
|
||||
impl BufferLiftController {
|
||||
// If at inner limit within a tolerance
|
||||
fn is_at_position(&self, target_position: Length, tolerance: Length) -> bool {
|
||||
let upper_tolerance = target_position + tolerance.abs();
|
||||
let lower_tolerance = target_position - tolerance.abs();
|
||||
if self.position >= lower_tolerance && self.position <= upper_tolerance {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates distance to position
|
||||
fn distance_to_position(&self, target_position: Length) -> Length {
|
||||
if self.position > target_position {
|
||||
return self.position - target_position;
|
||||
} else if self.position < target_position {
|
||||
return target_position - self.position;
|
||||
} else {
|
||||
return Length::ZERO;
|
||||
}
|
||||
}
|
||||
|
||||
// Changes the direction of the speed based on the current position and target position
|
||||
fn speed_to_position(&self, target_position: Length, absolute_speed: Velocity) -> Velocity {
|
||||
// If we are over the target position we need to move negative
|
||||
if self.position > target_position {
|
||||
return -absolute_speed.abs();
|
||||
} else if self.position < target_position {
|
||||
return absolute_speed.abs();
|
||||
} else {
|
||||
return Velocity::ZERO;
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the current lift position as a [`Length`].
|
||||
pub fn sync_position(&mut self, stepper_driver: &StepperVelocityEL70x1) {
|
||||
let steps = stepper_driver.get_position();
|
||||
self.position = self.microstep_converter.steps_to_distance(steps as f64);
|
||||
}
|
||||
|
||||
/// Update the [`did_change_state`] flag
|
||||
/// Only consider the major state not the sub states
|
||||
fn update_did_change_state(&mut self, old_state: &State) -> bool {
|
||||
match self.state {
|
||||
State::NotHomed => !matches!(old_state, State::NotHomed),
|
||||
State::Idle => !matches!(old_state, State::Idle),
|
||||
State::GoingUp => !matches!(old_state, State::GoingUp),
|
||||
State::GoingDown => !matches!(old_state, State::GoingDown),
|
||||
State::Homing(_) => !matches!(old_state, State::Homing(_)),
|
||||
State::Buffering(_) => !matches!(old_state, State::Buffering(_)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates a desired speed based on the current state and the end stop status.
|
||||
///
|
||||
/// Positive speed moves Up, negative speed moves down
|
||||
fn get_speed(
|
||||
&mut self,
|
||||
stepper_driver: &mut StepperVelocityEL70x1,
|
||||
lift_end_stop: &DigitalInput,
|
||||
) -> Velocity {
|
||||
// Don't move if not enabled or in a state that doesn't result in movement
|
||||
if !self.enabled {
|
||||
return Velocity::ZERO;
|
||||
}
|
||||
|
||||
self.sync_position(stepper_driver);
|
||||
|
||||
// save state before
|
||||
let old_state = self.state.clone();
|
||||
|
||||
// Automatic Transitions
|
||||
match &self.state {
|
||||
State::NotHomed => {}
|
||||
State::Idle => {}
|
||||
State::GoingDown => {
|
||||
// If lower limit is reached
|
||||
if self.is_at_position(self.limit_bottom, Length::new::<millimeter>(0.01)) {
|
||||
// Put Into Idle
|
||||
self.state = State::Idle;
|
||||
}
|
||||
}
|
||||
State::GoingUp => {
|
||||
// If upper limit is reached
|
||||
if self.is_at_position(self.limit_top, Length::new::<millimeter>(0.01)) {
|
||||
// Put Into Idle
|
||||
self.state = State::Idle;
|
||||
}
|
||||
}
|
||||
State::Homing(homing_state) => match homing_state {
|
||||
HomingState::Initialize => {
|
||||
// If endstop is triggered, escape the endstop
|
||||
if lift_end_stop.get_value().unwrap_or(false) == true {
|
||||
self.state = State::Homing(HomingState::EscapeEndstop);
|
||||
} else {
|
||||
// If endstop is not triggered, move to the endstop
|
||||
self.state = State::Homing(HomingState::FindEndstopCoarse);
|
||||
}
|
||||
}
|
||||
HomingState::EscapeEndstop => {
|
||||
// Move out until endstop is not triggered anymore
|
||||
if lift_end_stop.get_value().unwrap_or(false) == false {
|
||||
self.state = State::Homing(HomingState::FindEnstopFineDistancing);
|
||||
}
|
||||
}
|
||||
HomingState::FindEnstopFineDistancing => {
|
||||
// Move out until endstop is not triggered anymore
|
||||
if lift_end_stop.get_value().unwrap_or(false) == false {
|
||||
// Find endstop fine
|
||||
self.state = State::Homing(HomingState::FindEndStopFine);
|
||||
}
|
||||
}
|
||||
HomingState::FindEndStopFine => {
|
||||
// If endstop is reached change to idle
|
||||
if lift_end_stop.get_value().unwrap_or(false) == true {
|
||||
// Set poition of lift to 0
|
||||
stepper_driver.set_position(0);
|
||||
// Put Into Idle
|
||||
self.state = State::Homing(HomingState::Validate(Instant::now()));
|
||||
}
|
||||
}
|
||||
HomingState::FindEndstopCoarse => {
|
||||
// Move to endstop
|
||||
if lift_end_stop.get_value().unwrap_or(false) == true {
|
||||
// Move awaiy from endstop
|
||||
self.state = State::Homing(HomingState::FindEnstopFineDistancing);
|
||||
}
|
||||
}
|
||||
HomingState::Validate(instant) => {
|
||||
// If 100ms have passed check if position is actually 0.0
|
||||
if instant.elapsed().as_millis() > 100 {
|
||||
if self.is_at_position(Length::ZERO, Length::new::<millimeter>(0.01)) {
|
||||
// If position is 0.0, put into idle
|
||||
self.state = State::Idle;
|
||||
} else {
|
||||
// If position is not 0.0, redo homing
|
||||
self.state = State::Homing(HomingState::Initialize);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// If state changed we
|
||||
State::Buffering(buffering_state) => match buffering_state {
|
||||
BufferingState::GoingUp => {}
|
||||
BufferingState::Emptying => {}
|
||||
BufferingState::Filling => {}
|
||||
},
|
||||
}
|
||||
|
||||
// Set the [`did_change_state`] flag
|
||||
if self.did_change_state == false {
|
||||
self.did_change_state = self.update_did_change_state(&old_state);
|
||||
info!("{:?}", self.state);
|
||||
}
|
||||
|
||||
// Speed
|
||||
let speed = match &self.state {
|
||||
State::NotHomed => Velocity::ZERO, // Not homed, no movement
|
||||
State::Idle => Velocity::ZERO, // No movement in idle state
|
||||
State::GoingDown => {
|
||||
// Move down at a speed of 10-100 mm/s
|
||||
self.speed_to_position(
|
||||
self.limit_bottom,
|
||||
match self.distance_to_position(self.limit_bottom).abs()
|
||||
> Length::new::<millimeter>(1.0)
|
||||
{
|
||||
true => Velocity::new::<millimeter_per_second>(100.0),
|
||||
false => Velocity::new::<millimeter_per_second>(10.0),
|
||||
},
|
||||
)
|
||||
}
|
||||
State::GoingUp => {
|
||||
// Move up at a speed of 10-100 mm/s
|
||||
self.speed_to_position(
|
||||
self.limit_top,
|
||||
match self.distance_to_position(self.limit_top).abs()
|
||||
> Length::new::<millimeter>(1.0)
|
||||
{
|
||||
true => Velocity::new::<millimeter_per_second>(100.0),
|
||||
false => Velocity::new::<millimeter_per_second>(10.0),
|
||||
},
|
||||
)
|
||||
}
|
||||
State::Homing(homing_state) => match homing_state {
|
||||
HomingState::Initialize => Velocity::ZERO,
|
||||
HomingState::EscapeEndstop => {
|
||||
// Move down at a speed of 10 mm/s
|
||||
Velocity::new::<millimeter_per_second>(10.0)
|
||||
}
|
||||
HomingState::FindEnstopFineDistancing => {
|
||||
// Move up at a speed of 2 mm/s
|
||||
Velocity::new::<millimeter_per_second>(2.0)
|
||||
}
|
||||
HomingState::FindEndstopCoarse => {
|
||||
// Move in at a speed of -100 mm/s
|
||||
Velocity::new::<millimeter_per_second>(-100.0)
|
||||
}
|
||||
HomingState::FindEndStopFine => {
|
||||
// move into the endstop at 2 mm/s
|
||||
Velocity::new::<millimeter_per_second>(-2.0)
|
||||
}
|
||||
HomingState::Validate(_) => {
|
||||
// We stand still until the validation cooldown has passed
|
||||
Velocity::ZERO
|
||||
}
|
||||
}, // Homing speed
|
||||
State::Buffering(buffering_state) => match buffering_state {
|
||||
BufferingState::GoingUp => {
|
||||
// Move top at a speed of 100 mm/s
|
||||
self.speed_to_position(
|
||||
self.limit_top - self.padding + Length::new::<millimeter>(0.01),
|
||||
Velocity::new::<millimeter_per_second>(100.0),
|
||||
)
|
||||
}
|
||||
BufferingState::Filling => self.speed_to_position(
|
||||
self.limit_top + self.padding - Length::new::<millimeter>(0.01),
|
||||
Self::calculate_buffer_lift_speed(self),
|
||||
),
|
||||
BufferingState::Emptying => self.speed_to_position(
|
||||
self.limit_bottom - self.padding + Length::new::<millimeter>(0.01),
|
||||
Self::calculate_buffer_lift_speed(self),
|
||||
),
|
||||
},
|
||||
};
|
||||
info!("{}", speed.get::<millimeter_per_second>());
|
||||
speed
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
use ethercat_hal::io::stepper_velocity_el70x1::StepperVelocityEL70x1;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BufferTowerController {
|
||||
enabled: bool,
|
||||
/// Stepper driver. Controls buffer stepper motor
|
||||
pub stepper_driver: StepperVelocityEL70x1,
|
||||
}
|
||||
|
||||
impl BufferTowerController {
|
||||
pub const fn new(driver: StepperVelocityEL70x1) -> Self {
|
||||
Self {
|
||||
enabled: false,
|
||||
stepper_driver: driver,
|
||||
}
|
||||
}
|
||||
pub fn set_enabled(&mut self, enabled: bool) {
|
||||
self.enabled = enabled;
|
||||
if enabled {
|
||||
self.stepper_driver.set_enabled(true);
|
||||
let _ = self.stepper_driver.set_speed(10.0);
|
||||
} else {
|
||||
self.stepper_driver.set_enabled(false);
|
||||
let _ = self.stepper_driver.set_speed(0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,55 @@
|
||||
pub mod act;
|
||||
pub mod api;
|
||||
pub mod buffer_tower_controller;
|
||||
pub mod buffer_lift_controller;
|
||||
pub mod new;
|
||||
pub mod puller_speed_controller;
|
||||
|
||||
use api::{Buffer1Namespace, BufferV1Events, LiveValuesEvent, ModeState, StateEvent};
|
||||
use buffer_tower_controller::BufferTowerController;
|
||||
use control_core::machines::connection::{CrossConnectableMachine, MachineCrossConnection};
|
||||
use buffer_lift_controller::BufferLiftController;
|
||||
use control_core::{
|
||||
converters::linear_step_converter::LinearStepConverter,
|
||||
machines::{
|
||||
connection::{CrossConnectableMachine, MachineConnection, MachineCrossConnection},
|
||||
identification::{MachineIdentification, MachineIdentificationUnique},
|
||||
manager::MachineManager,
|
||||
},
|
||||
socketio::namespace::NamespaceCacheingLogic,
|
||||
uom_extensions::velocity::meter_per_minute,
|
||||
};
|
||||
use control_core_derive::Machine;
|
||||
use ethercat_hal::io::{
|
||||
digital_input::DigitalInput, stepper_velocity_el70x1::StepperVelocityEL70x1,
|
||||
};
|
||||
use puller_speed_controller::PullerRegulationMode;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use smol::lock::RwLock;
|
||||
use std::{sync::Weak, time::Instant};
|
||||
use uom::si::{
|
||||
f64::{Length, Velocity},
|
||||
length::millimeter,
|
||||
};
|
||||
|
||||
use crate::machines::{MACHINE_BUFFER_V1, VENDOR_QITECH, winder2::Winder2};
|
||||
use crate::machines::{
|
||||
MACHINE_BUFFER_V1, VENDOR_QITECH,
|
||||
buffer1::{
|
||||
api::{CurrentInputSpeedState, LiftState, PullerState},
|
||||
puller_speed_controller::PullerSpeedController,
|
||||
},
|
||||
winder2::{BufferState, Winder2, Winder2Mode},
|
||||
};
|
||||
|
||||
#[derive(Debug, Machine)]
|
||||
pub struct BufferV1 {
|
||||
// drivers
|
||||
pub lift: StepperVelocityEL70x1,
|
||||
pub lift_end_stop: DigitalInput,
|
||||
pub puller: StepperVelocityEL70x1,
|
||||
|
||||
// controllers
|
||||
pub buffer_tower_controller: BufferTowerController,
|
||||
pub buffer_lift_controller: BufferLiftController,
|
||||
pub puller_speed_controller: PullerSpeedController,
|
||||
|
||||
pub lift_step_converter: LinearStepConverter,
|
||||
|
||||
// socketio
|
||||
namespace: Buffer1Namespace,
|
||||
@@ -58,7 +84,24 @@ impl BufferV1 {
|
||||
machine: MACHINE_BUFFER_V1,
|
||||
};
|
||||
pub fn emit_live_values(&mut self) {
|
||||
let live_values = LiveValuesEvent {};
|
||||
// Calculate puller speed from current motor steps
|
||||
let steps_per_second = self.puller.get_speed();
|
||||
let angular_velocity = self
|
||||
.puller_speed_controller
|
||||
.converter
|
||||
.steps_to_angular_velocity(steps_per_second as f64);
|
||||
let puller_speed = self
|
||||
.puller_speed_controller
|
||||
.angular_velocity_to_speed(angular_velocity);
|
||||
|
||||
// live values to be emittet
|
||||
let live_values = LiveValuesEvent {
|
||||
lift_position: self
|
||||
.buffer_lift_controller
|
||||
.get_current_position()
|
||||
.map(|x| x.get::<millimeter>()),
|
||||
puller_speed: puller_speed.get::<meter_per_minute>(),
|
||||
};
|
||||
|
||||
let event = live_values.build();
|
||||
self.namespace.emit(BufferV1Events::LiveValues(event));
|
||||
@@ -70,51 +113,195 @@ impl BufferV1 {
|
||||
mode: self.mode.clone(),
|
||||
},
|
||||
connected_machine_state: self.connected_winder.to_state(),
|
||||
lift_state: LiftState {
|
||||
limit_top: self
|
||||
.buffer_lift_controller
|
||||
.get_limit_top()
|
||||
.get::<millimeter>(),
|
||||
limit_bottom: self
|
||||
.buffer_lift_controller
|
||||
.get_limit_bottom()
|
||||
.get::<millimeter>(),
|
||||
position_in: self
|
||||
.buffer_lift_controller
|
||||
.get_limit_top()
|
||||
.get::<millimeter>(),
|
||||
position_out: self
|
||||
.buffer_lift_controller
|
||||
.get_limit_bottom()
|
||||
.get::<millimeter>(),
|
||||
is_going_up: self.buffer_lift_controller.is_going_up(),
|
||||
is_going_down: self.buffer_lift_controller.is_going_down(),
|
||||
is_homed: self.buffer_lift_controller.is_homed(),
|
||||
is_going_home: self.buffer_lift_controller.is_going_home(),
|
||||
is_buffering: self.buffer_lift_controller.is_buffering(),
|
||||
step_size: self
|
||||
.buffer_lift_controller
|
||||
.get_step_size()
|
||||
.get::<millimeter>(),
|
||||
padding: self
|
||||
.buffer_lift_controller
|
||||
.get_padding()
|
||||
.get::<millimeter>(),
|
||||
can_go_top: self.can_go_up(),
|
||||
can_go_bottom: self.can_go_down(),
|
||||
can_go_home: self.can_go_home(),
|
||||
},
|
||||
current_input_speed_state: CurrentInputSpeedState {
|
||||
current_input_speed: self
|
||||
.buffer_lift_controller
|
||||
.get_current_input_speed()
|
||||
.get::<meter_per_minute>(),
|
||||
},
|
||||
puller_state: PullerState {
|
||||
regulation: self.puller_speed_controller.regulation_mode.clone(),
|
||||
target_speed: self
|
||||
.puller_speed_controller
|
||||
.target_speed
|
||||
.get::<meter_per_minute>(),
|
||||
target_diameter: self
|
||||
.puller_speed_controller
|
||||
.target_diameter
|
||||
.get::<millimeter>(),
|
||||
forward: self.puller_speed_controller.forward,
|
||||
},
|
||||
};
|
||||
|
||||
let event = state.build();
|
||||
self.namespace.emit(BufferV1Events::State(event));
|
||||
}
|
||||
|
||||
// To be implemented
|
||||
/// Can buffer capability check
|
||||
pub fn can_buffer(&self) -> bool {
|
||||
// Check if tension arm is zeroed and traverse is homed
|
||||
self.buffer_lift_controller.is_homed() && !self.buffer_lift_controller.is_going_home()
|
||||
}
|
||||
|
||||
/// Can go to upper limit capability check
|
||||
pub fn can_go_up(&self) -> bool {
|
||||
self.buffer_lift_controller.is_homed()
|
||||
&& self.mode != BufferV1Mode::Standby
|
||||
&& !self.buffer_lift_controller.is_going_up()
|
||||
&& !self.buffer_lift_controller.is_going_home()
|
||||
&& !self.buffer_lift_controller.is_buffering()
|
||||
&& self.mode != BufferV1Mode::Filling
|
||||
&& self.mode != BufferV1Mode::Emptying
|
||||
}
|
||||
|
||||
/// Can go to lower limit capability check
|
||||
pub fn can_go_down(&self) -> bool {
|
||||
self.buffer_lift_controller.is_homed()
|
||||
&& self.mode != BufferV1Mode::Standby
|
||||
&& !self.buffer_lift_controller.is_going_down()
|
||||
&& !self.buffer_lift_controller.is_going_home()
|
||||
&& !self.buffer_lift_controller.is_buffering()
|
||||
&& self.mode != BufferV1Mode::Filling
|
||||
&& self.mode != BufferV1Mode::Emptying
|
||||
}
|
||||
|
||||
/// Can go home capability check
|
||||
pub fn can_go_home(&self) -> bool {
|
||||
// Check if not in standby, not traversing
|
||||
// Allow going home even when going in or out
|
||||
self.mode != BufferV1Mode::Standby
|
||||
&& !self.buffer_lift_controller.is_going_home()
|
||||
&& !self.buffer_lift_controller.is_buffering()
|
||||
&& self.mode != BufferV1Mode::Filling
|
||||
&& self.mode != BufferV1Mode::Emptying
|
||||
}
|
||||
}
|
||||
|
||||
impl BufferV1 {
|
||||
fn fill_buffer(&mut self) {
|
||||
todo!();
|
||||
// stop the winder until the buffer is full
|
||||
self.update_winder2_mode(Winder2Mode::Hold);
|
||||
self.update_winder2_buffer_state(BufferState::Buffering);
|
||||
}
|
||||
|
||||
fn empty_buffer(&mut self) {
|
||||
todo!();
|
||||
// Set the winder2 to a mode where its faster than before to empty the buffer slowly
|
||||
self.update_winder2_mode(Winder2Mode::Wind);
|
||||
self.update_winder2_buffer_state(BufferState::Emptying);
|
||||
}
|
||||
|
||||
fn update_winder2_mode(&mut self, mode: Winder2Mode) {
|
||||
if let Some(slot) = self.get_cross_connection().connected_machine.upgrade() {
|
||||
let slot = slot.lock_blocking();
|
||||
|
||||
if let MachineConnection::Connected(machine) = &slot.machine_connection {
|
||||
let mut winder = machine.lock_blocking();
|
||||
if winder.mode != mode {
|
||||
winder.mode = mode;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_winder2_buffer_state(&mut self, state: BufferState) {
|
||||
if let Some(slot) = self.get_cross_connection().connected_machine.upgrade() {
|
||||
let slot = slot.lock_blocking();
|
||||
|
||||
if let MachineConnection::Connected(machine) = &slot.machine_connection {
|
||||
let mut winder = machine.lock_blocking();
|
||||
if winder.buffer_state != state {
|
||||
winder.set_buffer_state(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Turn off motor and do nothing
|
||||
fn switch_to_standby(&mut self) {
|
||||
match self.mode {
|
||||
BufferV1Mode::Standby => (),
|
||||
BufferV1Mode::FillingBuffer => {}
|
||||
BufferV1Mode::EmptyingBuffer => {}
|
||||
BufferV1Mode::Hold => {}
|
||||
BufferV1Mode::Filling => {}
|
||||
BufferV1Mode::Emptying => {}
|
||||
};
|
||||
self.mode = BufferV1Mode::Standby;
|
||||
self.buffer_tower_controller.set_enabled(false);
|
||||
self.buffer_lift_controller.set_enabled(false);
|
||||
let _ = self.buffer_lift_controller.stepper_driver.set_speed(0.0);
|
||||
self.update_winder2_buffer_state(BufferState::Hold);
|
||||
}
|
||||
|
||||
// hold motor
|
||||
fn switch_to_hold(&mut self) {
|
||||
match self.mode {
|
||||
BufferV1Mode::Standby => {}
|
||||
BufferV1Mode::Hold => (),
|
||||
BufferV1Mode::Filling => {}
|
||||
BufferV1Mode::Emptying => {}
|
||||
};
|
||||
self.mode = BufferV1Mode::Hold;
|
||||
self.buffer_lift_controller.set_enabled(true);
|
||||
let _ = self.buffer_lift_controller.stepper_driver.set_speed(0.0);
|
||||
self.update_winder2_buffer_state(BufferState::Hold);
|
||||
}
|
||||
|
||||
// Turn on motor and fill buffer
|
||||
fn switch_to_filling(&mut self) {
|
||||
match self.mode {
|
||||
BufferV1Mode::Standby => self.fill_buffer(),
|
||||
BufferV1Mode::FillingBuffer => (),
|
||||
BufferV1Mode::EmptyingBuffer => {}
|
||||
BufferV1Mode::Hold => {}
|
||||
BufferV1Mode::Filling => (),
|
||||
BufferV1Mode::Emptying => {}
|
||||
};
|
||||
self.mode = BufferV1Mode::FillingBuffer;
|
||||
self.buffer_tower_controller.set_enabled(true);
|
||||
self.mode = BufferV1Mode::Filling;
|
||||
self.buffer_lift_controller.set_forward(true);
|
||||
self.buffer_lift_controller.set_enabled(true);
|
||||
}
|
||||
|
||||
// Turn on motor reverse and empty buffer
|
||||
fn switch_to_emptying(&mut self) {
|
||||
match self.mode {
|
||||
BufferV1Mode::Standby => self.empty_buffer(),
|
||||
BufferV1Mode::FillingBuffer => {}
|
||||
BufferV1Mode::EmptyingBuffer => (),
|
||||
BufferV1Mode::Hold => {}
|
||||
BufferV1Mode::Filling => {}
|
||||
BufferV1Mode::Emptying => (),
|
||||
};
|
||||
self.mode = BufferV1Mode::EmptyingBuffer;
|
||||
self.mode = BufferV1Mode::Emptying;
|
||||
self.buffer_lift_controller.set_forward(false);
|
||||
self.buffer_lift_controller.set_enabled(true);
|
||||
}
|
||||
|
||||
fn switch_mode(&mut self, mode: BufferV1Mode) {
|
||||
@@ -124,8 +311,9 @@ impl BufferV1 {
|
||||
|
||||
match mode {
|
||||
BufferV1Mode::Standby => self.switch_to_standby(),
|
||||
BufferV1Mode::FillingBuffer => self.switch_to_filling(),
|
||||
BufferV1Mode::EmptyingBuffer => self.switch_to_emptying(),
|
||||
BufferV1Mode::Hold => self.switch_to_hold(),
|
||||
BufferV1Mode::Filling => self.switch_to_filling(),
|
||||
BufferV1Mode::Emptying => self.switch_to_emptying(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,7 +322,89 @@ impl BufferV1 {
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Connecting/Disconnecting machine
|
||||
fn set_current_input_speed(&mut self, speed: f64) {
|
||||
// speed comes as a f64 represents m/min
|
||||
self.buffer_lift_controller.set_current_input_speed(speed);
|
||||
self.emit_state();
|
||||
}
|
||||
}
|
||||
|
||||
// Implement Puller
|
||||
impl BufferV1 {
|
||||
/// called by `act`
|
||||
pub fn sync_puller_speed(&mut self, t: Instant) {
|
||||
let angular_velocity = self.puller_speed_controller.calc_angular_velocity(t);
|
||||
let steps_per_second = self
|
||||
.puller_speed_controller
|
||||
.converter
|
||||
.angular_velocity_to_steps(angular_velocity);
|
||||
let _ = self.puller.set_speed(steps_per_second);
|
||||
|
||||
// sync puller speed to lift input speed
|
||||
let linear_velocity = self
|
||||
.puller_speed_controller
|
||||
.converter
|
||||
.angular_velocity_to_velocity(angular_velocity);
|
||||
self.buffer_lift_controller
|
||||
.set_current_input_speed(linear_velocity.get::<meter_per_minute>());
|
||||
}
|
||||
|
||||
pub fn puller_set_regulation(&mut self, puller_regulation_mode: PullerRegulationMode) {
|
||||
self.puller_speed_controller
|
||||
.set_regulation_mode(puller_regulation_mode);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set target speed in m/min
|
||||
pub fn puller_set_target_speed(&mut self, target_speed: f64) {
|
||||
// Convert m/min to velocity
|
||||
let target_speed = Velocity::new::<meter_per_minute>(target_speed);
|
||||
self.puller_speed_controller.set_target_speed(target_speed);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set target diameter in mm
|
||||
pub fn puller_set_target_diameter(&mut self, target_diameter: f64) {
|
||||
// Convert mm to length
|
||||
let target_diameter = Length::new::<millimeter>(target_diameter);
|
||||
self.puller_speed_controller
|
||||
.set_target_diameter(target_diameter);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set forward direction
|
||||
pub fn puller_set_forward(&mut self, forward: bool) {
|
||||
self.puller_speed_controller.set_forward(forward);
|
||||
self.emit_state();
|
||||
}
|
||||
}
|
||||
|
||||
// Implement Lift
|
||||
impl BufferV1 {
|
||||
pub fn sync_lift_speed(&mut self, t: Instant) {
|
||||
let linear_velocity =
|
||||
self.buffer_lift_controller
|
||||
.update_speed(&mut self.lift, &self.lift_end_stop, t);
|
||||
let steps_per_second = self.lift_step_converter.velocity_to_steps(linear_velocity);
|
||||
let _ = self.lift.set_speed(steps_per_second);
|
||||
}
|
||||
|
||||
pub fn lift_set_step_size(&mut self, step_size: f64) {
|
||||
let step_size = Length::new::<millimeter>(step_size);
|
||||
self.buffer_lift_controller.set_step_size(step_size);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn lift_goto_home(&mut self) {
|
||||
if self.can_go_home() {
|
||||
self.buffer_lift_controller.goto_home();
|
||||
}
|
||||
self.emit_state();
|
||||
}
|
||||
}
|
||||
|
||||
// Connecting/Disconnecting machine
|
||||
impl BufferV1 {
|
||||
/// set connected winder
|
||||
pub fn set_connected_winder(
|
||||
&mut self,
|
||||
@@ -175,6 +445,7 @@ impl BufferV1 {
|
||||
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
|
||||
pub enum BufferV1Mode {
|
||||
Standby,
|
||||
FillingBuffer,
|
||||
EmptyingBuffer,
|
||||
Hold,
|
||||
Filling,
|
||||
Emptying,
|
||||
}
|
||||
@@ -1,29 +1,34 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use anyhow::Error;
|
||||
use control_core::converters::linear_step_converter::LinearStepConverter;
|
||||
use control_core::machines::connection::MachineCrossConnection;
|
||||
use control_core::machines::new::{
|
||||
MachineNewHardware, MachineNewParams, MachineNewTrait, validate_no_role_dublicates,
|
||||
validate_same_machine_identification_unique,
|
||||
};
|
||||
use ethercat_hal::{
|
||||
coe::ConfigurableDevice,
|
||||
devices::el7031_0030::coe::EL7031_0030Configuration,
|
||||
devices::el7031_0030::pdo::EL7031_0030PredefinedPdoAssignment,
|
||||
devices::el7041_0052::coe::EL7041_0052Configuration,
|
||||
devices::{
|
||||
EthercatDeviceUsed,
|
||||
ek1100::{EK1100, EK1100_IDENTITY_A},
|
||||
el7031_0030::{EL7031_0030, EL7031_0030_IDENTITY_A},
|
||||
el7041_0052::{EL7041_0052, EL7041_0052_IDENTITY_A, EL7041_0052Port},
|
||||
},
|
||||
io::stepper_velocity_el70x1::StepperVelocityEL70x1,
|
||||
shared_config,
|
||||
shared_config::el70x1::{EL70x1OperationMode, StmMotorConfiguration},
|
||||
use control_core::uom_extensions::velocity::meter_per_minute;
|
||||
use ethercat_hal::coe::ConfigurableDevice;
|
||||
use ethercat_hal::devices::EthercatDeviceUsed;
|
||||
use ethercat_hal::devices::ek1100::EK1100;
|
||||
use ethercat_hal::devices::ek1100::EK1100_IDENTITY_A;
|
||||
use ethercat_hal::devices::el7031_0030::coe::EL7031_0030Configuration;
|
||||
use ethercat_hal::devices::el7031_0030::pdo::EL7031_0030PredefinedPdoAssignment;
|
||||
use ethercat_hal::devices::el7031_0030::{
|
||||
EL7031_0030, EL7031_0030_IDENTITY_A, EL7031_0030StepperPort,
|
||||
};
|
||||
use ethercat_hal::devices::el7041_0052::coe::EL7041_0052Configuration;
|
||||
use ethercat_hal::devices::el7041_0052::{EL7041_0052, EL7041_0052_IDENTITY_A, EL7041_0052Port};
|
||||
use ethercat_hal::io::digital_input::DigitalInput;
|
||||
use ethercat_hal::io::stepper_velocity_el70x1::StepperVelocityEL70x1;
|
||||
use ethercat_hal::shared_config;
|
||||
use ethercat_hal::shared_config::el70x1::{EL70x1OperationMode, StmMotorConfiguration};
|
||||
use uom::si::f64::{Length, Velocity};
|
||||
use uom::si::length::{centimeter, millimeter};
|
||||
|
||||
use crate::machines::buffer1::BufferV1Mode;
|
||||
use crate::machines::buffer1::buffer_tower_controller::BufferTowerController;
|
||||
use crate::machines::buffer1::buffer_lift_controller::BufferLiftController;
|
||||
use crate::machines::buffer1::puller_speed_controller::PullerSpeedController;
|
||||
use crate::machines::get_ethercat_device;
|
||||
|
||||
use super::{BufferV1, api::Buffer1Namespace};
|
||||
@@ -79,7 +84,7 @@ impl MachineNewTrait for BufferV1 {
|
||||
el7041
|
||||
.write()
|
||||
.await
|
||||
.write_config(&subdevice, &el7041_config)
|
||||
.write_config(subdevice, &el7041_config)
|
||||
.await?;
|
||||
{
|
||||
let mut device_guard = el7041.write().await;
|
||||
@@ -114,7 +119,7 @@ impl MachineNewTrait for BufferV1 {
|
||||
el7031
|
||||
.write()
|
||||
.await
|
||||
.write_config(&subdevice, &el7031_config)
|
||||
.write_config(subdevice, &el7031_config)
|
||||
.await?;
|
||||
{
|
||||
let mut device_guard = el7031.write().await;
|
||||
@@ -122,21 +127,36 @@ impl MachineNewTrait for BufferV1 {
|
||||
}
|
||||
|
||||
// Controller
|
||||
let buffer_tower_controller = BufferTowerController::new(StepperVelocityEL70x1::new(
|
||||
el7041.clone(),
|
||||
EL7041_0052Port::STM1,
|
||||
));
|
||||
let buffer_lift_controller = BufferLiftController::new(
|
||||
StepperVelocityEL70x1::new(el7041.clone(), EL7041_0052Port::STM1),
|
||||
Length::new::<centimeter>(135.0),
|
||||
Length::new::<centimeter>(0.0),
|
||||
64,
|
||||
);
|
||||
let puller_speed_controller = PullerSpeedController::new(
|
||||
Velocity::new::<meter_per_minute>(1.0),
|
||||
Length::new::<millimeter>(1.75),
|
||||
LinearStepConverter::from_diameter(200, Length::new::<centimeter>(8.0)),
|
||||
);
|
||||
|
||||
let machine_identification_unique = params.get_machine_identification_unique();
|
||||
|
||||
// create buffer instance
|
||||
let mut buffer: BufferV1 = Self {
|
||||
let mut buffer = Self {
|
||||
lift: StepperVelocityEL70x1::new(el7041.clone(), EL7041_0052Port::STM1),
|
||||
lift_end_stop: DigitalInput::new(el7041, EL7041_0052Port::DI1),
|
||||
puller: StepperVelocityEL70x1::new(el7031.clone(), EL7031_0030StepperPort::STM1),
|
||||
lift_step_converter: LinearStepConverter::from_diameter(
|
||||
200,
|
||||
Length::new::<millimeter>(32.22),
|
||||
),
|
||||
namespace: Buffer1Namespace {
|
||||
namespace: params.namespace.clone(),
|
||||
},
|
||||
last_measurement_emit: Instant::now(),
|
||||
mode: BufferV1Mode::Standby,
|
||||
buffer_tower_controller,
|
||||
buffer_lift_controller,
|
||||
puller_speed_controller,
|
||||
machine_manager: params.machine_manager.clone(),
|
||||
machine_identification_unique: machine_identification_unique.clone(),
|
||||
connected_winder: MachineCrossConnection::new(
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use control_core::{
|
||||
controllers::second_degree_motion::linear_jerk_speed_controller::LinearJerkSpeedController,
|
||||
converters::linear_step_converter::LinearStepConverter,
|
||||
uom_extensions::{
|
||||
acceleration::meter_per_minute_per_second, jerk::meter_per_minute_per_second_squared,
|
||||
velocity::meter_per_minute,
|
||||
},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uom::{
|
||||
ConstZero,
|
||||
si::f64::{Acceleration, AngularVelocity, Jerk, Length, Velocity},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PullerSpeedController {
|
||||
enabled: bool,
|
||||
pub target_speed: Velocity,
|
||||
pub target_diameter: Length,
|
||||
pub regulation_mode: PullerRegulationMode,
|
||||
/// Forward rotation direction. If false, applies negative sign to speed
|
||||
pub forward: bool,
|
||||
/// Linear acceleration controller to dampen speed change
|
||||
acceleration_controller: LinearJerkSpeedController,
|
||||
/// Converter for linear to angular transformations
|
||||
pub converter: LinearStepConverter,
|
||||
pub last_speed: Velocity,
|
||||
}
|
||||
|
||||
impl PullerSpeedController {
|
||||
pub fn new(
|
||||
target_speed: Velocity,
|
||||
target_diameter: Length,
|
||||
converter: LinearStepConverter,
|
||||
) -> Self {
|
||||
let acceleration = Acceleration::new::<meter_per_minute_per_second>(5.0);
|
||||
let jerk = Jerk::new::<meter_per_minute_per_second_squared>(10.0);
|
||||
let speed = Velocity::new::<meter_per_minute>(50.0);
|
||||
|
||||
Self {
|
||||
enabled: false,
|
||||
target_speed,
|
||||
target_diameter,
|
||||
regulation_mode: PullerRegulationMode::Speed,
|
||||
forward: true,
|
||||
acceleration_controller: LinearJerkSpeedController::new_simple(
|
||||
Some(speed),
|
||||
acceleration,
|
||||
jerk,
|
||||
),
|
||||
converter,
|
||||
last_speed: Velocity::ZERO,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_enabled(&mut self, enabled: bool) {
|
||||
self.enabled = enabled;
|
||||
}
|
||||
|
||||
pub fn set_target_speed(&mut self, target: Velocity) {
|
||||
self.target_speed = target;
|
||||
}
|
||||
|
||||
pub fn set_target_diameter(&mut self, target: Length) {
|
||||
self.target_diameter = target;
|
||||
}
|
||||
|
||||
pub fn set_regulation_mode(&mut self, regulation: PullerRegulationMode) {
|
||||
self.regulation_mode = regulation;
|
||||
}
|
||||
|
||||
pub fn set_forward(&mut self, forward: bool) {
|
||||
self.forward = forward;
|
||||
}
|
||||
|
||||
fn update_speed(&mut self, t: Instant) -> Velocity {
|
||||
let speed = match self.enabled {
|
||||
true => match self.regulation_mode {
|
||||
PullerRegulationMode::Speed => self.target_speed,
|
||||
PullerRegulationMode::Diameter => unimplemented!(),
|
||||
},
|
||||
false => Velocity::ZERO,
|
||||
};
|
||||
|
||||
let speed = if self.forward { speed } else { -speed };
|
||||
|
||||
let speed = self.acceleration_controller.update(speed, t);
|
||||
|
||||
self.last_speed = speed;
|
||||
speed
|
||||
}
|
||||
|
||||
pub fn speed_to_angular_velocity(&self, speed: Velocity) -> AngularVelocity {
|
||||
// Use the converter to transform from linear velocity to angular velocity
|
||||
self.converter.velocity_to_angular_velocity(speed)
|
||||
}
|
||||
|
||||
pub fn angular_velocity_to_speed(&self, angular_speed: AngularVelocity) -> Velocity {
|
||||
// Use the converter to transform from angular velocity to linear velocity
|
||||
self.converter.angular_velocity_to_velocity(angular_speed)
|
||||
}
|
||||
|
||||
pub fn calc_angular_velocity(&mut self, t: Instant) -> AngularVelocity {
|
||||
let speed = self.update_speed(t);
|
||||
self.speed_to_angular_velocity(speed)
|
||||
}
|
||||
|
||||
pub fn get_target_speed(&self) -> Velocity {
|
||||
self.target_speed
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub enum PullerRegulationMode {
|
||||
Speed,
|
||||
Diameter,
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use super::Winder2;
|
||||
use control_core::machines::new::MachineAct;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
impl MachineAct for Winder2 {
|
||||
fn act(&mut self, now: Instant) {
|
||||
@@ -20,10 +21,17 @@ impl MachineAct for Winder2 {
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
// sync the puller speed to the buffer output speed
|
||||
self.sync_buffer_speed();
|
||||
|
||||
// more than 33ms have passed since last emit (30 "fps" target)
|
||||
if now.duration_since(self.last_measurement_emit) > Duration::from_secs_f64(1.0 / 30.0) {
|
||||
self.emit_live_values();
|
||||
self.last_measurement_emit = now;
|
||||
// check if traverse state changed
|
||||
if self.traverse_controller.did_change_state() {
|
||||
self.emit_state();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -71,6 +71,8 @@ pub struct Winder2 {
|
||||
// machine connection
|
||||
pub machine_manager: Weak<RwLock<MachineManager>>,
|
||||
pub machine_identification_unique: MachineIdentificationUnique,
|
||||
// Buffer State
|
||||
pub buffer_state: BufferState,
|
||||
|
||||
// connected machines
|
||||
pub connected_buffer: MachineCrossConnection<Winder2, BufferV1>,
|
||||
@@ -304,6 +306,15 @@ impl Winder2 {
|
||||
.angular_velocity_to_steps(angular_velocity);
|
||||
let _ = self.puller.set_speed(steps_per_second);
|
||||
}
|
||||
|
||||
/// Implement Buffer
|
||||
pub fn sync_buffer_speed(&mut self) {
|
||||
todo!();
|
||||
}
|
||||
|
||||
pub fn set_buffer_state(&mut self, state: BufferState) {
|
||||
self.buffer_state = state;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
@@ -357,6 +368,14 @@ pub enum PullerMode {
|
||||
Pull,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum BufferState {
|
||||
Disconnected,
|
||||
Hold,
|
||||
Buffering,
|
||||
Emptying,
|
||||
}
|
||||
|
||||
impl From<Winder2Mode> for PullerMode {
|
||||
fn from(mode: Winder2Mode) -> Self {
|
||||
match mode {
|
||||
|
||||
@@ -4,6 +4,7 @@ use super::api::Winder2Namespace;
|
||||
use super::tension_arm::TensionArm;
|
||||
use super::{Winder2, Winder2Mode};
|
||||
use crate::machines::get_ethercat_device;
|
||||
use crate::machines::winder2::BufferState;
|
||||
use crate::machines::winder2::puller_speed_controller::PullerSpeedController;
|
||||
use crate::machines::winder2::spool_speed_controller::SpoolSpeedController;
|
||||
use crate::machines::winder2::traverse_controller::TraverseController;
|
||||
@@ -234,6 +235,7 @@ impl MachineNewTrait for Winder2 {
|
||||
params.machine_manager.clone(),
|
||||
¶ms.get_machine_identification_unique(),
|
||||
),
|
||||
buffer_state: BufferState::Disconnected,
|
||||
};
|
||||
|
||||
// initalize events
|
||||
|
||||
Reference in new issue
Block a user