Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a31a21e56e | ||
|
|
d2d9055903 | ||
|
|
524708a373 | ||
|
|
ae6eaf1d08 | ||
|
|
1f4f8f7788 | ||
|
|
c12e4052a1 | ||
|
|
ec3e02fb32 | ||
|
|
f82aadd1ed | ||
|
|
ed2f2f82e8 | ||
|
|
774fb2d64b | ||
|
|
2d88411630 | ||
|
|
511b67df7d | ||
|
|
510cb64b95 | ||
|
|
9cb667a55b | ||
|
|
9594e06403 | ||
|
|
ed8879c1ea | ||
|
|
52ba340917 | ||
|
|
36618d20c0 | ||
|
|
1dfc66170c | ||
|
|
9d1587a91f | ||
|
|
52da8bae6b | ||
|
|
9b2e493091 | ||
|
|
0b5c734472 | ||
|
|
b68d18dbfb | ||
|
|
7d92d9654e | ||
|
|
b30cd36eb4 |
No files matched your search
@@ -5,15 +5,33 @@ 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";
|
||||
|
||||
export function Buffer1ControlPage() {
|
||||
const { state, setBufferMode } = useBuffer1();
|
||||
const {
|
||||
state,
|
||||
defaultState,
|
||||
setBufferMode,
|
||||
setCurrentInputSpeed,
|
||||
pullerSpeed,
|
||||
setPullerRegulationMode,
|
||||
setPullerTargetSpeed,
|
||||
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 +42,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 +64,60 @@ 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" 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,42 @@ 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({
|
||||
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(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Mode state event schema
|
||||
@@ -41,6 +60,10 @@ export const modeStateSchema = z.object({
|
||||
mode: modeSchema,
|
||||
});
|
||||
|
||||
export const currentInputSpeedSchema = z.object({
|
||||
current_input_speed: z.number(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Connected machine state scheme
|
||||
*/
|
||||
@@ -65,7 +88,9 @@ export const connectedMachineStateSchema = z.object({
|
||||
|
||||
export const stateEventDataSchema = z.object({
|
||||
mode_state: modeStateSchema,
|
||||
puller_state: pullerStateSchema,
|
||||
connected_machine_state: connectedMachineStateSchema,
|
||||
current_input_speed_state: currentInputSpeedSchema,
|
||||
});
|
||||
|
||||
// ========== Event Schemas with Wrappers ==========
|
||||
@@ -80,8 +105,21 @@ 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
|
||||
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);
|
||||
|
||||
/**
|
||||
* Creates a message handler for Buffer1 namespace events with validation and appropriate caching strategies
|
||||
* @param store The store to update when messages are received
|
||||
@@ -111,11 +149,28 @@ export function buffer1MessageHandler(
|
||||
state: 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 { puller_speed } = liveValuesEvent.data;
|
||||
|
||||
const timestamp = liveValuesEvent.ts;
|
||||
updateStore((state) => {
|
||||
const newState = { ...state };
|
||||
|
||||
// Add puller speed
|
||||
const pullerSpeedValue: TimeSeriesValue = {
|
||||
value: puller_speed,
|
||||
timestamp,
|
||||
};
|
||||
newState.pullerSpeed = addPullerSpeed(
|
||||
state.pullerSpeed,
|
||||
pullerSpeedValue,
|
||||
);
|
||||
|
||||
return newState;
|
||||
});
|
||||
} else {
|
||||
handleUnhandledEventError(eventName);
|
||||
}
|
||||
@@ -134,7 +189,12 @@ export const createBuffer1NamespaceStore =
|
||||
(): StoreApi<Buffer1NamespaceStore> =>
|
||||
create<Buffer1NamespaceStore>(() => {
|
||||
return {
|
||||
// State event from server
|
||||
state: null,
|
||||
defaultState: null,
|
||||
|
||||
// Time series data for live values
|
||||
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,9 @@ export function useBuffer1() {
|
||||
const machine_identification_unique = machineIdentification;
|
||||
|
||||
// Get consolidated state and live values from namespace
|
||||
const { state } = useBuffer1Namespace(machineIdentification);
|
||||
const { state, defaultState, pullerSpeed } = useBuffer1Namespace(
|
||||
machineIdentification,
|
||||
);
|
||||
|
||||
// Single optimistic state for all state management
|
||||
const stateOptimistic = useStateOptimistic<StateEvent>();
|
||||
@@ -122,10 +130,76 @@ 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 },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
// Mutation hooks
|
||||
const { request: requestBufferMode } = useMachineMutation(
|
||||
z.object({
|
||||
SetBufferMode: z.enum(["Standby", "FillingBuffer", "EmptyingBuffer"]),
|
||||
SetBufferMode: z.enum(["Standby", "Hold", "Filling", "Emptying"]),
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -141,6 +215,31 @@ 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() }),
|
||||
);
|
||||
|
||||
// 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 +275,24 @@ export function useBuffer1() {
|
||||
filteredMachines,
|
||||
selectedMachine,
|
||||
|
||||
// Default state for initial values
|
||||
defaultState: defaultState?.data,
|
||||
|
||||
// Individual live values (TimeSeries)
|
||||
pullerSpeed,
|
||||
|
||||
// Loading states
|
||||
isLoading,
|
||||
isDisabled,
|
||||
|
||||
// Action functions (verb-first)
|
||||
setBufferMode,
|
||||
setConnectedMachine,
|
||||
disconnectMachine,
|
||||
setCurrentInputSpeed,
|
||||
setPullerTargetSpeed,
|
||||
setPullerTargetDiameter,
|
||||
setPullerRegulationMode,
|
||||
setPullerForward,
|
||||
};
|
||||
}
|
||||
@@ -188,18 +188,18 @@ export const buffer1ControlRoute = createRoute({
|
||||
component: () => <Buffer1ControlPage />,
|
||||
});
|
||||
|
||||
export const mock1PresetsRoute = createRoute({
|
||||
getParentRoute: () => mock1SerialRoute,
|
||||
path: "presets",
|
||||
component: () => <Mock1PresetsPage />,
|
||||
});
|
||||
|
||||
export const buffer1SettingsRoute = createRoute({
|
||||
getParentRoute: () => buffer1SerialRoute,
|
||||
path: "settings",
|
||||
component: () => <Buffer1SettingsPage />,
|
||||
});
|
||||
|
||||
export const mock1PresetsRoute = createRoute({
|
||||
getParentRoute: () => mock1SerialRoute,
|
||||
path: "presets",
|
||||
component: () => <Mock1PresetsPage />,
|
||||
});
|
||||
|
||||
export const setupRoute = createRoute({
|
||||
getParentRoute: () => sidebarRoute,
|
||||
path: "setup",
|
||||
@@ -315,7 +315,10 @@ export const rootTree = RootRoute.addChildren([
|
||||
mock1PresetsRoute,
|
||||
]),
|
||||
|
||||
buffer1SerialRoute.addChildren([buffer1ControlRoute]),
|
||||
buffer1SerialRoute.addChildren([
|
||||
buffer1ControlRoute,
|
||||
buffer1SettingsRoute,
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
]);
|
||||
@@ -4,6 +4,12 @@ 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);
|
||||
|
||||
// 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
|
||||
|
||||
@@ -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::{api::MachineApi, identification::MachineIdentificationUnique},
|
||||
@@ -18,7 +20,10 @@ use socketioxide::extract::SocketRef;
|
||||
use tracing::instrument;
|
||||
|
||||
#[derive(Serialize, Debug, Clone, Default)]
|
||||
pub struct LiveValuesEvent {}
|
||||
pub struct LiveValuesEvent {
|
||||
/// puller speed in m/min
|
||||
pub puller_speed: f64,
|
||||
}
|
||||
|
||||
impl LiveValuesEvent {
|
||||
pub fn build(&self) -> Event<Self> {
|
||||
@@ -30,8 +35,12 @@ impl LiveValuesEvent {
|
||||
pub struct StateEvent {
|
||||
/// mode state
|
||||
pub mode_state: ModeState,
|
||||
/// puller state
|
||||
pub puller_state: PullerState,
|
||||
/// connected machine state
|
||||
pub connected_machine_state: ConnectedMachineState,
|
||||
/// CurrentInputSpeed
|
||||
pub current_input_speed_state: CurrentInputSpeedState,
|
||||
}
|
||||
|
||||
impl StateEvent {
|
||||
@@ -53,8 +62,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)]
|
||||
@@ -64,6 +74,23 @@ pub struct ConnectedMachineState {
|
||||
pub is_available: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, 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 CurrentInputSpeedState {
|
||||
pub current_input_speed: f64,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
enum Mutation {
|
||||
// Mode
|
||||
@@ -74,6 +101,16 @@ enum Mutation {
|
||||
|
||||
// Disconnect Machine
|
||||
DisconnectMachine(MachineIdentificationUnique),
|
||||
|
||||
// Set current input speed
|
||||
SetCurrentInputSpeed(f64),
|
||||
|
||||
// Puller
|
||||
/// on = speed, off = stop
|
||||
SetPullerRegulationMode(PullerRegulationMode),
|
||||
SetPullerTargetSpeed(f64),
|
||||
SetPullerTargetDiameter(f64),
|
||||
SetPullerForward(bool),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -128,6 +165,12 @@ impl MachineApi for BufferV1 {
|
||||
Mutation::DisconnectMachine(machine_identification_unique) => {
|
||||
self.disconnect_winder(machine_identification_unique);
|
||||
}
|
||||
// Puller Mutations
|
||||
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),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,27 +1,117 @@
|
||||
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 ethercat_hal::io::stepper_velocity_el70x1::StepperVelocityEL70x1;
|
||||
use uom::{
|
||||
ConstZero,
|
||||
si::{
|
||||
f64::{Acceleration, Jerk, Velocity},
|
||||
velocity::millimeter_per_second,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BufferTowerController {
|
||||
/// Whether the speed controller is enabled or not
|
||||
enabled: bool,
|
||||
/// Stepper driver. Controls buffer stepper motor
|
||||
pub stepper_driver: StepperVelocityEL70x1,
|
||||
// Step Converter
|
||||
pub converter: LinearStepConverter,
|
||||
|
||||
/// Linear acceleration controller to dampen speed change
|
||||
acceleration_controller: LinearJerkSpeedController,
|
||||
|
||||
/// Forward rotation direction. If false, applies negative sign to speed
|
||||
pub forward: bool,
|
||||
|
||||
/// Fixed constants
|
||||
spool_amount: u8,
|
||||
|
||||
/// Variables
|
||||
current_input_speed: Velocity,
|
||||
target_output_speed: Velocity,
|
||||
lift_speed: Velocity,
|
||||
}
|
||||
|
||||
impl BufferTowerController {
|
||||
pub const fn new(driver: StepperVelocityEL70x1) -> 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,
|
||||
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);
|
||||
spool_amount: 13,
|
||||
converter,
|
||||
forward: true,
|
||||
acceleration_controller: LinearJerkSpeedController::new_simple(
|
||||
Some(speed),
|
||||
acceleration,
|
||||
jerk,
|
||||
),
|
||||
|
||||
current_input_speed: Velocity::ZERO,
|
||||
target_output_speed: Velocity::ZERO,
|
||||
lift_speed: Velocity::ZERO,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BufferTowerontroller {
|
||||
/// Calculate the speed of the buffer lift from current input speed
|
||||
///
|
||||
/// Formula: input_speed / ( 2 * spool_amount )
|
||||
pub fn calculate_buffer_lift_speed(&mut self) -> Velocity {
|
||||
self.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),
|
||||
);
|
||||
self.lift_speed
|
||||
}
|
||||
|
||||
pub fn update_speed(&mut self, t: Instant) -> Velocity {
|
||||
let speed = match self.enabled {
|
||||
true => self.calculate_buffer_lift_speed(),
|
||||
false => Velocity::ZERO,
|
||||
};
|
||||
|
||||
let speed = if self.forward { speed } else { -speed };
|
||||
|
||||
self.acceleration_controller.update(speed, t)
|
||||
}
|
||||
}
|
||||
|
||||
impl BufferTowerController {
|
||||
pub fn set_enabled(&mut self, enabled: bool) {
|
||||
self.enabled = enabled;
|
||||
self.stepper_driver.set_enabled(enabled);
|
||||
}
|
||||
pub fn set_forward(&mut self, forward: bool) {
|
||||
self.forward = forward;
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -2,34 +2,58 @@ pub mod act;
|
||||
pub mod api;
|
||||
pub mod buffer_tower_controller;
|
||||
pub mod new;
|
||||
pub mod puller_speed_controller;
|
||||
|
||||
use api::{Buffer1Namespace, BufferV1Events, LiveValuesEvent, ModeState, StateEvent};
|
||||
use buffer_tower_controller::BufferTowerController;
|
||||
use control_core::{
|
||||
converters::linear_step_converter::LinearStepConverter,
|
||||
machines::{
|
||||
ConnectedMachine, ConnectedMachineData, downcast_machine,
|
||||
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 futures::executor::block_on;
|
||||
use puller_speed_controller::PullerRegulationMode;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use smol::lock::{Mutex, RwLock};
|
||||
use std::{
|
||||
sync::{Arc, Weak},
|
||||
time::Instant,
|
||||
};
|
||||
use uom::si::{
|
||||
f64::{Length, Velocity},
|
||||
length::millimeter,
|
||||
};
|
||||
|
||||
use crate::machines::{
|
||||
MACHINE_BUFFER_V1, VENDOR_QITECH, buffer1::api::ConnectedMachineState, winder2::Winder2,
|
||||
MACHINE_BUFFER_V1, VENDOR_QITECH,
|
||||
buffer1::{
|
||||
api::{ConnectedMachineState, CurrentInputSpeedState, 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 puller_speed_controller: PullerSpeedController,
|
||||
|
||||
pub lift_step_converter: LinearStepConverter,
|
||||
|
||||
// socketio
|
||||
namespace: Buffer1Namespace,
|
||||
@@ -61,7 +85,20 @@ impl BufferV1 {
|
||||
|
||||
impl BufferV1 {
|
||||
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 {
|
||||
puller_speed: puller_speed.get::<meter_per_minute>(),
|
||||
};
|
||||
|
||||
let event = live_values.build();
|
||||
self.namespace.emit(BufferV1Events::LiveValues(event));
|
||||
@@ -86,6 +123,24 @@ impl BufferV1 {
|
||||
})
|
||||
.unwrap_or(false),
|
||||
},
|
||||
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();
|
||||
@@ -94,45 +149,85 @@ impl BufferV1 {
|
||||
}
|
||||
|
||||
impl BufferV1 {
|
||||
// To be implemented
|
||||
fn fill_buffer(&mut self) {
|
||||
todo!();
|
||||
// stop the winder until the buffer is ful
|
||||
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::Pull);
|
||||
self.update_winder2_buffer_state(BufferState::Emptying);
|
||||
}
|
||||
|
||||
fn update_winder2_mode(&mut self, mode: Winder2Mode) {
|
||||
self.get_winder(|winder2| {
|
||||
if winder2.mode != mode {
|
||||
winder2.mode = mode;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn update_winder2_buffer_state(&mut self, state: BufferState) {
|
||||
self.get_winder(|winder2| {
|
||||
if winder2.buffer_state != state {
|
||||
winder2.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(false);
|
||||
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);
|
||||
}
|
||||
|
||||
fn switch_mode(&mut self, mode: BufferV1Mode) {
|
||||
@@ -142,8 +237,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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -153,9 +249,91 @@ impl BufferV1 {
|
||||
self.switch_mode(mode);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
/// Connecting/Disconnecting machine
|
||||
// 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(t);
|
||||
if self.can_move() {
|
||||
let steps_per_second = self.lift_step_converter.velocity_to_steps(linear_velocity);
|
||||
let _ = self.lift.set_speed(steps_per_second);
|
||||
} else {
|
||||
let _ = self.lift.set_speed(0.0);
|
||||
}
|
||||
}
|
||||
|
||||
fn can_move(&mut self) -> bool {
|
||||
match self.lift_end_stop.get_value() {
|
||||
Ok(reached) => {
|
||||
if reached {
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Connecting/Disconnecting machine
|
||||
impl BufferV1 {
|
||||
/// set connected winder
|
||||
pub fn set_connected_winder(
|
||||
@@ -240,11 +418,34 @@ impl BufferV1 {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This helper function provides an easy way
|
||||
/// to get the machine out of the Weak Reference
|
||||
///
|
||||
/// Usage:
|
||||
///
|
||||
/// self.get_winder(|winder2| {
|
||||
/// winder2.do_something // Use the Winder here as usual
|
||||
/// });
|
||||
fn get_winder<F, R>(&self, func: F) -> Option<R>
|
||||
where
|
||||
F: FnOnce(&mut Winder2) -> R,
|
||||
{
|
||||
self.connected_winder
|
||||
.as_ref()?
|
||||
.machine
|
||||
.upgrade()
|
||||
.map(|winder_arc| {
|
||||
let mut winder = block_on(winder_arc.lock());
|
||||
func(&mut winder)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
|
||||
pub enum BufferV1Mode {
|
||||
Standby,
|
||||
FillingBuffer,
|
||||
EmptyingBuffer,
|
||||
Hold,
|
||||
Filling,
|
||||
Emptying,
|
||||
}
|
||||
@@ -1,27 +1,35 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use anyhow::Error;
|
||||
use control_core::converters::linear_step_converter::LinearStepConverter;
|
||||
use control_core::machines::identification::DeviceHardwareIdentification;
|
||||
use control_core::machines::new::{
|
||||
MachineNewHardware, MachineNewParams, MachineNewTrait, get_device_identification_by_role,
|
||||
get_ethercat_device_by_index, get_subdevice_by_index, validate_no_role_dublicates,
|
||||
validate_same_machine_identification_unique,
|
||||
};
|
||||
use control_core::uom_extensions::velocity::meter_per_minute;
|
||||
use ethercat_hal::coe::ConfigurableDevice;
|
||||
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::{self, EL7031_0030, EL7031_0030_IDENTITY_A};
|
||||
use ethercat_hal::devices::el7031_0030::{
|
||||
self, 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::devices::{EthercatDeviceUsed, downcast_device, subdevice_identity_to_tuple};
|
||||
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_tower_controller::BufferLiftController;
|
||||
use crate::machines::buffer1::puller_speed_controller::PullerSpeedController;
|
||||
|
||||
use super::{BufferV1, api::Buffer1Namespace};
|
||||
|
||||
@@ -85,7 +93,7 @@ impl MachineNewTrait for BufferV1 {
|
||||
}
|
||||
|
||||
// Role 1
|
||||
// 1x Stepper Buffer
|
||||
// 1x Stepper Buffer Lift
|
||||
// EL7041-0052
|
||||
let (el7041, _el7041_config) = {
|
||||
let device_identification =
|
||||
@@ -125,7 +133,7 @@ impl MachineNewTrait for BufferV1 {
|
||||
..Default::default()
|
||||
},
|
||||
stm_motor: StmMotorConfiguration {
|
||||
max_current: 6000,
|
||||
max_current: 2800,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
@@ -145,7 +153,7 @@ impl MachineNewTrait for BufferV1 {
|
||||
// Role 2
|
||||
// 1x Stepper Puller
|
||||
// EL7031
|
||||
let (_el7031_0030, _el7031_0030_config) = {
|
||||
let (el7031_0030, _el7031_0030_config) = {
|
||||
let device_identification =
|
||||
get_device_identification_by_role(params.device_group, 2)?;
|
||||
let device_hardware_identification_ethercat =
|
||||
@@ -205,11 +213,17 @@ impl MachineNewTrait for BufferV1 {
|
||||
};
|
||||
|
||||
// Controller
|
||||
let buffer_tower_controller = BufferTowerController::new(StepperVelocityEL70x1::new(
|
||||
el7041.clone(),
|
||||
EL7041_0052Port::STM1,
|
||||
));
|
||||
let buffer_tower_controller = BufferLiftController::new(
|
||||
StepperVelocityEL70x1::new(el7041.clone(), EL7041_0052Port::STM1),
|
||||
LinearStepConverter::from_diameter(200, Length::new::<centimeter>(8.0)),
|
||||
);
|
||||
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)),
|
||||
);
|
||||
|
||||
// Get machine identification unique
|
||||
let machine_id = params
|
||||
.device_group
|
||||
.first()
|
||||
@@ -220,10 +234,21 @@ impl MachineNewTrait for BufferV1 {
|
||||
|
||||
// create buffer instance
|
||||
let mut buffer: Self = Self {
|
||||
lift: StepperVelocityEL70x1::new(el7041.clone(), EL7041_0052Port::STM1),
|
||||
lift_end_stop: DigitalInput::new(el7041, EL7041_0052Port::DI1),
|
||||
puller: StepperVelocityEL70x1::new(
|
||||
el7031_0030.clone(),
|
||||
EL7031_0030StepperPort::STM1,
|
||||
),
|
||||
lift_step_converter: LinearStepConverter::from_diameter(
|
||||
200,
|
||||
Length::new::<centimeter>(5.0),
|
||||
),
|
||||
namespace: Buffer1Namespace::new(params.socket_queue_tx.clone()),
|
||||
last_measurement_emit: Instant::now(),
|
||||
mode: BufferV1Mode::Standby,
|
||||
buffer_tower_controller,
|
||||
buffer_lift_controller: buffer_tower_controller,
|
||||
puller_speed_controller: puller_speed_controller,
|
||||
machine_manager: params.machine_manager.clone(),
|
||||
machine_identification_unique: machine_id,
|
||||
connected_winder: None,
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
@@ -16,6 +16,9 @@ impl MachineAct for Winder2 {
|
||||
// automatically stops or pulls after N Meters if enabled
|
||||
self.stop_or_pull_spool(now);
|
||||
|
||||
// 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();
|
||||
|
||||
@@ -84,6 +84,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: Option<ConnectedMachine<Weak<Mutex<BufferV1>>>>,
|
||||
@@ -391,7 +393,7 @@ impl Winder2 {}
|
||||
|
||||
/// Implement Mode
|
||||
impl Winder2 {
|
||||
fn set_mode(&mut self, mode: &Winder2Mode) {
|
||||
pub fn set_mode(&mut self, mode: &Winder2Mode) {
|
||||
let should_update = *mode != Winder2Mode::Wind || self.can_wind();
|
||||
|
||||
if should_update {
|
||||
@@ -779,8 +781,24 @@ impl Winder2 {
|
||||
}
|
||||
}
|
||||
|
||||
/// implement machine connection
|
||||
/// Buffer
|
||||
impl Winder2 {
|
||||
/// Sync Puller to Buffer
|
||||
pub fn sync_buffer_speed(&mut self) {
|
||||
// send puller speed to buffer, if connected
|
||||
self.get_buffer(|buffer| {
|
||||
buffer.buffer_lift_controller.set_target_output_speed(
|
||||
Velocity::get::<meter_per_minute>(&self.puller_speed_controller.get_target_speed()),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// Set Buffer State
|
||||
pub fn set_buffer_state(&mut self, state: BufferState) {
|
||||
self.buffer_state = state;
|
||||
}
|
||||
|
||||
/// Buffer Connection Logic
|
||||
/// set connected buffer
|
||||
pub fn set_connected_buffer(
|
||||
&mut self,
|
||||
@@ -863,6 +881,28 @@ impl Winder2 {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This helper function provides an easy way
|
||||
/// to get the machine out of the Weak Reference
|
||||
///
|
||||
/// Usage:
|
||||
///
|
||||
/// self.get_buffer(|buffer| {
|
||||
/// buffer.do_something // Use the Buffer here as usual
|
||||
/// });
|
||||
fn get_buffer<F, R>(&self, func: F) -> Option<R>
|
||||
where
|
||||
F: FnOnce(&mut BufferV1) -> R,
|
||||
{
|
||||
self.connected_buffer
|
||||
.as_ref()?
|
||||
.machine
|
||||
.upgrade()
|
||||
.map(|buffer_arc| {
|
||||
let mut buffer = block_on(buffer_arc.lock());
|
||||
func(&mut buffer)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
@@ -916,6 +956,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 {
|
||||
|
||||
@@ -3,6 +3,7 @@ use std::time::Instant;
|
||||
use super::api::Winder2Namespace;
|
||||
use super::tension_arm::TensionArm;
|
||||
use super::{Winder2, Winder2Mode};
|
||||
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;
|
||||
@@ -381,6 +382,7 @@ impl MachineNewTrait for Winder2 {
|
||||
machine_manager: params.machine_manager.clone(),
|
||||
machine_identification_unique: machine_id,
|
||||
connected_buffer: None,
|
||||
buffer_state: BufferState::Disconnected,
|
||||
};
|
||||
|
||||
// initalize events
|
||||
|
||||
Reference in new issue
Block a user