Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d6102774bc | ||
|
|
b0b4e2732b | ||
|
|
b54fb95cd4 | ||
|
|
5bace31ab0 | ||
|
|
274cff4494 | ||
|
|
24c9a8f9ac | ||
|
|
d5aeb6f751 | ||
|
|
8c09287a0e | ||
|
|
520148b149 | ||
|
|
2f270e17b4 | ||
|
|
3b3f04d307 | ||
|
|
2ffb3e5dfa | ||
|
|
c5b855e9aa | ||
|
|
8bb77d2a34 | ||
|
|
4a4e57663a | ||
|
|
1355aea687 | ||
|
|
9ea05f94e2 | ||
|
|
a119a89835 | ||
|
|
1dd9c5f263 | ||
|
|
e99a94c6e8 | ||
|
|
7d6b4e1446 | ||
|
|
22b6b464f5 | ||
|
|
257c5c453e | ||
|
|
8303c091f7 | ||
|
|
6030b5a795 | ||
|
|
60b016229f | ||
|
|
15e6db0c1b | ||
|
|
d900195566 |
No files matched your search
@@ -261,25 +261,6 @@ export function Winder2ControlPage() {
|
||||
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}
|
||||
|
||||
@@ -608,7 +608,7 @@ export function useWinder2() {
|
||||
m.machine_identification_unique.machine_identification.vendor ===
|
||||
VENDOR_QITECH &&
|
||||
m.machine_identification_unique.machine_identification.machine ===
|
||||
0x0008,
|
||||
0x0006, // id of laser machine
|
||||
),
|
||||
[machines, machineIdentification],
|
||||
);
|
||||
|
||||
@@ -34,8 +34,8 @@ export const liveValuesEventDataSchema = z.object({
|
||||
/**
|
||||
* Puller regulation type enum
|
||||
*/
|
||||
export const pullerRegulationSchema = z.enum(["Speed", "Diameter"]);
|
||||
export type PullerRegulation = z.infer<typeof pullerRegulationSchema>;
|
||||
// export const pullerRegulationSchema = z.enum(["Speed", "Diameter"]);
|
||||
// export type PullerRegulation = z.infer<typeof pullerRegulationSchema>;
|
||||
|
||||
/**
|
||||
* Gear ratio enum for winding speed
|
||||
@@ -120,7 +120,7 @@ export const traverseStateSchema = z.object({
|
||||
* Puller state schema
|
||||
*/
|
||||
export const pullerStateSchema = z.object({
|
||||
regulation: pullerRegulationSchema,
|
||||
// regulation: pullerRegulationSchema,
|
||||
target_speed: z.number(),
|
||||
target_diameter: z.number(),
|
||||
forward: z.boolean(),
|
||||
@@ -273,7 +273,7 @@ export function winder2MessageHandler(
|
||||
};
|
||||
|
||||
try {
|
||||
if (eventName === "StateEvent") {
|
||||
if (eventName === "State") {
|
||||
console.log(event);
|
||||
// Parse and validate the state event
|
||||
const stateEvent = stateEventSchema.parse(event);
|
||||
@@ -294,7 +294,7 @@ export function winder2MessageHandler(
|
||||
? stateEvent
|
||||
: state.defaultState,
|
||||
}));
|
||||
} else if (eventName === "LiveValuesEvent") {
|
||||
} else if (eventName === "LiveValues") {
|
||||
// Parse and validate the live values event
|
||||
const liveValuesEvent = liveValuesEventSchema.parse(event);
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ impl MachineAct for AnalogInputTestMachine {
|
||||
.expect("Failed to send values");
|
||||
sender.close();
|
||||
}
|
||||
MachineMessage::ReceiveLiveValues(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -61,6 +61,7 @@ impl MachineAct for AquaPathV1 {
|
||||
.expect("Failed to send values");
|
||||
sender.close();
|
||||
}
|
||||
MachineMessage::ReceiveLiveValues(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,6 +49,7 @@ impl MachineAct for BufferV1 {
|
||||
.expect("Failed to send values");
|
||||
sender.close();
|
||||
}
|
||||
MachineMessage::ReceiveLiveValues(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,7 @@ impl MachineAct for DigitalInputTestMachine {
|
||||
.expect("Failed to send values");
|
||||
sender.close();
|
||||
}
|
||||
MachineMessage::ReceiveLiveValues(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,6 +77,7 @@ impl MachineAct for ExtruderV2 {
|
||||
.expect("Failed to send values");
|
||||
sender.close();
|
||||
}
|
||||
MachineMessage::ReceiveLiveValues(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,6 +51,7 @@ impl MachineAct for ExtruderV2 {
|
||||
.expect("Failed to send values");
|
||||
sender.close();
|
||||
}
|
||||
MachineMessage::ReceiveLiveValues(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,6 +80,7 @@ impl MachineAct for ExtruderV3 {
|
||||
.expect("Failed to send values");
|
||||
sender.close();
|
||||
}
|
||||
MachineMessage::ReceiveLiveValues(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,6 +48,7 @@ impl MachineAct for ExtruderV2 {
|
||||
.expect("Failed to send values");
|
||||
sender.close();
|
||||
}
|
||||
MachineMessage::ReceiveLiveValues(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
use units::angle::revolution;
|
||||
use units::f64::*;
|
||||
|
||||
use super::{Clamp, ClampedValue};
|
||||
|
||||
pub fn clamp_revolution_uom(value: Angle, min: Angle, max: Angle) -> ClampedValue<Angle>
|
||||
{
|
||||
let value = value.get::<revolution>();
|
||||
let min = min.get::<revolution>();
|
||||
let max = max.get::<revolution>();
|
||||
|
||||
// clamp
|
||||
let clamped_value = clamp_revolution(value, min, max);
|
||||
|
||||
// convert back to uom
|
||||
ClampedValue {
|
||||
value: Angle::new::<revolution>(clamped_value.value),
|
||||
clamp: clamped_value.clamp
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clamp_revolution(value: f64, min: f64, max: f64) -> ClampedValue<f64>
|
||||
{
|
||||
// normalize value from 0..1
|
||||
let value = wrap_revolution(value);
|
||||
let min = wrap_revolution(min);
|
||||
let max = wrap_revolution(max);
|
||||
|
||||
// check if in acceptable range
|
||||
if revolution_in_range(value, min, max) {
|
||||
return ClampedValue::new(value, Clamp::None);
|
||||
}
|
||||
|
||||
// calculates the distance between min and max
|
||||
let (clamp_to_min_min, clamp_to_min_max, clamp_to_max_min, clamp_to_max_max) =
|
||||
clamping_ranges(min, max);
|
||||
|
||||
// check if in min clamping range
|
||||
if revolution_in_range(value, clamp_to_min_min, clamp_to_min_max) {
|
||||
return ClampedValue::new(min, Clamp::Min);
|
||||
}
|
||||
// check if in max clamping range
|
||||
if revolution_in_range(value, clamp_to_max_min, clamp_to_max_max) {
|
||||
return ClampedValue::new(max, Clamp::Max);
|
||||
}
|
||||
|
||||
// at this point our input value should be either retured (cause in spec) or clamped to min or max
|
||||
// so this point should never be reached
|
||||
// in case it does we just clamp to min
|
||||
ClampedValue::new(min, Clamp::Min)
|
||||
//TODO: consider using unreachable!() if truly unreachable
|
||||
}
|
||||
|
||||
pub fn scale_revolution_to_range(value: f64, min: f64, max: f64) -> f64
|
||||
{
|
||||
// we calculate the distance between min and max
|
||||
let distance = revolution_distance(min, max);
|
||||
|
||||
// we scale the value to the distance
|
||||
|
||||
(value - min) / distance
|
||||
}
|
||||
|
||||
fn clamping_ranges(min: f64, max: f64) -> (f64, f64, f64, f64)
|
||||
{
|
||||
// normalize min and max
|
||||
let min = wrap_revolution(min);
|
||||
let max = wrap_revolution(max);
|
||||
|
||||
// calculates the distance between min and max (distance A in the test)
|
||||
let in_spec_distance = revolution_distance(min, max);
|
||||
|
||||
// calculate distance B and clamping distance as per the test comment
|
||||
let out_spec_distance = 1.0 - in_spec_distance;
|
||||
let clamping_distance = out_spec_distance / 2.0;
|
||||
|
||||
let clamp_to_min_min = wrap_revolution(min - clamping_distance);
|
||||
let clamp_to_min_max = min;
|
||||
let clamp_to_max_min = max;
|
||||
let clamp_to_max_max = wrap_revolution(max + clamping_distance);
|
||||
|
||||
(
|
||||
clamp_to_min_min,
|
||||
clamp_to_min_max,
|
||||
clamp_to_max_min,
|
||||
clamp_to_max_max,
|
||||
)
|
||||
}
|
||||
|
||||
fn revolution_distance(min: f64, max: f64) -> f64
|
||||
{
|
||||
// Normalize the values to ensure they're in the [0, 1) range
|
||||
let normalized_min = wrap_revolution(min);
|
||||
let normalized_max = wrap_revolution(max);
|
||||
|
||||
// Check if the range crosses zero
|
||||
if normalized_min > normalized_max {
|
||||
// For ranges that cross zero (e.g., min = 0.9, max = 0.1)
|
||||
// The distance is (1 - min) + max
|
||||
1.0 - normalized_min + normalized_max
|
||||
} else {
|
||||
// For normal ranges (e.g., min = 0.1, max = 0.3)
|
||||
// The distance is simply max - min
|
||||
normalized_max - normalized_min
|
||||
}
|
||||
}
|
||||
|
||||
fn wrap_revolution(value: f64) -> f64
|
||||
{
|
||||
debug_assert!(value.is_finite(), "value must be finite");
|
||||
|
||||
match value % 1.0
|
||||
{
|
||||
0.0 if value >= 1.0 => 1.0,
|
||||
n if n < 0.0 => n + 1.0,
|
||||
n => n,
|
||||
}
|
||||
}
|
||||
|
||||
fn revolution_in_range(value: f64, min: f64, max: f64) -> bool
|
||||
{
|
||||
debug_assert!(value.is_finite(), "value must be finite");
|
||||
debug_assert!(min.is_finite(), "min must be finite");
|
||||
debug_assert!(max.is_finite(), "max must be finite");
|
||||
|
||||
debug_assert!(value >= 0.0 && value < 1.0, "value out of [0,1)");
|
||||
debug_assert!(min >= 0.0 && min < 1.0, "min out of [0,1)");
|
||||
debug_assert!(max >= 0.0 && max < 1.0, "max out of [0,1)");
|
||||
|
||||
let wraps = min > max;
|
||||
|
||||
if wraps {
|
||||
// Range wraps across boundary (e.g., 0.9 → 0.1)
|
||||
min <= value || value <= max
|
||||
} else {
|
||||
// Standard interval
|
||||
min <= value && value <= max
|
||||
}
|
||||
}
|
||||
File renamed without changes.
@@ -0,0 +1,29 @@
|
||||
// keep for backwards reference
|
||||
mod clamp_revolution_old;
|
||||
|
||||
mod clamp_revolution;
|
||||
pub use clamp_revolution::clamp_revolution;
|
||||
pub use clamp_revolution::clamp_revolution_uom;
|
||||
pub use clamp_revolution::scale_revolution_to_range;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Clamp
|
||||
{
|
||||
None,
|
||||
Min,
|
||||
Max,
|
||||
}
|
||||
|
||||
pub struct ClampedValue<T>
|
||||
{
|
||||
pub value: T,
|
||||
pub clamp: Clamp,
|
||||
}
|
||||
|
||||
impl<T> ClampedValue<T>
|
||||
{
|
||||
pub fn new(value: T, clamp: Clamp) -> Self
|
||||
{
|
||||
Self { value, clamp }
|
||||
}
|
||||
}
|
||||
@@ -53,6 +53,7 @@ impl MachineAct for IP20TestMachine {
|
||||
.expect("Failed to send values");
|
||||
sender.close();
|
||||
}
|
||||
MachineMessage::ReceiveLiveValues(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
use super::LaserMachine;
|
||||
use crate::MachinesLiveValues;
|
||||
use crate::MachineAct;
|
||||
use crate::MachineMessage;
|
||||
use crate::MachineValues;
|
||||
@@ -34,6 +35,26 @@ impl MachineAct for LaserMachine {
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
// send live values to all connected machines every frame
|
||||
if now.duration_since(self.last_machine_send) > Duration::from_millis(1) {
|
||||
let values = MachinesLiveValues::Laser(self.get_live_values());
|
||||
|
||||
for machine in self.connected_machines.iter() {
|
||||
const CHANNEL_MESSAGE_CAP: usize = 5;
|
||||
|
||||
// ensure we don' flood the channel. Cap it at 5
|
||||
if machine.connection.len() <= CHANNEL_MESSAGE_CAP {
|
||||
use MachineMessage::ReceiveLiveValues;
|
||||
_ = machine
|
||||
.connection
|
||||
.try_send(ReceiveLiveValues(values.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
// reset timer
|
||||
self.last_machine_send = now;
|
||||
}
|
||||
|
||||
// more than 33ms have passed since last emit (30 "fps" target)
|
||||
if now.duration_since(self.last_measurement_emit) > Duration::from_secs_f64(1.0 / 30.0) {
|
||||
self.emit_live_values();
|
||||
@@ -60,12 +81,18 @@ impl MachineAct for LaserMachine {
|
||||
use crate::MachineApi;
|
||||
let _res = self.api_mutate(value);
|
||||
}
|
||||
MachineMessage::ConnectToMachine(_machine_connection) =>
|
||||
/*Doesnt connect to any Machine so do nothing*/
|
||||
{}
|
||||
MachineMessage::DisconnectMachine(_machine_connection) =>
|
||||
/*Doesnt connect to any Machine so do nothing*/
|
||||
{}
|
||||
MachineMessage::ConnectToMachine(machine_connection) => {
|
||||
if self.connected_machines.len() >= Self::MAX_CONNECTIONS {
|
||||
tracing::debug!("Not adding machine connection. Max capacity reached!");
|
||||
return;
|
||||
}
|
||||
|
||||
self.connected_machines.push(machine_connection);
|
||||
}
|
||||
MachineMessage::DisconnectMachine(machine_connection) => {
|
||||
self.connected_machines
|
||||
.retain(|machine| machine.ident != machine_connection.ident);
|
||||
}
|
||||
MachineMessage::RequestValues(sender) => {
|
||||
sender
|
||||
.send_blocking(MachineValues {
|
||||
@@ -79,6 +106,7 @@ impl MachineAct for LaserMachine {
|
||||
|
||||
()
|
||||
}
|
||||
MachineMessage::ReceiveLiveValues(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ use crate::{
|
||||
MACHINE_LASER_V1, VENDOR_QITECH,
|
||||
machine_identification::{MachineIdentification, MachineIdentificationUnique},
|
||||
};
|
||||
use crate::{Machine, MachineMessage};
|
||||
use crate::{Machine, MachineConnection, MachineMessage};
|
||||
use api::{LaserEvents, LaserMachineNamespace, LaserState, LiveValuesEvent, StateEvent};
|
||||
use control_core::socketio::namespace::NamespaceCacheingLogic;
|
||||
use smol::{
|
||||
@@ -53,6 +53,11 @@ pub struct LaserMachine {
|
||||
/// This way we can signal to the client that the first state emission is a default state
|
||||
emitted_default_state: bool,
|
||||
did_change_state: bool,
|
||||
|
||||
/// last time we sent live values to connected machines
|
||||
last_machine_send: Instant,
|
||||
|
||||
connected_machines: Vec<MachineConnection>,
|
||||
}
|
||||
|
||||
impl Machine for LaserMachine {
|
||||
@@ -97,6 +102,8 @@ impl LaserMachine {
|
||||
machine: MACHINE_LASER_V1,
|
||||
};
|
||||
|
||||
pub const MAX_CONNECTIONS: usize = 2;
|
||||
|
||||
pub fn get_live_values(&self) -> LiveValuesEvent {
|
||||
let diameter = self.diameter.get::<millimeter>();
|
||||
let x_diameter = self.x_diameter.map(|x| x.get::<millimeter>());
|
||||
|
||||
@@ -55,6 +55,8 @@ impl MachineNewTrait for LaserMachine {
|
||||
higher_tolerance: Length::new::<millimeter>(0.05),
|
||||
in_tolerance: true,
|
||||
did_change_state: true,
|
||||
last_machine_send: Instant::now(),
|
||||
connected_machines: Vec::new(),
|
||||
};
|
||||
|
||||
Ok(laser_machine)
|
||||
|
||||
+29
-7
@@ -26,6 +26,7 @@ pub mod extruder1;
|
||||
pub mod extruder2;
|
||||
pub mod ip20_test_machine;
|
||||
pub mod laser;
|
||||
mod live_values;
|
||||
pub mod machine_identification;
|
||||
pub mod mock;
|
||||
pub mod motor_test_machine;
|
||||
@@ -38,6 +39,11 @@ pub mod wago_ai_test_machine;
|
||||
pub mod wago_do_test_machine;
|
||||
pub mod wago_power;
|
||||
pub mod winder2;
|
||||
pub mod types;
|
||||
pub mod helpers;
|
||||
|
||||
pub use live_values::MachinesLiveValues;
|
||||
// pub use live_values::
|
||||
|
||||
pub const VENDOR_QITECH: u16 = 0x0001;
|
||||
pub const MACHINE_WINDER_V1: u16 = 0x0002;
|
||||
@@ -75,6 +81,7 @@ pub struct CrossConnection {
|
||||
pub enum AsyncThreadMessage {
|
||||
NoMsg,
|
||||
ConnectOneWayRequest(CrossConnection),
|
||||
ConnectTwoWayRequest(CrossConnection),
|
||||
DisconnectMachines(CrossConnection),
|
||||
}
|
||||
|
||||
@@ -307,6 +314,7 @@ pub enum MachineMessage {
|
||||
ConnectToMachine(MachineConnection),
|
||||
DisconnectMachine(MachineConnection),
|
||||
RequestValues(Sender<MachineValues>),
|
||||
ReceiveLiveValues(MachinesLiveValues),
|
||||
}
|
||||
|
||||
pub trait MachineApi {
|
||||
@@ -482,6 +490,10 @@ pub trait MachineWithChannel: Send + Debug + Sync {
|
||||
fn get_live_values(&self) -> Option<Self::LiveValues> {
|
||||
None
|
||||
}
|
||||
|
||||
fn on_receive_live_values(&mut self,live_values: MachinesLiveValues) { _ = live_values; }
|
||||
fn on_connect_to_machine(&mut self,connection: MachineConnection) { _ = connection; }
|
||||
fn on_disconnect_machine(&mut self,connection: MachineConnection) { _ = connection; }
|
||||
}
|
||||
|
||||
impl<C> MachineApi for C
|
||||
@@ -511,9 +523,15 @@ impl<C> MachineAct for C
|
||||
where
|
||||
C: MachineWithChannel,
|
||||
{
|
||||
fn act(&mut self, now: Instant) {
|
||||
while let Ok(msg) = self.get_machine_channel_mut().api_receiver.try_recv() {
|
||||
self.act_machine_message(msg);
|
||||
fn act(&mut self, now: Instant)
|
||||
{
|
||||
for _ in 0..5 // Limit to 5 requests max
|
||||
{
|
||||
match self.get_machine_channel_mut().api_receiver.try_recv()
|
||||
{
|
||||
Ok(msg) => self.act_machine_message(msg),
|
||||
Err(_) => break,
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(e) = self.update(now) {
|
||||
@@ -535,11 +553,11 @@ where
|
||||
MachineMessage::HttpApiJsonRequest(value) => {
|
||||
let _ = self.api_mutate(value);
|
||||
}
|
||||
MachineMessage::ConnectToMachine(_machine_connection) => {
|
||||
todo!();
|
||||
MachineMessage::ConnectToMachine(connection) => {
|
||||
self.on_connect_to_machine(connection);
|
||||
}
|
||||
MachineMessage::DisconnectMachine(_machine_connection) => {
|
||||
todo!();
|
||||
MachineMessage::DisconnectMachine(connection) => {
|
||||
self.on_disconnect_machine(connection);
|
||||
}
|
||||
MachineMessage::RequestValues(sender) => {
|
||||
sender
|
||||
@@ -552,6 +570,10 @@ where
|
||||
.expect("Failed to send values");
|
||||
sender.close();
|
||||
}
|
||||
MachineMessage::ReceiveLiveValues(live_values) =>
|
||||
{
|
||||
self.on_receive_live_values(live_values);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
use crate::laser::api::LiveValuesEvent as LaserLiveValues;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum MachinesLiveValues {
|
||||
Laser(LaserLiveValues),
|
||||
}
|
||||
@@ -68,6 +68,7 @@ impl MachineAct for MockMachine {
|
||||
.expect("Failed to send values");
|
||||
sender.close();
|
||||
}
|
||||
MachineMessage::ReceiveLiveValues(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,7 @@ impl MachineAct for TestMachine {
|
||||
.expect("Failed to send values");
|
||||
sender.close();
|
||||
}
|
||||
MachineMessage::ReceiveLiveValues(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ impl MachineAct for TestMachineStepper {
|
||||
MachineMessage::ConnectToMachine(_machine_connection) => {}
|
||||
MachineMessage::DisconnectMachine(_machine_connection) => {}
|
||||
MachineMessage::RequestValues(_sender) => {}
|
||||
MachineMessage::ReceiveLiveValues(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug,Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub enum Direction
|
||||
{
|
||||
Forward,
|
||||
Reverse,
|
||||
}
|
||||
|
||||
impl Direction
|
||||
{
|
||||
pub fn from_bool(forward: bool) -> Self
|
||||
{
|
||||
match forward
|
||||
{
|
||||
true => Self::Forward,
|
||||
false => Self::Reverse,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_forward(self) -> bool
|
||||
{
|
||||
self == Self::Forward
|
||||
}
|
||||
|
||||
pub fn is_reverse(self) -> bool
|
||||
{
|
||||
self != Self::Forward
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,7 @@ impl MachineAct for Wago8chDigitalIOTestMachine {
|
||||
.expect("Failed to send values");
|
||||
sender.close();
|
||||
}
|
||||
MachineMessage::ReceiveLiveValues(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ impl MachineAct for WagoAiTestMachine {
|
||||
.expect("Failed to send values");
|
||||
sender.close();
|
||||
}
|
||||
MachineMessage::ReceiveLiveValues(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ impl MachineAct for WagoDOTestMachine {
|
||||
.expect("Failed to send values");
|
||||
sender.close();
|
||||
}
|
||||
MachineMessage::ReceiveLiveValues(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use super::Winder2;
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use crate::{MachineAct, MachineMessage, MachineValues};
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
impl MachineAct for Winder2 {
|
||||
fn act(&mut self, now: Instant) {
|
||||
let machine_message = self.api_receiver.try_recv();
|
||||
match machine_message {
|
||||
Ok(machine_message) => self.act_machine_message(machine_message),
|
||||
Err(_e) => (),
|
||||
};
|
||||
// sync the spool speed
|
||||
self.sync_spool_speed(now);
|
||||
|
||||
// sync the puller speed
|
||||
self.sync_puller_speed(now);
|
||||
|
||||
// sync the traverse speed
|
||||
self.sync_traverse_speed();
|
||||
|
||||
// automatically stops or pulls after N Meters if enabled
|
||||
self.stop_or_pull_spool(now);
|
||||
|
||||
if self.traverse_controller.did_change_state() {
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
fn act_machine_message(&mut self, msg: MachineMessage) {
|
||||
match msg {
|
||||
MachineMessage::SubscribeNamespace(namespace) => {
|
||||
self.namespace.namespace = Some(namespace);
|
||||
self.emit_state();
|
||||
}
|
||||
MachineMessage::UnsubscribeNamespace => self.namespace.namespace = None,
|
||||
MachineMessage::HttpApiJsonRequest(value) => {
|
||||
use crate::MachineApi;
|
||||
let _res = self.api_mutate(value);
|
||||
}
|
||||
MachineMessage::ConnectToMachine(machine_connection) => {
|
||||
if self.connected_machines.len() >= self.max_connected_machines {
|
||||
tracing::debug!(
|
||||
"Refusing to add Machine Connection {:?}, since self.connected_machines would be over the limit of {:?}",
|
||||
machine_connection,
|
||||
self.max_connected_machines
|
||||
);
|
||||
return;
|
||||
}
|
||||
self.connected_machines.push(machine_connection);
|
||||
}
|
||||
MachineMessage::DisconnectMachine(_machine_connection) => {
|
||||
self.connected_machines.clear();
|
||||
}
|
||||
MachineMessage::RequestValues(sender) => {
|
||||
sender
|
||||
.send_blocking(MachineValues {
|
||||
state: serde_json::to_value(self.build_state_event())
|
||||
.expect("Failed to serialize state"),
|
||||
live_values: serde_json::to_value(self.get_live_values())
|
||||
.expect("Failed to serialize live values"),
|
||||
})
|
||||
.expect("Failed to send values");
|
||||
sender.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,402 +0,0 @@
|
||||
#[cfg(feature = "mock-machine")]
|
||||
mod winder2_imports {
|
||||
pub use super::super::Winder2Mode;
|
||||
pub use super::super::puller_speed_controller::{GearRatio, PullerRegulationMode};
|
||||
pub use control_core::socketio::{
|
||||
event::{Event, GenericEvent},
|
||||
namespace::{
|
||||
CacheFn, CacheableEvents, Namespace, NamespaceCacheingLogic, cache_duration,
|
||||
cache_first_and_last_event,
|
||||
},
|
||||
};
|
||||
|
||||
pub use control_core_derive::BuildEvent;
|
||||
pub use serde::{Deserialize, Serialize};
|
||||
pub use serde_json::Value;
|
||||
pub use smol::lock::Mutex;
|
||||
pub use std::{
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
pub use tracing::instrument;
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
mod winder2_imports {
|
||||
pub use super::super::puller_speed_controller::{GearRatio, PullerRegulationMode};
|
||||
pub use super::super::{Winder2, Winder2Mode};
|
||||
pub use control_core::socketio::{
|
||||
event::{Event, GenericEvent},
|
||||
namespace::{
|
||||
CacheFn, CacheableEvents, Namespace, NamespaceCacheingLogic, cache_duration,
|
||||
cache_first_and_last_event,
|
||||
},
|
||||
};
|
||||
|
||||
pub use control_core_derive::BuildEvent;
|
||||
pub use serde::{Deserialize, Serialize};
|
||||
pub use serde_json::Value;
|
||||
pub use smol::lock::Mutex;
|
||||
pub use std::{
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
pub use tracing::instrument;
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use smol::channel::Sender;
|
||||
pub use winder2_imports::*;
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use crate::{MachineApi, MachineMessage};
|
||||
use crate::{MachineCrossConnectionState, machine_identification::MachineIdentificationUnique};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||
pub enum Mode {
|
||||
#[default]
|
||||
Standby,
|
||||
Hold,
|
||||
Pull,
|
||||
Wind,
|
||||
}
|
||||
|
||||
impl From<Winder2Mode> for Mode {
|
||||
fn from(mode: Winder2Mode) -> Self {
|
||||
match mode {
|
||||
Winder2Mode::Standby => Self::Standby,
|
||||
Winder2Mode::Hold => Self::Hold,
|
||||
Winder2Mode::Pull => Self::Pull,
|
||||
Winder2Mode::Wind => Self::Wind,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Mode> for Winder2Mode {
|
||||
fn from(mode: Mode) -> Self {
|
||||
match mode {
|
||||
Mode::Standby => Self::Standby,
|
||||
Mode::Hold => Self::Hold,
|
||||
Mode::Pull => Self::Pull,
|
||||
Mode::Wind => Self::Wind,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub enum Mutation {
|
||||
// Traverse
|
||||
/// Position in mm from home point
|
||||
SetTraverseLimitOuter(f64),
|
||||
/// Position in mm from home point
|
||||
SetTraverseLimitInner(f64),
|
||||
/// Step size in mm for traverse movement
|
||||
SetTraverseStepSize(f64),
|
||||
/// Padding in mm for traverse movement limits
|
||||
SetTraversePadding(f64),
|
||||
GotoTraverseLimitOuter,
|
||||
GotoTraverseLimitInner,
|
||||
/// Find home point
|
||||
GotoTraverseHome,
|
||||
EnableTraverseLaserpointer(bool),
|
||||
|
||||
// Puller
|
||||
/// on = speed, off = stop
|
||||
SetPullerRegulationMode(PullerRegulationMode),
|
||||
SetPullerTargetSpeed(f64),
|
||||
SetPullerTargetDiameter(f64),
|
||||
SetPullerForward(bool),
|
||||
SetPullerGearRatio(GearRatio),
|
||||
|
||||
// Spool Speed Controller
|
||||
SetSpoolRegulationMode(super::spool_speed_controller::SpoolSpeedControllerType),
|
||||
SetSpoolMinMaxMinSpeed(f64),
|
||||
SetSpoolMinMaxMaxSpeed(f64),
|
||||
SetSpoolForward(bool),
|
||||
|
||||
// Adaptive Spool Speed Controller Parameters
|
||||
SetSpoolAdaptiveTensionTarget(f64),
|
||||
SetSpoolAdaptiveRadiusLearningRate(f64),
|
||||
SetSpoolAdaptiveMaxSpeedMultiplier(f64),
|
||||
SetSpoolAdaptiveAccelerationFactor(f64),
|
||||
SetSpoolAdaptiveDeaccelerationUrgencyMultiplier(f64),
|
||||
|
||||
// Spool Auto Stop/Pull
|
||||
SetSpoolAutomaticRequiredMeters(f64),
|
||||
SetSpoolAutomaticAction(SpoolAutomaticActionMode),
|
||||
ResetSpoolProgress,
|
||||
|
||||
// Tension Arm
|
||||
ZeroTensionArmAngle,
|
||||
|
||||
// Mode
|
||||
SetMode(Mode),
|
||||
|
||||
// Connected Machine
|
||||
SetConnectedMachine(MachineIdentificationUnique),
|
||||
|
||||
// Disconnect Machine
|
||||
DisconnectMachine(MachineIdentificationUnique),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone, Default)]
|
||||
pub struct LiveValuesEvent {
|
||||
/// traverse position in mm
|
||||
pub traverse_position: Option<f64>,
|
||||
/// puller speed in m/min
|
||||
pub puller_speed: f64,
|
||||
/// spool rpm
|
||||
pub spool_rpm: f64,
|
||||
/// tension arm angle in degrees
|
||||
pub tension_arm_angle: f64,
|
||||
// spool progress in meters (pulled distance of filament)
|
||||
pub spool_progress: f64,
|
||||
}
|
||||
|
||||
impl LiveValuesEvent {
|
||||
pub fn build(&self) -> Event<Self> {
|
||||
Event::new("LiveValuesEvent", self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone, BuildEvent)]
|
||||
pub struct StateEvent {
|
||||
pub is_default_state: bool,
|
||||
/// traverse state
|
||||
pub traverse_state: TraverseState,
|
||||
/// puller state
|
||||
pub puller_state: PullerState,
|
||||
/// spool automatic action state and progress
|
||||
pub spool_automatic_action_state: SpoolAutomaticActionState,
|
||||
/// mode state
|
||||
pub mode_state: ModeState,
|
||||
/// tension arm state
|
||||
pub tension_arm_state: TensionArmState,
|
||||
/// spool speed controller state
|
||||
pub spool_speed_controller_state: SpoolSpeedControllerState,
|
||||
/// Is a Machine Connected?
|
||||
pub connected_machine_state: MachineCrossConnectionState,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone, Default)]
|
||||
pub struct TraverseState {
|
||||
/// min position in mm
|
||||
pub limit_inner: f64,
|
||||
/// max position in mm
|
||||
pub limit_outer: f64,
|
||||
/// position in mm
|
||||
pub position_in: f64,
|
||||
/// position out in mm
|
||||
pub position_out: f64,
|
||||
/// is going to position in
|
||||
pub is_going_in: bool,
|
||||
/// is going to position out
|
||||
pub is_going_out: bool,
|
||||
/// if is homed
|
||||
pub is_homed: bool,
|
||||
/// if is homing
|
||||
pub is_going_home: bool,
|
||||
/// if is traversing
|
||||
pub is_traversing: bool,
|
||||
/// laserpointer is on
|
||||
pub laserpointer: bool,
|
||||
/// step size in mm
|
||||
pub step_size: f64,
|
||||
/// padding in mm
|
||||
pub padding: f64,
|
||||
/// can go in (to inner limit)
|
||||
pub can_go_in: bool,
|
||||
/// can go out (to outer limit)
|
||||
pub can_go_out: bool,
|
||||
/// can home
|
||||
pub can_go_home: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone, Default)]
|
||||
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,
|
||||
/// gear ratio for winding speed
|
||||
pub gear_ratio: GearRatio,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone, Default)]
|
||||
pub enum SpoolAutomaticActionMode {
|
||||
#[default]
|
||||
NoAction,
|
||||
Pull,
|
||||
Hold,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone, Default)]
|
||||
pub struct SpoolAutomaticActionState {
|
||||
pub spool_required_meters: f64,
|
||||
pub spool_automatic_action_mode: SpoolAutomaticActionMode,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone, Default)]
|
||||
pub struct ModeState {
|
||||
/// mode
|
||||
pub mode: Mode,
|
||||
/// can wind
|
||||
pub can_wind: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone, Default)]
|
||||
pub struct TensionArmState {
|
||||
/// is zeroed
|
||||
pub zeroed: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone, Default)]
|
||||
pub struct SpoolSpeedControllerState {
|
||||
/// regulation mode
|
||||
pub regulation_mode: super::spool_speed_controller::SpoolSpeedControllerType,
|
||||
/// min speed in rpm for minmax mode
|
||||
pub minmax_min_speed: f64,
|
||||
/// max speed in rpm for minmax mode
|
||||
pub minmax_max_speed: f64,
|
||||
/// tension target for adaptive mode (0.0-1.0)
|
||||
pub adaptive_tension_target: f64,
|
||||
/// radius learning rate for adaptive mode
|
||||
pub adaptive_radius_learning_rate: f64,
|
||||
/// max speed multiplier for adaptive mode
|
||||
pub adaptive_max_speed_multiplier: f64,
|
||||
/// acceleration factor for adaptive mode
|
||||
pub adaptive_acceleration_factor: f64,
|
||||
/// deacceleration urgency multiplier for adaptive mode
|
||||
pub adaptive_deacceleration_urgency_multiplier: f64,
|
||||
/// forward rotation direction
|
||||
pub forward: bool,
|
||||
}
|
||||
|
||||
pub enum Winder2Events {
|
||||
LiveValues(Event<LiveValuesEvent>),
|
||||
State(Event<StateEvent>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Winder2Namespace {
|
||||
pub namespace: Option<Namespace>,
|
||||
}
|
||||
|
||||
impl NamespaceCacheingLogic<Winder2Events> for Winder2Namespace {
|
||||
#[instrument(skip_all)]
|
||||
fn emit(&mut self, events: Winder2Events) {
|
||||
let event = Arc::new(events.event_value());
|
||||
let buffer_fn = events.event_cache_fn();
|
||||
match &mut self.namespace {
|
||||
Some(ns) => ns.emit(event, &buffer_fn),
|
||||
None => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CacheableEvents<Self> for Winder2Events {
|
||||
fn event_value(&self) -> GenericEvent {
|
||||
match self {
|
||||
Self::LiveValues(event) => event.into(),
|
||||
Self::State(event) => event.into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn event_cache_fn(&self) -> CacheFn {
|
||||
let cache_first_and_last = cache_first_and_last_event();
|
||||
match self {
|
||||
Self::LiveValues(_) => cache_first_and_last,
|
||||
Self::State(_) => cache_first_and_last,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
impl MachineApi for Winder2 {
|
||||
fn api_get_sender(&self) -> Sender<MachineMessage> {
|
||||
self.api_sender.clone()
|
||||
}
|
||||
|
||||
fn api_mutate(&mut self, request_body: Value) -> Result<(), anyhow::Error> {
|
||||
use crate::Machine;
|
||||
|
||||
let mutation: Mutation = serde_json::from_value(request_body)?;
|
||||
match mutation {
|
||||
Mutation::EnableTraverseLaserpointer(enable) => self.set_laser(enable),
|
||||
Mutation::SetMode(mode) => self.set_mode(&mode.into()),
|
||||
Mutation::SetTraverseLimitOuter(limit) => self.traverse_set_limit_outer(limit),
|
||||
Mutation::SetTraverseLimitInner(limit) => self.traverse_set_limit_inner(limit),
|
||||
Mutation::SetTraverseStepSize(size) => self.traverse_set_step_size(size),
|
||||
Mutation::SetTraversePadding(padding) => self.traverse_set_padding(padding),
|
||||
Mutation::GotoTraverseLimitOuter => self.traverse_goto_limit_outer(),
|
||||
Mutation::GotoTraverseLimitInner => self.traverse_goto_limit_inner(),
|
||||
Mutation::GotoTraverseHome => self.traverse_goto_home(),
|
||||
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::SetPullerGearRatio(gear_ratio) => self.puller_set_gear_ratio(gear_ratio),
|
||||
Mutation::SetSpoolRegulationMode(mode) => self.spool_set_regulation_mode(mode),
|
||||
Mutation::SetSpoolMinMaxMinSpeed(speed) => self.spool_set_minmax_min_speed(speed),
|
||||
Mutation::SetSpoolMinMaxMaxSpeed(speed) => self.spool_set_minmax_max_speed(speed),
|
||||
Mutation::SetSpoolForward(value) => self.spool_set_forward(value),
|
||||
Mutation::SetSpoolAdaptiveTensionTarget(value) => {
|
||||
self.spool_set_adaptive_tension_target(value)
|
||||
}
|
||||
Mutation::SetSpoolAdaptiveRadiusLearningRate(value) => {
|
||||
self.spool_set_adaptive_radius_learning_rate(value)
|
||||
}
|
||||
Mutation::SetSpoolAdaptiveMaxSpeedMultiplier(value) => {
|
||||
self.spool_set_adaptive_max_speed_multiplier(value)
|
||||
}
|
||||
Mutation::SetSpoolAdaptiveAccelerationFactor(value) => {
|
||||
self.spool_set_adaptive_acceleration_factor(value)
|
||||
}
|
||||
Mutation::SetSpoolAdaptiveDeaccelerationUrgencyMultiplier(value) => {
|
||||
self.spool_set_adaptive_deacceleration_urgency_multiplier(value)
|
||||
}
|
||||
Mutation::SetSpoolAutomaticRequiredMeters(meters) => {
|
||||
self.set_spool_automatic_required_meters(meters)
|
||||
}
|
||||
Mutation::SetSpoolAutomaticAction(mode) => self.set_spool_automatic_mode(mode),
|
||||
Mutation::ResetSpoolProgress => self.stop_or_pull_spool_reset(Instant::now()),
|
||||
Mutation::ZeroTensionArmAngle => self.tension_arm_zero(),
|
||||
Mutation::SetConnectedMachine(machine_identification_unique) => {
|
||||
let main_sender = match &self.main_sender {
|
||||
Some(sender) => sender,
|
||||
None => {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Machine cannot connect to others! {:?}",
|
||||
self.get_machine_identification_unique()
|
||||
));
|
||||
}
|
||||
};
|
||||
main_sender.try_send(crate::AsyncThreadMessage::ConnectOneWayRequest(
|
||||
crate::CrossConnection {
|
||||
src: self.get_machine_identification_unique(),
|
||||
dest: machine_identification_unique,
|
||||
},
|
||||
))?;
|
||||
self.emit_state();
|
||||
}
|
||||
Mutation::DisconnectMachine(_machine_identification_unique) => {
|
||||
self.connected_machines.clear();
|
||||
/*let main_sender = match &self.main_sender {
|
||||
Some(sender) => sender,
|
||||
None => return Err(anyhow::anyhow!("[DisconnectMachine] Machine cannot connect to others! {:?}", self.machine_identification_unique)),
|
||||
};*/
|
||||
//main_sender.try_send(crate::AsyncThreadMessage::ConnectOneWayRequest(crate::CrossConnection { src: self.get_machine_identification_unique(), dest: machine_identification_unique }))?;
|
||||
self.emit_state();
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn api_event_namespace(&mut self) -> Option<Namespace> {
|
||||
self.namespace.namespace.clone()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use control_core::socketio::{event::{BuildEvent, GenericEvent}, namespace::{CacheFn, CacheableEvents, cache_duration}};
|
||||
use control_core_derive::BuildEvent;
|
||||
use serde::Serialize;
|
||||
use units::{angle::degree, angular_velocity::revolution_per_minute, length::{meter, millimeter}, velocity::meter_per_minute};
|
||||
|
||||
use crate::winder2::Winder2;
|
||||
|
||||
mod state;
|
||||
pub use state::State;
|
||||
|
||||
mod mutation;
|
||||
|
||||
// Live values
|
||||
#[derive(Serialize, Debug, Clone, Default, BuildEvent)]
|
||||
pub struct LiveValues
|
||||
{
|
||||
/// traverse position in mm
|
||||
pub traverse_position: Option<f64>,
|
||||
/// puller speed in m/min
|
||||
pub puller_speed: f64,
|
||||
/// spool rpm
|
||||
pub spool_rpm: f64,
|
||||
/// tension arm angle in degrees
|
||||
pub tension_arm_angle: f64,
|
||||
// spool progress in meters (pulled distance of filament)
|
||||
pub spool_progress: f64,
|
||||
}
|
||||
|
||||
impl CacheableEvents<Self> for LiveValues
|
||||
{
|
||||
fn event_value(&self) -> GenericEvent
|
||||
{
|
||||
self.build().into()
|
||||
}
|
||||
|
||||
fn event_cache_fn(&self) -> CacheFn {
|
||||
cache_duration(Duration::from_secs(60 * 60), Duration::from_secs(1))
|
||||
}
|
||||
}
|
||||
|
||||
impl Winder2
|
||||
{
|
||||
pub fn create_live_values(&self) -> LiveValues
|
||||
{
|
||||
let tension_arm_angle = self.tension_arm.get_angle().get::<degree>();
|
||||
|
||||
// Wrap [270;<360] to [-90; 0]
|
||||
// This is done to reduce flicker in the graphs around the zero point
|
||||
let tension_arm_angle = match tension_arm_angle >= 270.0
|
||||
{
|
||||
true => tension_arm_angle - 360.0,
|
||||
false => tension_arm_angle,
|
||||
};
|
||||
|
||||
LiveValues {
|
||||
traverse_position: self.traverse.current_position().map(|x| x.get::<millimeter>()),
|
||||
puller_speed: self.puller.motor_speed().get::<meter_per_minute>(),
|
||||
spool_rpm: self.spool.speed().get::<revolution_per_minute>(),
|
||||
spool_progress: self.spool_length_task.current_length().get::<meter>(),
|
||||
tension_arm_angle,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,385 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use units::{
|
||||
AngularVelocity,
|
||||
Length,
|
||||
Velocity,
|
||||
angular_velocity::revolution_per_minute,
|
||||
length::{meter, millimeter},
|
||||
velocity::meter_per_minute
|
||||
};
|
||||
|
||||
use crate::{
|
||||
machine_identification::MachineIdentificationUnique,
|
||||
types::Direction,
|
||||
winder2::{Winder2, devices::{OperationState, PullerGearRatio, SpoolSpeedControlMode}, types::{Mode, SpoolLengthTaskCompletedAction}}};
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub enum Mutation
|
||||
{
|
||||
// Traverse
|
||||
/// Position in mm from home point
|
||||
SetTraverseLimitOuter(f64),
|
||||
/// Position in mm from home point
|
||||
SetTraverseLimitInner(f64),
|
||||
/// Step size in mm for traverse movement
|
||||
SetTraverseStepSize(f64),
|
||||
/// Padding in mm for traverse movement limits
|
||||
SetTraversePadding(f64),
|
||||
GotoTraverseLimitOuter,
|
||||
GotoTraverseLimitInner,
|
||||
/// Find home point
|
||||
GotoTraverseHome,
|
||||
EnableTraverseLaserpointer(bool),
|
||||
|
||||
// Puller
|
||||
/// on = speed, off = stop
|
||||
// SetPullerRegulationMode(PullerRegulationMode), TODO: replace
|
||||
SetPullerTargetSpeed(f64),
|
||||
SetPullerTargetDiameter(f64),
|
||||
SetPullerForward(bool),
|
||||
SetPullerGearRatio(PullerGearRatio),
|
||||
|
||||
// Spool Speed Controller
|
||||
SetSpoolRegulationMode(SpoolSpeedControlMode),
|
||||
SetSpoolMinMaxMinSpeed(f64),
|
||||
SetSpoolMinMaxMaxSpeed(f64),
|
||||
SetSpoolForward(bool),
|
||||
|
||||
// Adaptive Spool Speed Controller Parameters
|
||||
SetSpoolAdaptiveTensionTarget(f64),
|
||||
SetSpoolAdaptiveRadiusLearningRate(f64),
|
||||
SetSpoolAdaptiveMaxSpeedMultiplier(f64),
|
||||
SetSpoolAdaptiveAccelerationFactor(f64),
|
||||
SetSpoolAdaptiveDeaccelerationUrgencyMultiplier(f64),
|
||||
|
||||
// Spool Auto Stop/Pull
|
||||
SetSpoolAutomaticRequiredMeters(f64),
|
||||
SetSpoolAutomaticAction(SpoolLengthTaskCompletedAction),
|
||||
ResetSpoolProgress,
|
||||
|
||||
// Tension Arm
|
||||
ZeroTensionArmAngle,
|
||||
|
||||
// Mode
|
||||
SetMode(Mode),
|
||||
|
||||
// Connected Machine
|
||||
SetConnectedMachine(MachineIdentificationUnique),
|
||||
|
||||
// Disconnect Machine
|
||||
DisconnectMachine(MachineIdentificationUnique),
|
||||
}
|
||||
|
||||
impl Winder2
|
||||
{
|
||||
pub fn handle_mutation(&mut self, value: serde_json::Value) -> Result<(), anyhow::Error>
|
||||
{
|
||||
use Mutation::*;
|
||||
|
||||
let mutation: Mutation = serde_json::from_value(value)?;
|
||||
|
||||
match mutation
|
||||
{
|
||||
// machine
|
||||
SetMode(v) => self.set_mode(v),
|
||||
|
||||
//traverse
|
||||
SetTraverseLimitOuter(v) => self.traverse_set_limit_outer(v),
|
||||
SetTraverseLimitInner(v) => self.traverse_set_limit_inner(v),
|
||||
SetTraverseStepSize(v) => self.traverse_set_step_size(v),
|
||||
SetTraversePadding(v) => self.traverse_set_padding(v),
|
||||
GotoTraverseLimitOuter => self.traverse_goto_limit_outer(),
|
||||
GotoTraverseLimitInner => self.traverse_goto_limit_inner(),
|
||||
GotoTraverseHome => self.traverse_goto_home(),
|
||||
EnableTraverseLaserpointer(v) => self.traverse_set_laser_pointer_enabled(v),
|
||||
|
||||
// puller
|
||||
// SetPullerRegulationMode(_) => todo!(),
|
||||
SetConnectedMachine(_) => todo!(), //TODO: change to REFERENCE
|
||||
DisconnectMachine(_) => todo!(), //TODO: change to REFERENCE
|
||||
SetPullerTargetSpeed(v) => self.puller_set_target_speed(v),
|
||||
SetPullerTargetDiameter(v) => self.puller_set_target_diameter(v),
|
||||
SetPullerForward(v) => self.puller_set_direction(Direction::from_bool(v)),
|
||||
SetPullerGearRatio(v) => self.puller_set_gear_ratio(v),
|
||||
|
||||
// spool
|
||||
SetSpoolRegulationMode(_) => todo!(),
|
||||
SetSpoolMinMaxMinSpeed(_) => todo!(),
|
||||
SetSpoolMinMaxMaxSpeed(_) => todo!(),
|
||||
SetSpoolForward(_) => todo!(),
|
||||
SetSpoolAdaptiveTensionTarget(_) => todo!(),
|
||||
SetSpoolAdaptiveRadiusLearningRate(_) => todo!(),
|
||||
SetSpoolAdaptiveMaxSpeedMultiplier(_) => todo!(),
|
||||
SetSpoolAdaptiveAccelerationFactor(_) => todo!(),
|
||||
SetSpoolAdaptiveDeaccelerationUrgencyMultiplier(_) => todo!(),
|
||||
|
||||
// tension arm
|
||||
ZeroTensionArmAngle => todo!(),
|
||||
|
||||
// spool length task
|
||||
SetSpoolAutomaticRequiredMeters(_) => todo!(),
|
||||
SetSpoolAutomaticAction(_) => todo!(),
|
||||
ResetSpoolProgress => todo!(),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// Machine
|
||||
impl Winder2
|
||||
{
|
||||
pub fn set_mode(&mut self, mode: Mode)
|
||||
{
|
||||
let should_update = mode != Mode::Wind || self.can_wind();
|
||||
|
||||
if should_update
|
||||
{
|
||||
self.mode = mode;
|
||||
match self.mode
|
||||
{
|
||||
Mode::Standby =>
|
||||
{
|
||||
let state = OperationState::Disabled;
|
||||
self.spool.set_operation_state(state);
|
||||
self.puller.set_device_state(state);
|
||||
self.traverse.set_device_state(state);
|
||||
},
|
||||
Mode::Hold =>
|
||||
{
|
||||
let state = OperationState::Holding;
|
||||
self.spool.set_operation_state(state);
|
||||
self.puller.set_device_state(state);
|
||||
self.traverse.set_device_state(state);
|
||||
},
|
||||
Mode::Pull =>
|
||||
{
|
||||
use OperationState::*;
|
||||
self.spool.set_operation_state(Holding);
|
||||
self.puller.set_device_state(Running);
|
||||
self.traverse.set_device_state(Holding);
|
||||
},
|
||||
Mode::Wind =>
|
||||
{
|
||||
let state = OperationState::Running;
|
||||
self.spool.set_operation_state(state);
|
||||
self.puller.set_device_state(state);
|
||||
self.traverse.set_device_state(state);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
self.emit_state();
|
||||
}
|
||||
}
|
||||
|
||||
// Tension Arm (COMPLETE)
|
||||
impl Winder2
|
||||
{
|
||||
pub fn tension_arm_calibrate(&mut self)
|
||||
{
|
||||
self.tension_arm.calibrate();
|
||||
self.emit_live_values(); // For angle update
|
||||
self.emit_state();
|
||||
}
|
||||
}
|
||||
|
||||
// Puller
|
||||
impl Winder2
|
||||
{
|
||||
// TODO: replace with connection logic
|
||||
// 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.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 m/min to velocity
|
||||
let target_diameter = Length::new::<millimeter>(target_diameter);
|
||||
self.puller.set_target_diameter(target_diameter);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set direction
|
||||
pub fn puller_set_direction(&mut self, direction: Direction)
|
||||
{
|
||||
self.puller.set_direction(direction);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set gear ratio for winding speed
|
||||
pub fn puller_set_gear_ratio(&mut self, gear_ratio: PullerGearRatio)
|
||||
{
|
||||
self.puller.set_gear_ratio(gear_ratio);
|
||||
self.emit_state();
|
||||
}
|
||||
}
|
||||
|
||||
// Spool
|
||||
impl Winder2
|
||||
{
|
||||
/// Set forward rotation direction
|
||||
pub fn spool_set_direction(&mut self, value: Direction)
|
||||
{
|
||||
self.spool.set_direction(value);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set speed control mode
|
||||
pub fn spool_set_speed_control_mode(&mut self, value: SpoolSpeedControlMode)
|
||||
{
|
||||
self.spool.set_speed_control_mode(value);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set minimum speed for minmax mode in RPM
|
||||
pub fn spool_set_minmax_min_speed(&mut self, min_speed_rpm: f64)
|
||||
{
|
||||
let min_speed = AngularVelocity::new::<revolution_per_minute>(min_speed_rpm);
|
||||
|
||||
if let Err(e) = self.spool.speed_controllers.minmax.set_min_speed(min_speed)
|
||||
{
|
||||
tracing::error!("Failed to set spool min speed: {:?}", e);
|
||||
}
|
||||
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set maximum speed for minmax mode in RPM
|
||||
pub fn spool_set_minmax_max_speed(&mut self, max_speed_rpm: f64)
|
||||
{
|
||||
let max_speed = AngularVelocity::new::<revolution_per_minute>(max_speed_rpm);
|
||||
|
||||
if let Err(e) = self.spool.speed_controllers.minmax.set_max_speed(max_speed)
|
||||
{
|
||||
tracing::error!("Failed to set spool max speed: {:?}", e);
|
||||
}
|
||||
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set tension target for adaptive mode (0.0-1.0)
|
||||
pub fn spool_set_adaptive_tension_target(&mut self, tension_target: f64)
|
||||
{
|
||||
self.spool.speed_controllers.adaptive.set_tension_target(tension_target);
|
||||
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set radius learning rate for adaptive mode
|
||||
pub fn spool_set_adaptive_radius_learning_rate(&mut self, radius_learning_rate: f64) {
|
||||
self.spool.speed_controllers.adaptive
|
||||
.set_radius_learning_rate(radius_learning_rate);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set max speed multiplier for adaptive mode
|
||||
pub fn spool_set_adaptive_max_speed_multiplier(&mut self, max_speed_multiplier: f64) {
|
||||
self.spool.speed_controllers.adaptive
|
||||
.set_max_speed_multiplier(max_speed_multiplier);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set acceleration factor for adaptive mode
|
||||
pub fn spool_set_adaptive_acceleration_factor(&mut self, acceleration_factor: f64) {
|
||||
self.spool.speed_controllers.adaptive
|
||||
.set_acceleration_factor(acceleration_factor);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set deacceleration urgency multiplier for adaptive mode
|
||||
pub fn spool_set_adaptive_deacceleration_urgency_multiplier(
|
||||
&mut self,
|
||||
deacceleration_urgency_multiplier: f64,
|
||||
) {
|
||||
self.spool.speed_controllers.adaptive
|
||||
.set_deacceleration_urgency_multiplier(deacceleration_urgency_multiplier);
|
||||
self.emit_state();
|
||||
}
|
||||
}
|
||||
|
||||
// Traverse
|
||||
impl Winder2
|
||||
{
|
||||
pub fn traverse_set_laser_pointer_enabled(&mut self, value: bool)
|
||||
{
|
||||
self.traverse.set_laser_pointer_enabled(value);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn traverse_set_limit_inner(&mut self, limit: f64)
|
||||
{
|
||||
let limit_inner = Length::new::<millimeter>(limit);
|
||||
_ = self.traverse.try_set_limit_inner(limit_inner);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn traverse_set_limit_outer(&mut self, limit: f64)
|
||||
{
|
||||
let limit_outer = Length::new::<millimeter>(limit);
|
||||
_ = self.traverse.try_set_limit_outer(limit_outer);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn traverse_set_step_size(&mut self, step_size: f64)
|
||||
{
|
||||
let step_size = Length::new::<millimeter>(step_size);
|
||||
self.traverse.set_step_size(step_size);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn traverse_set_padding(&mut self, padding: f64)
|
||||
{
|
||||
let padding = Length::new::<millimeter>(padding);
|
||||
self.traverse.set_padding(padding);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn traverse_goto_limit_inner(&mut self)
|
||||
{
|
||||
_ = self.traverse.try_goto_limit_inner();
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn traverse_goto_limit_outer(&mut self)
|
||||
{
|
||||
_ = self.traverse.try_goto_limit_outer();
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn traverse_goto_home(&mut self)
|
||||
{
|
||||
_ = self.traverse.try_goto_home();
|
||||
self.emit_state();
|
||||
}
|
||||
}
|
||||
|
||||
// Spool Length Task
|
||||
impl Winder2
|
||||
{
|
||||
pub fn set_spool_length_task_target_length(&mut self, meters: f64)
|
||||
{
|
||||
let target_length = Length::new::<meter>(meters);
|
||||
self.spool_length_task.set_target_length(target_length);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn set_on_spool_length_task_completed_action(
|
||||
&mut self, action: SpoolLengthTaskCompletedAction)
|
||||
{
|
||||
self.on_spool_length_task_complete = action;
|
||||
self.emit_state();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
use control_core::socketio::{event::{BuildEvent, GenericEvent}, namespace::{CacheFn, CacheableEvents, cache_first_and_last_event}};
|
||||
use control_core_derive::BuildEvent;
|
||||
use serde::Serialize;
|
||||
use units::{angular_velocity::revolution_per_minute, length::{meter, millimeter}, velocity::meter_per_minute};
|
||||
|
||||
use crate::{
|
||||
MachineCrossConnectionState, types::Direction, winder2::{
|
||||
Winder2,
|
||||
devices::{
|
||||
PullerGearRatio,
|
||||
PullerSpeedRegulation,
|
||||
SpoolSpeedControlMode
|
||||
}, types::{Mode, SpoolLengthTaskCompletedAction}
|
||||
}
|
||||
};
|
||||
|
||||
// State
|
||||
#[derive(Serialize, Debug, Clone, BuildEvent)]
|
||||
pub struct State
|
||||
{
|
||||
pub is_default_state: bool,
|
||||
/// traverse state
|
||||
pub traverse_state: TraverseState,
|
||||
/// puller state
|
||||
pub puller_state: PullerState,
|
||||
/// spool automatic action state and progress
|
||||
pub spool_automatic_action_state: SpoolLengthTaskState,
|
||||
/// mode state
|
||||
pub mode_state: ModeState,
|
||||
/// tension arm state
|
||||
pub tension_arm_state: TensionArmState,
|
||||
/// spool speed controller state
|
||||
pub spool_speed_controller_state: SpoolState,
|
||||
|
||||
/// Is a Machine Connected?
|
||||
pub connected_machine_state: MachineCrossConnectionState,
|
||||
}
|
||||
|
||||
impl CacheableEvents<Self> for State
|
||||
{
|
||||
fn event_value(&self) -> GenericEvent {
|
||||
self.build().into()
|
||||
}
|
||||
|
||||
fn event_cache_fn(&self) -> CacheFn {
|
||||
cache_first_and_last_event()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
pub struct ModeState
|
||||
{
|
||||
/// mode
|
||||
pub mode: Mode,
|
||||
/// can wind
|
||||
pub can_wind: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone, Default)]
|
||||
pub struct TensionArmState
|
||||
{
|
||||
/// is zeroed/calibrated
|
||||
pub zeroed: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
pub struct SpoolLengthTaskState
|
||||
{
|
||||
pub spool_required_meters: f64,
|
||||
pub spool_automatic_action_mode: SpoolLengthTaskCompletedAction,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
pub struct SpoolState
|
||||
{
|
||||
/// regulation mode
|
||||
pub regulation_mode: SpoolSpeedControlMode,
|
||||
/// min speed in rpm for minmax mode
|
||||
pub minmax_min_speed: f64,
|
||||
/// max speed in rpm for minmax mode
|
||||
pub minmax_max_speed: f64,
|
||||
/// tension target for adaptive mode (0.0-1.0)
|
||||
pub adaptive_tension_target: f64,
|
||||
/// radius learning rate for adaptive mode
|
||||
pub adaptive_radius_learning_rate: f64,
|
||||
/// max speed multiplier for adaptive mode
|
||||
pub adaptive_max_speed_multiplier: f64,
|
||||
/// acceleration factor for adaptive mode
|
||||
pub adaptive_acceleration_factor: f64,
|
||||
/// deacceleration urgency multiplier for adaptive mode
|
||||
pub adaptive_deacceleration_urgency_multiplier: f64,
|
||||
/// forward rotation direction
|
||||
pub forward: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
pub struct PullerState
|
||||
{
|
||||
/// regulation type
|
||||
pub regulation: PullerSpeedRegulation,
|
||||
/// target speed in m/min
|
||||
pub target_speed: f64,
|
||||
/// target diameter in mm
|
||||
pub target_diameter: f64,
|
||||
/// forward rotation direction
|
||||
pub forward: bool,
|
||||
/// gear ratio for winding speed
|
||||
pub gear_ratio: PullerGearRatio,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone, Default)]
|
||||
pub struct TraverseState {
|
||||
/// min position in mm
|
||||
pub limit_inner: f64,
|
||||
/// max position in mm
|
||||
pub limit_outer: f64,
|
||||
/// position in mm
|
||||
pub position_in: f64,
|
||||
/// position out in mm
|
||||
pub position_out: f64,
|
||||
/// is going to position in
|
||||
pub is_going_in: bool,
|
||||
/// is going to position out
|
||||
pub is_going_out: bool,
|
||||
/// if is homed
|
||||
pub is_homed: bool,
|
||||
/// if is homing
|
||||
pub is_going_home: bool,
|
||||
/// if is traversing
|
||||
pub is_traversing: bool,
|
||||
/// laserpointer is on
|
||||
pub laserpointer: bool,
|
||||
/// step size in mm
|
||||
pub step_size: f64,
|
||||
/// padding in mm
|
||||
pub padding: f64,
|
||||
/// can go in (to inner limit)
|
||||
pub can_go_in: bool,
|
||||
/// can go out (to outer limit)
|
||||
pub can_go_out: bool,
|
||||
/// can home
|
||||
pub can_go_home: bool,
|
||||
}
|
||||
|
||||
impl Winder2
|
||||
{
|
||||
pub fn create_state(&self) -> State
|
||||
{
|
||||
let is_default_state = !self.emitted_default_state;
|
||||
|
||||
let connected_machine_state = match &self.puller_speed_reference_machine
|
||||
{
|
||||
Some(connection) => MachineCrossConnectionState {
|
||||
machine_identification_unique: Some(connection.ident.clone()),
|
||||
is_available: true
|
||||
},
|
||||
None => MachineCrossConnectionState {
|
||||
machine_identification_unique: None,
|
||||
is_available: false
|
||||
},
|
||||
};
|
||||
|
||||
State {
|
||||
is_default_state,
|
||||
traverse_state: self.create_traverse_state(),
|
||||
puller_state: self.create_puller_state(),
|
||||
spool_automatic_action_state: self.create_spool_length_task_state(),
|
||||
mode_state: self.create_mode_state(),
|
||||
tension_arm_state: self.create_tension_arm_state(),
|
||||
spool_speed_controller_state: self.create_spool_state(),
|
||||
connected_machine_state,
|
||||
}
|
||||
}
|
||||
|
||||
fn create_mode_state(&self) -> ModeState
|
||||
{
|
||||
ModeState { mode: self.mode, can_wind: self.can_wind() }
|
||||
}
|
||||
|
||||
fn create_tension_arm_state(&self) -> TensionArmState
|
||||
{
|
||||
TensionArmState {
|
||||
zeroed: self.tension_arm.is_calibrated()
|
||||
}
|
||||
}
|
||||
|
||||
fn create_spool_state(&self) -> SpoolState
|
||||
{
|
||||
use revolution_per_minute as rpm;
|
||||
|
||||
let speed_controllers = &self.spool.speed_controllers;
|
||||
let minmax = &speed_controllers.minmax;
|
||||
let adaptive = &speed_controllers.adaptive;
|
||||
|
||||
let adaptive_deacceleration_urgency_multiplier =
|
||||
adaptive.deacceleration_urgency_multiplier();
|
||||
|
||||
SpoolState {
|
||||
regulation_mode: self.spool.speed_control_mode(),
|
||||
forward: self.spool.direction().is_forward(),
|
||||
// min max speed controller
|
||||
minmax_min_speed: minmax.min_speed().get::<rpm>(),
|
||||
minmax_max_speed: minmax.max_speed().get::<rpm>(),
|
||||
// adaptive speed controller
|
||||
adaptive_tension_target: adaptive.tension_target(),
|
||||
adaptive_radius_learning_rate: adaptive.tension_target(),
|
||||
adaptive_max_speed_multiplier: adaptive.max_speed_multiplier(),
|
||||
adaptive_acceleration_factor: adaptive.acceleration_factor(),
|
||||
adaptive_deacceleration_urgency_multiplier,
|
||||
}
|
||||
}
|
||||
|
||||
fn create_puller_state(&self) -> PullerState
|
||||
{
|
||||
let puller = &self.puller;
|
||||
|
||||
PullerState {
|
||||
regulation: puller.speed_regulation_mode(),
|
||||
target_speed: puller.target_speed().get::<meter_per_minute>(),
|
||||
target_diameter: puller.target_diameter().get::<millimeter>(),
|
||||
forward: puller.direction() == Direction::Forward,
|
||||
gear_ratio: puller.gear_ratio(),
|
||||
}
|
||||
}
|
||||
|
||||
// COMPLETE
|
||||
fn create_traverse_state(&self) -> TraverseState
|
||||
{
|
||||
// NOTE(JSE): why is limit_inner and position_in identical?
|
||||
|
||||
let traverse = &self.traverse;
|
||||
|
||||
TraverseState {
|
||||
limit_inner: traverse.limit_inner().get::<millimeter>(),
|
||||
limit_outer: traverse.limit_outer().get::<millimeter>(),
|
||||
position_in: traverse.limit_inner().get::<millimeter>(),
|
||||
position_out: traverse.limit_outer().get::<millimeter>(),
|
||||
is_going_in: traverse.is_going_in(),
|
||||
is_going_out: traverse.is_going_out(),
|
||||
is_homed: traverse.is_homed(),
|
||||
is_going_home: traverse.is_going_home(),
|
||||
is_traversing: traverse.is_traversing(),
|
||||
laserpointer: traverse.laser_pointer_enabled(),
|
||||
step_size: traverse.step_size().get::<millimeter>(),
|
||||
padding: traverse.padding().get::<millimeter>(),
|
||||
can_go_in: traverse.can_goto_limit_inner(),
|
||||
can_go_out: traverse.can_goto_limit_outer(),
|
||||
can_go_home: traverse.can_go_home()
|
||||
}
|
||||
}
|
||||
|
||||
// COMPLETE
|
||||
fn create_spool_length_task_state(&self) -> SpoolLengthTaskState
|
||||
{
|
||||
let spool_required_meters = self.spool_length_task.target_length().get::<meter>();
|
||||
|
||||
SpoolLengthTaskState {
|
||||
spool_required_meters,
|
||||
spool_automatic_action_mode: self.on_spool_length_task_complete,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
mod spool;
|
||||
pub use spool::Spool;
|
||||
pub use spool::SpeedControlMode as SpoolSpeedControlMode;
|
||||
|
||||
mod puller;
|
||||
pub use puller::Puller;
|
||||
pub use puller::GearRatio as PullerGearRatio;
|
||||
pub use puller::SpeedRegulation as PullerSpeedRegulation;
|
||||
|
||||
mod tension_arm;
|
||||
pub use tension_arm::TensionArm;
|
||||
|
||||
mod traverse;
|
||||
pub use traverse::Traverse;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum OperationState
|
||||
{
|
||||
Disabled,
|
||||
Holding,
|
||||
Running,
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use units::{
|
||||
acceleration::meter_per_minute_per_second,
|
||||
f64::*,
|
||||
jerk::meter_per_minute_per_second_squared,
|
||||
length::{centimeter, millimeter},
|
||||
velocity::meter_per_minute,
|
||||
};
|
||||
|
||||
use control_core::{
|
||||
controllers::second_degree_motion::linear_jerk_speed_controller::LinearJerkSpeedController,
|
||||
converters::linear_step_converter::LinearStepConverter,
|
||||
};
|
||||
|
||||
use ethercat_hal::io::stepper_velocity_el70x1::StepperVelocityEL70x1;
|
||||
|
||||
use crate::types::Direction;
|
||||
|
||||
use super::OperationState;
|
||||
|
||||
/// Represents the puller motor
|
||||
#[derive(Debug)]
|
||||
pub struct Puller
|
||||
{
|
||||
motor: StepperVelocityEL70x1,
|
||||
|
||||
state: OperationState,
|
||||
direction: Direction,
|
||||
gear_ratio: GearRatio,
|
||||
regulation: SpeedRegulation,
|
||||
|
||||
/// actual speed of the motor
|
||||
current_speed: Velocity,
|
||||
target_speed: Velocity,
|
||||
target_diameter: Length,
|
||||
|
||||
step_converter: LinearStepConverter,
|
||||
acceleration_controller: LinearJerkSpeedController,
|
||||
}
|
||||
|
||||
// constants
|
||||
impl Puller
|
||||
{
|
||||
const STEPS_PER_REVOLUTION: i16 = 200; // in steps
|
||||
const WHEEL_DIAMETER: f64 = 8.0; // in cm
|
||||
const MOTOR_SPEED_MAX: f64 = 50.0; // in meters per minute
|
||||
const MOTOR_ACCELERATION_MAX: f64 = 5.0; // in meters per minute per second
|
||||
const MOTOR_JERK_MAX: f64 = 10.0; // in meters per minute per second squared
|
||||
}
|
||||
|
||||
// public interface
|
||||
impl Puller
|
||||
{
|
||||
pub fn new(motor: StepperVelocityEL70x1) -> Self
|
||||
{
|
||||
let step_converter = Self::create_step_converter();
|
||||
let acceleration_controller = Self::create_acceleration_controller();
|
||||
|
||||
Self {
|
||||
// config
|
||||
state: OperationState::Disabled,
|
||||
direction: Direction::Forward,
|
||||
gear_ratio: GearRatio::OneToOne,
|
||||
target_speed: Velocity::new::<meter_per_minute>(1.0),
|
||||
target_diameter: Length::new::<millimeter>(1.75),
|
||||
regulation: SpeedRegulation::Adapative,
|
||||
|
||||
// values
|
||||
current_speed: Velocity::new::<meter_per_minute>(1.0),
|
||||
|
||||
// misc
|
||||
motor,
|
||||
step_converter,
|
||||
acceleration_controller,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self, t: Instant)
|
||||
{
|
||||
// Compute target motor speed considering gear ratio and direction
|
||||
let target_speed = self.target_speed * self.gear_ratio.multiplier();
|
||||
|
||||
let target_speed = match self.direction
|
||||
{
|
||||
Direction::Forward => target_speed,
|
||||
Direction::Reverse => -target_speed,
|
||||
};
|
||||
|
||||
// Apply acceleration control
|
||||
let controlled_speed = self.acceleration_controller.update(target_speed, t);
|
||||
self.current_speed = controlled_speed;
|
||||
|
||||
// Convert to steps/sec and write to hardware
|
||||
let steps_per_second = self.step_converter.velocity_to_steps(controlled_speed);
|
||||
_ = self.motor.set_speed(steps_per_second);
|
||||
}
|
||||
}
|
||||
|
||||
// getter + setter
|
||||
impl Puller
|
||||
{
|
||||
#[allow(dead_code)]
|
||||
pub fn device_state(&self) -> OperationState
|
||||
{
|
||||
self.state
|
||||
}
|
||||
|
||||
pub fn set_device_state(&mut self, state: OperationState)
|
||||
{
|
||||
self.state = state;
|
||||
}
|
||||
|
||||
pub fn speed_regulation_mode(&self) -> SpeedRegulation
|
||||
{
|
||||
self.regulation
|
||||
}
|
||||
|
||||
pub fn set_speed_regulation_mode(&mut self, value: SpeedRegulation)
|
||||
{
|
||||
self.regulation = value;
|
||||
}
|
||||
|
||||
pub fn direction(&self) -> Direction
|
||||
{
|
||||
self.direction
|
||||
}
|
||||
|
||||
pub fn set_direction(&mut self, value: Direction)
|
||||
{
|
||||
self.direction = value;
|
||||
}
|
||||
|
||||
pub fn gear_ratio(&self) -> GearRatio
|
||||
{
|
||||
self.gear_ratio
|
||||
}
|
||||
|
||||
pub fn set_gear_ratio(&mut self, value: GearRatio)
|
||||
{
|
||||
self.gear_ratio = value;
|
||||
}
|
||||
|
||||
pub fn target_speed(&self) -> Velocity
|
||||
{
|
||||
self.target_speed
|
||||
}
|
||||
|
||||
pub fn set_target_speed(&mut self, value: Velocity)
|
||||
{
|
||||
self.target_speed = value;
|
||||
}
|
||||
|
||||
pub fn target_diameter(&self) -> Length
|
||||
{
|
||||
self.target_diameter
|
||||
}
|
||||
|
||||
pub fn set_target_diameter(&mut self, value: Length)
|
||||
{
|
||||
self.target_diameter = value;
|
||||
}
|
||||
|
||||
pub fn motor_speed(&self) -> Velocity
|
||||
{
|
||||
self.current_speed
|
||||
}
|
||||
|
||||
pub fn output_speed(&self) -> Velocity
|
||||
{
|
||||
self.current_speed / self.gear_ratio.multiplier()
|
||||
}
|
||||
}
|
||||
|
||||
// utils
|
||||
impl Puller
|
||||
{
|
||||
fn create_step_converter() -> LinearStepConverter
|
||||
{
|
||||
let diameter = Length::new::<centimeter>(Self::WHEEL_DIAMETER);
|
||||
LinearStepConverter::from_diameter(Self::STEPS_PER_REVOLUTION, diameter)
|
||||
}
|
||||
|
||||
fn create_acceleration_controller() -> LinearJerkSpeedController
|
||||
{
|
||||
let acceleration
|
||||
= Acceleration::new::<meter_per_minute_per_second>(Self::MOTOR_ACCELERATION_MAX);
|
||||
|
||||
let jerk
|
||||
= Jerk::new::<meter_per_minute_per_second_squared>(Self::MOTOR_JERK_MAX);
|
||||
|
||||
let speed
|
||||
= Velocity::new::<meter_per_minute>(Self::MOTOR_SPEED_MAX);
|
||||
|
||||
LinearJerkSpeedController::new_simple(Some(speed),acceleration, jerk)
|
||||
}
|
||||
}
|
||||
|
||||
// other types
|
||||
|
||||
// state
|
||||
// gear ratio
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
pub enum GearRatio
|
||||
{
|
||||
OneToOne,
|
||||
OneToFive,
|
||||
OneToTen,
|
||||
}
|
||||
|
||||
impl GearRatio
|
||||
{
|
||||
/// Get the speed multiplier for this gear ratio
|
||||
pub fn multiplier(&self) -> f64
|
||||
{
|
||||
match self
|
||||
{
|
||||
GearRatio::OneToOne => 1.0,
|
||||
GearRatio::OneToFive => 5.0,
|
||||
GearRatio::OneToTen => 10.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// speed regulation mode
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum SpeedRegulation
|
||||
{
|
||||
Fixed,
|
||||
Adapative,
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use units::AngularVelocity;
|
||||
use control_core::converters::angular_step_converter::AngularStepConverter;
|
||||
use ethercat_hal::io::stepper_velocity_el70x1::StepperVelocityEL70x1;
|
||||
|
||||
use crate::types::Direction;
|
||||
use crate::winder2::devices::{ Puller, TensionArm };
|
||||
|
||||
use super::OperationState;
|
||||
|
||||
mod speed_controller;
|
||||
use speed_controller::{SpeedController, AdaptiveSpeedController, MinMaxSpeedController};
|
||||
|
||||
/// Represents the spool motor
|
||||
#[derive(Debug)]
|
||||
pub struct Spool
|
||||
{
|
||||
motor: StepperVelocityEL70x1,
|
||||
state: OperationState,
|
||||
direction: Direction,
|
||||
pub speed_controllers: SpeedControllers,
|
||||
speed_control_mode: SpeedControlMode,
|
||||
step_converter: AngularStepConverter,
|
||||
}
|
||||
|
||||
// public interface
|
||||
impl Spool
|
||||
{
|
||||
pub fn new(motor: StepperVelocityEL70x1) -> Self
|
||||
{
|
||||
Self {
|
||||
motor,
|
||||
state: OperationState::Disabled,
|
||||
direction: Direction::Forward,
|
||||
speed_controllers: SpeedControllers::new(),
|
||||
speed_control_mode: SpeedControlMode::Adaptive,
|
||||
step_converter: AngularStepConverter::new(200),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self, t: Instant,tension_arm: &TensionArm, puller: &Puller)
|
||||
{
|
||||
let velocity = self.active_controller_mut().update_speed(t, tension_arm, puller);
|
||||
|
||||
let velocity = if self.direction == Direction::Forward { velocity } else { -velocity };
|
||||
|
||||
let steps_per_second = self.step_converter.angular_velocity_to_steps(velocity);
|
||||
|
||||
_ = self.motor.set_speed(steps_per_second);
|
||||
}
|
||||
}
|
||||
|
||||
// getter + setter
|
||||
impl Spool
|
||||
{
|
||||
pub fn speed_control_mode(&self) -> SpeedControlMode
|
||||
{
|
||||
self.speed_control_mode
|
||||
}
|
||||
|
||||
pub fn set_speed_control_mode(&mut self, value: SpeedControlMode)
|
||||
{
|
||||
if self.speed_control_mode == value { return; }
|
||||
|
||||
// grab speed from active speed controller
|
||||
let current_speed = self.active_controller().speed();
|
||||
|
||||
// change active speed controller
|
||||
self.speed_control_mode = value;
|
||||
|
||||
// get active speed controller
|
||||
let controller = self.active_controller_mut();
|
||||
|
||||
// Set the speed in the target controller and reset it for smooth transition
|
||||
controller.set_speed(current_speed);
|
||||
controller.reset();
|
||||
controller.set_speed(current_speed);
|
||||
}
|
||||
|
||||
pub fn speed(&self) -> AngularVelocity
|
||||
{
|
||||
self.active_controller().speed()
|
||||
}
|
||||
|
||||
pub fn set_speed(&mut self, value: AngularVelocity)
|
||||
{
|
||||
self.active_controller_mut().set_speed(value);
|
||||
}
|
||||
|
||||
pub fn operation_state(&self) -> OperationState
|
||||
{
|
||||
self.state
|
||||
}
|
||||
|
||||
pub fn set_operation_state(&mut self, state: OperationState)
|
||||
{
|
||||
self.state = state;
|
||||
}
|
||||
|
||||
pub fn direction(&self) -> Direction
|
||||
{
|
||||
self.direction
|
||||
}
|
||||
|
||||
pub fn set_direction(&mut self, direction: Direction)
|
||||
{
|
||||
self.direction = direction;
|
||||
}
|
||||
}
|
||||
|
||||
// utils
|
||||
impl Spool
|
||||
{
|
||||
fn active_controller(&self) -> &dyn SpeedController
|
||||
{
|
||||
use SpeedControlMode::*;
|
||||
|
||||
match self.speed_control_mode
|
||||
{
|
||||
Adaptive => &self.speed_controllers.adaptive,
|
||||
MinMax => &self.speed_controllers.minmax,
|
||||
}
|
||||
}
|
||||
|
||||
fn active_controller_mut(&mut self) -> &mut dyn SpeedController
|
||||
{
|
||||
use SpeedControlMode::*;
|
||||
|
||||
match self.speed_control_mode
|
||||
{
|
||||
Adaptive => &mut self.speed_controllers.adaptive,
|
||||
MinMax => &mut self.speed_controllers.minmax,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// other types
|
||||
#[derive(Debug)]
|
||||
pub struct SpeedControllers
|
||||
{
|
||||
pub minmax: MinMaxSpeedController,
|
||||
pub adaptive: AdaptiveSpeedController,
|
||||
}
|
||||
|
||||
impl SpeedControllers
|
||||
{
|
||||
pub fn new() -> Self
|
||||
{
|
||||
Self {
|
||||
minmax: MinMaxSpeedController::new(),
|
||||
adaptive: AdaptiveSpeedController::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum SpeedControlMode
|
||||
{
|
||||
Adaptive,
|
||||
MinMax,
|
||||
}
|
||||
+154
-234
@@ -1,9 +1,3 @@
|
||||
use crate::winder2::{
|
||||
clamp_revolution::clamp_revolution_uom, filament_tension::FilamentTensionCalculator,
|
||||
puller_speed_controller::PullerSpeedController,
|
||||
};
|
||||
|
||||
use super::{clamp_revolution::Clamping, tension_arm::TensionArm};
|
||||
use control_core::{
|
||||
controllers::first_degree_motion::angular_acceleration_speed_controller::AngularAccelerationSpeedController,
|
||||
helpers::{interpolation::scale, moving_time_window::MovingTimeWindow},
|
||||
@@ -18,13 +12,13 @@ use units::f64::*;
|
||||
use units::length::{centimeter, meter};
|
||||
use units::velocity::meter_per_second;
|
||||
|
||||
/// Adaptive spool speed controller that automatically adjusts to maintain optimal filament tension.
|
||||
///
|
||||
/// This controller monitors filament tension via the tension arm and learns the appropriate
|
||||
/// maximum speed based on puller speed and tension feedback. It uses closed-loop control
|
||||
/// to minimize tension error and applies smooth acceleration to prevent sudden motor commands.
|
||||
use crate::{helpers::{Clamp, clamp_revolution_uom}, winder2::devices::spool::speed_controller::SpeedController};
|
||||
use crate::winder2::devices::{Puller, TensionArm};
|
||||
|
||||
use super::helpers::FilamentTensionCalculator;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AdaptiveSpoolSpeedController {
|
||||
pub struct AdaptiveSpeedController {
|
||||
/// Last commanded angular velocity sent to the spool motor
|
||||
last_speed: AngularVelocity,
|
||||
/// Whether the speed controller is enabled (false = always returns zero speed)
|
||||
@@ -52,13 +46,9 @@ pub struct AdaptiveSpoolSpeedController {
|
||||
deacceleration_urgency_multiplier: f64,
|
||||
}
|
||||
|
||||
impl Default for AdaptiveSpoolSpeedController {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl AdaptiveSpoolSpeedController {
|
||||
// Constants
|
||||
impl AdaptiveSpeedController
|
||||
{
|
||||
/// Maximum speed limit (in RPM) used to initialize the acceleration controller
|
||||
const INITIAL_MAX_SPEED_RPM: f64 = 150.0;
|
||||
|
||||
@@ -87,7 +77,7 @@ impl AdaptiveSpoolSpeedController {
|
||||
|
||||
const FACTOR_MAX: f64 = 20.0;
|
||||
|
||||
/// if the tension is the lowest, the speed can be up to 2x the puller speed
|
||||
/// if the tension is the lowest, the speed can be up to X the puller speed
|
||||
const MAX_SPEED_MULTIPLIER: f64 = 4.0;
|
||||
|
||||
/// Base acceleration as a fraction of max possible speed (per second)
|
||||
@@ -98,15 +88,77 @@ impl AdaptiveSpoolSpeedController {
|
||||
|
||||
/// Minimum acceleration limit to prevent completely frozen motion
|
||||
const MIN_ACCELERATION_LIMIT: f64 = 0.5; // rad/s²
|
||||
}
|
||||
|
||||
/// Creates a new adaptive spool speed controller with default settings.
|
||||
///
|
||||
/// Initializes all internal components and sets baseline parameters.
|
||||
///
|
||||
/// # Returns
|
||||
/// A new `AdaptiveSpoolSpeedController` instance ready for use.
|
||||
pub fn new() -> Self {
|
||||
let max_speed = AngularVelocity::new::<revolution_per_minute>(Self::INITIAL_MAX_SPEED_RPM);
|
||||
// getter + setter
|
||||
impl AdaptiveSpeedController
|
||||
{
|
||||
pub fn speed(&self) -> AngularVelocity {
|
||||
self.last_speed
|
||||
}
|
||||
|
||||
pub fn set_speed(&mut self, speed: AngularVelocity) {
|
||||
self.last_speed = speed;
|
||||
self.acceleration_controller.reset(speed);
|
||||
}
|
||||
|
||||
pub fn speed_factor(&self) -> Length {
|
||||
self.speed_factor
|
||||
}
|
||||
|
||||
// Getters and setters for the new configurable parameters
|
||||
pub const fn tension_target(&self) -> f64 {
|
||||
self.tension_target
|
||||
}
|
||||
|
||||
pub const fn set_tension_target(&mut self, value: f64) {
|
||||
self.tension_target = value.clamp(0.0, 1.0);
|
||||
}
|
||||
|
||||
pub const fn radius_learning_rate(&self) -> f64 {
|
||||
self.radius_learning_rate
|
||||
}
|
||||
|
||||
pub const fn set_radius_learning_rate(&mut self, radius_learning_rate: f64) {
|
||||
self.radius_learning_rate = radius_learning_rate.max(0.0);
|
||||
}
|
||||
|
||||
pub const fn max_speed_multiplier(&self) -> f64 {
|
||||
self.max_speed_multiplier
|
||||
}
|
||||
|
||||
pub const fn set_max_speed_multiplier(&mut self, max_speed_multiplier: f64) {
|
||||
self.max_speed_multiplier = max_speed_multiplier.max(0.1);
|
||||
}
|
||||
|
||||
pub const fn acceleration_factor(&self) -> f64 {
|
||||
self.acceleration_factor
|
||||
}
|
||||
|
||||
pub const fn set_acceleration_factor(&mut self, acceleration_factor: f64) {
|
||||
self.acceleration_factor = acceleration_factor.clamp(0.01, 1.0);
|
||||
}
|
||||
|
||||
pub const fn deacceleration_urgency_multiplier(&self) -> f64 {
|
||||
self.deacceleration_urgency_multiplier
|
||||
}
|
||||
|
||||
pub const fn set_deacceleration_urgency_multiplier(
|
||||
&mut self,
|
||||
deacceleration_urgency_multiplier: f64,
|
||||
) {
|
||||
self.deacceleration_urgency_multiplier = deacceleration_urgency_multiplier.max(1.0);
|
||||
}
|
||||
}
|
||||
|
||||
// public interface
|
||||
impl AdaptiveSpeedController
|
||||
{
|
||||
pub fn new() -> Self
|
||||
{
|
||||
use revolution_per_minute as rpm;
|
||||
|
||||
let max_speed = AngularVelocity::new::<rpm>(Self::INITIAL_MAX_SPEED_RPM);
|
||||
|
||||
Self {
|
||||
last_speed: AngularVelocity::ZERO,
|
||||
@@ -135,71 +187,36 @@ impl AdaptiveSpoolSpeedController {
|
||||
deacceleration_urgency_multiplier: Self::DEACCELERATION_URGENCY_MULTIPLIER,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates the current maximum speed based on puller speed and learned factor.
|
||||
///
|
||||
/// Uses the fundamental relationship between linear and angular velocity:
|
||||
/// ω = (v / r) × multiplier
|
||||
///
|
||||
/// Where:
|
||||
/// - ω (omega) = angular velocity in radians per second
|
||||
/// - v = linear velocity in meters per second
|
||||
/// - r = radius in meters
|
||||
/// - multiplier = MAX_SPEED_MULTIPLIER for speed scaling
|
||||
///
|
||||
/// This ensures the spool's rim speed is properly scaled relative to the puller's
|
||||
/// linear speed, with the multiplier allowing for tension control adjustments.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `puller_speed_controller`: Reference to puller for baseline speed calculation
|
||||
///
|
||||
/// # Returns
|
||||
/// Current maximum angular velocity for the spool in radians per second
|
||||
fn get_max_speed(&self, puller_speed_controller: &PullerSpeedController) -> AngularVelocity {
|
||||
AngularVelocity::new::<radian_per_second>(
|
||||
(puller_speed_controller.last_speed.get::<meter_per_second>()
|
||||
/ self.speed_factor.get::<meter>())
|
||||
* self.max_speed_multiplier,
|
||||
)
|
||||
}
|
||||
|
||||
/// Calculates the desired spool speed based on filament tension feedback.
|
||||
///
|
||||
/// Reads the tension arm angle, converts it to normalized tension, and calculates
|
||||
/// the appropriate speed. Also updates the learned maximum speed based on tension error.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `t`: Current timestamp for learning updates
|
||||
/// - `tension_arm`: Reference to tension arm for angle measurements
|
||||
/// - `puller_speed_controller`: Reference to puller for baseline speed calculations
|
||||
///
|
||||
/// # Returns
|
||||
/// The calculated target angular velocity for the spool motor
|
||||
// utils
|
||||
impl AdaptiveSpeedController
|
||||
{
|
||||
fn calculate_speed(
|
||||
&mut self,
|
||||
t: Instant,
|
||||
tension_arm: &TensionArm,
|
||||
puller_speed_controller: &PullerSpeedController,
|
||||
puller: &Puller,
|
||||
) -> AngularVelocity {
|
||||
let min_speed = AngularVelocity::ZERO;
|
||||
let max_speed = self.get_max_speed(puller_speed_controller).abs();
|
||||
let max_speed = self.max_speed(puller).abs();
|
||||
|
||||
// Calculate filament tension from arm angle
|
||||
let tension_arm_angle = tension_arm.get_angle();
|
||||
let (clamped_angle, clamping_state) = clamp_revolution_uom(
|
||||
tension_arm_angle,
|
||||
let tension_arm_revolution = clamp_revolution_uom(
|
||||
tension_arm.get_angle(),
|
||||
self.filament_calc.get_max_angle(), // Inverted because min angle = max tension
|
||||
self.filament_calc.get_min_angle(),
|
||||
);
|
||||
|
||||
// Return minimum speed if tension arm is at limits
|
||||
if matches!(clamping_state, Clamping::Min | Clamping::Max) {
|
||||
if matches!(tension_arm_revolution.clamp, Clamp::Min | Clamp::Max)
|
||||
{
|
||||
return min_speed;
|
||||
}
|
||||
|
||||
// 1.0 means maximum tension (high angle, low speed)
|
||||
// 0.0 means minimum tension (low angle, high speed)
|
||||
let filament_tension = self.filament_calc.calc_filament_tension(clamped_angle);
|
||||
let filament_tension = self.filament_calc.calc_filament_tension(tension_arm_revolution.value);
|
||||
|
||||
self.update_speed_factor(filament_tension, t);
|
||||
|
||||
@@ -212,27 +229,15 @@ impl AdaptiveSpoolSpeedController {
|
||||
))
|
||||
}
|
||||
|
||||
/// Simplified urgency-weighted acceleration that adapts to current operating conditions.
|
||||
///
|
||||
/// This approach uses the current max speed (calculated from puller speed and learned diameter)
|
||||
/// as the basis for acceleration limits, ensuring smooth operation across all speed ranges
|
||||
/// while providing instant deceleration capability when approaching zero.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `target_speed`: Target speed to accelerate towards
|
||||
/// - `puller_speed_controller`: Reference to puller for current max speed calculation
|
||||
/// - `t`: Current timestamp for the acceleration controller
|
||||
///
|
||||
/// # Returns
|
||||
/// Smoothly accelerated speed with urgency-weighted limits
|
||||
fn accelerate_speed(
|
||||
&mut self,
|
||||
target_speed: AngularVelocity,
|
||||
puller_speed_controller: &PullerSpeedController,
|
||||
puller: &Puller,
|
||||
t: Instant,
|
||||
) -> AngularVelocity {
|
||||
) -> AngularVelocity
|
||||
{
|
||||
let target_speed_rad_s = target_speed.get::<radian_per_second>();
|
||||
let current_max_speed = self.get_max_speed(puller_speed_controller);
|
||||
let current_max_speed = self.max_speed(puller);
|
||||
let max_speed_rad_s = current_max_speed.get::<radian_per_second>();
|
||||
|
||||
// Base acceleration proportional to current max operating speed
|
||||
@@ -253,48 +258,31 @@ impl AdaptiveSpoolSpeedController {
|
||||
AngularAcceleration::new::<radian_per_second_squared>(acceleration_limit);
|
||||
|
||||
// Update acceleration controller limits
|
||||
self.acceleration_controller
|
||||
.set_max_acceleration(acceleration);
|
||||
self.acceleration_controller
|
||||
.set_min_acceleration(-acceleration);
|
||||
self.acceleration_controller.set_max_acceleration(acceleration);
|
||||
self.acceleration_controller.set_min_acceleration(-acceleration);
|
||||
|
||||
let new_speed = self.acceleration_controller.update(target_speed, t);
|
||||
|
||||
// Record for diagnostics
|
||||
self.speed_time_window
|
||||
.update(new_speed.get::<radian_per_second>(), t);
|
||||
self.speed_time_window.update(new_speed.get::<radian_per_second>(), t);
|
||||
|
||||
new_speed
|
||||
}
|
||||
|
||||
/// Safety function that enforces absolute speed limits.
|
||||
///
|
||||
/// Ensures the spool speed never exceeds safe operational limits to protect hardware.
|
||||
/// Acts as a final safety check after all other speed calculations.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `speed`: The speed to be safety-checked and clamped
|
||||
///
|
||||
/// # Returns
|
||||
/// Speed guaranteed to be within safe operational limits
|
||||
fn clamp_speed(&self, speed: AngularVelocity) -> AngularVelocity {
|
||||
fn clamp_speed(&self, speed: AngularVelocity) -> AngularVelocity
|
||||
{
|
||||
use revolution_per_minute as rpm;
|
||||
|
||||
let min_speed = AngularVelocity::ZERO;
|
||||
let max_speed = AngularVelocity::new::<revolution_per_minute>(Self::SAFETY_MAX_SPEED_RPM);
|
||||
let max_speed = AngularVelocity::new::<rpm>(Self::SAFETY_MAX_SPEED_RPM);
|
||||
|
||||
speed.max(min_speed).min(max_speed)
|
||||
}
|
||||
|
||||
/// Adaptive learning algorithm that adjusts maximum speed factor based on tension feedback.
|
||||
///
|
||||
/// Uses proportional control to adjust the speed factor based on tension error.
|
||||
/// The controller learns over time to maintain optimal tension by scaling the
|
||||
/// baseline speed derived from puller speed.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `filament_tension`: Normalized tension value
|
||||
/// - `t`: Current timestamp for time-aware learning rate
|
||||
fn update_speed_factor(&mut self, filament_tension: f64, t: Instant) {
|
||||
let delta_t = match self.last_max_speed_factor_update {
|
||||
fn update_speed_factor(&mut self, filament_tension: f64, t: Instant)
|
||||
{
|
||||
let delta_t = match self.last_max_speed_factor_update
|
||||
{
|
||||
Some(last_update) => t.duration_since(last_update).as_secs_f64(),
|
||||
None => {
|
||||
// First call, initialize and return early
|
||||
@@ -320,27 +308,48 @@ impl AdaptiveSpoolSpeedController {
|
||||
self.last_max_speed_factor_update = Some(t);
|
||||
}
|
||||
|
||||
/// Main update function that orchestrates the complete speed control pipeline.
|
||||
///
|
||||
/// This is the primary interface that calculates the target speed, applies enable/disable
|
||||
/// logic, smooths speed changes, and applies safety limits.
|
||||
///
|
||||
/// Call this function every control loop iteration to get the commanded spool speed.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `t`: Current timestamp for time-based calculations
|
||||
/// - `tension_arm`: Reference to tension arm for feedback
|
||||
/// - `puller_speed_controller`: Reference for adaptive speed scaling
|
||||
///
|
||||
/// # Returns
|
||||
/// Final commanded angular velocity for the spool motor
|
||||
pub fn update_speed(
|
||||
&mut self,
|
||||
t: Instant,
|
||||
tension_arm: &TensionArm,
|
||||
puller_speed_controller: &PullerSpeedController,
|
||||
) -> AngularVelocity {
|
||||
let target_speed = self.calculate_speed(t, tension_arm, puller_speed_controller);
|
||||
fn max_speed(&self, puller: &Puller) -> AngularVelocity
|
||||
{
|
||||
let puller_speed = puller.output_speed().get::<meter_per_second>();
|
||||
let speed_factor = self.speed_factor.get::<meter>();
|
||||
|
||||
let speed = (puller_speed / speed_factor) * self.max_speed_multiplier;
|
||||
|
||||
AngularVelocity::new::<radian_per_second>(speed)
|
||||
}
|
||||
}
|
||||
|
||||
impl SpeedController for AdaptiveSpeedController
|
||||
{
|
||||
fn speed(&self) -> AngularVelocity
|
||||
{
|
||||
self.last_speed
|
||||
}
|
||||
|
||||
fn set_speed(&mut self, value: AngularVelocity)
|
||||
{
|
||||
self.last_speed = value;
|
||||
self.acceleration_controller.reset(value);
|
||||
}
|
||||
|
||||
fn is_enabled(&self) -> bool
|
||||
{
|
||||
self.enabled
|
||||
}
|
||||
|
||||
fn set_enabled(&mut self, value: bool)
|
||||
{
|
||||
self.enabled = value;
|
||||
}
|
||||
|
||||
fn update_speed(
|
||||
&mut self,
|
||||
t: Instant,
|
||||
tension_arm: &TensionArm,
|
||||
puller: &Puller
|
||||
) -> AngularVelocity
|
||||
{
|
||||
let target_speed = self.calculate_speed(t, tension_arm, puller);
|
||||
|
||||
let enabled_speed = if self.enabled {
|
||||
target_speed
|
||||
@@ -348,37 +357,16 @@ impl AdaptiveSpoolSpeedController {
|
||||
AngularVelocity::ZERO
|
||||
};
|
||||
|
||||
let accelerated_speed = self.accelerate_speed(enabled_speed, puller_speed_controller, t);
|
||||
let accelerated_speed = self.accelerate_speed(enabled_speed, puller, t);
|
||||
|
||||
// Store speed before clamping to preserve the actual commanded value
|
||||
self.last_speed = accelerated_speed;
|
||||
|
||||
self.clamp_speed(accelerated_speed)
|
||||
}
|
||||
|
||||
/// Enables or disables the speed controller.
|
||||
///
|
||||
/// When disabled, the controller returns zero speed regardless of tension feedback.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `enabled`: True to enable speed control, false to disable
|
||||
pub const fn set_enabled(&mut self, enabled: bool) {
|
||||
self.enabled = enabled;
|
||||
}
|
||||
|
||||
/// Returns whether the speed controller is currently enabled.
|
||||
///
|
||||
/// # Returns
|
||||
/// True if the controller is enabled, false if disabled
|
||||
pub const fn is_enabled(&self) -> bool {
|
||||
self.enabled
|
||||
}
|
||||
|
||||
/// Resets the controller to initial state, clearing all learned parameters.
|
||||
///
|
||||
/// Use this when starting a new winding operation or after significant
|
||||
/// process changes that would invalidate learned parameters.
|
||||
pub fn reset(&mut self) {
|
||||
|
||||
fn reset(&mut self)
|
||||
{
|
||||
self.last_speed = AngularVelocity::ZERO;
|
||||
self.acceleration_controller.reset(AngularVelocity::ZERO);
|
||||
self.speed_factor = Length::new::<centimeter>(4.25);
|
||||
@@ -389,72 +377,4 @@ impl AdaptiveSpoolSpeedController {
|
||||
self.acceleration_factor = Self::ACCELERATION_FACTOR;
|
||||
self.deacceleration_urgency_multiplier = Self::DEACCELERATION_URGENCY_MULTIPLIER;
|
||||
}
|
||||
|
||||
/// Returns the last commanded speed from the controller.
|
||||
///
|
||||
/// # Returns
|
||||
/// The angular velocity that was last commanded to the spool motor
|
||||
pub fn get_speed(&self) -> AngularVelocity {
|
||||
self.last_speed
|
||||
}
|
||||
|
||||
/// Manually sets the current speed, bypassing normal calculation.
|
||||
///
|
||||
/// Use this for manual control or when switching control modes to ensure
|
||||
/// smooth transitions.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `speed`: The angular velocity to set as the current speed
|
||||
pub fn set_speed(&mut self, speed: AngularVelocity) {
|
||||
self.last_speed = speed;
|
||||
self.acceleration_controller.reset(speed);
|
||||
}
|
||||
|
||||
pub fn get_speed_factor(&self) -> Length {
|
||||
self.speed_factor
|
||||
}
|
||||
|
||||
// Getters and setters for the new configurable parameters
|
||||
pub const fn get_tension_target(&self) -> f64 {
|
||||
self.tension_target
|
||||
}
|
||||
|
||||
pub const fn set_tension_target(&mut self, tension_target: f64) {
|
||||
self.tension_target = tension_target.clamp(0.0, 1.0);
|
||||
}
|
||||
|
||||
pub const fn get_radius_learning_rate(&self) -> f64 {
|
||||
self.radius_learning_rate
|
||||
}
|
||||
|
||||
pub const fn set_radius_learning_rate(&mut self, radius_learning_rate: f64) {
|
||||
self.radius_learning_rate = radius_learning_rate.max(0.0);
|
||||
}
|
||||
|
||||
pub const fn get_max_speed_multiplier(&self) -> f64 {
|
||||
self.max_speed_multiplier
|
||||
}
|
||||
|
||||
pub const fn set_max_speed_multiplier(&mut self, max_speed_multiplier: f64) {
|
||||
self.max_speed_multiplier = max_speed_multiplier.max(0.1);
|
||||
}
|
||||
|
||||
pub const fn get_acceleration_factor(&self) -> f64 {
|
||||
self.acceleration_factor
|
||||
}
|
||||
|
||||
pub const fn set_acceleration_factor(&mut self, acceleration_factor: f64) {
|
||||
self.acceleration_factor = acceleration_factor.clamp(0.01, 1.0);
|
||||
}
|
||||
|
||||
pub const fn get_deacceleration_urgency_multiplier(&self) -> f64 {
|
||||
self.deacceleration_urgency_multiplier
|
||||
}
|
||||
|
||||
pub const fn set_deacceleration_urgency_multiplier(
|
||||
&mut self,
|
||||
deacceleration_urgency_multiplier: f64,
|
||||
) {
|
||||
self.deacceleration_urgency_multiplier = deacceleration_urgency_multiplier.max(1.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
File renamed without changes.
+98
-127
@@ -1,9 +1,15 @@
|
||||
use crate::winder2::{
|
||||
clamp_revolution::clamp_revolution_uom, filament_tension::FilamentTensionCalculator,
|
||||
puller_speed_controller::PullerSpeedController,
|
||||
use std::time::Instant;
|
||||
|
||||
use units::{
|
||||
ConstZero,
|
||||
f64::*,
|
||||
angle::degree,
|
||||
angular_acceleration::{radian_per_second_squared, revolution_per_minute_per_second},
|
||||
angular_velocity::{radian_per_second, revolution_per_minute, revolution_per_second},
|
||||
length::meter,
|
||||
velocity::meter_per_second,
|
||||
};
|
||||
|
||||
use super::{clamp_revolution::Clamping, tension_arm::TensionArm};
|
||||
use control_core::{
|
||||
controllers::{
|
||||
first_degree_motion::angular_acceleration_speed_controller::AngularAccelerationSpeedController,
|
||||
@@ -14,20 +20,16 @@ use control_core::{
|
||||
moving_time_window::MovingTimeWindow,
|
||||
},
|
||||
};
|
||||
use std::time::Instant;
|
||||
|
||||
use units::ConstZero;
|
||||
use units::angle::degree;
|
||||
use units::angular_acceleration::{radian_per_second_squared, revolution_per_minute_per_second};
|
||||
use units::angular_velocity::{radian_per_second, revolution_per_minute, revolution_per_second};
|
||||
use units::f64::*;
|
||||
use units::length::meter;
|
||||
use units::velocity::meter_per_second;
|
||||
use crate::{helpers::{Clamp, clamp_revolution_uom}, winder2::devices::{Puller, spool::speed_controller::SpeedController}};
|
||||
use crate::winder2::devices::TensionArm;
|
||||
use super::helpers::FilamentTensionCalculator;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MinMaxSpoolSpeedController {
|
||||
pub struct MinMaxSpeedController
|
||||
{
|
||||
/// Current speed in
|
||||
last_speed: AngularVelocity,
|
||||
speed: AngularVelocity,
|
||||
/// Whether the speed controller is enabled or not
|
||||
enabled: bool,
|
||||
/// Acceleration controller to dampen speed change
|
||||
@@ -38,23 +40,14 @@ pub struct MinMaxSpoolSpeedController {
|
||||
speed_time_window: MovingTimeWindow<f64>,
|
||||
}
|
||||
|
||||
impl Default for MinMaxSpoolSpeedController {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl MinMaxSpoolSpeedController {
|
||||
/// Parameters:
|
||||
/// - `min_speed`: Minimum speed
|
||||
/// - `max_speed`: Maximum speed
|
||||
/// - `acceleration`: Acceleration
|
||||
/// - `deceleration`: Deceleration (preferably negative)
|
||||
pub fn new() -> Self {
|
||||
impl MinMaxSpeedController
|
||||
{
|
||||
pub fn new() -> Self
|
||||
{
|
||||
let max_speed = AngularVelocity::new::<revolution_per_minute>(150.0);
|
||||
|
||||
Self {
|
||||
last_speed: AngularVelocity::ZERO,
|
||||
speed: AngularVelocity::ZERO,
|
||||
enabled: false,
|
||||
acceleration_controller: AngularAccelerationSpeedController::new(
|
||||
Some(AngularVelocity::ZERO),
|
||||
@@ -75,44 +68,13 @@ impl MinMaxSpoolSpeedController {
|
||||
}
|
||||
}
|
||||
|
||||
impl MinMaxSpoolSpeedController {
|
||||
/// Helper method to get min speed without Option type
|
||||
fn min_speed(&self) -> AngularVelocity {
|
||||
self.acceleration_controller
|
||||
.get_min_speed()
|
||||
.unwrap_or(AngularVelocity::ZERO)
|
||||
}
|
||||
|
||||
/// Helper method to get max speed without Option type
|
||||
fn max_speed(&self) -> AngularVelocity {
|
||||
self.acceleration_controller
|
||||
.get_max_speed()
|
||||
.unwrap_or(AngularVelocity::new::<radian_per_second>(f64::INFINITY))
|
||||
}
|
||||
|
||||
/// Calculates the desired speed based on the tension arm angle.
|
||||
///
|
||||
/// If the arm is over it's maximum angle, the speed is set to the minimum speed.
|
||||
/// If the arm is under it's minimum angle, the speed is set to the maximum speed.
|
||||
/// If the arm is within the range, the speed is interpolated between the minimum and maximum speed based on the tension arm angle.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `t`: The current time.
|
||||
/// - `tension_arm`: A reference to the `TensionArm` instance that provides the angle of the tension arm.
|
||||
///
|
||||
/// Returns:
|
||||
/// - speed
|
||||
fn speed_raw(&mut self, _t: Instant, tension_arm: &TensionArm) -> AngularVelocity {
|
||||
impl MinMaxSpeedController
|
||||
{
|
||||
fn speed_raw(&mut self, _t: Instant, tension_arm_angle: Angle) -> AngularVelocity
|
||||
{
|
||||
let min_speed = AngularVelocity::ZERO;
|
||||
|
||||
// Convert puller speed to angular velocity using a magic factor
|
||||
// The factor is adjusted so that the tension arm is reasonably high when a standard spool is at its lowest diameter
|
||||
|
||||
// Respect the configured max speed limits by taking the minimum of dynamic calculation and configured max
|
||||
let max_speed = self.max_speed();
|
||||
|
||||
// calculate filament tension
|
||||
let tension_arm_angle = tension_arm.get_angle();
|
||||
let tension_arm_revolution = clamp_revolution_uom(
|
||||
tension_arm_angle,
|
||||
// inverted because min angle is max tension
|
||||
@@ -120,24 +82,20 @@ impl MinMaxSpoolSpeedController {
|
||||
self.filament_calc.get_min_angle(),
|
||||
);
|
||||
|
||||
match tension_arm_revolution.1 {
|
||||
Clamping::Min => return min_speed,
|
||||
Clamping::Max => return min_speed,
|
||||
_ => {}
|
||||
};
|
||||
// if value was clamped return min speed.
|
||||
// why? Idk ask guy who left
|
||||
if matches!(tension_arm_revolution.clamp, Clamp::Min | Clamp::Max) {
|
||||
return min_speed;
|
||||
}
|
||||
|
||||
let filament_tension = self
|
||||
.filament_calc
|
||||
.calc_filament_tension(tension_arm_revolution.0);
|
||||
.calc_filament_tension(tension_arm_revolution.value);
|
||||
|
||||
let filament_tension_inverted = 1.0 - filament_tension;
|
||||
|
||||
// use exponetial interpolation to make the speed change more sensitive in the lower range
|
||||
let filament_tension_exponential = interpolate_exponential(filament_tension_inverted, 2.0);
|
||||
|
||||
// interpolate speed linear
|
||||
|
||||
// save speed
|
||||
AngularVelocity::new::<radian_per_second>(scale(
|
||||
filament_tension_exponential,
|
||||
min_speed.get::<radian_per_second>(),
|
||||
@@ -145,14 +103,6 @@ impl MinMaxSpoolSpeedController {
|
||||
))
|
||||
}
|
||||
|
||||
/// Accelerates the speed using the acceleration controller.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `speed`: The current speed
|
||||
/// - `t`: The current time.
|
||||
///
|
||||
/// Returns:
|
||||
/// - The new speed after applying acceleration.
|
||||
fn accelerate_speed(&mut self, speed: AngularVelocity, t: Instant) -> AngularVelocity {
|
||||
// The min/mac acceleration depends on the max speed of the last 5secs or the target speed (whatever is higher)
|
||||
let acceleration = AngularAcceleration::new::<radian_per_second_squared>(
|
||||
@@ -179,13 +129,6 @@ impl MinMaxSpoolSpeedController {
|
||||
new_speed
|
||||
}
|
||||
|
||||
/// Clamps the speed to the defined minimum and maximum speed.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `speed`: The speed to be clamped.
|
||||
///
|
||||
/// Returns:
|
||||
/// - The clamped speed, ensuring it is within the range of `min_speed` and `max_speed`.
|
||||
fn clamp_speed(&mut self, speed: AngularVelocity) -> AngularVelocity {
|
||||
let min_speed = self.min_speed();
|
||||
let max_speed = self.max_speed();
|
||||
@@ -200,34 +143,8 @@ impl MinMaxSpoolSpeedController {
|
||||
}
|
||||
}
|
||||
|
||||
impl MinMaxSpoolSpeedController {
|
||||
pub fn update_speed(&mut self, t: Instant, tension_arm: &TensionArm) -> AngularVelocity {
|
||||
let speed = self.speed_raw(t, tension_arm);
|
||||
let speed = match self.enabled {
|
||||
true => speed,
|
||||
false => AngularVelocity::ZERO,
|
||||
};
|
||||
let speed = self.accelerate_speed(speed, t);
|
||||
|
||||
// save speed before clamping or it will stay 0.0
|
||||
self.last_speed = speed;
|
||||
|
||||
self.clamp_speed(speed)
|
||||
}
|
||||
|
||||
pub const fn set_enabled(&mut self, enabled: bool) {
|
||||
self.enabled = enabled;
|
||||
}
|
||||
|
||||
pub const fn is_enabled(&self) -> bool {
|
||||
self.enabled
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
self.last_speed = AngularVelocity::ZERO;
|
||||
self.acceleration_controller.reset(AngularVelocity::ZERO);
|
||||
}
|
||||
|
||||
impl MinMaxSpeedController
|
||||
{
|
||||
fn update_acceleration(&mut self) -> Result<(), MotionControllerError> {
|
||||
// Set acceleration to 1/4 of the range between min and max speed
|
||||
// The spool will accelerate from min to max speed in 4 seconds
|
||||
@@ -262,32 +179,36 @@ impl MinMaxSpoolSpeedController {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_max_speed(&self) -> AngularVelocity {
|
||||
self.max_speed()
|
||||
pub fn max_speed(&self) -> AngularVelocity {
|
||||
self.acceleration_controller
|
||||
.get_max_speed()
|
||||
.unwrap_or(AngularVelocity::ZERO)
|
||||
}
|
||||
|
||||
pub fn get_min_speed(&self) -> AngularVelocity {
|
||||
self.min_speed()
|
||||
pub fn min_speed(&self) -> AngularVelocity {
|
||||
self.acceleration_controller
|
||||
.get_min_speed()
|
||||
.unwrap_or(AngularVelocity::ZERO)
|
||||
}
|
||||
|
||||
pub fn get_speed(&self) -> AngularVelocity {
|
||||
self.last_speed
|
||||
self.speed
|
||||
}
|
||||
|
||||
pub fn set_speed(&mut self, speed: AngularVelocity) {
|
||||
self.last_speed = speed;
|
||||
self.speed = speed;
|
||||
// Also update the acceleration controller's current speed to ensure smooth transitions
|
||||
self.acceleration_controller.reset(speed);
|
||||
}
|
||||
|
||||
/// derive the radius from the puller speed and the current angular speed
|
||||
pub fn get_radius(&self, puller_speed_controller: &PullerSpeedController) -> Length {
|
||||
let puller_speed: Velocity = puller_speed_controller.get_target_speed();
|
||||
let angular_speed: AngularVelocity = self.last_speed;
|
||||
pub fn get_radius(&self, puller: &Puller) -> Length
|
||||
{
|
||||
let puller_speed = puller.target_speed().get::<meter_per_second>();
|
||||
let angular_speed = self.speed.get::<revolution_per_second>();
|
||||
|
||||
// Calculate the radius using the formula: radius = speed / angular_speed
|
||||
let radius =
|
||||
puller_speed.get::<meter_per_second>() / angular_speed.get::<revolution_per_second>();
|
||||
let radius = puller_speed / angular_speed;
|
||||
|
||||
// Ensure the radius is a normal number, otherwise default to 0.0
|
||||
let radius = Some(radius).filter(|&n| n.is_normal()).unwrap_or(0.0);
|
||||
@@ -295,3 +216,53 @@ impl MinMaxSpoolSpeedController {
|
||||
Length::new::<meter>(radius)
|
||||
}
|
||||
}
|
||||
|
||||
impl SpeedController for MinMaxSpeedController
|
||||
{
|
||||
fn speed(&self) -> AngularVelocity {
|
||||
self.speed
|
||||
}
|
||||
|
||||
fn set_speed(&mut self, speed: AngularVelocity) {
|
||||
self.speed = speed;
|
||||
// Also update the acceleration controller's
|
||||
// current speed to ensure smooth transitions
|
||||
self.acceleration_controller.reset(speed);
|
||||
}
|
||||
|
||||
fn set_enabled(&mut self, enabled: bool) {
|
||||
self.enabled = enabled;
|
||||
}
|
||||
|
||||
fn is_enabled(&self) -> bool {
|
||||
self.enabled
|
||||
}
|
||||
|
||||
fn update_speed(
|
||||
&mut self,
|
||||
t: Instant,
|
||||
tension_arm: &TensionArm,
|
||||
puller: &Puller
|
||||
) -> AngularVelocity
|
||||
{
|
||||
_ = puller;
|
||||
|
||||
let speed = self.speed_raw(t, tension_arm.get_angle());
|
||||
let speed = match self.enabled
|
||||
{
|
||||
true => speed,
|
||||
false => AngularVelocity::ZERO,
|
||||
};
|
||||
let speed = self.accelerate_speed(speed, t);
|
||||
|
||||
// save speed before clamping or it will stay 0.0
|
||||
self.speed = speed;
|
||||
|
||||
self.clamp_speed(speed)
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.speed = AngularVelocity::ZERO;
|
||||
self.acceleration_controller.reset(AngularVelocity::ZERO);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use units::AngularVelocity;
|
||||
|
||||
mod helpers;
|
||||
|
||||
mod min_max;
|
||||
pub use min_max::MinMaxSpeedController;
|
||||
|
||||
mod adapative;
|
||||
pub use adapative::AdaptiveSpeedController;
|
||||
|
||||
use crate::winder2::devices::{Puller, TensionArm};
|
||||
|
||||
pub trait SpeedController
|
||||
{
|
||||
fn speed(&self) -> AngularVelocity;
|
||||
fn set_speed(&mut self, speed: AngularVelocity);
|
||||
|
||||
fn is_enabled(&self) -> bool;
|
||||
fn set_enabled(&mut self, enabled: bool);
|
||||
|
||||
fn reset(&mut self);
|
||||
|
||||
fn update_speed(
|
||||
&mut self,
|
||||
t: Instant,
|
||||
tension_arm: &TensionArm,
|
||||
puller: &Puller
|
||||
) -> AngularVelocity;
|
||||
}
|
||||
@@ -1,69 +1,83 @@
|
||||
use ethercat_hal::io::analog_input::{AnalogInput, physical::AnalogInputValue};
|
||||
use units::angle::revolution;
|
||||
use units::electric_potential::volt;
|
||||
use units::f64::*;
|
||||
|
||||
use ethercat_hal::io::analog_input::{AnalogInput, physical::AnalogInputValue};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TensionArm {
|
||||
pub analog_input: AnalogInput,
|
||||
pub zero: Angle,
|
||||
/// was zeroed at least once
|
||||
pub zeroed: bool,
|
||||
pub struct TensionArm
|
||||
{
|
||||
sensor: AnalogInput,
|
||||
zero_offset: Angle,
|
||||
is_calibrated: bool,
|
||||
}
|
||||
|
||||
impl TensionArm {
|
||||
pub fn new(analog_input: AnalogInput) -> Self {
|
||||
// public interface
|
||||
impl TensionArm
|
||||
{
|
||||
pub fn new(sensor: AnalogInput) -> Self
|
||||
{
|
||||
Self {
|
||||
analog_input,
|
||||
zero: Angle::new::<revolution>(0.0),
|
||||
zeroed: false,
|
||||
sensor,
|
||||
zero_offset: Angle::new::<revolution>(0.0),
|
||||
is_calibrated: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn volts_to_angle(&self, volts: f64) -> Angle {
|
||||
// 0V = 0deg 5V = 3600deg
|
||||
// always wrap into 0..1 revolution
|
||||
Angle::new::<revolution>(volts / 5.0) % Angle::new::<revolution>(1.0)
|
||||
pub fn calibrate(&mut self)
|
||||
{
|
||||
self.zero_offset = self.raw_angle();
|
||||
self.is_calibrated = true;
|
||||
}
|
||||
|
||||
fn get_volts(&self) -> f64 {
|
||||
// get the normalized value from the analog input
|
||||
let value = self.analog_input.get_physical();
|
||||
|
||||
match value {
|
||||
AnalogInputValue::Potential(v) => v.get::<volt>(),
|
||||
_ => panic!("Expected a potential value"),
|
||||
}
|
||||
pub const fn is_calibrated(&self) -> bool
|
||||
{
|
||||
self.is_calibrated
|
||||
}
|
||||
|
||||
fn raw_angle(&self) -> Angle {
|
||||
// get volts
|
||||
let volts = self.get_volts();
|
||||
|
||||
// 0V = 0deg 5V = 3600deg
|
||||
self.volts_to_angle(volts)
|
||||
}
|
||||
|
||||
pub fn get_angle(&self) -> Angle {
|
||||
pub fn get_angle(&self) -> Angle
|
||||
{
|
||||
// revolution is maping -1/1 to 0/1
|
||||
let raw = self.raw_angle();
|
||||
|
||||
// Handle the wraparound case
|
||||
if raw < self.zero {
|
||||
if raw < self.zero_offset {
|
||||
// We've wrapped around, so add a full revolution
|
||||
(raw + Angle::new::<revolution>(1.0)) - self.zero
|
||||
(raw + Angle::new::<revolution>(1.0)) - self.zero_offset
|
||||
} else {
|
||||
// Normal case
|
||||
raw - self.zero
|
||||
raw - self.zero_offset
|
||||
}
|
||||
}
|
||||
|
||||
pub fn zero(&mut self) {
|
||||
self.zero = self.raw_angle();
|
||||
self.zeroed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// utils
|
||||
impl TensionArm
|
||||
{
|
||||
fn volts_to_angle(&self, volts: f64) -> Angle
|
||||
{
|
||||
// 0V = 0deg -> 5V = 3600deg
|
||||
// always wrap into 0..1 revolution
|
||||
Angle::new::<revolution>(volts / 5.0) % Angle::new::<revolution>(1.0)
|
||||
}
|
||||
|
||||
fn get_volts(&self) -> f64
|
||||
{
|
||||
// TODO: reconsider panicking, to allow system to shutdown gracefully.
|
||||
// but since I am only moving code, I will not touch this...
|
||||
let AnalogInputValue::Potential(potential) =
|
||||
self.sensor.get_physical() else { panic!("Expected a potential value"); };
|
||||
|
||||
potential.get::<volt>()
|
||||
}
|
||||
|
||||
fn raw_angle(&self) -> Angle
|
||||
{
|
||||
self.volts_to_angle(self.get_volts())
|
||||
}
|
||||
}
|
||||
|
||||
// tests
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -73,7 +87,6 @@ mod tests {
|
||||
analog_input::{AnalogInputInput, physical::AnalogInputRange},
|
||||
analog_input_dummy::AnalogInputDummy,
|
||||
};
|
||||
use std::i16;
|
||||
|
||||
#[test]
|
||||
fn volts_to_angle() {
|
||||
@@ -130,7 +143,7 @@ mod tests {
|
||||
normalized: (5.0 / 10.0),
|
||||
wiring_error: false,
|
||||
});
|
||||
let physical = tension_arm.analog_input.get_physical();
|
||||
let physical = tension_arm.sensor.get_physical();
|
||||
match physical {
|
||||
AnalogInputValue::Potential(v) => {
|
||||
assert_relative_eq!(v.get::<volt>(), 5.0, epsilon = f64::EPSILON);
|
||||
@@ -0,0 +1,651 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use control_core::converters::linear_step_converter::LinearStepConverter;
|
||||
use ethercat_hal::io::digital_input::DigitalInput;
|
||||
use ethercat_hal::io::digital_output::DigitalOutput;
|
||||
use ethercat_hal::io::stepper_velocity_el70x1::StepperVelocityEL70x1;
|
||||
use units::AngularVelocity;
|
||||
use units::ConstZero;
|
||||
use units::Length;
|
||||
use units::Velocity;
|
||||
use units::angular_velocity::revolution_per_second;
|
||||
use units::length::millimeter;
|
||||
use units::velocity::millimeter_per_second;
|
||||
|
||||
use crate::winder2::devices::Spool;
|
||||
|
||||
use super::OperationState;
|
||||
|
||||
/// Represents the puller motor
|
||||
#[derive(Debug)]
|
||||
pub struct Traverse
|
||||
{
|
||||
// hardware
|
||||
motor: StepperVelocityEL70x1,
|
||||
laser_pointer: DigitalOutput,
|
||||
limit_switch: DigitalInput,
|
||||
|
||||
// config
|
||||
device_state: OperationState,
|
||||
position: Length,
|
||||
limit_inner: Length,
|
||||
limit_outer: Length,
|
||||
step_size: Length,
|
||||
padding: Length,
|
||||
state: State,
|
||||
|
||||
// converters
|
||||
fullstep_converter: LinearStepConverter,
|
||||
microstep_converter: LinearStepConverter,
|
||||
}
|
||||
|
||||
// constants
|
||||
impl Traverse
|
||||
{
|
||||
const DEFAULT_LIMIT_INNER: f64 = 22.0; // in mm
|
||||
const DEFAULT_LIMIT_OUTER: f64 = 92.0; // in mm
|
||||
const DEFAULT_PADDING: f64 = 0.88; // in mm
|
||||
const DEFAULT_STEP_SIZE: f64 = 1.75; // in mm
|
||||
const CIRCUMFERENCE: f64 = 35.0; // in mm
|
||||
const STEPS_PER_REVOLUTION: i16 = 200;
|
||||
const MICRO_STEPS_COUNT: i16 = 64;
|
||||
}
|
||||
|
||||
// public interface
|
||||
impl Traverse
|
||||
{
|
||||
pub fn new(
|
||||
motor: StepperVelocityEL70x1,
|
||||
limit_switch: DigitalInput,
|
||||
laser_pointer: DigitalOutput,
|
||||
) -> Self
|
||||
{
|
||||
let limit_inner = Length::new::<millimeter>(Self::DEFAULT_LIMIT_INNER);
|
||||
let limit_outer = Length::new::<millimeter>(Self::DEFAULT_LIMIT_OUTER);
|
||||
let circumference = Length::new::<millimeter>(Self::CIRCUMFERENCE);
|
||||
|
||||
let fullstep_converter = LinearStepConverter::from_circumference(
|
||||
Self::STEPS_PER_REVOLUTION,
|
||||
circumference,
|
||||
);
|
||||
|
||||
let microstep_converter = LinearStepConverter::from_circumference(
|
||||
Self::STEPS_PER_REVOLUTION * Self::MICRO_STEPS_COUNT,
|
||||
circumference,
|
||||
);
|
||||
|
||||
let step_size = Length::new::<millimeter>(Self::DEFAULT_STEP_SIZE);
|
||||
let padding = Length::new::<millimeter>(Self::DEFAULT_PADDING);
|
||||
|
||||
Self {
|
||||
device_state: OperationState::Disabled,
|
||||
motor,
|
||||
laser_pointer,
|
||||
limit_switch,
|
||||
fullstep_converter,
|
||||
microstep_converter,
|
||||
limit_inner,
|
||||
limit_outer,
|
||||
step_size,
|
||||
padding,
|
||||
position: Length::ZERO,
|
||||
state: State::NotHomed,
|
||||
}
|
||||
}
|
||||
|
||||
/// update the traverse. Returns true if the state
|
||||
/// changed from this update
|
||||
pub fn update(&mut self, spool: &Spool) -> bool
|
||||
{
|
||||
if self.device_state == OperationState::Disabled { return false; }
|
||||
|
||||
let old_state = self.state;
|
||||
|
||||
self.update_position();
|
||||
self.update_state();
|
||||
|
||||
let steps_per_second = self.compute_output_steps(spool.speed());
|
||||
_ = self.motor.set_speed(steps_per_second);
|
||||
|
||||
old_state == self.state
|
||||
}
|
||||
}
|
||||
|
||||
// getters + setters
|
||||
impl Traverse
|
||||
{
|
||||
#[allow(dead_code)]
|
||||
pub const fn device_state(&self) -> OperationState { self.device_state }
|
||||
|
||||
pub fn set_device_state(&mut self, device_state: OperationState)
|
||||
{
|
||||
use OperationState::*;
|
||||
|
||||
// No change, nothing to do
|
||||
if self.device_state == device_state { return; }
|
||||
|
||||
// Leaving standby, enable motor
|
||||
if self.device_state == Disabled
|
||||
{
|
||||
self.motor.set_enabled(true);
|
||||
}
|
||||
|
||||
match device_state // guranteed state change
|
||||
{
|
||||
Disabled => self.motor.set_enabled(false),
|
||||
Holding => self.goto_home(),
|
||||
Running => self.start_traversing(),
|
||||
}
|
||||
|
||||
self.device_state = device_state;
|
||||
}
|
||||
|
||||
pub fn limit_inner(&self) -> Length { self.limit_inner }
|
||||
|
||||
pub fn try_set_limit_inner(&mut self, limit_inner: Length) -> Result<(), ()>
|
||||
{
|
||||
// Validate the new inner limit against current outer limit
|
||||
if !Self::validate_traverse_limits(limit_inner, self.limit_outer)
|
||||
{
|
||||
// Don't update if validation fails - keep the current value
|
||||
return Err(());
|
||||
}
|
||||
|
||||
self.limit_inner = limit_inner;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn limit_outer(&self) -> Length { self.limit_outer }
|
||||
pub fn try_set_limit_outer(&mut self, limit_outer: Length) -> Result<(), ()>
|
||||
{
|
||||
// Validate the new outer limit against current inner limit
|
||||
if !Self::validate_traverse_limits(self.limit_inner, limit_outer)
|
||||
{
|
||||
// Don't update if validation fails - keep the current value
|
||||
return Err(());
|
||||
}
|
||||
|
||||
self.limit_outer = limit_outer;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn step_size(&self) -> Length { self.step_size }
|
||||
pub fn set_step_size(&mut self, step_size: Length) { self.step_size = step_size; }
|
||||
|
||||
pub fn padding(&self) -> Length { self.padding }
|
||||
pub fn set_padding(&mut self, padding: Length) { self.padding = padding; }
|
||||
|
||||
pub fn current_position(&self) -> Option<Length>
|
||||
{
|
||||
match self.is_homed()
|
||||
{
|
||||
true => Some(self.position),
|
||||
false => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn laser_pointer_enabled(&self) -> bool
|
||||
{
|
||||
self.laser_pointer.get()
|
||||
}
|
||||
|
||||
pub fn set_laser_pointer_enabled(&mut self, value: bool)
|
||||
{
|
||||
self.laser_pointer.set(value);
|
||||
}
|
||||
}
|
||||
|
||||
// State management
|
||||
impl Traverse
|
||||
{
|
||||
pub fn can_goto_limit_inner(&self) -> bool
|
||||
{
|
||||
self.device_state == OperationState::Disabled
|
||||
|| !self.is_homed()
|
||||
|| self.is_going_in()
|
||||
|| self.is_going_home()
|
||||
|| self.is_traversing()
|
||||
}
|
||||
|
||||
pub fn try_goto_limit_inner(&mut self) -> Result<(), ()>
|
||||
{
|
||||
if !self.can_goto_limit_inner()
|
||||
{
|
||||
return Err(());
|
||||
}
|
||||
|
||||
self.state = State::GoingIn;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn can_goto_limit_outer(&self)-> bool
|
||||
{
|
||||
self.device_state == OperationState::Disabled
|
||||
|| !self.is_homed()
|
||||
|| self.is_going_out()
|
||||
|| self.is_going_home()
|
||||
|| self.is_traversing()
|
||||
}
|
||||
|
||||
pub fn try_goto_limit_outer(&mut self) -> Result<(), ()>
|
||||
{
|
||||
if !self.can_goto_limit_outer()
|
||||
{
|
||||
return Err(());
|
||||
}
|
||||
|
||||
self.state = State::GoingOut;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn can_go_home(&self)-> bool
|
||||
{
|
||||
self.device_state == OperationState::Disabled
|
||||
|| !self.is_homed()
|
||||
|| self.is_going_out()
|
||||
|| self.is_going_home()
|
||||
|| self.is_traversing()
|
||||
}
|
||||
|
||||
pub fn try_goto_home(&mut self) -> Result<(), ()>
|
||||
{
|
||||
if !self.can_go_home()
|
||||
{
|
||||
return Err(());
|
||||
}
|
||||
|
||||
self.goto_home();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn goto_home(&mut self)
|
||||
{
|
||||
self.state = State::Homing(HomingState::Initialize);
|
||||
}
|
||||
|
||||
fn start_traversing(&mut self)
|
||||
{
|
||||
self.state = State::Traversing(TraversingState::GoingOut);
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn is_idle(&self) -> bool
|
||||
{
|
||||
self.state == State::Idle
|
||||
}
|
||||
|
||||
pub fn is_homed(&self) -> bool {
|
||||
// if not [`State::NotHomed`], then it is homed
|
||||
!matches!(self.state, State::NotHomed)
|
||||
}
|
||||
|
||||
pub fn is_going_in(&self) -> bool {
|
||||
self.state == State::GoingIn
|
||||
}
|
||||
|
||||
pub fn is_going_out(&self) -> bool {
|
||||
self.state == State::GoingOut
|
||||
}
|
||||
|
||||
pub fn is_going_home(&self) -> bool {
|
||||
matches!(self.state, State::Homing(_))
|
||||
}
|
||||
|
||||
pub fn is_traversing(&self) -> bool {
|
||||
matches!(self.state, State::Traversing(_))
|
||||
}
|
||||
}
|
||||
|
||||
// helpers
|
||||
impl Traverse
|
||||
{
|
||||
fn compute_output_steps(&self, spool_speed: AngularVelocity) -> f64
|
||||
{
|
||||
let speed = self.veloctity_from_state(spool_speed);
|
||||
self.fullstep_converter.velocity_to_steps(speed)
|
||||
}
|
||||
|
||||
pub fn update_position(&mut self)
|
||||
{
|
||||
let steps = self.motor.get_position() as f64;
|
||||
self.position = self.microstep_converter.steps_to_distance(steps);
|
||||
}
|
||||
|
||||
fn endstop_triggered(&self) -> bool
|
||||
{
|
||||
self.limit_switch.get_value().unwrap_or(false)
|
||||
}
|
||||
|
||||
fn calculate_traverse_speed(spool_speed: AngularVelocity, step_size: Length) -> Velocity
|
||||
{
|
||||
let spool_speed = spool_speed.get::<revolution_per_second>();
|
||||
let step_size = step_size.get::<millimeter>();
|
||||
|
||||
// Calculate the traverse speed directly from spool speed and step size
|
||||
Velocity::new::<millimeter_per_second>(spool_speed * step_size)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
-absolute_speed.abs()
|
||||
} else if self.position < target_position {
|
||||
absolute_speed.abs()
|
||||
} else {
|
||||
Velocity::ZERO
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate distance to position
|
||||
fn distance_to_position(&self, target_position: Length) -> Length
|
||||
{
|
||||
(self.position - target_position).abs()
|
||||
}
|
||||
|
||||
// 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();
|
||||
lower_tolerance <= self.position && self.position <= upper_tolerance
|
||||
}
|
||||
|
||||
/// Validates that traverse limits maintain proper constraints:
|
||||
/// - Inner limit must be smaller than outer limit
|
||||
/// - At least 0.9mm difference between inner and outer limits
|
||||
fn validate_traverse_limits(inner: Length, outer: Length) -> bool
|
||||
{
|
||||
outer > inner + Length::new::<millimeter>(0.9)
|
||||
}
|
||||
}
|
||||
|
||||
// state updates
|
||||
impl Traverse
|
||||
{
|
||||
fn update_state(&mut self)
|
||||
{
|
||||
use State::*;
|
||||
|
||||
match self.state
|
||||
{
|
||||
NotHomed | Idle => {}
|
||||
GoingIn =>
|
||||
{
|
||||
// If inner limit is reached
|
||||
if self.is_at_position(self.limit_inner, Length::new::<millimeter>(0.01))
|
||||
{
|
||||
// Put Into Idle
|
||||
self.state = State::Idle;
|
||||
}
|
||||
}
|
||||
GoingOut =>
|
||||
{
|
||||
// If outer limit is reached
|
||||
if self.is_at_position(self.limit_outer, Length::new::<millimeter>(0.01)) {
|
||||
// Put Into Idle
|
||||
self.state = State::Idle;
|
||||
}
|
||||
}
|
||||
Homing(state) => self.update_homing_state(state),
|
||||
Traversing(state) => self.update_traversing_state(state),
|
||||
};
|
||||
}
|
||||
|
||||
fn update_homing_state(&mut self, homing_state: HomingState)
|
||||
{
|
||||
use HomingState::*;
|
||||
|
||||
match homing_state
|
||||
{
|
||||
Initialize =>
|
||||
{
|
||||
// If endstop is triggered, escape the endstop
|
||||
if self.endstop_triggered() {
|
||||
self.state = State::Homing(EscapeEndstop);
|
||||
} else {
|
||||
// If endstop is not triggered, move to the endstop
|
||||
self.state = State::Homing(FindEndstopCoarse);
|
||||
}
|
||||
}
|
||||
EscapeEndstop => {
|
||||
// Move out until endstop is not triggered anymore
|
||||
if !self.endstop_triggered() {
|
||||
self.state = State::Homing(FindEndstopFineDistancing);
|
||||
}
|
||||
}
|
||||
FindEndstopFineDistancing => {
|
||||
// Move out until endstop is not triggered anymore
|
||||
if !self.endstop_triggered() {
|
||||
// Find endstop fine
|
||||
self.state = State::Homing(FindEndtopFine);
|
||||
}
|
||||
}
|
||||
FindEndtopFine => {
|
||||
// If endstop is reached change to idle
|
||||
if self.endstop_triggered() {
|
||||
// Set poition of traverse to 0
|
||||
self.motor.set_position(0);
|
||||
// Put Into Idle
|
||||
self.state = State::Homing(Validate(Instant::now()));
|
||||
}
|
||||
}
|
||||
FindEndstopCoarse => {
|
||||
// Move to endstop
|
||||
if self.endstop_triggered() {
|
||||
// Move awaiy from endstop
|
||||
self.state = State::Homing(FindEndstopFineDistancing);
|
||||
}
|
||||
}
|
||||
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(Initialize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_traversing_state(&mut self, state: TraversingState)
|
||||
{
|
||||
use TraversingState::*;
|
||||
|
||||
match state {
|
||||
GoingOut => {
|
||||
// If outer limit is reached
|
||||
if self.position >= self.limit_outer - self.padding {
|
||||
// Turn around
|
||||
self.state = State::Traversing(TraversingIn);
|
||||
}
|
||||
}
|
||||
TraversingIn => {
|
||||
// If inner limit is reached
|
||||
if self.position <= self.limit_inner + self.padding {
|
||||
// Turn around
|
||||
self.state = State::Traversing(TraversingOut);
|
||||
}
|
||||
}
|
||||
TraversingOut => {
|
||||
// If outer limit is reached
|
||||
if self.position >= self.limit_outer - self.padding {
|
||||
// Turn around
|
||||
self.state = State::Traversing(TraversingIn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// velocity computation
|
||||
impl Traverse
|
||||
{
|
||||
fn veloctity_from_state(&self, spool_speed: AngularVelocity) -> Velocity
|
||||
{
|
||||
use State::*;
|
||||
|
||||
match self.state {
|
||||
NotHomed => Velocity::ZERO, // Not homed, no movement
|
||||
Idle => Velocity::ZERO, // No movement in idle state
|
||||
GoingIn => {
|
||||
// Move in at a speed of 10-100 mm/s
|
||||
self.speed_to_position(
|
||||
self.limit_inner,
|
||||
match self.distance_to_position(self.limit_inner).abs()
|
||||
> Length::new::<millimeter>(1.0)
|
||||
{
|
||||
true => Velocity::new::<millimeter_per_second>(100.0),
|
||||
false => Velocity::new::<millimeter_per_second>(10.0),
|
||||
},
|
||||
)
|
||||
},
|
||||
GoingOut => {
|
||||
// Move out at a speed of 10-100 mm/s
|
||||
self.speed_to_position(
|
||||
self.limit_outer,
|
||||
match self.distance_to_position(self.limit_outer).abs()
|
||||
> Length::new::<millimeter>(1.0)
|
||||
{
|
||||
true => Velocity::new::<millimeter_per_second>(100.0),
|
||||
false => Velocity::new::<millimeter_per_second>(10.0),
|
||||
},
|
||||
)
|
||||
}
|
||||
Homing(state) => Self::velocity_from_homing_state(state),
|
||||
Traversing(state) => self.velocity_from_traversing_state(state, spool_speed),
|
||||
}
|
||||
}
|
||||
|
||||
fn velocity_from_homing_state(homing_state: HomingState) -> Velocity
|
||||
{
|
||||
use units::velocity::millimeter_per_second as mmps;
|
||||
use HomingState::*;
|
||||
|
||||
match homing_state {
|
||||
Initialize => Velocity::ZERO,
|
||||
// Move out at a speed of 10 mm/s
|
||||
EscapeEndstop => Velocity::new::<mmps>(10.0),
|
||||
// Move out at a speed of 2 mm/s
|
||||
FindEndstopFineDistancing => Velocity::new::<mmps>(2.0),
|
||||
// Move in at a speed of -100 mm/s
|
||||
FindEndstopCoarse => Velocity::new::<mmps>(-100.0),
|
||||
// move into the endstop at 2 mm/s
|
||||
FindEndtopFine => Velocity::new::<mmps>(-2.0),
|
||||
// We stand still until the validation cooldown has passed
|
||||
Validate(_) => Velocity::ZERO,
|
||||
}
|
||||
}
|
||||
|
||||
fn velocity_from_traversing_state(
|
||||
&self,
|
||||
traversing_state: TraversingState,
|
||||
spool_speed: AngularVelocity
|
||||
) -> Velocity
|
||||
{
|
||||
use TraversingState::*;
|
||||
|
||||
match traversing_state {
|
||||
GoingOut => {
|
||||
// Move out at a speed of 100 mm/s
|
||||
self.speed_to_position(
|
||||
self.limit_outer - self.padding + Length::new::<millimeter>(0.01),
|
||||
Velocity::new::<millimeter_per_second>(100.0),
|
||||
)
|
||||
}
|
||||
TraversingIn =>
|
||||
{
|
||||
let offset = Length::new::<millimeter>(0.01);
|
||||
let target_position = self.limit_inner + self.padding - offset;
|
||||
let absolute_speed = Self::calculate_traverse_speed(spool_speed, self.step_size);
|
||||
self.speed_to_position(target_position, absolute_speed)
|
||||
}
|
||||
TraversingOut =>
|
||||
{
|
||||
let offset = Length::new::<millimeter>(0.01);
|
||||
let target_position = self.limit_outer - self.padding + offset;
|
||||
let absolute_speed = Self::calculate_traverse_speed(spool_speed, self.step_size);
|
||||
self.speed_to_position(target_position, absolute_speed)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// other types
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum State
|
||||
{
|
||||
/// Initial state
|
||||
NotHomed,
|
||||
|
||||
/// Doing nothing
|
||||
/// Already homed
|
||||
Idle,
|
||||
|
||||
/// Going to inner limit
|
||||
///
|
||||
/// After reaching the inner limit, the state will change to [`State::Idle`]
|
||||
GoingIn,
|
||||
|
||||
/// Going to outer limit
|
||||
///
|
||||
/// After reaching the outer limit, the state will change to [`State::Idle`]
|
||||
GoingOut,
|
||||
|
||||
/// Homing is in progress
|
||||
///
|
||||
/// After homing is done, the state will change to [`State::Idle`]
|
||||
Homing(HomingState),
|
||||
|
||||
/// Move between inner and outer limits
|
||||
Traversing(TraversingState),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum TraversingState
|
||||
{
|
||||
/// Like [`State::GoingOut`] but
|
||||
/// - will go into [`State::GoingIn`] after reaching the outer limit
|
||||
GoingOut,
|
||||
|
||||
/// Like [`State::GoingIn`] but
|
||||
/// - will go into [`State::GoingOut`] after reaching the inner limit
|
||||
/// - speed is synced to spool speed
|
||||
TraversingIn,
|
||||
|
||||
/// Like [`State::GoingOut`] but
|
||||
/// - will go into [`State::GoingIn`] after reaching the outer limit
|
||||
/// - speed is synced to spool speed
|
||||
TraversingOut,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum HomingState
|
||||
{
|
||||
/// In this state the traverse 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 traverse 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`]
|
||||
FindEndstopFineDistancing,
|
||||
|
||||
/// In this state the traverse is fast until it reaches the endstop
|
||||
FindEndstopCoarse,
|
||||
|
||||
/// In this state the traverse is moving slowly until it reaches the endstop
|
||||
FindEndtopFine,
|
||||
|
||||
/// In this state we check if th current position is actually 0.0, if not we redo the homing routine
|
||||
Validate(Instant),
|
||||
}
|
||||
@@ -1,507 +0,0 @@
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
mod winder2_imports {
|
||||
pub use super::super::puller_speed_controller::PullerRegulationMode;
|
||||
pub use super::super::{TraverseMode, Winder2, Winder2Mode, api, spool_speed_controller};
|
||||
pub use crate::buffer1::BufferV1;
|
||||
pub use api::{
|
||||
LiveValuesEvent, ModeState, PullerState, SpoolAutomaticActionMode,
|
||||
SpoolAutomaticActionState, SpoolSpeedControllerState, StateEvent, TensionArmState,
|
||||
TraverseState, Winder2Events,
|
||||
};
|
||||
pub use control_core::socketio::event::BuildEvent;
|
||||
pub use control_core::socketio::namespace::NamespaceCacheingLogic;
|
||||
pub use std::time::Instant;
|
||||
pub use units::{
|
||||
angle::degree,
|
||||
angular_velocity::revolution_per_minute,
|
||||
f64::*,
|
||||
length::{meter, millimeter},
|
||||
velocity::meter_per_minute,
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
pub use winder2_imports::*;
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
impl Winder2 {
|
||||
/// Implement Spool
|
||||
/// called by `act`
|
||||
pub fn sync_spool_speed(&mut self, t: Instant) {
|
||||
let angular_velocity = self.spool_speed_controller.update_speed(
|
||||
t,
|
||||
&self.tension_arm,
|
||||
&self.puller_speed_controller,
|
||||
);
|
||||
|
||||
// Apply direction based on forward setting
|
||||
let directed_angular_velocity = if self.spool_speed_controller.get_forward() {
|
||||
angular_velocity
|
||||
} else {
|
||||
-angular_velocity
|
||||
};
|
||||
|
||||
let steps_per_second = self
|
||||
.spool_step_converter
|
||||
.angular_velocity_to_steps(directed_angular_velocity);
|
||||
let _ = self.spool.set_speed(steps_per_second);
|
||||
}
|
||||
|
||||
pub fn stop_or_pull_spool(&mut self, now: Instant) {
|
||||
if matches!(
|
||||
self.spool_automatic_action.mode,
|
||||
SpoolAutomaticActionMode::NoAction
|
||||
) {
|
||||
self.calculate_spool_auto_progress_(now);
|
||||
return;
|
||||
}
|
||||
|
||||
match self.mode {
|
||||
Winder2Mode::Pull => self.calculate_spool_auto_progress_(now),
|
||||
Winder2Mode::Wind => self.calculate_spool_auto_progress_(now),
|
||||
_ => {
|
||||
self.spool_automatic_action.progress_last_check = now;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if self.spool_automatic_action.progress >= self.spool_automatic_action.target_length {
|
||||
match self.spool_automatic_action.mode {
|
||||
SpoolAutomaticActionMode::NoAction => (),
|
||||
SpoolAutomaticActionMode::Pull => {
|
||||
self.stop_or_pull_spool_reset(now);
|
||||
self.set_mode(&Winder2Mode::Pull);
|
||||
}
|
||||
SpoolAutomaticActionMode::Hold => {
|
||||
self.stop_or_pull_spool_reset(now);
|
||||
self.set_mode(&Winder2Mode::Hold);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Implement Mode
|
||||
pub fn set_mode(&mut self, mode: &Winder2Mode) {
|
||||
let should_update = *mode != Winder2Mode::Wind || self.can_wind();
|
||||
|
||||
if should_update {
|
||||
// all transitions are allowed
|
||||
self.mode = mode.clone();
|
||||
|
||||
// Apply the mode changes to the spool and puller
|
||||
self.set_spool_mode(mode);
|
||||
self.set_puller_mode(mode);
|
||||
self.set_traverse_mode(mode);
|
||||
}
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Implement Traverse
|
||||
pub fn set_laser(&mut self, value: bool) {
|
||||
self.laser.set(value);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn traverse_set_limit_inner(&mut self, limit: f64) {
|
||||
let new_inner = Length::new::<millimeter>(limit);
|
||||
let current_outer = self.traverse_controller.get_limit_outer();
|
||||
|
||||
// Validate the new inner limit against current outer limit
|
||||
if !Self::validate_traverse_limits(new_inner, current_outer) {
|
||||
// Don't update if validation fails - keep the current value
|
||||
return;
|
||||
}
|
||||
self.traverse_controller.set_limit_inner(new_inner);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn traverse_set_limit_outer(&mut self, limit: f64) {
|
||||
let new_outer = Length::new::<millimeter>(limit);
|
||||
let current_inner = self.traverse_controller.get_limit_inner();
|
||||
|
||||
// Validate the new outer limit against current inner limit
|
||||
if !Self::validate_traverse_limits(current_inner, new_outer) {
|
||||
// Don't update if validation fails - keep the current value
|
||||
return;
|
||||
}
|
||||
|
||||
self.traverse_controller.set_limit_outer(new_outer);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn traverse_set_step_size(&mut self, step_size: f64) {
|
||||
let step_size = Length::new::<millimeter>(step_size);
|
||||
self.traverse_controller.set_step_size(step_size);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn traverse_set_padding(&mut self, padding: f64) {
|
||||
let padding = Length::new::<millimeter>(padding);
|
||||
self.traverse_controller.set_padding(padding);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn traverse_goto_limit_inner(&mut self) {
|
||||
if self.can_go_in() {
|
||||
self.traverse_controller.goto_limit_inner();
|
||||
}
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn traverse_goto_limit_outer(&mut self) {
|
||||
if self.can_go_out() {
|
||||
self.traverse_controller.goto_limit_outer();
|
||||
}
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn traverse_goto_home(&mut self) {
|
||||
if self.can_go_home() {
|
||||
self.traverse_controller.goto_home();
|
||||
}
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn get_live_values(&self) -> LiveValuesEvent {
|
||||
let angle_deg = self.tension_arm.get_angle().get::<degree>();
|
||||
|
||||
// Wrap [270;<360] to [-90; 0]
|
||||
// This is done to reduce flicker in the graphs around the zero point
|
||||
let angle_deg = if angle_deg >= 270.0 {
|
||||
angle_deg - 360.0
|
||||
} else {
|
||||
angle_deg
|
||||
};
|
||||
|
||||
// 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 motor_speed = self
|
||||
.puller_speed_controller
|
||||
.angular_velocity_to_speed(angular_velocity);
|
||||
|
||||
// Divide by gear ratio to get actual puller/material speed
|
||||
let puller_speed = motor_speed / self.puller_speed_controller.get_gear_ratio().multiplier();
|
||||
|
||||
// Calculate spool RPM from current motor steps (always positive regardless of direction)
|
||||
let spool_rpm = self
|
||||
.spool_step_converter
|
||||
.steps_to_angular_velocity(self.spool.get_speed() as f64)
|
||||
.get::<revolution_per_minute>()
|
||||
.abs();
|
||||
|
||||
LiveValuesEvent {
|
||||
traverse_position: self
|
||||
.traverse_controller
|
||||
.get_current_position()
|
||||
.map(|x| x.get::<millimeter>()),
|
||||
puller_speed: puller_speed.get::<meter_per_minute>().abs(),
|
||||
spool_rpm,
|
||||
tension_arm_angle: angle_deg,
|
||||
spool_progress: self.spool_automatic_action.progress.get::<meter>(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn emit_live_values(&mut self) {
|
||||
let event = self.get_live_values().build();
|
||||
self.namespace.emit(Winder2Events::LiveValues(event));
|
||||
}
|
||||
|
||||
pub fn build_state_event(&mut self) -> StateEvent {
|
||||
use crate::MachineCrossConnectionState;
|
||||
|
||||
let connected_machine = self.connected_machines.get(0);
|
||||
let ident = match connected_machine {
|
||||
Some(machine) => Some(machine.ident.clone()),
|
||||
None => None,
|
||||
};
|
||||
let cross_conn = MachineCrossConnectionState {
|
||||
machine_identification_unique: ident,
|
||||
is_available: connected_machine.is_some(),
|
||||
};
|
||||
|
||||
StateEvent {
|
||||
is_default_state: !std::mem::replace(&mut self.emitted_default_state, true),
|
||||
traverse_state: TraverseState {
|
||||
limit_inner: self
|
||||
.traverse_controller
|
||||
.get_limit_inner()
|
||||
.get::<millimeter>(),
|
||||
limit_outer: self
|
||||
.traverse_controller
|
||||
.get_limit_outer()
|
||||
.get::<millimeter>(),
|
||||
position_in: self
|
||||
.traverse_controller
|
||||
.get_limit_inner()
|
||||
.get::<millimeter>(),
|
||||
position_out: self
|
||||
.traverse_controller
|
||||
.get_limit_outer()
|
||||
.get::<millimeter>(),
|
||||
is_going_in: self.traverse_controller.is_going_in(),
|
||||
is_going_out: self.traverse_controller.is_going_out(),
|
||||
is_homed: self.traverse_controller.is_homed(),
|
||||
is_going_home: self.traverse_controller.is_going_home(),
|
||||
is_traversing: self.traverse_controller.is_traversing(),
|
||||
laserpointer: self.laser.get(),
|
||||
step_size: self.traverse_controller.get_step_size().get::<millimeter>(),
|
||||
padding: self.traverse_controller.get_padding().get::<millimeter>(),
|
||||
can_go_in: self.can_go_in(),
|
||||
can_go_out: self.can_go_out(),
|
||||
can_go_home: self.can_go_home(),
|
||||
},
|
||||
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,
|
||||
gear_ratio: self.puller_speed_controller.gear_ratio,
|
||||
},
|
||||
mode_state: ModeState {
|
||||
mode: self.mode.clone().into(),
|
||||
can_wind: self.can_wind(),
|
||||
},
|
||||
tension_arm_state: TensionArmState {
|
||||
zeroed: self.tension_arm.zeroed,
|
||||
},
|
||||
spool_speed_controller_state: SpoolSpeedControllerState {
|
||||
regulation_mode: self.spool_speed_controller.get_type().clone(),
|
||||
minmax_min_speed: self
|
||||
.spool_speed_controller
|
||||
.get_minmax_min_speed()
|
||||
.get::<revolution_per_minute>(),
|
||||
minmax_max_speed: self
|
||||
.spool_speed_controller
|
||||
.get_minmax_max_speed()
|
||||
.get::<revolution_per_minute>(),
|
||||
adaptive_tension_target: self.spool_speed_controller.get_adaptive_tension_target(),
|
||||
adaptive_radius_learning_rate: self
|
||||
.spool_speed_controller
|
||||
.get_adaptive_radius_learning_rate(),
|
||||
adaptive_max_speed_multiplier: self
|
||||
.spool_speed_controller
|
||||
.get_adaptive_max_speed_multiplier(),
|
||||
adaptive_acceleration_factor: self
|
||||
.spool_speed_controller
|
||||
.get_adaptive_acceleration_factor(),
|
||||
adaptive_deacceleration_urgency_multiplier: self
|
||||
.spool_speed_controller
|
||||
.get_adaptive_deacceleration_urgency_multiplier(),
|
||||
forward: self.spool_speed_controller.get_forward(),
|
||||
},
|
||||
spool_automatic_action_state: SpoolAutomaticActionState {
|
||||
spool_required_meters: self.spool_automatic_action.target_length.get::<meter>(),
|
||||
spool_automatic_action_mode: self.spool_automatic_action.mode.clone(),
|
||||
},
|
||||
connected_machine_state: cross_conn,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn emit_state(&mut self) {
|
||||
let state_event = self.build_state_event();
|
||||
let event = state_event.build();
|
||||
self.namespace.emit(Winder2Events::State(event));
|
||||
}
|
||||
|
||||
/// Apply the mode changes to the spool
|
||||
///
|
||||
/// It contains a transition matrix for atomic changes.
|
||||
/// It will set [`Self::spool_mode`]
|
||||
fn set_traverse_mode(&mut self, mode: &Winder2Mode) {
|
||||
// Convert to `Winder2Mode` to `TraverseMode`
|
||||
let mode: TraverseMode = mode.clone().into();
|
||||
|
||||
// If coming out of standby
|
||||
if self.traverse_mode == TraverseMode::Standby && mode != TraverseMode::Standby {
|
||||
self.traverse.set_enabled(true);
|
||||
self.traverse_controller.set_enabled(true);
|
||||
}
|
||||
|
||||
// If going into standby
|
||||
if mode == TraverseMode::Standby && self.traverse_mode != TraverseMode::Standby {
|
||||
// If we are going into standby, we need to stop the traverse
|
||||
self.traverse.set_enabled(false);
|
||||
self.traverse_controller.set_enabled(false);
|
||||
}
|
||||
|
||||
// Transition matrix
|
||||
match self.traverse_mode {
|
||||
TraverseMode::Standby => match mode {
|
||||
TraverseMode::Standby => {}
|
||||
TraverseMode::Hold => {
|
||||
// From [`TraverseMode::Standby`] to [`TraverseMode::Hold`]
|
||||
self.traverse.set_enabled(true);
|
||||
self.traverse_controller.set_enabled(true);
|
||||
self.traverse_controller.goto_home();
|
||||
}
|
||||
TraverseMode::Traverse => {
|
||||
// From [`TraverseMode::Standby`] to [`TraverseMode::Wind`]
|
||||
self.traverse.set_enabled(true);
|
||||
self.traverse_controller.set_enabled(true);
|
||||
self.traverse_controller.start_traversing();
|
||||
}
|
||||
},
|
||||
TraverseMode::Hold => match mode {
|
||||
TraverseMode::Standby => {
|
||||
// From [`TraverseMode::Hold`] to [`TraverseMode::Standby`]
|
||||
self.traverse.set_enabled(false);
|
||||
self.traverse_controller.set_enabled(false);
|
||||
}
|
||||
TraverseMode::Hold => {}
|
||||
TraverseMode::Traverse => {
|
||||
// From [`TraverseMode::Hold`] to [`TraverseMode::Wind`]
|
||||
self.traverse_controller.start_traversing();
|
||||
}
|
||||
},
|
||||
TraverseMode::Traverse => match mode {
|
||||
TraverseMode::Standby => {
|
||||
// From [`TraverseMode::Wind`] to [`TraverseMode::Standby`]
|
||||
self.traverse.set_enabled(false);
|
||||
self.traverse_controller.set_enabled(false);
|
||||
}
|
||||
TraverseMode::Hold => {
|
||||
// From [`TraverseMode::Wind`] to [`TraverseMode::Hold`]
|
||||
self.traverse_controller.goto_home();
|
||||
}
|
||||
TraverseMode::Traverse => {}
|
||||
},
|
||||
}
|
||||
|
||||
// Update the internal state
|
||||
self.traverse_mode = mode;
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Implement Tension Arm
|
||||
pub fn tension_arm_zero(&mut self) {
|
||||
self.tension_arm.zero();
|
||||
self.emit_live_values(); // For angle update
|
||||
// For state update
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn set_spool_automatic_required_meters(&mut self, meters: f64) {
|
||||
self.spool_automatic_action.target_length = Length::new::<meter>(meters);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn set_spool_automatic_mode(&mut self, mode: SpoolAutomaticActionMode) {
|
||||
self.spool_automatic_action.mode = mode;
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
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 m/min to velocity
|
||||
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();
|
||||
}
|
||||
|
||||
/// Set gear ratio for winding speed
|
||||
pub fn puller_set_gear_ratio(&mut self, gear_ratio: super::puller_speed_controller::GearRatio) {
|
||||
self.puller_speed_controller.set_gear_ratio(gear_ratio);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
// Spool Speed Controller API methods
|
||||
pub fn spool_set_regulation_mode(
|
||||
&mut self,
|
||||
regulation_mode: spool_speed_controller::SpoolSpeedControllerType,
|
||||
) {
|
||||
self.spool_speed_controller.set_type(regulation_mode);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set minimum speed for minmax mode in RPM
|
||||
pub fn spool_set_minmax_min_speed(&mut self, min_speed_rpm: f64) {
|
||||
let min_speed = AngularVelocity::new::<revolution_per_minute>(min_speed_rpm);
|
||||
if let Err(e) = self.spool_speed_controller.set_minmax_min_speed(min_speed) {
|
||||
tracing::error!("Failed to set spool min speed: {:?}", e);
|
||||
}
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set maximum speed for minmax mode in RPM
|
||||
pub fn spool_set_minmax_max_speed(&mut self, max_speed_rpm: f64) {
|
||||
let max_speed = AngularVelocity::new::<revolution_per_minute>(max_speed_rpm);
|
||||
if let Err(e) = self.spool_speed_controller.set_minmax_max_speed(max_speed) {
|
||||
tracing::error!("Failed to set spool max speed: {:?}", e);
|
||||
}
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set tension target for adaptive mode (0.0-1.0)
|
||||
pub fn spool_set_adaptive_tension_target(&mut self, tension_target: f64) {
|
||||
self.spool_speed_controller
|
||||
.set_adaptive_tension_target(tension_target);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set radius learning rate for adaptive mode
|
||||
pub fn spool_set_adaptive_radius_learning_rate(&mut self, radius_learning_rate: f64) {
|
||||
self.spool_speed_controller
|
||||
.set_adaptive_radius_learning_rate(radius_learning_rate);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set max speed multiplier for adaptive mode
|
||||
pub fn spool_set_adaptive_max_speed_multiplier(&mut self, max_speed_multiplier: f64) {
|
||||
self.spool_speed_controller
|
||||
.set_adaptive_max_speed_multiplier(max_speed_multiplier);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set acceleration factor for adaptive mode
|
||||
pub fn spool_set_adaptive_acceleration_factor(&mut self, acceleration_factor: f64) {
|
||||
self.spool_speed_controller
|
||||
.set_adaptive_acceleration_factor(acceleration_factor);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set deacceleration urgency multiplier for adaptive mode
|
||||
pub fn spool_set_adaptive_deacceleration_urgency_multiplier(
|
||||
&mut self,
|
||||
deacceleration_urgency_multiplier: f64,
|
||||
) {
|
||||
self.spool_speed_controller
|
||||
.set_adaptive_deacceleration_urgency_multiplier(deacceleration_urgency_multiplier);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set forward rotation direction
|
||||
pub fn spool_set_forward(&mut self, forward: bool) {
|
||||
self.spool_speed_controller.set_forward(forward);
|
||||
self.emit_state();
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
use super::Winder2;
|
||||
use crate::MachineAct;
|
||||
use crate::MachineMessage;
|
||||
use crate::MachineValues;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
impl MachineAct for Winder2 {
|
||||
fn act(&mut self, now: Instant) {
|
||||
let msg = self.api_receiver.try_recv();
|
||||
match msg {
|
||||
Ok(msg) => {
|
||||
let _res = self.act_machine_message(msg);
|
||||
}
|
||||
Err(_) => (),
|
||||
};
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
fn act_machine_message(&mut self, msg: crate::MachineMessage) {
|
||||
match msg {
|
||||
MachineMessage::SubscribeNamespace(namespace) => {
|
||||
self.namespace.namespace = Some(namespace);
|
||||
self.emit_state();
|
||||
tracing::info!("extruder1 received subscribe");
|
||||
}
|
||||
MachineMessage::UnsubscribeNamespace => self.namespace.namespace = None,
|
||||
MachineMessage::HttpApiJsonRequest(value) => {
|
||||
use crate::MachineApi;
|
||||
|
||||
let _res = self.api_mutate(value);
|
||||
}
|
||||
MachineMessage::ConnectToMachine(_machine_connection) => (),
|
||||
MachineMessage::DisconnectMachine(_machine_connection) =>
|
||||
/*Doesnt connec to any Machine do nothing*/
|
||||
{
|
||||
()
|
||||
}
|
||||
MachineMessage::RequestValues(sender) => {
|
||||
sender
|
||||
.send_blocking(MachineValues {
|
||||
state: serde_json::to_value(self.build_state_event())
|
||||
.expect("Failed to serialize state"),
|
||||
live_values: serde_json::to_value(self.get_live_values())
|
||||
.expect("Failed to serialize live values"),
|
||||
})
|
||||
.expect("Failed to send values");
|
||||
sender.close();
|
||||
|
||||
()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
use super::Winder2;
|
||||
use crate::{MachineApi, winder2::api::Mutation};
|
||||
use serde_json::Value;
|
||||
use std::time::Instant;
|
||||
|
||||
impl MachineApi for Winder2 {
|
||||
fn api_mutate(&mut self, request_body: Value) -> Result<(), anyhow::Error> {
|
||||
let mutation: Mutation = serde_json::from_value(request_body)?;
|
||||
match mutation {
|
||||
Mutation::EnableTraverseLaserpointer(enable) => self.set_laser(enable),
|
||||
Mutation::SetMode(mode) => self.set_mode(&mode.into()),
|
||||
Mutation::SetTraverseLimitOuter(limit) => self.traverse_set_limit_outer(limit),
|
||||
Mutation::SetTraverseLimitInner(limit) => self.traverse_set_limit_inner(limit),
|
||||
Mutation::SetTraverseStepSize(size) => self.traverse_set_step_size(size),
|
||||
Mutation::SetTraversePadding(padding) => self.traverse_set_padding(padding),
|
||||
Mutation::GotoTraverseLimitOuter => self.traverse_goto_limit_outer(),
|
||||
Mutation::GotoTraverseLimitInner => self.traverse_goto_limit_inner(),
|
||||
Mutation::GotoTraverseHome => self.traverse_goto_home(),
|
||||
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::SetPullerGearRatio(gear_ratio) => self.puller_set_gear_ratio(gear_ratio),
|
||||
Mutation::SetSpoolRegulationMode(mode) => self.spool_set_regulation_mode(mode),
|
||||
Mutation::SetSpoolMinMaxMinSpeed(speed) => self.spool_set_minmax_min_speed(speed),
|
||||
Mutation::SetSpoolMinMaxMaxSpeed(speed) => self.spool_set_minmax_max_speed(speed),
|
||||
Mutation::SetSpoolForward(value) => self.spool_set_forward(value),
|
||||
Mutation::SetSpoolAdaptiveTensionTarget(value) => {
|
||||
self.spool_set_adaptive_tension_target(value)
|
||||
}
|
||||
Mutation::SetSpoolAdaptiveRadiusLearningRate(value) => {
|
||||
self.spool_set_adaptive_radius_learning_rate(value)
|
||||
}
|
||||
Mutation::SetSpoolAdaptiveMaxSpeedMultiplier(value) => {
|
||||
self.spool_set_adaptive_max_speed_multiplier(value)
|
||||
}
|
||||
Mutation::SetSpoolAdaptiveAccelerationFactor(value) => {
|
||||
self.spool_set_adaptive_acceleration_factor(value)
|
||||
}
|
||||
Mutation::SetSpoolAdaptiveDeaccelerationUrgencyMultiplier(value) => {
|
||||
self.spool_set_adaptive_deacceleration_urgency_multiplier(value)
|
||||
}
|
||||
Mutation::SetSpoolAutomaticRequiredMeters(meters) => {
|
||||
self.set_spool_automatic_required_meters(meters)
|
||||
}
|
||||
Mutation::SetSpoolAutomaticAction(mode) => self.set_spool_automatic_mode(mode),
|
||||
Mutation::ResetSpoolProgress => self.stop_or_pull_spool_reset(Instant::now()),
|
||||
Mutation::ZeroTensionArmAngle => self.tension_arm_zero(),
|
||||
Mutation::SetConnectedMachine(machine_identification_unique) => {
|
||||
self.set_connected_buffer(machine_identification_unique)
|
||||
}
|
||||
Mutation::DisconnectMachine(machine_identification_unique) => {
|
||||
self.disconnect_buffer(machine_identification_unique)
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn api_event_namespace(
|
||||
&mut self,
|
||||
) -> std::option::Option<control_core::socketio::namespace::Namespace> {
|
||||
self.namespace.namespace.clone()
|
||||
}
|
||||
|
||||
fn api_get_sender(&self) -> smol::channel::Sender<crate::MachineMessage> {
|
||||
self.api_sender.clone()
|
||||
}
|
||||
}
|
||||
@@ -1,254 +0,0 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use super::Winder2;
|
||||
use crate::machine_identification::{MachineIdentification, MachineIdentificationUnique};
|
||||
use crate::winder2::Winder2Mode;
|
||||
use crate::winder2::api::LiveValuesEvent;
|
||||
use crate::winder2::api::{ModeState, SpoolAutomaticActionMode, StateEvent, Winder2Events};
|
||||
use crate::winder2::puller_speed_controller::{GearRatio, PullerRegulationMode};
|
||||
use crate::winder2::spool_speed_controller::SpoolSpeedControllerType;
|
||||
use crate::{MACHINE_WINDER_V1, VENDOR_QITECH};
|
||||
use control_core::socketio::event::BuildEvent;
|
||||
use control_core::socketio::namespace::NamespaceCacheingLogic;
|
||||
|
||||
impl Winder2 {
|
||||
pub const MACHINE_IDENTIFICATION: MachineIdentification = MachineIdentification {
|
||||
vendor: VENDOR_QITECH,
|
||||
machine: MACHINE_WINDER_V1,
|
||||
};
|
||||
|
||||
pub fn stop_or_pull_spool_reset(&mut self, _now: Instant) {}
|
||||
/// Implement Mode
|
||||
pub fn set_mode(&mut self, mode: &Winder2Mode) {
|
||||
self.mode_state = ModeState {
|
||||
mode: mode.clone().into(),
|
||||
can_wind: true,
|
||||
};
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Implement Traverse
|
||||
pub fn set_laser(&mut self, value: bool) {
|
||||
self.traverse_state.laserpointer = value;
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn traverse_set_limit_inner(&mut self, limit: f64) {
|
||||
self.traverse_state.limit_inner = limit;
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn traverse_set_limit_outer(&mut self, limit: f64) {
|
||||
self.traverse_state.limit_outer = limit;
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn traverse_set_step_size(&mut self, step_size: f64) {
|
||||
self.traverse_state.step_size = step_size;
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn traverse_set_padding(&mut self, padding: f64) {
|
||||
self.traverse_state.padding = padding;
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn traverse_goto_limit_inner(&mut self) {
|
||||
self.traverse_state.position_in = self.traverse_state.limit_inner;
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn traverse_goto_limit_outer(&mut self) {
|
||||
self.traverse_state.position_in = self.traverse_state.limit_outer;
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn traverse_goto_home(&mut self) {
|
||||
self.traverse_state.position_in = 0.0;
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn emit_live_values(&mut self) {
|
||||
let event = self.get_live_values().build();
|
||||
self.namespace.emit(Winder2Events::LiveValues(event));
|
||||
}
|
||||
|
||||
pub fn get_live_values(&self) -> LiveValuesEvent {
|
||||
LiveValuesEvent {
|
||||
traverse_position: Some(0.0),
|
||||
puller_speed: 0.0,
|
||||
spool_rpm: 0.0,
|
||||
tension_arm_angle: 0.0,
|
||||
spool_progress: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_state_event(&mut self) -> StateEvent {
|
||||
use crate::MachineCrossConnectionState;
|
||||
|
||||
let connected_machine = self.connected_machines.get(0);
|
||||
|
||||
let ident = match connected_machine {
|
||||
Some(machine) => Some(machine.ident.clone()),
|
||||
None => None,
|
||||
};
|
||||
|
||||
let cross_conn = MachineCrossConnectionState {
|
||||
machine_identification_unique: ident,
|
||||
is_available: connected_machine.is_some(),
|
||||
};
|
||||
|
||||
StateEvent {
|
||||
is_default_state: self.is_default_state,
|
||||
traverse_state: self.traverse_state.clone(),
|
||||
puller_state: self.puller_state.clone(),
|
||||
spool_automatic_action_state: self.spool_automatic_action_state.clone(),
|
||||
mode_state: self.mode_state.clone(),
|
||||
tension_arm_state: self.tension_arm_state.clone(),
|
||||
spool_speed_controller_state: self.spool_speed_controller_state.clone(),
|
||||
connected_machine_state: cross_conn,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn emit_state(&mut self) {
|
||||
let state = self.build_state_event();
|
||||
let event = state.build();
|
||||
self.namespace.emit(Winder2Events::State(event));
|
||||
}
|
||||
|
||||
/// Apply the mode changes to the spool
|
||||
///
|
||||
/// It contains a transition matrix for atomic changes.
|
||||
/// It will set [`Self::spool_mode`]
|
||||
fn set_traverse_mode(&mut self, _mode: &Winder2Mode) {
|
||||
self.mode_state = ModeState {
|
||||
mode: crate::winder2::api::Mode::Standby,
|
||||
can_wind: true,
|
||||
};
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Implement Tension Arm
|
||||
pub fn tension_arm_zero(&mut self) {
|
||||
self.tension_arm_state.zeroed = true;
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn set_spool_automatic_required_meters(&mut self, meters: f64) {
|
||||
self.spool_automatic_action_state.spool_required_meters = meters;
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn set_spool_automatic_mode(&mut self, mode: SpoolAutomaticActionMode) {
|
||||
self.spool_automatic_action_state
|
||||
.spool_automatic_action_mode = mode;
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn puller_set_regulation(&mut self, puller_regulation_mode: PullerRegulationMode) {
|
||||
self.puller_state.regulation = puller_regulation_mode;
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set target speed in m/min
|
||||
pub fn puller_set_target_speed(&mut self, target_speed: f64) {
|
||||
self.puller_state.target_speed = target_speed;
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set target diameter in mm
|
||||
pub fn puller_set_target_diameter(&mut self, target_diameter: f64) {
|
||||
self.puller_state.target_diameter = target_diameter;
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set forward direction
|
||||
pub fn puller_set_forward(&mut self, forward: bool) {
|
||||
self.puller_state.forward = forward;
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set gear ratio for winding speed
|
||||
pub fn puller_set_gear_ratio(&mut self, gear_ratio: GearRatio) {
|
||||
self.puller_state.gear_ratio = gear_ratio;
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
// Spool Speed Controller API methods
|
||||
pub fn spool_set_regulation_mode(&mut self, regulation_mode: SpoolSpeedControllerType) {
|
||||
self.spool_speed_controller_state.regulation_mode = regulation_mode;
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set minimum speed for minmax mode in RPM
|
||||
pub fn spool_set_minmax_min_speed(&mut self, min_speed_rpm: f64) {
|
||||
self.spool_speed_controller_state.minmax_min_speed = min_speed_rpm;
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set maximum speed for minmax mode in RPM
|
||||
pub fn spool_set_minmax_max_speed(&mut self, max_speed_rpm: f64) {
|
||||
self.spool_speed_controller_state.minmax_max_speed = max_speed_rpm;
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set tension target for adaptive mode (0.0-1.0)
|
||||
pub fn spool_set_adaptive_tension_target(&mut self, tension_target: f64) {
|
||||
self.spool_speed_controller_state.adaptive_tension_target = tension_target;
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set radius learning rate for adaptive mode
|
||||
pub fn spool_set_adaptive_radius_learning_rate(&mut self, radius_learning_rate: f64) {
|
||||
self.spool_speed_controller_state
|
||||
.adaptive_radius_learning_rate = radius_learning_rate;
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set max speed multiplier for adaptive mode
|
||||
pub fn spool_set_adaptive_max_speed_multiplier(&mut self, max_speed_multiplier: f64) {
|
||||
self.spool_speed_controller_state
|
||||
.adaptive_max_speed_multiplier = max_speed_multiplier;
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set acceleration factor for adaptive mode
|
||||
pub fn spool_set_adaptive_acceleration_factor(&mut self, acceleration_factor: f64) {
|
||||
self.spool_speed_controller_state
|
||||
.adaptive_acceleration_factor = acceleration_factor;
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set deacceleration urgency multiplier for adaptive mode
|
||||
pub fn spool_set_adaptive_deacceleration_urgency_multiplier(
|
||||
&mut self,
|
||||
deacceleration_urgency_multiplier: f64,
|
||||
) {
|
||||
self.spool_speed_controller_state
|
||||
.adaptive_deacceleration_urgency_multiplier = deacceleration_urgency_multiplier;
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set forward rotation direction
|
||||
pub fn spool_set_forward(&mut self, forward: bool) {
|
||||
self.spool_speed_controller_state.forward = forward;
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// implement machine connection
|
||||
/// set connected buffer
|
||||
pub fn set_connected_buffer(
|
||||
&mut self,
|
||||
_machine_identification_unique: MachineIdentificationUnique,
|
||||
) {
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// disconnect buffer
|
||||
pub fn disconnect_buffer(
|
||||
&mut self,
|
||||
_machine_identification_unique: MachineIdentificationUnique,
|
||||
) {
|
||||
self.emit_state();
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
use std::time::Instant;
|
||||
|
||||
pub mod act;
|
||||
pub mod api;
|
||||
pub mod mock_emit;
|
||||
pub mod new;
|
||||
|
||||
use super::api::{
|
||||
ModeState, PullerState, SpoolAutomaticActionState, SpoolSpeedControllerState, TensionArmState,
|
||||
TraverseState, Winder2Namespace,
|
||||
};
|
||||
use crate::{
|
||||
AsyncThreadMessage, Machine, MachineConnection, MachineMessage,
|
||||
machine_identification::MachineIdentificationUnique,
|
||||
};
|
||||
use smol::channel::{Receiver, Sender};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Winder2 {
|
||||
machine_identification_unique: MachineIdentificationUnique,
|
||||
namespace: Winder2Namespace,
|
||||
last_measurement_emit: Instant,
|
||||
pub is_default_state: bool,
|
||||
/// traverse state
|
||||
pub traverse_state: TraverseState,
|
||||
/// puller state
|
||||
pub puller_state: PullerState,
|
||||
/// spool automatic action state and progress
|
||||
pub spool_automatic_action_state: SpoolAutomaticActionState,
|
||||
/// mode state
|
||||
pub mode_state: ModeState,
|
||||
/// tension arm state
|
||||
pub tension_arm_state: TensionArmState,
|
||||
/// spool speed controller state
|
||||
pub spool_speed_controller_state: SpoolSpeedControllerState,
|
||||
|
||||
/// Receive from Api or MainThread
|
||||
api_receiver: Receiver<MachineMessage>,
|
||||
api_sender: Sender<MachineMessage>,
|
||||
|
||||
/// Communicate with main thread
|
||||
main_sender: Option<Sender<AsyncThreadMessage>>,
|
||||
|
||||
/// All currently "connected" Machines
|
||||
connected_machines: Vec<MachineConnection>,
|
||||
/// Defaults to limit of 2
|
||||
max_connected_machines: usize,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Winder2 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Winder2")
|
||||
}
|
||||
}
|
||||
|
||||
impl Machine for Winder2 {
|
||||
fn get_machine_identification_unique(&self) -> MachineIdentificationUnique {
|
||||
self.machine_identification_unique.clone()
|
||||
}
|
||||
|
||||
fn get_main_sender(&self) -> Option<Sender<AsyncThreadMessage>> {
|
||||
self.main_sender.clone()
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
use crate::{
|
||||
MachineNewParams, MachineNewTrait,
|
||||
winder2::api::{
|
||||
ModeState, PullerState, SpoolAutomaticActionState, SpoolSpeedControllerState,
|
||||
TensionArmState, TraverseState, Winder2Namespace,
|
||||
},
|
||||
};
|
||||
|
||||
use super::Winder2;
|
||||
|
||||
impl MachineNewTrait for Winder2 {
|
||||
fn new(params: &MachineNewParams<'_, '_, '_, '_, '_, '_, '_>) -> Result<Self, anyhow::Error>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
// Mock machine can work with either Serial or Ethercat hardware
|
||||
// For the mock machine, we don't need to actually use the hardware
|
||||
// We just validate that we have the expected hardware type
|
||||
match params.hardware {
|
||||
crate::MachineNewHardware::Serial(_) => {
|
||||
// For serial mode, we could potentially use the serial device if needed
|
||||
// but for a mock machine, we'll just note it and proceed
|
||||
}
|
||||
crate::MachineNewHardware::Ethercat(_) => {
|
||||
// For ethercat mode, we could potentially use the ethercat devices
|
||||
// but for a mock machine, we'll just note it and proceed
|
||||
}
|
||||
}
|
||||
|
||||
let now = std::time::Instant::now();
|
||||
|
||||
let (sender, receiver) = smol::channel::unbounded();
|
||||
let mut winder_mock_machine = Self {
|
||||
main_sender: params.main_thread_channel.clone(),
|
||||
max_connected_machines: 2,
|
||||
api_receiver: receiver,
|
||||
api_sender: sender,
|
||||
machine_identification_unique: params.get_machine_identification_unique(),
|
||||
namespace: Winder2Namespace {
|
||||
namespace: params.namespace.clone(),
|
||||
},
|
||||
last_measurement_emit: now,
|
||||
is_default_state: true,
|
||||
traverse_state: TraverseState::default(),
|
||||
puller_state: PullerState::default(),
|
||||
spool_automatic_action_state: SpoolAutomaticActionState::default(),
|
||||
mode_state: ModeState::default(),
|
||||
tension_arm_state: TensionArmState::default(),
|
||||
spool_speed_controller_state: SpoolSpeedControllerState::default(),
|
||||
connected_machines: vec![],
|
||||
};
|
||||
|
||||
winder_mock_machine.emit_state();
|
||||
|
||||
Ok(winder_mock_machine)
|
||||
}
|
||||
}
|
||||
+202
-420
@@ -1,458 +1,240 @@
|
||||
pub mod act;
|
||||
pub mod adaptive_spool_speed_controller;
|
||||
pub mod api;
|
||||
pub mod clamp_revolution;
|
||||
pub mod emit;
|
||||
pub mod filament_tension;
|
||||
pub mod minmax_spool_speed_controller;
|
||||
pub mod new;
|
||||
pub mod puller_speed_controller;
|
||||
pub mod spool_speed_controller;
|
||||
pub mod tension_arm;
|
||||
pub mod traverse_controller;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
#[cfg(feature = "mock-machine")]
|
||||
pub mod mock;
|
||||
use control_core::socketio::namespace::NamespaceCacheingLogic;
|
||||
|
||||
#[cfg(feature = "mock-machine")]
|
||||
mod winder2_imports {
|
||||
pub use super::api::SpoolAutomaticActionMode;
|
||||
pub use std::time::Instant;
|
||||
pub use units::f64::Length;
|
||||
pub use units::length::meter;
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
mod winder2_imports {
|
||||
pub use super::api::SpoolAutomaticActionMode;
|
||||
pub use super::api::Winder2Namespace;
|
||||
pub use super::puller_speed_controller::PullerSpeedController;
|
||||
pub use super::spool_speed_controller::SpoolSpeedController;
|
||||
pub use super::tension_arm::TensionArm;
|
||||
pub use super::traverse_controller::TraverseController;
|
||||
pub use control_core::converters::angular_step_converter::AngularStepConverter;
|
||||
pub use ethercat_hal::io::{
|
||||
digital_input::DigitalInput, digital_output::DigitalOutput,
|
||||
stepper_velocity_el70x1::StepperVelocityEL70x1,
|
||||
};
|
||||
pub use smol::channel::{Receiver, Sender};
|
||||
pub use smol::lock::RwLock;
|
||||
pub use std::{fmt::Debug, sync::Weak, time::Instant};
|
||||
|
||||
pub use crate::buffer1::BufferV1;
|
||||
pub use crate::{AsyncThreadMessage, Machine};
|
||||
pub use units::ConstZero;
|
||||
pub use units::f64::Length;
|
||||
pub use units::{length::meter, length::millimeter, velocity::meter_per_second};
|
||||
}
|
||||
|
||||
pub use winder2_imports::*;
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use crate::{
|
||||
MACHINE_WINDER_V1, MachineConnection, MachineMessage, VENDOR_QITECH,
|
||||
machine_identification::{MachineIdentification, MachineIdentificationUnique},
|
||||
MachineChannel,
|
||||
MachineWithChannel,
|
||||
MachineConnection,
|
||||
MachinesLiveValues,
|
||||
VENDOR_QITECH,
|
||||
machine_identification::MachineIdentification
|
||||
};
|
||||
|
||||
mod types;
|
||||
use types::{Mode, Hardware, SpoolLengthTaskCompletedAction};
|
||||
|
||||
mod tasks;
|
||||
use tasks::SpoolLengthTask;
|
||||
|
||||
mod devices;
|
||||
use devices::{Spool, Traverse, Puller, TensionArm};
|
||||
|
||||
mod api;
|
||||
use api::{LiveValues, State};
|
||||
|
||||
mod new;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SpoolAutomaticAction {
|
||||
pub progress: Length,
|
||||
progress_last_check: Instant,
|
||||
pub target_length: Length,
|
||||
pub mode: SpoolAutomaticActionMode,
|
||||
}
|
||||
|
||||
impl Default for SpoolAutomaticAction {
|
||||
fn default() -> Self {
|
||||
SpoolAutomaticAction {
|
||||
progress: Length::new::<meter>(0.0),
|
||||
progress_last_check: Instant::now(),
|
||||
target_length: Length::new::<meter>(0.0),
|
||||
mode: SpoolAutomaticActionMode::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
impl Machine for Winder2 {
|
||||
fn get_machine_identification_unique(&self) -> MachineIdentificationUnique {
|
||||
self.machine_identification_unique.clone()
|
||||
}
|
||||
|
||||
fn get_main_sender(&self) -> Option<Sender<AsyncThreadMessage>> {
|
||||
self.main_sender.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
#[derive(Debug)]
|
||||
pub struct Winder2 {
|
||||
api_receiver: Receiver<MachineMessage>,
|
||||
api_sender: Sender<MachineMessage>,
|
||||
main_sender: Option<Sender<AsyncThreadMessage>>,
|
||||
|
||||
connected_machines: Vec<MachineConnection>,
|
||||
max_connected_machines: usize,
|
||||
// drivers
|
||||
pub traverse: StepperVelocityEL70x1,
|
||||
pub puller: StepperVelocityEL70x1,
|
||||
pub spool: StepperVelocityEL70x1,
|
||||
pub tension_arm: TensionArm,
|
||||
pub laser: DigitalOutput,
|
||||
|
||||
// controllers
|
||||
pub traverse_controller: TraverseController,
|
||||
pub traverse_end_stop: DigitalInput,
|
||||
|
||||
// socketio
|
||||
namespace: Winder2Namespace,
|
||||
last_measurement_emit: Instant,
|
||||
pub machine_identification_unique: MachineIdentificationUnique,
|
||||
|
||||
// mode
|
||||
pub mode: Winder2Mode,
|
||||
pub spool_mode: SpoolMode,
|
||||
pub traverse_mode: TraverseMode,
|
||||
pub puller_mode: PullerMode,
|
||||
|
||||
// control circuit arm/spool
|
||||
pub spool_speed_controller: SpoolSpeedController,
|
||||
pub spool_step_converter: AngularStepConverter,
|
||||
|
||||
// spool automatic action state
|
||||
pub spool_automatic_action: SpoolAutomaticAction,
|
||||
|
||||
// control circuit puller
|
||||
pub puller_speed_controller: PullerSpeedController,
|
||||
|
||||
/// Will be initialized as false and set to true by emit_state
|
||||
/// This way we can signal to the client that the first state emission is a default state
|
||||
pub struct Winder2
|
||||
{
|
||||
// common machine fields
|
||||
channel: MachineChannel,
|
||||
last_emit: Instant,
|
||||
emitted_default_state: bool,
|
||||
|
||||
// machine config
|
||||
mode: Mode,
|
||||
on_spool_length_task_complete: SpoolLengthTaskCompletedAction,
|
||||
|
||||
// devices
|
||||
spool: Spool,
|
||||
puller: Puller,
|
||||
traverse: Traverse,
|
||||
tension_arm: TensionArm,
|
||||
|
||||
// tasks
|
||||
spool_length_task: SpoolLengthTask,
|
||||
|
||||
// reference machine for the pullers adaptive speed mode
|
||||
puller_speed_reference_machine: Option<MachineConnection>,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
impl Winder2 {
|
||||
pub const MACHINE_IDENTIFICATION: MachineIdentification = MachineIdentification {
|
||||
// public interface
|
||||
impl Winder2
|
||||
{
|
||||
pub const MACHINE_IDENTIFICATION: MachineIdentification = MachineIdentification
|
||||
{
|
||||
vendor: VENDOR_QITECH,
|
||||
machine: MACHINE_WINDER_V1,
|
||||
machine: crate::MACHINE_WINDER_V1,
|
||||
};
|
||||
|
||||
/// Validates that traverse limits maintain proper constraints:
|
||||
/// - Inner limit must be smaller than outer limit
|
||||
/// - At least 0.9mm difference between inner and outer limits
|
||||
fn validate_traverse_limits(inner: Length, outer: Length) -> bool {
|
||||
outer > inner + Length::new::<millimeter>(0.9)
|
||||
}
|
||||
pub fn new(channel: MachineChannel, hardware: Hardware) -> Self
|
||||
{
|
||||
let traverse = Traverse::new(
|
||||
hardware.traverse_motor,
|
||||
hardware.traverse_limit_switch,
|
||||
hardware.traverse_laser_pointer,
|
||||
);
|
||||
|
||||
pub fn sync_traverse_speed(&mut self) {
|
||||
self.traverse_controller.update_speed(
|
||||
&mut self.traverse,
|
||||
&self.traverse_end_stop,
|
||||
self.spool_speed_controller.get_speed(),
|
||||
)
|
||||
Self {
|
||||
channel,
|
||||
last_emit: Instant::now(),
|
||||
emitted_default_state: false,
|
||||
|
||||
mode: Mode::Standby,
|
||||
spool: Spool::new(hardware.spool_motor),
|
||||
puller: Puller::new(hardware.puller_motor),
|
||||
traverse,
|
||||
tension_arm: TensionArm::new(hardware.tension_arm_sensor),
|
||||
spool_length_task: SpoolLengthTask::new(),
|
||||
on_spool_length_task_complete: SpoolLengthTaskCompletedAction::NoAction,
|
||||
puller_speed_reference_machine: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Can wind capability check
|
||||
pub const fn can_wind(&self) -> bool {
|
||||
// Check if tension arm is zeroed and traverse is homed
|
||||
self.tension_arm.zeroed
|
||||
&& self.traverse_controller.is_homed()
|
||||
&& !self.traverse_controller.is_going_home()
|
||||
fn can_wind(&self) -> bool
|
||||
{
|
||||
// Check if tension arm is calibrated and traverse is homed
|
||||
self.tension_arm.is_calibrated()
|
||||
&& self.traverse.is_homed()
|
||||
&& !self.traverse.is_going_home()
|
||||
|
||||
// TODO(JSE): why the 2 checks for traverse?
|
||||
}
|
||||
}
|
||||
|
||||
/// Can go to inner limit capability check
|
||||
pub fn can_go_in(&self) -> bool {
|
||||
// Check if traverse is homed, not in standby, not traversing
|
||||
// Allow changing direction (even when going out)
|
||||
// Disallow when homing is in progress
|
||||
self.traverse_controller.is_homed()
|
||||
&& self.traverse_mode != TraverseMode::Standby
|
||||
&& !self.traverse_controller.is_going_in()
|
||||
&& !self.traverse_controller.is_going_home()
|
||||
&& !self.traverse_controller.is_traversing()
|
||||
&& self.mode != Winder2Mode::Wind
|
||||
}
|
||||
// utils
|
||||
impl Winder2
|
||||
{
|
||||
/// Updates the spool length task and changes the
|
||||
/// machines mode as appropiate
|
||||
fn update_spool_length_task(&mut self, now: Instant)
|
||||
{
|
||||
// TODO(JSE): find out if update_timer can be replaced
|
||||
// with update_progress(), since speed should be 0
|
||||
|
||||
/// Can go to outer limit capability check
|
||||
pub fn can_go_out(&self) -> bool {
|
||||
// Check if traverse is homed, not in standby, not traversing
|
||||
// Allow changing direction (even when going in)
|
||||
// Disallow when homing is in progress
|
||||
self.traverse_controller.is_homed()
|
||||
&& self.traverse_mode != TraverseMode::Standby
|
||||
&& !self.traverse_controller.is_going_out()
|
||||
&& !self.traverse_controller.is_going_home()
|
||||
&& !self.traverse_controller.is_traversing()
|
||||
&& self.mode != Winder2Mode::Wind
|
||||
}
|
||||
// refactor of: stop_or_pull_spool
|
||||
use SpoolLengthTaskCompletedAction::*;
|
||||
|
||||
/// 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.traverse_mode != TraverseMode::Standby
|
||||
&& !self.traverse_controller.is_going_home()
|
||||
&& !self.traverse_controller.is_traversing()
|
||||
&& self.mode != Winder2Mode::Wind
|
||||
}
|
||||
let velocity = self.puller.output_speed();
|
||||
|
||||
/// Apply the mode changes to the spool
|
||||
///
|
||||
/// It contains a transition matrix for atomic changes.
|
||||
/// It will set [`Self::spool_mode`]
|
||||
fn set_spool_mode(&mut self, mode: &Winder2Mode) {
|
||||
// Convert to `Winder2Mode` to `SpoolMode`
|
||||
let mode: SpoolMode = mode.clone().into();
|
||||
|
||||
// Transition matrix
|
||||
match self.spool_mode {
|
||||
SpoolMode::Standby => match mode {
|
||||
SpoolMode::Standby => {}
|
||||
SpoolMode::Hold => {
|
||||
// From [`SpoolMode::Standby`] to [`SpoolMode::Hold`]
|
||||
self.spool.set_enabled(true);
|
||||
}
|
||||
SpoolMode::Wind => {
|
||||
self.spool.set_enabled(true);
|
||||
// self.spool_speed_controller.reset();
|
||||
self.spool_speed_controller.set_enabled(true);
|
||||
}
|
||||
},
|
||||
SpoolMode::Hold => match mode {
|
||||
SpoolMode::Standby => {
|
||||
// From [`SpoolMode::Hold`] to [`SpoolMode::Standby`]
|
||||
self.spool.set_enabled(false);
|
||||
}
|
||||
SpoolMode::Hold => {}
|
||||
SpoolMode::Wind => {
|
||||
// From [`SpoolMode::Hold`] to [`SpoolMode::Wind`]
|
||||
// self.spool_speed_controller.reset();
|
||||
self.spool_speed_controller.set_enabled(true);
|
||||
}
|
||||
},
|
||||
SpoolMode::Wind => match mode {
|
||||
SpoolMode::Standby => {
|
||||
// From [`SpoolMode::Wind`] to [`SpoolMode::Standby`]
|
||||
self.spool.set_enabled(false);
|
||||
self.spool_speed_controller.set_enabled(false);
|
||||
}
|
||||
SpoolMode::Hold => {
|
||||
// From [`SpoolMode::Wind`] to [`SpoolMode::Hold`]
|
||||
self.spool_speed_controller.set_enabled(false);
|
||||
}
|
||||
SpoolMode::Wind => {}
|
||||
},
|
||||
// always update progress if no action
|
||||
if self.on_spool_length_task_complete == NoAction
|
||||
{
|
||||
self.spool_length_task.update_progress(now, velocity);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the internal state
|
||||
self.spool_mode = mode;
|
||||
}
|
||||
|
||||
/// Apply the mode changes to the puller
|
||||
///
|
||||
/// It contains a transition matrix for atomic changes.
|
||||
/// It will set [`Self::puller_mode`]
|
||||
fn set_puller_mode(&mut self, mode: &Winder2Mode) {
|
||||
// Convert to `Winder2Mode` to `PullerMode`
|
||||
let mode: PullerMode = mode.clone().into();
|
||||
|
||||
// Transition matrix
|
||||
match self.puller_mode {
|
||||
PullerMode::Standby => match mode {
|
||||
PullerMode::Standby => {}
|
||||
PullerMode::Hold => {
|
||||
// From [`PullerMode::Standby`] to [`PullerMode::Hold`]
|
||||
self.puller.set_enabled(true);
|
||||
}
|
||||
PullerMode::Pull => {
|
||||
// From [`PullerMode::Standby`] to [`PullerMode::Pull`]
|
||||
self.puller.set_enabled(true);
|
||||
self.puller_speed_controller.set_enabled(true);
|
||||
}
|
||||
},
|
||||
PullerMode::Hold => match mode {
|
||||
PullerMode::Standby => {
|
||||
// From [`PullerMode::Hold`] to [`PullerMode::Standby`]
|
||||
self.puller.set_enabled(false);
|
||||
}
|
||||
PullerMode::Hold => {}
|
||||
PullerMode::Pull => {
|
||||
// From [`PullerMode::Hold`] to [`PullerMode::Pull`]
|
||||
self.puller_speed_controller.set_enabled(true);
|
||||
}
|
||||
},
|
||||
PullerMode::Pull => match mode {
|
||||
PullerMode::Standby => {
|
||||
// From [`PullerMode::Pull`] to [`PullerMode::Standby`]
|
||||
self.puller.set_enabled(false);
|
||||
self.puller_speed_controller.set_enabled(false);
|
||||
}
|
||||
PullerMode::Hold => {
|
||||
// From [`PullerMode::Pull`] to [`PullerMode::Hold`]
|
||||
self.puller_speed_controller.set_enabled(false);
|
||||
}
|
||||
PullerMode::Pull => {}
|
||||
},
|
||||
// don't update progress if no mode to move the puller
|
||||
// is active...
|
||||
//
|
||||
// jse: But why even bother, if it ain't moving anyway?
|
||||
// if the motor doesn't stop immediately won't we have
|
||||
// a false value then since we stop counting while
|
||||
// it slows down??
|
||||
if self.mode != Mode::Pull && self.mode != Mode::Wind
|
||||
{
|
||||
self.spool_length_task.update_timer(now);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the internal state
|
||||
self.puller_mode = mode;
|
||||
}
|
||||
self.spool_length_task.update_progress(now, velocity);
|
||||
|
||||
pub const fn stop_or_pull_spool_reset(&mut self, now: Instant) {
|
||||
self.spool_automatic_action.progress = Length::ZERO;
|
||||
self.spool_automatic_action.progress_last_check = now;
|
||||
}
|
||||
if self.spool_length_task.is_complete()
|
||||
{
|
||||
let mode = match self.on_spool_length_task_complete
|
||||
{
|
||||
Pull => Mode::Pull,
|
||||
Hold => Mode::Hold,
|
||||
NoAction => return, // unreachable
|
||||
};
|
||||
|
||||
pub fn calculate_spool_auto_progress_(&mut self, now: Instant) {
|
||||
// Calculate time elapsed since last progress check (in minutes)
|
||||
|
||||
let dt = now
|
||||
.duration_since(self.spool_automatic_action.progress_last_check)
|
||||
.as_secs_f64();
|
||||
|
||||
// Calculate distance pulled during this time interval
|
||||
let meters_pulled_this_interval = Length::new::<meter>(
|
||||
self.puller_speed_controller
|
||||
.last_speed
|
||||
.get::<meter_per_second>()
|
||||
* dt,
|
||||
);
|
||||
|
||||
// Update total meters pulled
|
||||
self.spool_automatic_action.progress += meters_pulled_this_interval.abs();
|
||||
self.spool_automatic_action.progress_last_check = now;
|
||||
}
|
||||
|
||||
/// Implement Puller
|
||||
/// 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);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "mock-machine")]
|
||||
pub use mock::Winder2;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Winder2Mode {
|
||||
Standby,
|
||||
Hold,
|
||||
Pull,
|
||||
Wind,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum SpoolMode {
|
||||
Standby,
|
||||
Hold,
|
||||
Wind,
|
||||
}
|
||||
|
||||
impl From<Winder2Mode> for SpoolMode {
|
||||
fn from(mode: Winder2Mode) -> Self {
|
||||
match mode {
|
||||
Winder2Mode::Standby => Self::Standby,
|
||||
Winder2Mode::Hold => Self::Hold,
|
||||
Winder2Mode::Pull => Self::Hold,
|
||||
Winder2Mode::Wind => Self::Wind,
|
||||
self.spool_length_task.reset(now);
|
||||
self.set_mode(mode);
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_state(&mut self)
|
||||
{
|
||||
let event = self.get_state();
|
||||
self.emitted_default_state = true;
|
||||
self.channel.emit(event);
|
||||
}
|
||||
|
||||
fn emit_live_values(&mut self)
|
||||
{
|
||||
let event = self.create_live_values();
|
||||
self.channel.emit(event);
|
||||
self.last_emit = Instant::now();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum TraverseMode {
|
||||
Standby,
|
||||
Hold,
|
||||
Traverse,
|
||||
}
|
||||
// base machine trait
|
||||
impl MachineWithChannel for Winder2
|
||||
{
|
||||
type State = State;
|
||||
type LiveValues = LiveValues;
|
||||
|
||||
impl From<Winder2Mode> for TraverseMode {
|
||||
fn from(mode: Winder2Mode) -> Self {
|
||||
match mode {
|
||||
Winder2Mode::Standby => Self::Standby,
|
||||
Winder2Mode::Hold => Self::Hold,
|
||||
Winder2Mode::Pull => Self::Hold,
|
||||
Winder2Mode::Wind => Self::Traverse,
|
||||
fn update(&mut self, now: std::time::Instant) -> anyhow::Result<()>
|
||||
{
|
||||
if !self.emitted_default_state
|
||||
{
|
||||
self.emit_state();
|
||||
self.emitted_default_state = true;
|
||||
}
|
||||
|
||||
self.spool.update(now, &self.tension_arm, &self.puller);
|
||||
self.puller.update(now);
|
||||
|
||||
let state_changed = self.traverse.update(&self.spool);
|
||||
|
||||
// update last since it can mutate mode
|
||||
self.update_spool_length_task(now);
|
||||
|
||||
if state_changed
|
||||
{
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
if now.duration_since(self.last_emit) > Duration::from_secs_f64(1.0 / 30.0)
|
||||
{
|
||||
self.emit_live_values();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_state(&self) -> State
|
||||
{
|
||||
self.create_state()
|
||||
}
|
||||
|
||||
fn get_live_values(&self) -> Option<LiveValues>
|
||||
{
|
||||
Some(self.create_live_values())
|
||||
}
|
||||
|
||||
fn mutate(&mut self, value: serde_json::Value) -> anyhow::Result<()>
|
||||
{
|
||||
self.handle_mutation(value)
|
||||
}
|
||||
|
||||
fn on_namespace(&mut self)
|
||||
{
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
fn on_receive_live_values(&mut self, live_values: MachinesLiveValues)
|
||||
{
|
||||
use crate::MachinesLiveValues::Laser;
|
||||
|
||||
#[allow(irrefutable_let_patterns)]
|
||||
if let Laser(live_values) = live_values
|
||||
{
|
||||
_ = live_values;
|
||||
// TODO: use data to regulate speed. But not idea how, when what?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum PullerMode {
|
||||
Standby,
|
||||
Hold,
|
||||
Pull,
|
||||
}
|
||||
|
||||
impl From<Winder2Mode> for PullerMode {
|
||||
fn from(mode: Winder2Mode) -> Self {
|
||||
match mode {
|
||||
Winder2Mode::Standby => Self::Standby,
|
||||
Winder2Mode::Hold => Self::Hold,
|
||||
Winder2Mode::Pull => Self::Pull,
|
||||
Winder2Mode::Wind => Self::Pull,
|
||||
}
|
||||
fn get_machine_channel(&self) -> &MachineChannel
|
||||
{
|
||||
&self.channel
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
impl std::fmt::Display for Winder2 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Winder2")
|
||||
fn get_machine_channel_mut(&mut self) -> &mut MachineChannel
|
||||
{
|
||||
&mut self.channel
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_validate_traverse_limits() {
|
||||
// Test case 1: Valid limits with exactly 1.0mm difference (should pass)
|
||||
let inner = Length::new::<millimeter>(15.0);
|
||||
let outer = Length::new::<millimeter>(16.0);
|
||||
assert!(Winder2::validate_traverse_limits(inner, outer));
|
||||
|
||||
// Test case 2: Invalid limits with exactly 0.9mm difference (should fail)
|
||||
let inner = Length::new::<millimeter>(15.0);
|
||||
let outer = Length::new::<millimeter>(15.9);
|
||||
assert!(!Winder2::validate_traverse_limits(inner, outer));
|
||||
|
||||
// Test case 3: Invalid limits with less than 0.9mm difference (should fail)
|
||||
let inner = Length::new::<millimeter>(15.0);
|
||||
let outer = Length::new::<millimeter>(15.5);
|
||||
assert!(!Winder2::validate_traverse_limits(inner, outer));
|
||||
|
||||
// Test case 4: Invalid limits where inner equals outer (should fail)
|
||||
let inner = Length::new::<millimeter>(20.0);
|
||||
let outer = Length::new::<millimeter>(20.0);
|
||||
assert!(!Winder2::validate_traverse_limits(inner, outer));
|
||||
|
||||
// Test case 5: Invalid limits where inner is greater than outer (should fail)
|
||||
let inner = Length::new::<millimeter>(25.0);
|
||||
let outer = Length::new::<millimeter>(20.0);
|
||||
assert!(!Winder2::validate_traverse_limits(inner, outer));
|
||||
|
||||
// Test case 6: Valid limits with large difference (should pass)
|
||||
let inner = Length::new::<millimeter>(10.0);
|
||||
let outer = Length::new::<millimeter>(80.0);
|
||||
assert!(Winder2::validate_traverse_limits(inner, outer));
|
||||
|
||||
// Test case 7: Edge case - exactly 0.91mm difference (should pass)
|
||||
let inner = Length::new::<millimeter>(15.0);
|
||||
let outer = Length::new::<millimeter>(15.91);
|
||||
assert!(Winder2::validate_traverse_limits(inner, outer));
|
||||
|
||||
// Test case 8: Edge case - exactly 0.89mm difference (should fail)
|
||||
let inner = Length::new::<millimeter>(15.0);
|
||||
let outer = Length::new::<millimeter>(15.89);
|
||||
assert!(!Winder2::validate_traverse_limits(inner, outer));
|
||||
}
|
||||
}
|
||||
}
|
||||
+47
-127
@@ -1,62 +1,21 @@
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
mod winder2_imports {
|
||||
pub use super::super::api::Winder2Namespace;
|
||||
pub use super::super::tension_arm::TensionArm;
|
||||
pub use super::super::{Winder2, Winder2Mode};
|
||||
pub use crate::winder2::puller_speed_controller::PullerSpeedController;
|
||||
pub use crate::winder2::spool_speed_controller::SpoolSpeedController;
|
||||
pub use crate::winder2::traverse_controller::TraverseController;
|
||||
pub use crate::{
|
||||
MachineNewHardware, MachineNewParams, MachineNewTrait, validate_no_role_dublicates,
|
||||
validate_same_machine_identification_unique,
|
||||
};
|
||||
pub use anyhow::Error;
|
||||
pub use control_core::converters::angular_step_converter::AngularStepConverter;
|
||||
pub use control_core::converters::linear_step_converter::LinearStepConverter;
|
||||
use anyhow::Error;
|
||||
use ethercat_hal::{coe::ConfigurableDevice, devices::{ek1100::{EK1100, EK1100_IDENTITY_A}, el2002::{EL2002, EL2002_IDENTITY_A, EL2002_IDENTITY_B, EL2002Port}, el7031::{EL7031, EL7031_IDENTITY_A, EL7031_IDENTITY_B, EL7031DigitalInputPort, EL7031StepperPort, coe::EL7031Configuration, pdo::EL7031PredefinedPdoAssignment}, el7031_0030::{self, EL7031_0030, EL7031_0030_IDENTITY_A, EL7031_0030AnalogInputPort, EL7031_0030StepperPort, coe::EL7031_0030Configuration, pdo::EL7031_0030PredefinedPdoAssignment}, el7041_0052::{EL7041_0052, EL7041_0052_IDENTITY_A, EL7041_0052Port, coe::EL7041_0052Configuration}}, io::{analog_input::AnalogInput, digital_input::DigitalInput, digital_output::DigitalOutput, stepper_velocity_el70x1::StepperVelocityEL70x1}, shared_config::{self, el70x1::{EL70x1OperationMode, StmMotorConfiguration}}};
|
||||
|
||||
pub use ethercat_hal::coe::ConfigurableDevice;
|
||||
pub use ethercat_hal::devices::ek1100::EK1100;
|
||||
pub use ethercat_hal::devices::el2002::{EL2002, EL2002_IDENTITY_B, EL2002Port};
|
||||
pub use ethercat_hal::devices::el7031::coe::EL7031Configuration;
|
||||
pub use ethercat_hal::devices::el7031::pdo::EL7031PredefinedPdoAssignment;
|
||||
pub use ethercat_hal::devices::el7031::{
|
||||
EL7031, EL7031_IDENTITY_A, EL7031_IDENTITY_B, EL7031DigitalInputPort, EL7031StepperPort,
|
||||
};
|
||||
pub use ethercat_hal::devices::el7031_0030::coe::EL7031_0030Configuration;
|
||||
pub use ethercat_hal::devices::el7031_0030::pdo::EL7031_0030PredefinedPdoAssignment;
|
||||
pub use ethercat_hal::devices::el7031_0030::{
|
||||
self, EL7031_0030, EL7031_0030_IDENTITY_A, EL7031_0030AnalogInputPort,
|
||||
EL7031_0030StepperPort,
|
||||
};
|
||||
pub use ethercat_hal::devices::el7041_0052::coe::EL7041_0052Configuration;
|
||||
pub use ethercat_hal::devices::el7041_0052::{
|
||||
EL7041_0052, EL7041_0052_IDENTITY_A, EL7041_0052Port,
|
||||
};
|
||||
pub use ethercat_hal::devices::{ek1100::EK1100_IDENTITY_A, el2002::EL2002_IDENTITY_A};
|
||||
pub use ethercat_hal::io::analog_input::AnalogInput;
|
||||
pub use ethercat_hal::io::digital_input::DigitalInput;
|
||||
pub use ethercat_hal::io::digital_output::DigitalOutput;
|
||||
pub use ethercat_hal::io::stepper_velocity_el70x1::StepperVelocityEL70x1;
|
||||
pub use ethercat_hal::shared_config;
|
||||
pub use ethercat_hal::shared_config::el70x1::{EL70x1OperationMode, StmMotorConfiguration};
|
||||
pub use std::time::Instant;
|
||||
pub use units::ConstZero;
|
||||
pub use units::f64::*;
|
||||
pub use units::length::{centimeter, meter, millimeter};
|
||||
pub use units::velocity::meter_per_minute;
|
||||
}
|
||||
use crate::{MachineChannel, get_ethercat_device, winder2::types::Hardware};
|
||||
pub use crate::{
|
||||
MachineNewHardware,
|
||||
MachineNewParams,
|
||||
MachineNewTrait,
|
||||
validate_no_role_dublicates,
|
||||
validate_same_machine_identification_unique,
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
pub use winder2_imports::*;
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use crate::get_ethercat_device;
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
impl MachineNewTrait for Winder2 {
|
||||
fn new<'maindevice>(params: &MachineNewParams) -> Result<Self, Error> {
|
||||
// validate general stuff
|
||||
use super::Winder2;
|
||||
|
||||
impl MachineNewTrait for Winder2
|
||||
{
|
||||
fn new<'maindevice>(params: &MachineNewParams) -> Result<Self, Error>
|
||||
{
|
||||
let device_identification = params.device_group.to_vec();
|
||||
|
||||
validate_same_machine_identification_unique(&device_identification)?;
|
||||
@@ -72,10 +31,9 @@ impl MachineNewTrait for Winder2 {
|
||||
}
|
||||
};
|
||||
|
||||
// using block_on because making this funciton async creates a lifetime issue
|
||||
// if its async the compiler thinks &subdevices is persisted in the future which might never execute
|
||||
// so we can't drop subdevices unless this machine is dropped, which is bad
|
||||
smol::block_on(async {
|
||||
// map the required hardware devices
|
||||
let (el2002, el7041, el7031, el7031_0030) = smol::block_on(async
|
||||
{
|
||||
// Role 0: Buscoupler EK1100
|
||||
let _ek1100 =
|
||||
get_ethercat_device::<EK1100>(hardware, params, 0, vec![EK1100_IDENTITY_A]).await?;
|
||||
@@ -86,9 +44,7 @@ impl MachineNewTrait for Winder2 {
|
||||
params,
|
||||
1,
|
||||
vec![EL2002_IDENTITY_A, EL2002_IDENTITY_B],
|
||||
)
|
||||
.await?
|
||||
.0;
|
||||
).await?.0;
|
||||
|
||||
// Role 2: Stepper Spool EL7041-0052
|
||||
let el7041 = {
|
||||
@@ -118,6 +74,7 @@ impl MachineNewTrait for Winder2 {
|
||||
.await
|
||||
.write_config(&device.1, &el7041_config)
|
||||
.await?;
|
||||
|
||||
device.0
|
||||
};
|
||||
|
||||
@@ -184,73 +141,36 @@ impl MachineNewTrait for Winder2 {
|
||||
.await
|
||||
.write_config(&device.1, &el7031_0030_config)
|
||||
.await?;
|
||||
|
||||
device.0
|
||||
};
|
||||
|
||||
let mode = Winder2Mode::Standby;
|
||||
Ok::<_, anyhow::Error>((el2002, el7041, el7031, el7031_0030))
|
||||
})?;
|
||||
|
||||
let machine_id = params
|
||||
.device_group
|
||||
.first()
|
||||
.expect("device group must have at least one device")
|
||||
.device_machine_identification
|
||||
.machine_identification_unique
|
||||
.clone();
|
||||
let (sender, receiver) = smol::channel::unbounded();
|
||||
let mut new = Self {
|
||||
main_sender: params.main_thread_channel.clone(),
|
||||
max_connected_machines: 2,
|
||||
api_receiver: receiver,
|
||||
api_sender: sender,
|
||||
traverse: StepperVelocityEL70x1::new(el7031.clone(), EL7031StepperPort::STM1),
|
||||
traverse_end_stop: DigitalInput::new(el7031, EL7031DigitalInputPort::DI1),
|
||||
puller: StepperVelocityEL70x1::new(
|
||||
el7031_0030.clone(),
|
||||
EL7031_0030StepperPort::STM1,
|
||||
),
|
||||
spool: StepperVelocityEL70x1::new(el7041, EL7041_0052Port::STM1),
|
||||
tension_arm: TensionArm::new(AnalogInput::new(
|
||||
el7031_0030,
|
||||
EL7031_0030AnalogInputPort::AI1,
|
||||
)),
|
||||
laser: DigitalOutput::new(el2002, EL2002Port::DO1),
|
||||
namespace: Winder2Namespace {
|
||||
namespace: params.namespace.clone(),
|
||||
},
|
||||
mode: mode.clone(),
|
||||
spool_step_converter: AngularStepConverter::new(200),
|
||||
spool_speed_controller: SpoolSpeedController::new(),
|
||||
last_measurement_emit: Instant::now(),
|
||||
spool_mode: mode.clone().into(),
|
||||
traverse_mode: mode.clone().into(),
|
||||
puller_mode: mode.into(),
|
||||
puller_speed_controller: PullerSpeedController::new(
|
||||
Velocity::new::<meter_per_minute>(1.0),
|
||||
Length::new::<millimeter>(1.75),
|
||||
LinearStepConverter::from_diameter(
|
||||
200, // Assuming 200 steps per revolution for the puller stepper,
|
||||
Length::new::<centimeter>(8.0), // 8cm diameter of the puller wheel
|
||||
),
|
||||
),
|
||||
traverse_controller: TraverseController::new(
|
||||
Length::new::<millimeter>(22.0), // Default inner limit
|
||||
Length::new::<millimeter>(92.0), // Default outer limit
|
||||
64, // Microsteps
|
||||
),
|
||||
emitted_default_state: false,
|
||||
spool_automatic_action: super::SpoolAutomaticAction {
|
||||
progress: Length::ZERO,
|
||||
progress_last_check: Instant::now(),
|
||||
target_length: Length::new::<meter>(250.0),
|
||||
mode: super::api::SpoolAutomaticActionMode::NoAction,
|
||||
},
|
||||
machine_identification_unique: machine_id,
|
||||
connected_machines: vec![],
|
||||
};
|
||||
let machine_id = params
|
||||
.device_group
|
||||
.first()
|
||||
.expect("device group must have at least one device")
|
||||
.device_machine_identification
|
||||
.machine_identification_unique
|
||||
.clone();
|
||||
|
||||
// initalize events
|
||||
new.emit_state();
|
||||
Ok(new)
|
||||
})
|
||||
let channel = MachineChannel::new(machine_id);
|
||||
|
||||
let hardware = Hardware {
|
||||
spool_motor: StepperVelocityEL70x1::new(el7041, EL7041_0052Port::STM1),
|
||||
traverse_motor: StepperVelocityEL70x1::new(el7031.clone(), EL7031StepperPort::STM1),
|
||||
traverse_limit_switch: DigitalInput::new(el7031, EL7031DigitalInputPort::DI1),
|
||||
puller_motor: StepperVelocityEL70x1::new(el7031_0030.clone(), EL7031_0030StepperPort::STM1),
|
||||
tension_arm_sensor: AnalogInput::new(el7031_0030, EL7031_0030AnalogInputPort::AI1),
|
||||
traverse_laser_pointer: DigitalOutput::new(el2002, EL2002Port::DO1),
|
||||
};
|
||||
|
||||
let mut instance = Self::new(channel, hardware);
|
||||
|
||||
instance.emit_state();
|
||||
|
||||
Ok(instance)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use control_core::{
|
||||
controllers::second_degree_motion::linear_jerk_speed_controller::LinearJerkSpeedController,
|
||||
converters::linear_step_converter::LinearStepConverter,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use units::ConstZero;
|
||||
use units::acceleration::meter_per_minute_per_second;
|
||||
use units::f64::*;
|
||||
use units::jerk::meter_per_minute_per_second_squared;
|
||||
use units::velocity::meter_per_minute;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
|
||||
pub enum GearRatio {
|
||||
OneToOne,
|
||||
OneToFive,
|
||||
OneToTen,
|
||||
}
|
||||
|
||||
impl GearRatio {
|
||||
/// Get the speed multiplier for this gear ratio
|
||||
pub fn multiplier(&self) -> f64 {
|
||||
match self {
|
||||
GearRatio::OneToOne => 1.0,
|
||||
GearRatio::OneToFive => 5.0,
|
||||
GearRatio::OneToTen => 10.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for GearRatio {
|
||||
fn default() -> Self {
|
||||
GearRatio::OneToOne
|
||||
}
|
||||
}
|
||||
|
||||
#[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,
|
||||
/// Gear ratio for winding speed (1:5 or 1:10)
|
||||
pub gear_ratio: GearRatio,
|
||||
/// 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,
|
||||
gear_ratio: GearRatio::default(),
|
||||
acceleration_controller: LinearJerkSpeedController::new_simple(
|
||||
Some(speed),
|
||||
acceleration,
|
||||
jerk,
|
||||
),
|
||||
converter,
|
||||
last_speed: Velocity::ZERO,
|
||||
}
|
||||
}
|
||||
|
||||
pub const 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 const fn set_regulation_mode(&mut self, regulation: PullerRegulationMode) {
|
||||
self.regulation_mode = regulation;
|
||||
}
|
||||
|
||||
pub const fn set_forward(&mut self, forward: bool) {
|
||||
self.forward = forward;
|
||||
}
|
||||
|
||||
pub const fn set_gear_ratio(&mut self, gear_ratio: GearRatio) {
|
||||
self.gear_ratio = gear_ratio;
|
||||
}
|
||||
|
||||
pub const fn get_gear_ratio(&self) -> GearRatio {
|
||||
self.gear_ratio
|
||||
}
|
||||
|
||||
fn update_speed(&mut self, t: Instant) -> Velocity {
|
||||
let base_speed = match self.enabled {
|
||||
true => match self.regulation_mode {
|
||||
PullerRegulationMode::Speed => self.target_speed,
|
||||
PullerRegulationMode::Diameter => unimplemented!(),
|
||||
},
|
||||
false => Velocity::ZERO,
|
||||
};
|
||||
|
||||
// Apply gear ratio multiplier
|
||||
let speed = base_speed * self.gear_ratio.multiplier();
|
||||
|
||||
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, Default)]
|
||||
pub enum PullerRegulationMode {
|
||||
#[default]
|
||||
Speed,
|
||||
Diameter,
|
||||
}
|
||||
@@ -1,194 +0,0 @@
|
||||
use crate::winder2::{
|
||||
adaptive_spool_speed_controller::AdaptiveSpoolSpeedController,
|
||||
minmax_spool_speed_controller::MinMaxSpoolSpeedController,
|
||||
puller_speed_controller::PullerSpeedController,
|
||||
};
|
||||
use control_core::controllers::second_degree_motion::acceleration_position_controller::MotionControllerError;
|
||||
|
||||
use super::tension_arm::TensionArm;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Instant;
|
||||
use units::f64::*;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
pub enum SpoolSpeedControllerType {
|
||||
#[default]
|
||||
Adaptive,
|
||||
MinMax,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SpoolSpeedController {
|
||||
adaptive_controller: AdaptiveSpoolSpeedController,
|
||||
minmax_controller: MinMaxSpoolSpeedController,
|
||||
r#type: SpoolSpeedControllerType,
|
||||
forward: bool,
|
||||
}
|
||||
|
||||
impl Default for SpoolSpeedController {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl SpoolSpeedController {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
adaptive_controller: AdaptiveSpoolSpeedController::new(),
|
||||
minmax_controller: MinMaxSpoolSpeedController::new(),
|
||||
r#type: SpoolSpeedControllerType::Adaptive,
|
||||
forward: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_speed(&self) -> AngularVelocity {
|
||||
match self.r#type {
|
||||
SpoolSpeedControllerType::Adaptive => self.adaptive_controller.get_speed(),
|
||||
SpoolSpeedControllerType::MinMax => self.minmax_controller.get_speed(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_speed(&mut self, speed: AngularVelocity) {
|
||||
match self.r#type {
|
||||
SpoolSpeedControllerType::Adaptive => self.adaptive_controller.set_speed(speed),
|
||||
SpoolSpeedControllerType::MinMax => self.minmax_controller.set_speed(speed),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn set_enabled(&mut self, enabled: bool) {
|
||||
self.adaptive_controller.set_enabled(enabled);
|
||||
self.minmax_controller.set_enabled(enabled);
|
||||
}
|
||||
|
||||
pub const fn is_enabled(&self) -> bool {
|
||||
match self.r#type {
|
||||
SpoolSpeedControllerType::Adaptive => self.adaptive_controller.is_enabled(),
|
||||
SpoolSpeedControllerType::MinMax => self.minmax_controller.is_enabled(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_type(&mut self, r#type: SpoolSpeedControllerType) {
|
||||
// If we're switching to a different type, copy the current speed to the target controller
|
||||
if std::mem::discriminant(&self.r#type) != std::mem::discriminant(&r#type) {
|
||||
// Get the current speed from the active controller
|
||||
let current_speed = match self.r#type {
|
||||
SpoolSpeedControllerType::Adaptive => self.adaptive_controller.get_speed(),
|
||||
SpoolSpeedControllerType::MinMax => self.minmax_controller.get_speed(),
|
||||
};
|
||||
|
||||
// Set the speed in the target controller and reset it for smooth transition
|
||||
match r#type {
|
||||
SpoolSpeedControllerType::Adaptive => {
|
||||
self.adaptive_controller.set_speed(current_speed);
|
||||
self.adaptive_controller.reset();
|
||||
self.adaptive_controller.set_speed(current_speed); // Set again after reset to maintain speed
|
||||
}
|
||||
SpoolSpeedControllerType::MinMax => {
|
||||
self.minmax_controller.set_speed(current_speed);
|
||||
self.minmax_controller.reset();
|
||||
self.minmax_controller.set_speed(current_speed); // Set again after reset to maintain speed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.r#type = r#type;
|
||||
}
|
||||
|
||||
pub const fn get_type(&self) -> &SpoolSpeedControllerType {
|
||||
&self.r#type
|
||||
}
|
||||
|
||||
pub fn set_minmax_min_speed(
|
||||
&mut self,
|
||||
min_speed: AngularVelocity,
|
||||
) -> Result<(), MotionControllerError> {
|
||||
self.minmax_controller.set_min_speed(min_speed)
|
||||
}
|
||||
|
||||
pub fn set_minmax_max_speed(
|
||||
&mut self,
|
||||
max_speed: AngularVelocity,
|
||||
) -> Result<(), MotionControllerError> {
|
||||
self.minmax_controller.set_max_speed(max_speed)
|
||||
}
|
||||
|
||||
pub fn get_minmax_min_speed(&self) -> AngularVelocity {
|
||||
self.minmax_controller.get_min_speed()
|
||||
}
|
||||
|
||||
pub fn get_minmax_max_speed(&self) -> AngularVelocity {
|
||||
self.minmax_controller.get_max_speed()
|
||||
}
|
||||
|
||||
pub fn update_speed(
|
||||
&mut self,
|
||||
t: Instant,
|
||||
tension_arm: &TensionArm,
|
||||
puller_speed_controller: &PullerSpeedController,
|
||||
) -> AngularVelocity {
|
||||
match self.r#type {
|
||||
SpoolSpeedControllerType::Adaptive => {
|
||||
self.adaptive_controller
|
||||
.update_speed(t, tension_arm, puller_speed_controller)
|
||||
}
|
||||
SpoolSpeedControllerType::MinMax => self.minmax_controller.update_speed(t, tension_arm),
|
||||
}
|
||||
}
|
||||
|
||||
// Adaptive controller parameter getters and setters
|
||||
pub const fn get_adaptive_tension_target(&self) -> f64 {
|
||||
self.adaptive_controller.get_tension_target()
|
||||
}
|
||||
|
||||
pub const fn set_adaptive_tension_target(&mut self, tension_target: f64) {
|
||||
self.adaptive_controller.set_tension_target(tension_target);
|
||||
}
|
||||
|
||||
pub const fn get_adaptive_radius_learning_rate(&self) -> f64 {
|
||||
self.adaptive_controller.get_radius_learning_rate()
|
||||
}
|
||||
|
||||
pub const fn set_adaptive_radius_learning_rate(&mut self, radius_learning_rate: f64) {
|
||||
self.adaptive_controller
|
||||
.set_radius_learning_rate(radius_learning_rate);
|
||||
}
|
||||
|
||||
pub const fn get_adaptive_max_speed_multiplier(&self) -> f64 {
|
||||
self.adaptive_controller.get_max_speed_multiplier()
|
||||
}
|
||||
|
||||
pub const fn set_adaptive_max_speed_multiplier(&mut self, max_speed_multiplier: f64) {
|
||||
self.adaptive_controller
|
||||
.set_max_speed_multiplier(max_speed_multiplier);
|
||||
}
|
||||
|
||||
pub const fn get_adaptive_acceleration_factor(&self) -> f64 {
|
||||
self.adaptive_controller.get_acceleration_factor()
|
||||
}
|
||||
|
||||
pub const fn set_adaptive_acceleration_factor(&mut self, acceleration_factor: f64) {
|
||||
self.adaptive_controller
|
||||
.set_acceleration_factor(acceleration_factor);
|
||||
}
|
||||
|
||||
pub const fn get_adaptive_deacceleration_urgency_multiplier(&self) -> f64 {
|
||||
self.adaptive_controller
|
||||
.get_deacceleration_urgency_multiplier()
|
||||
}
|
||||
|
||||
pub const fn set_adaptive_deacceleration_urgency_multiplier(
|
||||
&mut self,
|
||||
deacceleration_urgency_multiplier: f64,
|
||||
) {
|
||||
self.adaptive_controller
|
||||
.set_deacceleration_urgency_multiplier(deacceleration_urgency_multiplier);
|
||||
}
|
||||
|
||||
pub const fn get_forward(&self) -> bool {
|
||||
self.forward
|
||||
}
|
||||
|
||||
pub const fn set_forward(&mut self, forward: bool) {
|
||||
self.forward = forward;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use units::{ConstZero, Length, Velocity};
|
||||
use units::length::meter;
|
||||
use units::velocity::meter_per_second;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SpoolLengthTask
|
||||
{
|
||||
current_length: Length,
|
||||
target_length: Length,
|
||||
last_check: Instant,
|
||||
}
|
||||
|
||||
impl SpoolLengthTask
|
||||
{
|
||||
pub fn new() -> Self
|
||||
{
|
||||
SpoolLengthTask {
|
||||
target_length: Length::new::<meter>(0.0),
|
||||
current_length: Length::new::<meter>(0.0),
|
||||
last_check: Instant::now(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_complete(&self) -> bool
|
||||
{
|
||||
self.current_length >= self.target_length
|
||||
}
|
||||
|
||||
pub const fn current_length(&self) -> Length
|
||||
{
|
||||
self.current_length
|
||||
}
|
||||
|
||||
pub const fn target_length(&self) -> Length
|
||||
{
|
||||
self.target_length
|
||||
}
|
||||
|
||||
pub fn set_target_length(&mut self, target_length: Length)
|
||||
{
|
||||
self.target_length = target_length;
|
||||
}
|
||||
|
||||
pub fn update_timer(&mut self, now: Instant)
|
||||
{
|
||||
self.last_check = now;
|
||||
}
|
||||
|
||||
pub fn update_progress(&mut self, now: Instant, velocity: Velocity)
|
||||
{
|
||||
// Calculate time elapsed since last progress check (in minutes)
|
||||
let dt = now.duration_since(self.last_check).as_secs_f64();
|
||||
|
||||
// Calculate distance pulled during this time interval
|
||||
let meters_pulled_this_interval = Length::new::<meter>(
|
||||
velocity.get::<meter_per_second>() * dt,
|
||||
);
|
||||
|
||||
// Update total meters pulled
|
||||
self.current_length += meters_pulled_this_interval.abs();
|
||||
self.last_check = now;
|
||||
}
|
||||
|
||||
pub fn reset(&mut self, now: Instant)
|
||||
{
|
||||
self.current_length = Length::ZERO;
|
||||
self.last_check = now;
|
||||
}
|
||||
}
|
||||
@@ -1,517 +0,0 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use control_core::converters::linear_step_converter::LinearStepConverter;
|
||||
use ethercat_hal::io::{
|
||||
digital_input::DigitalInput, stepper_velocity_el70x1::StepperVelocityEL70x1,
|
||||
};
|
||||
use units::ConstZero;
|
||||
use units::angular_velocity::revolution_per_second;
|
||||
use units::f64::{AngularVelocity, Length, Velocity};
|
||||
use units::length::millimeter;
|
||||
use units::velocity::millimeter_per_second;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TraverseController {
|
||||
enabled: bool,
|
||||
position: Length,
|
||||
limit_inner: Length,
|
||||
limit_outer: Length,
|
||||
step_size: Length,
|
||||
padding: Length,
|
||||
state: State,
|
||||
fullstep_converter: LinearStepConverter,
|
||||
microstep_converter: LinearStepConverter,
|
||||
// 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 inner limit
|
||||
///
|
||||
/// After reaching the inner limit, the state will change to [`State::Idle`]
|
||||
GoingIn,
|
||||
|
||||
/// Going to outer limit
|
||||
///
|
||||
/// After reaching the outer limit, the state will change to [`State::Idle`]
|
||||
GoingOut,
|
||||
|
||||
/// Homing is in progress
|
||||
///
|
||||
/// After homing is done, the state will change to [`State::Idle`]
|
||||
Homing(HomingState),
|
||||
|
||||
/// Move between inner and outer limits
|
||||
Traversing(TraversingState),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum TraversingState {
|
||||
/// Like [`State::GoingOut`] but
|
||||
/// - will go into [`State::GoingIn`] after reaching the outer limit
|
||||
GoingOut,
|
||||
|
||||
/// Like [`State::GoingIn`] but
|
||||
/// - will go into [`State::GoingOut`] after reaching the inner limit
|
||||
/// - speed is synced to spool speed
|
||||
TraversingIn,
|
||||
|
||||
/// Like [`State::GoingOut`] but
|
||||
/// - will go into [`State::GoingIn`] after reaching the outer limit
|
||||
/// - speed is synced to spool speed
|
||||
TraversingOut,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum HomingState {
|
||||
/// In this state the traverse 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 traverse 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`]
|
||||
FindEndstopFineDistancing,
|
||||
|
||||
/// In this state the traverse is fast until it reaches the endstop
|
||||
FindEndstopCoarse,
|
||||
|
||||
/// In this state the traverse is moving slowly until it reaches the endstop
|
||||
FindEndtopFine,
|
||||
|
||||
/// In this state we check if th current position is actually 0.0, if not we redo the homing routine
|
||||
Validate(Instant),
|
||||
}
|
||||
|
||||
impl TraverseController {
|
||||
pub fn new(limit_inner: Length, limit_outer: Length, microsteps: u8) -> Self {
|
||||
Self {
|
||||
enabled: false,
|
||||
position: Length::ZERO,
|
||||
limit_inner,
|
||||
limit_outer,
|
||||
step_size: Length::new::<millimeter>(1.75), // Default step size
|
||||
padding: Length::new::<millimeter>(0.88), // Default padding
|
||||
state: State::NotHomed,
|
||||
did_change_state: false,
|
||||
fullstep_converter: LinearStepConverter::from_circumference(
|
||||
200,
|
||||
Length::new::<millimeter>(35.0),
|
||||
),
|
||||
microstep_converter: LinearStepConverter::from_circumference(
|
||||
200 * microsteps as i16,
|
||||
Length::new::<millimeter>(35.0),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Getter & Setter
|
||||
impl TraverseController {
|
||||
pub const fn set_enabled(&mut self, enabled: bool) {
|
||||
self.enabled = enabled;
|
||||
}
|
||||
|
||||
pub fn set_limit_inner(&mut self, limit: Length) {
|
||||
self.limit_inner = limit;
|
||||
}
|
||||
|
||||
pub fn set_limit_outer(&mut self, limit: Length) {
|
||||
self.limit_outer = 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_inner(&self) -> Length {
|
||||
self.limit_inner
|
||||
}
|
||||
|
||||
pub fn get_limit_outer(&self) -> Length {
|
||||
self.limit_outer
|
||||
}
|
||||
|
||||
pub fn get_step_size(&self) -> Length {
|
||||
self.step_size
|
||||
}
|
||||
|
||||
pub fn get_padding(&self) -> Length {
|
||||
self.padding
|
||||
}
|
||||
|
||||
pub fn get_current_position(&self) -> Option<Length> {
|
||||
match self.is_homed() {
|
||||
true => Some(self.position),
|
||||
false => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn is_enabled(&self) -> bool {
|
||||
self.enabled
|
||||
}
|
||||
|
||||
pub const fn did_change_state(&mut self) -> bool {
|
||||
let did_change = self.did_change_state;
|
||||
// Reset the flag
|
||||
self.did_change_state = false;
|
||||
did_change
|
||||
}
|
||||
}
|
||||
|
||||
// State management
|
||||
impl TraverseController {
|
||||
pub const fn goto_limit_inner(&mut self) {
|
||||
self.state = State::GoingIn;
|
||||
}
|
||||
|
||||
pub const fn goto_limit_outer(&mut self) {
|
||||
self.state = State::GoingOut;
|
||||
}
|
||||
|
||||
pub const fn goto_home(&mut self) {
|
||||
self.state = State::Homing(HomingState::Initialize);
|
||||
}
|
||||
|
||||
pub const fn start_traversing(&mut self) {
|
||||
self.state = State::Traversing(TraversingState::GoingOut);
|
||||
}
|
||||
|
||||
pub const fn is_homed(&self) -> bool {
|
||||
// if not [`State::NotHomed`], then it is homed
|
||||
!matches!(self.state, State::NotHomed)
|
||||
}
|
||||
|
||||
pub const fn is_going_in(&self) -> bool {
|
||||
// [`State::GoingIn`]
|
||||
matches!(self.state, State::GoingIn)
|
||||
}
|
||||
|
||||
pub const fn is_going_out(&self) -> bool {
|
||||
// [`State::GoingOut`]
|
||||
matches!(self.state, State::GoingOut)
|
||||
}
|
||||
|
||||
pub const fn is_going_home(&self) -> bool {
|
||||
// [`State::Homing`]
|
||||
matches!(self.state, State::Homing(_))
|
||||
}
|
||||
|
||||
pub const fn is_traversing(&self) -> bool {
|
||||
// [`State::Traversing`]
|
||||
matches!(self.state, State::Traversing(_))
|
||||
}
|
||||
}
|
||||
|
||||
impl TraverseController {
|
||||
// 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();
|
||||
self.position >= lower_tolerance && self.position <= upper_tolerance
|
||||
}
|
||||
|
||||
/// Calculate distance to position
|
||||
fn distance_to_position(&self, target_position: Length) -> Length {
|
||||
if self.position > target_position {
|
||||
self.position - target_position
|
||||
} else if self.position < target_position {
|
||||
target_position - self.position
|
||||
} else {
|
||||
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 {
|
||||
-absolute_speed.abs()
|
||||
} else if self.position < target_position {
|
||||
absolute_speed.abs()
|
||||
} else {
|
||||
Velocity::ZERO
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the current traverse position as a [`Length`].
|
||||
pub fn sync_position(&mut self, traverse: &StepperVelocityEL70x1) {
|
||||
let steps = traverse.get_position();
|
||||
self.position = self.microstep_converter.steps_to_distance(steps as f64);
|
||||
}
|
||||
|
||||
/// Update the [`did_change_state`] flag
|
||||
/// Only considers the major state not the sub states
|
||||
const 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::GoingIn => !matches!(old_state, State::GoingIn),
|
||||
State::GoingOut => !matches!(old_state, State::GoingOut),
|
||||
State::Homing(_) => !matches!(old_state, State::Homing(_)),
|
||||
State::Traversing(_) => !matches!(old_state, State::Traversing(_)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates a desired speed based on the current state and the end stop status.
|
||||
///
|
||||
/// Positive speed moved out, negative speed moves in.
|
||||
fn get_speed(
|
||||
&mut self,
|
||||
traverse: &mut StepperVelocityEL70x1,
|
||||
traverse_end_stop: &DigitalInput,
|
||||
spool_speed: AngularVelocity,
|
||||
) -> 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(traverse);
|
||||
|
||||
// save state before
|
||||
let old_state = self.state.clone();
|
||||
|
||||
// Automatic Transitions
|
||||
match &self.state {
|
||||
State::NotHomed => {}
|
||||
State::Idle => {}
|
||||
State::GoingIn => {
|
||||
// If inner limit is reached
|
||||
if self.is_at_position(self.limit_inner, Length::new::<millimeter>(0.01)) {
|
||||
// Put Into Idle
|
||||
self.state = State::Idle;
|
||||
}
|
||||
}
|
||||
State::GoingOut => {
|
||||
// If outer limit is reached
|
||||
if self.is_at_position(self.limit_outer, 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 traverse_end_stop.get_value().unwrap_or(false) {
|
||||
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 !traverse_end_stop.get_value().unwrap_or(false) {
|
||||
self.state = State::Homing(HomingState::FindEndstopFineDistancing);
|
||||
}
|
||||
}
|
||||
HomingState::FindEndstopFineDistancing => {
|
||||
// Move out until endstop is not triggered anymore
|
||||
if !traverse_end_stop.get_value().unwrap_or(false) {
|
||||
// Find endstop fine
|
||||
self.state = State::Homing(HomingState::FindEndtopFine);
|
||||
}
|
||||
}
|
||||
HomingState::FindEndtopFine => {
|
||||
// If endstop is reached change to idle
|
||||
if traverse_end_stop.get_value().unwrap_or(false) {
|
||||
// Set poition of traverse to 0
|
||||
traverse.set_position(0);
|
||||
// Put Into Idle
|
||||
self.state = State::Homing(HomingState::Validate(Instant::now()));
|
||||
}
|
||||
}
|
||||
HomingState::FindEndstopCoarse => {
|
||||
// Move to endstop
|
||||
if traverse_end_stop.get_value().unwrap_or(false) {
|
||||
// Move awaiy from endstop
|
||||
self.state = State::Homing(HomingState::FindEndstopFineDistancing);
|
||||
}
|
||||
}
|
||||
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::Traversing(traversing_state) => match traversing_state {
|
||||
TraversingState::GoingOut => {
|
||||
// If outer limit is reached
|
||||
if self.position >= self.limit_outer - self.padding {
|
||||
// Turn around
|
||||
self.state = State::Traversing(TraversingState::TraversingIn);
|
||||
}
|
||||
}
|
||||
TraversingState::TraversingIn => {
|
||||
// If inner limit is reached
|
||||
if self.position <= self.limit_inner + self.padding {
|
||||
// Turn around
|
||||
self.state = State::Traversing(TraversingState::TraversingOut);
|
||||
}
|
||||
}
|
||||
TraversingState::TraversingOut => {
|
||||
// If outer limit is reached
|
||||
if self.position >= self.limit_outer - self.padding {
|
||||
// Turn around
|
||||
self.state = State::Traversing(TraversingState::TraversingIn);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// Set the [`did_change_state`] flag
|
||||
if !self.did_change_state {
|
||||
self.did_change_state = self.update_did_change_state(&old_state);
|
||||
}
|
||||
|
||||
// Speed
|
||||
|
||||
match &self.state {
|
||||
State::NotHomed => Velocity::ZERO, // Not homed, no movement
|
||||
State::Idle => Velocity::ZERO, // No movement in idle state
|
||||
State::GoingIn => {
|
||||
// Move in at a speed of 10-100 mm/s
|
||||
self.speed_to_position(
|
||||
self.limit_inner,
|
||||
match self.distance_to_position(self.limit_inner).abs()
|
||||
> Length::new::<millimeter>(1.0)
|
||||
{
|
||||
true => Velocity::new::<millimeter_per_second>(100.0),
|
||||
false => Velocity::new::<millimeter_per_second>(10.0),
|
||||
},
|
||||
)
|
||||
}
|
||||
State::GoingOut => {
|
||||
// Move out at a speed of 10-100 mm/s
|
||||
self.speed_to_position(
|
||||
self.limit_outer,
|
||||
match self.distance_to_position(self.limit_outer).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 out at a speed of 10 mm/s
|
||||
Velocity::new::<millimeter_per_second>(10.0)
|
||||
}
|
||||
HomingState::FindEndstopFineDistancing => {
|
||||
// Move out 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::FindEndtopFine => {
|
||||
// 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::Traversing(traversing_state) => match traversing_state {
|
||||
TraversingState::GoingOut => {
|
||||
// Move out at a speed of 100 mm/s
|
||||
self.speed_to_position(
|
||||
self.limit_outer - self.padding + Length::new::<millimeter>(0.01),
|
||||
Velocity::new::<millimeter_per_second>(100.0),
|
||||
)
|
||||
}
|
||||
TraversingState::TraversingIn => self.speed_to_position(
|
||||
self.limit_inner + self.padding - Length::new::<millimeter>(0.01),
|
||||
Self::calculate_traverse_speed(spool_speed, self.step_size),
|
||||
),
|
||||
TraversingState::TraversingOut => self.speed_to_position(
|
||||
self.limit_outer - self.padding + Length::new::<millimeter>(0.01),
|
||||
Self::calculate_traverse_speed(spool_speed, self.step_size),
|
||||
),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate the traverse speed
|
||||
///
|
||||
/// The traverse speed is the linear speed at which the winding mechanism moves along the spool.
|
||||
/// It's directly proportional to how fast the spool rotates and how far the traverse moves per rotation.
|
||||
///
|
||||
/// - Traverse Distance per Revolution [mm] = Step Size [mm]
|
||||
/// - Traverse Speed [mm/s] = Spool Speed [rev/s or rad/s] * Step Size [mm]
|
||||
///
|
||||
/// Note: While the traverse range (from outer limit minus padding to inner limit plus padding)
|
||||
/// determines the total area to be covered, the traverse speed itself depends only on
|
||||
/// the step size and spool rotation speed.
|
||||
pub fn calculate_traverse_speed(spool_speed: AngularVelocity, step_size: Length) -> Velocity {
|
||||
// Calculate the traverse speed directly from spool speed and step size
|
||||
let traverse_speed: Velocity = Velocity::new::<millimeter_per_second>(
|
||||
spool_speed.get::<revolution_per_second>() * step_size.get::<millimeter>(),
|
||||
);
|
||||
|
||||
traverse_speed
|
||||
}
|
||||
|
||||
pub fn update_speed(
|
||||
&mut self,
|
||||
traverse: &mut StepperVelocityEL70x1,
|
||||
traverse_end_stop: &DigitalInput,
|
||||
spool_speed: AngularVelocity,
|
||||
) {
|
||||
let speed = self.get_speed(traverse, traverse_end_stop, spool_speed);
|
||||
let steps_per_second = self.fullstep_converter.velocity_to_steps(speed);
|
||||
// ignore if we can't set speed
|
||||
let _ = traverse.set_speed(steps_per_second);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use approx::assert_relative_eq;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_calculate_traverse_speed() {
|
||||
let spool_speed = AngularVelocity::new::<revolution_per_second>(1.0); // 1 revolution per second
|
||||
let step_size = Length::new::<millimeter>(1.75); // 1.75 mm step size
|
||||
|
||||
let traverse_speed = TraverseController::calculate_traverse_speed(spool_speed, step_size);
|
||||
assert_relative_eq!(
|
||||
traverse_speed.get::<millimeter_per_second>(),
|
||||
1.75,
|
||||
epsilon = f64::EPSILON
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
use ethercat_hal::io::{
|
||||
analog_input::AnalogInput,
|
||||
digital_input::DigitalInput,
|
||||
digital_output::DigitalOutput,
|
||||
stepper_velocity_el70x1::StepperVelocityEL70x1
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum Mode
|
||||
{
|
||||
Standby,
|
||||
Hold,
|
||||
Pull,
|
||||
Wind,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum SpoolLengthTaskCompletedAction
|
||||
{
|
||||
NoAction,
|
||||
Pull,
|
||||
Hold,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Hardware
|
||||
{
|
||||
pub spool_motor: StepperVelocityEL70x1,
|
||||
pub traverse_motor: StepperVelocityEL70x1,
|
||||
pub traverse_limit_switch: DigitalInput,
|
||||
pub traverse_laser_pointer: DigitalOutput,
|
||||
pub puller_motor: StepperVelocityEL70x1,
|
||||
pub tension_arm_sensor: AnalogInput,
|
||||
}
|
||||
+54
-1
@@ -3,6 +3,8 @@ use crate::{
|
||||
socketio::main_namespace::machines_event::MachineObj,
|
||||
};
|
||||
use control_core::socketio::namespace::NamespaceCacheingLogic;
|
||||
use control_core::socketio::event::GenericEvent;
|
||||
|
||||
use machines::{
|
||||
AsyncThreadMessage, MachineConnection, MachineNewHardware, MachineNewHardwareSerial,
|
||||
MachineNewParams, SerialDevice, SerialDeviceIdentification, SerialDeviceNew,
|
||||
@@ -13,8 +15,8 @@ use machines::{
|
||||
},
|
||||
registry::{MACHINE_REGISTRY, MachineRegistry},
|
||||
serial::{devices::laser::Laser, init::SerialDetection},
|
||||
winder2::api::GenericEvent,
|
||||
};
|
||||
|
||||
use socketio::{
|
||||
main_namespace::{MainNamespaceEvents, ethercat_devices_event::EthercatDevicesEventBuilder},
|
||||
queue::start_socketio_queue,
|
||||
@@ -256,6 +258,7 @@ async fn handle_async_requests(recv: Receiver<AsyncThreadMessage>, shared_state:
|
||||
while let Ok(message) = recv.recv().await {
|
||||
match message {
|
||||
AsyncThreadMessage::NoMsg => (),
|
||||
|
||||
AsyncThreadMessage::ConnectOneWayRequest(cross_connection) => {
|
||||
let api_machines_guard = shared_state.api_machines.lock().await;
|
||||
// The Src Connection is from the machine that recvs the request to connect
|
||||
@@ -267,6 +270,8 @@ async fn handle_async_requests(recv: Receiver<AsyncThreadMessage>, shared_state:
|
||||
None => continue,
|
||||
};
|
||||
|
||||
tracing::warn!("Received signal for ONE WAY REQUEST");
|
||||
|
||||
let dest_sender = match api_machines_guard.get(&dest_ident) {
|
||||
Some(sender) => sender,
|
||||
None => continue,
|
||||
@@ -283,6 +288,54 @@ async fn handle_async_requests(recv: Receiver<AsyncThreadMessage>, shared_state:
|
||||
Err(_) => tracing::error!("Failed to send MachineConnection"),
|
||||
}
|
||||
}
|
||||
AsyncThreadMessage::ConnectTwoWayRequest(cross_connection) => {
|
||||
let api_machines_guard = shared_state.api_machines.lock().await;
|
||||
|
||||
let src_ident = cross_connection.src;
|
||||
let dest_ident = cross_connection.dest;
|
||||
|
||||
let src_sender = match api_machines_guard.get(&src_ident) {
|
||||
Some(sender) => sender,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
let dest_sender = match api_machines_guard.get(&dest_ident) {
|
||||
Some(sender) => sender,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
// connect src -> dest
|
||||
let connection_src_to_dest = MachineConnection {
|
||||
ident: dest_ident.clone(),
|
||||
connection: dest_sender.clone(),
|
||||
};
|
||||
|
||||
let res = src_sender
|
||||
.send(machines::MachineMessage::ConnectToMachine(
|
||||
connection_src_to_dest,
|
||||
))
|
||||
.await;
|
||||
match res {
|
||||
Ok(_) => (),
|
||||
Err(_) => tracing::error!("Failed to send MachineConnection src -> dest"),
|
||||
}
|
||||
|
||||
// connect dest -> src
|
||||
let connection_dest_to_src = MachineConnection {
|
||||
ident: src_ident.clone(),
|
||||
connection: src_sender.clone(),
|
||||
};
|
||||
|
||||
let res = dest_sender
|
||||
.send(machines::MachineMessage::ConnectToMachine(
|
||||
connection_dest_to_src,
|
||||
))
|
||||
.await;
|
||||
match res {
|
||||
Ok(_) => (),
|
||||
Err(_) => tracing::error!("Failed to send MachineConnection dest -> src"),
|
||||
}
|
||||
}
|
||||
AsyncThreadMessage::DisconnectMachines(cross_connection) => {
|
||||
let api_machines_guard = shared_state.api_machines.lock().await;
|
||||
// The Src Connection is from the machine that recvs the request to connect
|
||||
|
||||
Reference in new issue
Block a user