Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a6f92f8a4 | ||
|
|
2e283ea95f | ||
|
|
829bb5ea1c | ||
|
|
0be18aaed0 | ||
|
|
ffe0f09061 | ||
|
|
730d61a18d | ||
|
|
fc07525976 | ||
|
|
c48953aa6b |
No files matched your search
@@ -64,12 +64,12 @@ We could combine the code of the frontend and backend using [Doxius](https://dio
|
||||
| Machine Type | Version | Release Date | Description | Change to Previous Version | Vendor ID | Machine ID | Implemented | Docs |
|
||||
| ------------ | ------- | ------------ | --------------------------- | ------------------------------------------------------ | -------------------------- | ---------- | ----------- | ------------------------------- |
|
||||
| Winder | V1 | ??? | Winding Filaments & Similar | - | 1 (Qitech Industries GmbH) | 1 | Reserved | - |
|
||||
| Winder | V2 | 2025 | Winding Filaments & Similar | Reengineered Traverse | 1 (Qitech Industries GmbH) | 2 | In Progress | [](./docs/machines/winder-1.md) |
|
||||
| Winder | V2 | 2025 | Winding Filaments & Similar | Reengineered Traverse | 1 (Qitech Industries GmbH) | 2 | Yes | [](./docs/machines/winder-1.md) |
|
||||
| Extruder | V1 | ??? | Single Screw Extruder | - | 1 (Qitech Industries GmbH) | 3 | Reserved | - |
|
||||
| Extruder | V2 | 2025 | Single Screw Extruder | PT100 Thermometers, Optional Additional Heating Zone 4 | 1 (Qitech Industries GmbH) | 4 | Not Yet | |
|
||||
| Waterway | V1 | 2025 | Filament Water Cooling | - | 1 (Qitech Industries GmbH) | 5 | Not Yet | |
|
||||
| Laser | V1 | ??? | Diameter Measuring Laser | - | 1 (Qitech Industries GmbH) | 6 | Not Yet | |
|
||||
| Mock | - | ??? | Mock Machine for Testing | - | 1 (Qitech Industries GmbH) | 7 | Not Yet | - |
|
||||
| Extruder | V2 | 2025 | Single Screw Extruder | PT100 Thermometers, Optional Additional Heating Zone 4 | 1 (Qitech Industries GmbH) | 4 | Yes | |
|
||||
| Waterway | V1 | 2025 | Filament Water Cooling | - | 1 (Qitech Industries GmbH) | 5 | In Progress | |
|
||||
| Laser | V1 | ??? | Diameter Measuring Laser | - | 1 (Qitech Industries GmbH) | 6 | Yes | |
|
||||
| Mock | - | ??? | Mock Machine for Testing | - | 1 (Qitech Industries GmbH) | 7 | Yes | - |
|
||||
|
||||
# More Docs
|
||||
|
||||
|
||||
@@ -40,10 +40,15 @@ fn machine_derive2(item: TokenStream2) -> Result<TokenStream2, Error> {
|
||||
let ast: DeriveInput = syn::parse2(item)?;
|
||||
|
||||
let ident = &ast.ident;
|
||||
|
||||
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
|
||||
|
||||
let expanded = quote! {
|
||||
impl #impl_generics control_core::machines::Machine for #ident #ty_generics #where_clause {}
|
||||
impl #impl_generics control_core::machines::Machine for #ident #ty_generics #where_clause {
|
||||
fn get_machine_identification_unique(&self) -> control_core::machines::identification::MachineIdentificationUnique {
|
||||
self.machine_identification_unique.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl #impl_generics control_core::machines::AnyGetters for #ident #ty_generics #where_clause {
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
use anyhow::anyhow;
|
||||
use smol::lock::Mutex;
|
||||
use std::any::Any;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub trait Downcast<T> {
|
||||
fn downcast(&self) -> Result<T, anyhow::Error>;
|
||||
}
|
||||
|
||||
pub trait DowncastRef<T> {
|
||||
fn downcast_ref(&self) -> Result<&T, anyhow::Error>;
|
||||
}
|
||||
|
||||
impl<T: 'static, U: Any + 'static> Downcast<Arc<Mutex<T>>> for Arc<Mutex<U>> {
|
||||
fn downcast(&self) -> Result<Arc<Mutex<T>>, anyhow::Error> {
|
||||
// Acquire a read lock on the RwLock
|
||||
let lock = self.lock_blocking();
|
||||
|
||||
// Check if the inner type can be downcasted to T
|
||||
let any: &dyn Any = &*lock;
|
||||
|
||||
if any.is::<T>() {
|
||||
// Clone the Arc and return it as the desired type
|
||||
let cloned: Arc<Mutex<dyn Any>> = self.clone();
|
||||
|
||||
// Transmute the Arc to the desired type
|
||||
unsafe {
|
||||
let arc = Arc::from_raw(Arc::into_raw(cloned) as *const Mutex<T>);
|
||||
|
||||
Ok(arc)
|
||||
}
|
||||
} else {
|
||||
Err(anyhow!("[{}::downcast] Downcast failed", module_path!()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
pub mod controllers;
|
||||
pub mod converters;
|
||||
pub mod downcast;
|
||||
pub mod ethercat;
|
||||
pub mod helpers;
|
||||
pub mod irq_handling;
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
use serde_json::Value;
|
||||
use smol::lock::Mutex;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::socketio::namespace::Namespace;
|
||||
|
||||
pub trait MachineApi {
|
||||
fn api_mutate(&mut self, value: Value) -> Result<(), anyhow::Error>;
|
||||
fn api_event_namespace(&mut self) -> &mut Namespace;
|
||||
fn api_event_namespace(&mut self) -> Arc<Mutex<Namespace>>;
|
||||
|
||||
/// Returns a list of available video stream identifiers for this machine
|
||||
#[cfg(feature = "video-streaming")]
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
use crate::downcast::Downcast;
|
||||
use crate::machines::identification::MachineIdentificationUnique;
|
||||
use crate::machines::manager::MachineManager;
|
||||
use crate::socketio::{event::GenericEvent, namespace::Namespace};
|
||||
use serde::Serialize;
|
||||
use smol::block_on;
|
||||
use smol::lock::RwLock;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Weak;
|
||||
|
||||
use super::Machine;
|
||||
use anyhow::anyhow;
|
||||
use smol::{channel::Sender, lock::Mutex};
|
||||
use socketioxide::extract::SocketRef;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum MachineConnection<M: Machine + ?Sized>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Error(anyhow::Error),
|
||||
Disconnected,
|
||||
Connected(Arc<Mutex<M>>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MachineSlot<M: Machine + ?Sized>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
pub machine_connection: MachineConnection<M>,
|
||||
pub namespace: Arc<Mutex<Namespace>>,
|
||||
}
|
||||
|
||||
pub type MachineConnectionGeneric = MachineConnection<dyn Machine>;
|
||||
|
||||
pub type MachineSlotGeneric = MachineSlot<dyn Machine>;
|
||||
|
||||
impl<M: Machine> Clone for MachineConnection<M> {
|
||||
fn clone(&self) -> Self {
|
||||
match self {
|
||||
Self::Connected(m) => Self::Connected(m.clone()),
|
||||
Self::Error(e) => Self::Error(anyhow!(e.to_string())),
|
||||
Self::Disconnected => Self::Disconnected,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<M: Machine + ?Sized> MachineConnection<M> {
|
||||
pub fn to_error(&self) -> Option<anyhow::Error> {
|
||||
match self {
|
||||
MachineConnection::Error(err) => Some(anyhow!(err.to_string())),
|
||||
Self::Disconnected => Some(anyhow!("Machine is disconnected")),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_machine(&self) -> Option<Arc<Mutex<M>>> {
|
||||
match self {
|
||||
Self::Connected(machine) => Some(machine.clone()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<M: Machine + ?Sized> MachineSlot<M> {
|
||||
pub fn new(socket_queue_tx: Sender<(SocketRef, Arc<GenericEvent>)>) -> Self {
|
||||
let namespace = Namespace::new(socket_queue_tx);
|
||||
Self {
|
||||
machine_connection: MachineConnection::Disconnected,
|
||||
namespace: Arc::new(Mutex::new(namespace)),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn is_connected(&self) -> bool {
|
||||
matches!(self.machine_connection, MachineConnection::Connected(_))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait CrossConnectableMachine<
|
||||
F: CrossConnectableMachine<F, T>,
|
||||
T: CrossConnectableMachine<T, F>,
|
||||
>: Machine
|
||||
{
|
||||
fn get_cross_connection(&mut self) -> &mut MachineCrossConnection<F, T>;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MachineCrossConnection<
|
||||
F: CrossConnectableMachine<F, T>,
|
||||
T: CrossConnectableMachine<T, F>,
|
||||
> {
|
||||
machine_manager: Weak<RwLock<MachineManager>>,
|
||||
machine_identification_unique: MachineIdentificationUnique,
|
||||
connected_machine: Weak<Mutex<MachineSlot<T>>>,
|
||||
_make_compiler_happy: std::marker::PhantomData<F>, // This marks tells the typechecker, that we
|
||||
// need F in our type arguemnts. We are ok with
|
||||
// it being used only in a fake field. Without
|
||||
// F being fixed, we would not be anble to implement
|
||||
// reverse connecting and disconnecting properly,
|
||||
// as the type on the other side would decay
|
||||
// to an unknown type. See also https://github.com/qitechgmbh/control/pull/625#discussion_r2379566315
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
pub struct MachineCrossConnectionState {
|
||||
/// Connected Machine
|
||||
pub machine_identification_unique: Option<MachineIdentificationUnique>,
|
||||
pub is_available: bool,
|
||||
}
|
||||
|
||||
impl<F: CrossConnectableMachine<F, T>, T: CrossConnectableMachine<T, F>>
|
||||
MachineCrossConnection<F, T>
|
||||
{
|
||||
pub fn new(
|
||||
manager: Weak<RwLock<MachineManager>>,
|
||||
machine_identification_unique: &MachineIdentificationUnique,
|
||||
) -> Self {
|
||||
Self {
|
||||
machine_manager: manager,
|
||||
connected_machine: Weak::new(),
|
||||
machine_identification_unique: machine_identification_unique.clone(),
|
||||
_make_compiler_happy: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_connected_machine(
|
||||
&mut self,
|
||||
machine_identification_unique: &MachineIdentificationUnique,
|
||||
) {
|
||||
if let Some(machine_manager) = self.machine_manager.upgrade() {
|
||||
let machine_manager = block_on(machine_manager.read());
|
||||
|
||||
let slot: Arc<Mutex<MachineSlotGeneric>> =
|
||||
match machine_manager.get(&machine_identification_unique) {
|
||||
Some(c) => c,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let slot: Arc<Mutex<MachineSlot<T>>> = match Downcast::downcast(&slot) {
|
||||
Ok(s) => s,
|
||||
Err(_) => return,
|
||||
};
|
||||
|
||||
self.connected_machine = Arc::downgrade(&slot);
|
||||
} else {
|
||||
panic!("The machine manager has died, cannot do anything at this point");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reverse_connect(&self) {
|
||||
if let Some(slot) = self.connected_machine.upgrade() {
|
||||
let slot = &mut slot.lock_blocking();
|
||||
|
||||
if let MachineConnection::Connected(machine) = &slot.machine_connection {
|
||||
let mut machine = machine.lock_blocking();
|
||||
let cross_connection = machine.get_cross_connection();
|
||||
|
||||
if !cross_connection.is_connected() {
|
||||
cross_connection.set_connected_machine(&self.machine_identification_unique);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub fn disconnect(&mut self) {
|
||||
self.connected_machine = Weak::new();
|
||||
}
|
||||
|
||||
pub fn reverse_disconnect(&mut self) {
|
||||
if let Some(slot) = self.connected_machine.upgrade() {
|
||||
let slot = slot.lock_blocking();
|
||||
|
||||
if let MachineConnection::Connected(machine) = &slot.machine_connection {
|
||||
let mut machine = machine.lock_blocking();
|
||||
let cross_connection = machine.get_cross_connection();
|
||||
cross_connection.disconnect();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_connected(&self) -> bool {
|
||||
let arc = self.connected_machine.upgrade();
|
||||
arc.is_some()
|
||||
}
|
||||
|
||||
pub fn to_state(&self) -> MachineCrossConnectionState {
|
||||
if let Some(slot) = self.connected_machine.upgrade() {
|
||||
let connection = &slot.lock_blocking().machine_connection;
|
||||
|
||||
if let MachineConnection::Connected(machine) = connection {
|
||||
let machine = machine.lock_blocking();
|
||||
|
||||
return MachineCrossConnectionState {
|
||||
machine_identification_unique: Some(
|
||||
machine.get_machine_identification_unique(),
|
||||
),
|
||||
is_available: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
MachineCrossConnectionState {
|
||||
machine_identification_unique: None,
|
||||
is_available: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,9 @@
|
||||
use super::{
|
||||
Machine,
|
||||
identification::{DeviceIdentification, DeviceIdentificationIdentified},
|
||||
new::MachineNewHardwareSerial,
|
||||
registry::MachineRegistry,
|
||||
};
|
||||
use smol::{
|
||||
channel::Sender,
|
||||
lock::{Mutex, RwLock},
|
||||
@@ -6,6 +12,9 @@ use socketioxide::extract::SocketRef;
|
||||
|
||||
use crate::{
|
||||
machines::{
|
||||
connection::{
|
||||
MachineConnection, MachineConnectionGeneric, MachineSlot, MachineSlotGeneric,
|
||||
},
|
||||
identification::MachineIdentificationUnique,
|
||||
new::{MachineNewHardware, MachineNewHardwareEthercat, MachineNewParams},
|
||||
},
|
||||
@@ -17,18 +26,10 @@ use std::{
|
||||
sync::{Arc, Weak},
|
||||
};
|
||||
|
||||
use super::{
|
||||
Machine,
|
||||
identification::{DeviceIdentification, DeviceIdentificationIdentified},
|
||||
new::MachineNewHardwareSerial,
|
||||
registry::MachineRegistry,
|
||||
};
|
||||
#[derive(Debug)]
|
||||
pub struct MachineManager {
|
||||
pub ethercat_machines:
|
||||
HashMap<MachineIdentificationUnique, Result<Arc<Mutex<dyn Machine>>, anyhow::Error>>,
|
||||
pub serial_machines:
|
||||
HashMap<MachineIdentificationUnique, Result<Arc<Mutex<dyn Machine>>, anyhow::Error>>,
|
||||
pub ethercat_machines: HashMap<MachineIdentificationUnique, Arc<Mutex<MachineSlotGeneric>>>,
|
||||
pub serial_machines: HashMap<MachineIdentificationUnique, Arc<Mutex<MachineSlotGeneric>>>,
|
||||
}
|
||||
|
||||
impl Default for MachineManager {
|
||||
@@ -53,9 +54,6 @@ impl MachineManager {
|
||||
socket_queue_tx: Sender<(SocketRef, Arc<GenericEvent>)>,
|
||||
machine_manager: Weak<RwLock<Self>>,
|
||||
) {
|
||||
// empty ethercat machines
|
||||
self.ethercat_machines.clear();
|
||||
|
||||
// group devices by machine device identification
|
||||
let device_grouping_result = group_devices_by_identification(device_identifications);
|
||||
|
||||
@@ -69,10 +67,18 @@ impl MachineManager {
|
||||
// iterate over all identified device groups but ignore unaffected machines
|
||||
for device_group in device_grouping_result.device_groups.iter() {
|
||||
// get the machine identification
|
||||
let machine_identification = match device_group.first() {
|
||||
Some(device_identification) => &device_identification.device_machine_identification,
|
||||
None => continue, // Skip this group if empty
|
||||
};
|
||||
let machine_identification_unique: MachineIdentificationUnique =
|
||||
match device_group.first() {
|
||||
Some(device_identification) => device_identification
|
||||
.device_machine_identification
|
||||
.machine_identification_unique
|
||||
.clone(),
|
||||
None => continue, // Skip this group if empty
|
||||
};
|
||||
|
||||
let slot =
|
||||
self.get_or_create_slot(socket_queue_tx.clone(), machine_identification_unique);
|
||||
let mut slot = slot.lock_blocking();
|
||||
|
||||
// create the machine
|
||||
let new_machine = machine_registry.new_machine(&MachineNewParams {
|
||||
@@ -80,23 +86,40 @@ impl MachineManager {
|
||||
hardware: &machine_new_hardware,
|
||||
socket_queue_tx: socket_queue_tx.clone(),
|
||||
machine_manager: machine_manager.clone(),
|
||||
namespace: slot.namespace.clone(),
|
||||
});
|
||||
|
||||
// insert the machine into the ethercat machines map
|
||||
self.ethercat_machines.insert(
|
||||
machine_identification.machine_identification_unique.clone(),
|
||||
new_machine,
|
||||
);
|
||||
slot.machine_connection = match new_machine {
|
||||
Err(err) => MachineConnectionGeneric::Error(err),
|
||||
Ok(machine) => MachineConnectionGeneric::Connected(machine),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn get_or_create_slot(
|
||||
&mut self,
|
||||
socket_queue_tx: Sender<(SocketRef, Arc<GenericEvent>)>,
|
||||
machine_identification: MachineIdentificationUnique,
|
||||
) -> Arc<Mutex<MachineSlotGeneric>> {
|
||||
if let Some(slot) = self.get(&machine_identification) {
|
||||
return slot;
|
||||
}
|
||||
|
||||
let slot = Arc::new(Mutex::new(MachineSlot::new(socket_queue_tx)));
|
||||
self.ethercat_machines
|
||||
.insert(machine_identification, slot.clone());
|
||||
|
||||
return slot;
|
||||
}
|
||||
|
||||
pub fn get(
|
||||
&self,
|
||||
machine_identification: &MachineIdentificationUnique,
|
||||
) -> Option<&Result<Arc<Mutex<dyn Machine>>, anyhow::Error>> {
|
||||
) -> Option<Arc<Mutex<MachineSlotGeneric>>> {
|
||||
self.ethercat_machines
|
||||
.get(machine_identification)
|
||||
.or_else(|| self.serial_machines.get(machine_identification))
|
||||
.map(|x| x.clone())
|
||||
}
|
||||
|
||||
pub fn add_serial_device(
|
||||
@@ -115,21 +138,28 @@ impl MachineManager {
|
||||
.try_into()
|
||||
.expect("Serial devices always have machine identification");
|
||||
|
||||
let machine_identification: MachineIdentificationUnique = device_identification_identified
|
||||
.device_machine_identification
|
||||
.machine_identification_unique
|
||||
.clone();
|
||||
|
||||
let slot = self.get_or_create_slot(socket_queue_tx.clone(), machine_identification);
|
||||
let mut slot = slot.lock_blocking();
|
||||
|
||||
let new_machine = machine_registry.new_machine(&MachineNewParams {
|
||||
device_group: &vec![device_identification_identified.clone()],
|
||||
hardware: &MachineNewHardware::Serial(&hardware),
|
||||
socket_queue_tx,
|
||||
machine_manager,
|
||||
machine_manager: machine_manager.clone(),
|
||||
namespace: slot.namespace.clone(),
|
||||
});
|
||||
|
||||
tracing::info!("Adding serial machine {:?}", new_machine);
|
||||
slot.machine_connection = match new_machine {
|
||||
Err(err) => MachineConnectionGeneric::Error(err),
|
||||
Ok(machine) => MachineConnectionGeneric::Connected(machine),
|
||||
};
|
||||
|
||||
self.serial_machines.insert(
|
||||
device_identification_identified
|
||||
.device_machine_identification
|
||||
.machine_identification_unique,
|
||||
new_machine,
|
||||
);
|
||||
tracing::info!("Adding serial machine {:?}", slot);
|
||||
}
|
||||
|
||||
pub fn remove_serial_device(&mut self, device_identification: &DeviceIdentification) {
|
||||
@@ -144,53 +174,45 @@ impl MachineManager {
|
||||
device_identification_identified
|
||||
);
|
||||
|
||||
self.serial_machines.remove(
|
||||
&device_identification_identified
|
||||
.device_machine_identification
|
||||
.machine_identification_unique,
|
||||
);
|
||||
let machine_identification_unique = &device_identification_identified
|
||||
.device_machine_identification
|
||||
.machine_identification_unique;
|
||||
|
||||
if let Some(slot) = self.serial_machines.get(machine_identification_unique) {
|
||||
let mut slot = slot.lock_blocking();
|
||||
slot.machine_connection = MachineConnection::Disconnected;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_machine_weak(
|
||||
pub fn get_ethercat_weak(
|
||||
&self,
|
||||
machine_identification: &MachineIdentificationUnique,
|
||||
) -> Option<Weak<Mutex<dyn Machine>>> {
|
||||
let machine = self.ethercat_machines.get(machine_identification);
|
||||
let machine = match machine {
|
||||
Some(machine) => machine,
|
||||
None => {
|
||||
return None;
|
||||
}
|
||||
};
|
||||
let machine = match machine {
|
||||
Ok(machine) => machine,
|
||||
Err(_) => {
|
||||
return None;
|
||||
}
|
||||
};
|
||||
let machine_weak = Arc::downgrade(machine);
|
||||
Some(machine_weak)
|
||||
self.get_weak(&self.ethercat_machines, machine_identification)
|
||||
}
|
||||
|
||||
pub fn get_serial_weak(
|
||||
&self,
|
||||
machine_identification: &MachineIdentificationUnique,
|
||||
) -> Option<Weak<Mutex<dyn Machine>>> {
|
||||
let machine = self.serial_machines.get(machine_identification);
|
||||
let machine = match machine {
|
||||
Some(machine) => machine,
|
||||
None => {
|
||||
return None;
|
||||
self.get_weak(&self.serial_machines, machine_identification)
|
||||
}
|
||||
|
||||
fn get_weak(
|
||||
&self,
|
||||
machines: &HashMap<MachineIdentificationUnique, Arc<Mutex<MachineSlotGeneric>>>,
|
||||
machine_identification: &MachineIdentificationUnique,
|
||||
) -> Option<Weak<Mutex<dyn Machine>>> {
|
||||
let slot = machines.get(machine_identification).clone();
|
||||
|
||||
return slot.and_then(|slot| {
|
||||
let connection = &slot.lock_blocking().machine_connection;
|
||||
match connection {
|
||||
MachineConnectionGeneric::Error(_) => None,
|
||||
MachineConnectionGeneric::Disconnected => None,
|
||||
MachineConnectionGeneric::Connected(machine) => Some(Arc::downgrade(&machine)),
|
||||
}
|
||||
};
|
||||
let machine = match machine {
|
||||
Ok(machine) => machine,
|
||||
Err(_) => {
|
||||
return None;
|
||||
}
|
||||
};
|
||||
let machine_weak = Arc::downgrade(machine);
|
||||
Some(machine_weak)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,22 +1,25 @@
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use smol::lock::Mutex;
|
||||
|
||||
use super::{Machine, identification::MachineIdentificationUnique, manager::MachineManager};
|
||||
use crate::machines::connection::MachineSlotGeneric;
|
||||
|
||||
use super::{identification::MachineIdentificationUnique, manager::MachineManager};
|
||||
|
||||
pub enum MachineManagerIter<'a> {
|
||||
EthercatMachines {
|
||||
iter: std::collections::hash_map::Iter<
|
||||
'a,
|
||||
MachineIdentificationUnique,
|
||||
Result<Arc<Mutex<dyn Machine>>, anyhow::Error>,
|
||||
Arc<Mutex<MachineSlotGeneric>>,
|
||||
>,
|
||||
},
|
||||
SerialMachines {
|
||||
iter: std::collections::hash_map::Iter<
|
||||
'a,
|
||||
MachineIdentificationUnique,
|
||||
Result<Arc<Mutex<dyn Machine>>, anyhow::Error>,
|
||||
Arc<Mutex<MachineSlotGeneric>>,
|
||||
>,
|
||||
},
|
||||
Done,
|
||||
@@ -24,14 +27,13 @@ pub enum MachineManagerIter<'a> {
|
||||
|
||||
pub struct MachineManagerIterator<'a> {
|
||||
iter: MachineManagerIter<'a>,
|
||||
serial_machines:
|
||||
&'a HashMap<MachineIdentificationUnique, Result<Arc<Mutex<dyn Machine>>, anyhow::Error>>,
|
||||
serial_machines: &'a HashMap<MachineIdentificationUnique, Arc<Mutex<MachineSlotGeneric>>>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for MachineManagerIterator<'a> {
|
||||
type Item = (
|
||||
&'a MachineIdentificationUnique,
|
||||
&'a Result<Arc<Mutex<dyn Machine>>, anyhow::Error>,
|
||||
&'a Arc<Mutex<MachineSlotGeneric>>,
|
||||
);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
@@ -71,7 +73,7 @@ impl<'a> Iterator for MachineManagerIterator<'a> {
|
||||
|
||||
impl MachineManager {
|
||||
// Returns an iterator over all machines (both ethercat and serial)
|
||||
pub fn iter(&'_ self) -> MachineManagerIterator<'_> {
|
||||
pub fn iter(&self) -> MachineManagerIterator<'_> {
|
||||
MachineManagerIterator {
|
||||
iter: MachineManagerIter::EthercatMachines {
|
||||
iter: self.ethercat_machines.iter(),
|
||||
|
||||
@@ -1,83 +1,23 @@
|
||||
use anyhow::anyhow;
|
||||
use api::MachineApi;
|
||||
use new::MachineNewTrait;
|
||||
use serde::Serialize;
|
||||
use smol::lock::Mutex;
|
||||
use std::any::Any;
|
||||
use std::fmt::Debug;
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
use crate::machines::identification::MachineIdentificationUnique;
|
||||
use crate::machines::new::MachineAct;
|
||||
use crate::machines::{
|
||||
api::MachineApi, identification::MachineIdentificationUnique, new::MachineAct,
|
||||
};
|
||||
pub mod api;
|
||||
pub mod connection;
|
||||
pub mod identification;
|
||||
pub mod manager;
|
||||
pub mod manager_iter;
|
||||
pub mod new;
|
||||
pub mod registry;
|
||||
|
||||
pub trait Machine: MachineAct + MachineNewTrait + MachineApi + AnyGetters + Debug + Send {}
|
||||
pub trait Machine: MachineAct + MachineNewTrait + MachineApi + Any + Debug + Send + Sync {
|
||||
fn get_machine_identification_unique(&self) -> MachineIdentificationUnique;
|
||||
}
|
||||
|
||||
pub trait AnyGetters: Any {
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any;
|
||||
}
|
||||
|
||||
/// Casts a `dyn Machine` to a specific machine type
|
||||
pub async fn downcast_machine<T: Machine>(
|
||||
machine: Arc<Mutex<dyn Machine>>,
|
||||
) -> Result<Arc<Mutex<T>>, anyhow::Error> {
|
||||
// Acquire a read lock on the RwLock
|
||||
let read_lock = machine.lock().await;
|
||||
|
||||
// Check if the inner type can be downcasted to T
|
||||
if read_lock.as_any().is::<T>() {
|
||||
// Clone the Arc and return it as the desired type
|
||||
let cloned_machine = Arc::clone(&machine);
|
||||
// Transmute the Arc to the desired type
|
||||
unsafe {
|
||||
Ok(Arc::from_raw(
|
||||
Arc::into_raw(cloned_machine) as *const Mutex<T>
|
||||
))
|
||||
}
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"[{}::downcast_machine] Downcast failed",
|
||||
module_path!()
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait to get strong count to see if Weak has reference
|
||||
pub trait GetStrongCount {
|
||||
fn get_strong_count(&self) -> usize;
|
||||
}
|
||||
|
||||
impl<T> GetStrongCount for Weak<Mutex<T>> {
|
||||
fn get_strong_count(&self) -> usize {
|
||||
self.strong_count()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ConnectedMachine<T: GetStrongCount> {
|
||||
pub machine_identification_unique: MachineIdentificationUnique,
|
||||
pub machine: T,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ConnectedMachineData {
|
||||
pub machine_identification_unique: MachineIdentificationUnique,
|
||||
pub is_available: bool,
|
||||
}
|
||||
|
||||
impl<T> From<&ConnectedMachine<T>> for ConnectedMachineData
|
||||
where
|
||||
T: GetStrongCount,
|
||||
{
|
||||
fn from(value: &ConnectedMachine<T>) -> Self {
|
||||
Self {
|
||||
machine_identification_unique: value.machine_identification_unique.clone(),
|
||||
is_available: value.machine.get_strong_count() != 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,8 @@
|
||||
use crate::{machines::manager::MachineManager, serial::SerialDevice};
|
||||
use crate::{
|
||||
machines::{identification::MachineIdentificationUnique, manager::MachineManager},
|
||||
serial::SerialDevice,
|
||||
socketio::namespace::Namespace,
|
||||
};
|
||||
|
||||
use super::identification::DeviceIdentificationIdentified;
|
||||
use anyhow::Error;
|
||||
@@ -6,7 +10,10 @@ use ethercat_hal::{
|
||||
devices::EthercatDevice, helpers::ethercrab_types::EthercrabSubDevicePreoperational,
|
||||
};
|
||||
use ethercrab::{SubDevice, SubDeviceRef};
|
||||
use smol::{channel::Sender, lock::RwLock};
|
||||
use smol::{
|
||||
channel::Sender,
|
||||
lock::{Mutex, RwLock},
|
||||
};
|
||||
use socketioxide::extract::SocketRef;
|
||||
use std::{
|
||||
sync::{Arc, Weak},
|
||||
@@ -49,6 +56,19 @@ pub struct MachineNewParams<
|
||||
>,
|
||||
pub socket_queue_tx: Sender<(SocketRef, Arc<GenericEvent>)>,
|
||||
pub machine_manager: Weak<RwLock<MachineManager>>,
|
||||
|
||||
pub namespace: Arc<Mutex<Namespace>>,
|
||||
}
|
||||
|
||||
impl MachineNewParams<'_, '_, '_, '_, '_, '_, '_> {
|
||||
pub fn get_machine_identification_unique(&self) -> MachineIdentificationUnique {
|
||||
self.device_group
|
||||
.first()
|
||||
.expect("device group must have at least one device")
|
||||
.device_machine_identification
|
||||
.machine_identification_unique
|
||||
.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub enum MachineNewHardware<
|
||||
|
||||
@@ -5,37 +5,15 @@ import React from "react";
|
||||
import { useBuffer1 } from "./useBuffer1";
|
||||
import { ControlCard } from "@/control/ControlCard";
|
||||
import { SelectionGroup } from "@/control/SelectionGroup";
|
||||
import { EditValue } from "@/control/EditValue";
|
||||
import { TimeSeriesValueNumeric } from "@/control/TimeSeriesValue";
|
||||
import { roundToDecimals } from "@/lib/decimal";
|
||||
import { Label } from "@/control/Label";
|
||||
import { TouchButton } from "@/components/touch/TouchButton";
|
||||
import { StatusBadge } from "@/control/StatusBadge";
|
||||
|
||||
export function Buffer1ControlPage() {
|
||||
const {
|
||||
state,
|
||||
defaultState,
|
||||
setBufferMode,
|
||||
setCurrentInputSpeed,
|
||||
pullerSpeed,
|
||||
setPullerRegulationMode,
|
||||
setPullerTargetSpeed,
|
||||
liftPosition,
|
||||
gotoLiftHome,
|
||||
isLoading,
|
||||
isDisabled,
|
||||
} = useBuffer1();
|
||||
|
||||
const current_input_speed =
|
||||
state?.current_input_speed_state.current_input_speed ?? 0.0;
|
||||
const default_speed = 0.0;
|
||||
const { state, setBufferMode } = useBuffer1();
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<ControlGrid>
|
||||
<ControlCard className="bg-red" title="Mode">
|
||||
<SelectionGroup<"Standby" | "Hold" | "Filling" | "Emptying">
|
||||
<SelectionGroup<"Standby" | "FillingBuffer" | "EmptyingBuffer">
|
||||
value={state?.mode_state.mode}
|
||||
orientation="vertical"
|
||||
className="grid h-full grid-cols-2 gap-2"
|
||||
@@ -46,19 +24,13 @@ export function Buffer1ControlPage() {
|
||||
isActiveClassName: "bg-green-600",
|
||||
className: "h-full",
|
||||
},
|
||||
Hold: {
|
||||
children: "Hold",
|
||||
icon: "lu:Pause",
|
||||
isActiveClassName: "bg-green-600",
|
||||
className: "h-full",
|
||||
},
|
||||
Filling: {
|
||||
children: "Filling",
|
||||
FillingBuffer: {
|
||||
children: "FillingBuffer",
|
||||
icon: "lu:Flame",
|
||||
isActiveClassName: "bg-green-600",
|
||||
className: "h-full",
|
||||
},
|
||||
Emptying: {
|
||||
EmptyingBuffer: {
|
||||
children: "EmptyingBuffer",
|
||||
icon: "lu:ArrowBigLeftDash",
|
||||
isActiveClassName: "bg-green-600",
|
||||
@@ -68,83 +40,6 @@ export function Buffer1ControlPage() {
|
||||
onChange={setBufferMode}
|
||||
/>
|
||||
</ControlCard>
|
||||
<ControlCard title="Set Current Input Speed - (For Debugging)">
|
||||
<EditValue
|
||||
title="Input Speed"
|
||||
unit="m/min"
|
||||
value={current_input_speed}
|
||||
defaultValue={default_speed}
|
||||
min={0.0}
|
||||
max={60.0}
|
||||
step={0.1}
|
||||
renderValue={(value) => roundToDecimals(value, 1)}
|
||||
onChange={setCurrentInputSpeed}
|
||||
/>
|
||||
</ControlCard>
|
||||
|
||||
<ControlCard className="bg-red" height={2} title="Lift">
|
||||
<TimeSeriesValueNumeric
|
||||
label="Position"
|
||||
unit="mm"
|
||||
timeseries={liftPosition}
|
||||
renderValue={(value) => roundToDecimals(value, 1)}
|
||||
/>
|
||||
<Label label="Home">
|
||||
<TouchButton
|
||||
variant="outline"
|
||||
icon="lu:House"
|
||||
onClick={() => gotoLiftHome()}
|
||||
disabled={isDisabled}
|
||||
isLoading={isLoading || state?.lift_state?.is_going_home}
|
||||
>
|
||||
Go to Home
|
||||
</TouchButton>
|
||||
{state?.lift_state?.is_homed !== true ? (
|
||||
<StatusBadge variant={"error"}>{"Not Homed"}</StatusBadge>
|
||||
) : null}
|
||||
</Label>
|
||||
</ControlCard>
|
||||
|
||||
<ControlCard className="bg-red" title="Puller">
|
||||
<TimeSeriesValueNumeric
|
||||
label="Speed"
|
||||
unit="m/min"
|
||||
timeseries={pullerSpeed}
|
||||
renderValue={(value) => roundToDecimals(value, 1)}
|
||||
/>
|
||||
<Label label="Regulation">
|
||||
<SelectionGroup
|
||||
value={state?.puller_state?.regulation}
|
||||
options={{
|
||||
Speed: {
|
||||
children: "Speed",
|
||||
icon: "lu:Gauge",
|
||||
},
|
||||
Diameter: {
|
||||
children: "Diameter",
|
||||
icon: "lu:Sun",
|
||||
disabled: true,
|
||||
},
|
||||
}}
|
||||
onChange={setPullerRegulationMode}
|
||||
disabled={isDisabled}
|
||||
loading={isLoading}
|
||||
/>
|
||||
</Label>
|
||||
<Label label="Target Speed">
|
||||
<EditValue
|
||||
value={state?.puller_state?.target_speed}
|
||||
unit="m/min"
|
||||
title="Target Speed"
|
||||
defaultValue={defaultState?.puller_state?.target_speed}
|
||||
min={0}
|
||||
max={75}
|
||||
step={0.1}
|
||||
renderValue={(value) => roundToDecimals(value, 1)}
|
||||
onChange={setPullerTargetSpeed}
|
||||
/>
|
||||
</Label>
|
||||
</ControlCard>
|
||||
</ControlGrid>
|
||||
</Page>
|
||||
);
|
||||
|
||||
@@ -16,55 +16,23 @@ import {
|
||||
ThrottledStoreUpdater,
|
||||
} from "../../../client/socketioStore";
|
||||
import { MachineIdentificationUnique } from "@/machines/types";
|
||||
import {
|
||||
createTimeSeries,
|
||||
TimeSeries,
|
||||
TimeSeriesValue,
|
||||
} from "@/lib/timeseries";
|
||||
|
||||
// ========== Event Schema Definitions ==========
|
||||
|
||||
/**
|
||||
* Machine operation mode enum
|
||||
*/
|
||||
export const modeSchema = z.enum(["Standby", "Hold", "Filling", "Emptying"]);
|
||||
export const modeSchema = z.enum([
|
||||
"Standby",
|
||||
"FillingBuffer",
|
||||
"EmptyingBuffer",
|
||||
]);
|
||||
export type Mode = z.infer<typeof modeSchema>;
|
||||
|
||||
/**
|
||||
* Consolidated live values event schema (30FPS data)
|
||||
*/
|
||||
export const liveValuesEventDataSchema = z.object({
|
||||
lift_position: z.number().nullable(),
|
||||
puller_speed: z.number(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Puller regulation type enum
|
||||
*/
|
||||
export const pullerRegulationSchema = z.enum(["Speed", "Diameter"]);
|
||||
export type PullerRegulation = z.infer<typeof pullerRegulationSchema>;
|
||||
|
||||
/**
|
||||
* Puller state event schmema
|
||||
*/
|
||||
export const pullerStateSchema = z.object({
|
||||
regulation: pullerRegulationSchema,
|
||||
target_speed: z.number(),
|
||||
target_diameter: z.number(),
|
||||
forward: z.boolean(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Lift state schema
|
||||
*/
|
||||
export const liftStateSchema = z.object({
|
||||
is_going_up: z.boolean(),
|
||||
is_going_down: z.boolean(),
|
||||
is_homed: z.boolean(),
|
||||
is_going_home: z.boolean(),
|
||||
is_buffering: z.boolean(),
|
||||
step_size: z.number(),
|
||||
});
|
||||
export const liveValuesEventDataSchema = z.object({});
|
||||
|
||||
/**
|
||||
* Mode state event schema
|
||||
@@ -73,10 +41,6 @@ export const modeStateSchema = z.object({
|
||||
mode: modeSchema,
|
||||
});
|
||||
|
||||
export const currentInputSpeedSchema = z.object({
|
||||
current_input_speed: z.number(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Connected machine state scheme
|
||||
*/
|
||||
@@ -101,10 +65,7 @@ export const connectedMachineStateSchema = z.object({
|
||||
|
||||
export const stateEventDataSchema = z.object({
|
||||
mode_state: modeStateSchema,
|
||||
lift_state: liftStateSchema,
|
||||
puller_state: pullerStateSchema,
|
||||
connected_machine_state: connectedMachineStateSchema,
|
||||
current_input_speed_state: currentInputSpeedSchema,
|
||||
});
|
||||
|
||||
// ========== Event Schemas with Wrappers ==========
|
||||
@@ -119,24 +80,8 @@ export type StateEvent = z.infer<typeof stateEventSchema>;
|
||||
export type Buffer1NamespaceStore = {
|
||||
// State events (latest only)
|
||||
state: StateEvent | null;
|
||||
defaultState: StateEvent | null;
|
||||
|
||||
// Time series data for live values
|
||||
liftPosition: TimeSeries;
|
||||
pullerSpeed: TimeSeries;
|
||||
};
|
||||
|
||||
// Constants for time durations
|
||||
const TWENTY_MILLISECOND = 20;
|
||||
const ONE_SECOND = 1000;
|
||||
const FIVE_SECOND = 5 * ONE_SECOND;
|
||||
const ONE_HOUR = 60 * 60 * ONE_SECOND;
|
||||
|
||||
const { initialTimeSeries: pullerSpeed, insert: addPullerSpeed } =
|
||||
createTimeSeries(TWENTY_MILLISECOND, ONE_SECOND, FIVE_SECOND, ONE_HOUR);
|
||||
const { initialTimeSeries: liftPosition, insert: addLiftPosition } =
|
||||
createTimeSeries(TWENTY_MILLISECOND, ONE_SECOND, FIVE_SECOND, ONE_HOUR);
|
||||
|
||||
/**
|
||||
* Creates a message handler for Buffer1 namespace events with validation and appropriate caching strategies
|
||||
* @param store The store to update when messages are received
|
||||
@@ -165,41 +110,12 @@ export function buffer1MessageHandler(
|
||||
...state,
|
||||
state: stateEvent,
|
||||
}));
|
||||
console.log(stateEvent);
|
||||
} else if (eventName === "LiveValuesEvent") {
|
||||
// Parse and validate the live values event
|
||||
const liveValuesEvent = liveValuesEventSchema.parse(event);
|
||||
|
||||
// Extract values and add to time series
|
||||
const { lift_position, puller_speed } = liveValuesEvent.data;
|
||||
|
||||
const timestamp = liveValuesEvent.ts;
|
||||
updateStore((state) => {
|
||||
const newState = { ...state };
|
||||
// Add lift position if not null
|
||||
if (lift_position !== null) {
|
||||
const timeseriesValue: TimeSeriesValue = {
|
||||
value: lift_position,
|
||||
timestamp,
|
||||
};
|
||||
newState.liftPosition = addLiftPosition(
|
||||
state.liftPosition,
|
||||
timeseriesValue,
|
||||
);
|
||||
}
|
||||
|
||||
// Add puller speed
|
||||
const pullerSpeedValue: TimeSeriesValue = {
|
||||
value: puller_speed,
|
||||
timestamp,
|
||||
};
|
||||
newState.pullerSpeed = addPullerSpeed(
|
||||
state.pullerSpeed,
|
||||
pullerSpeedValue,
|
||||
);
|
||||
|
||||
return newState;
|
||||
});
|
||||
const timestamp = event.ts;
|
||||
updateStore((state) => ({
|
||||
...state,
|
||||
}));
|
||||
} else {
|
||||
handleUnhandledEventError(eventName);
|
||||
}
|
||||
@@ -218,13 +134,7 @@ export const createBuffer1NamespaceStore =
|
||||
(): StoreApi<Buffer1NamespaceStore> =>
|
||||
create<Buffer1NamespaceStore>(() => {
|
||||
return {
|
||||
// State event from server
|
||||
state: null,
|
||||
defaultState: null,
|
||||
|
||||
// Time series data for live values
|
||||
liftPosition,
|
||||
pullerSpeed,
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -8,13 +8,7 @@ import {
|
||||
import { buffer1SerialRoute } from "@/routes/routes";
|
||||
import { z } from "zod";
|
||||
import { useEffect, useMemo } from "react";
|
||||
import {
|
||||
StateEvent,
|
||||
Mode,
|
||||
useBuffer1Namespace,
|
||||
pullerRegulationSchema,
|
||||
PullerRegulation,
|
||||
} from "./buffer1Namespace";
|
||||
import { StateEvent, Mode, useBuffer1Namespace } from "./buffer1Namespace";
|
||||
import { useStateOptimistic } from "@/lib/useStateOptimistic";
|
||||
import { produce } from "immer";
|
||||
import { useMachines } from "@/client/useMachines";
|
||||
@@ -51,8 +45,7 @@ export function useBuffer1() {
|
||||
const machine_identification_unique = machineIdentification;
|
||||
|
||||
// Get consolidated state and live values from namespace
|
||||
const { state, defaultState, liftPosition, pullerSpeed } =
|
||||
useBuffer1Namespace(machineIdentification);
|
||||
const { state } = useBuffer1Namespace(machineIdentification);
|
||||
|
||||
// Single optimistic state for all state management
|
||||
const stateOptimistic = useStateOptimistic<StateEvent>();
|
||||
@@ -129,102 +122,10 @@ export function useBuffer1() {
|
||||
);
|
||||
};
|
||||
|
||||
const setCurrentInputSpeed = (current_input_speed: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.current_input_speed_state.current_input_speed =
|
||||
current_input_speed;
|
||||
},
|
||||
() =>
|
||||
requestSetCurrentInputSpeed({
|
||||
machine_identification_unique,
|
||||
data: { SetCurrentInputSpeed: current_input_speed },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setPullerTargetSpeed = (targetSpeed: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.puller_state.target_speed = targetSpeed;
|
||||
},
|
||||
() =>
|
||||
requestPullerSetTargetSpeed({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetPullerTargetSpeed: targetSpeed },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setPullerTargetDiameter = (targetDiameter: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.puller_state.target_diameter = targetDiameter;
|
||||
},
|
||||
() =>
|
||||
requestPullerSetTargetDiameter({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetPullerTargetDiameter: targetDiameter },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setPullerRegulationMode = (regulationMode: PullerRegulation) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.puller_state.regulation = regulationMode;
|
||||
},
|
||||
() =>
|
||||
requestPullerSetRegulationMode({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetPullerRegulationMode: regulationMode },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setPullerForward = (forward: boolean) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.puller_state.forward = forward;
|
||||
},
|
||||
() =>
|
||||
requestPullerSetForward({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetPullerForward: forward },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const gotoLiftHome = () => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.lift_state.is_going_home = true;
|
||||
},
|
||||
() =>
|
||||
requestLiftGotoHome({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: "GotoLiftHome",
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setLiftStepSize = (stepSize: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.lift_state.step_size = stepSize;
|
||||
},
|
||||
() =>
|
||||
requestLiftSetStepSize({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetLiftStepSize: stepSize },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
// Mutation hooks
|
||||
const { request: requestBufferMode } = useMachineMutation(
|
||||
z.object({
|
||||
SetBufferMode: z.enum(["Standby", "Hold", "Filling", "Emptying"]),
|
||||
SetBufferMode: z.enum(["Standby", "FillingBuffer", "EmptyingBuffer"]),
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -240,38 +141,6 @@ export function useBuffer1() {
|
||||
}),
|
||||
);
|
||||
|
||||
const { request: requestSetCurrentInputSpeed } = useMachineMutation(
|
||||
z.object({
|
||||
SetCurrentInputSpeed: z.number(),
|
||||
}),
|
||||
);
|
||||
|
||||
const { request: requestPullerSetTargetSpeed } = useMachineMutation(
|
||||
z.object({ SetPullerTargetSpeed: z.number() }),
|
||||
);
|
||||
const { request: requestPullerSetTargetDiameter } = useMachineMutation(
|
||||
z.object({ SetPullerTargetDiameter: z.number() }),
|
||||
);
|
||||
const { request: requestPullerSetRegulationMode } = useMachineMutation(
|
||||
z.object({
|
||||
SetPullerRegulationMode: pullerRegulationSchema,
|
||||
}),
|
||||
);
|
||||
const { request: requestPullerSetForward } = useMachineMutation(
|
||||
z.object({ SetPullerForward: z.boolean() }),
|
||||
);
|
||||
|
||||
const { request: requestLiftGotoHome } = useMachineMutation(
|
||||
z.literal("GotoLiftHome"),
|
||||
);
|
||||
const { request: requestLiftSetStepSize } = useMachineMutation(
|
||||
z.object({ SetLiftStepSize: z.number() }),
|
||||
);
|
||||
|
||||
// Calculate loading states
|
||||
const isLoading = stateOptimistic.isOptimistic;
|
||||
const isDisabled = !stateOptimistic.isInitialized;
|
||||
|
||||
// General Helper functions
|
||||
const machines = useMachines();
|
||||
// Filter machines for the correct type
|
||||
@@ -307,27 +176,11 @@ export function useBuffer1() {
|
||||
filteredMachines,
|
||||
selectedMachine,
|
||||
|
||||
// Default state for initial values
|
||||
defaultState: defaultState?.data,
|
||||
|
||||
// Individual live values (TimeSeries)
|
||||
liftPosition,
|
||||
pullerSpeed,
|
||||
|
||||
// Loading states
|
||||
isLoading,
|
||||
isDisabled,
|
||||
|
||||
// Action functions (verb-first)
|
||||
setBufferMode,
|
||||
gotoLiftHome,
|
||||
setLiftStepSize,
|
||||
setConnectedMachine,
|
||||
disconnectMachine,
|
||||
setCurrentInputSpeed,
|
||||
setPullerTargetSpeed,
|
||||
setPullerTargetDiameter,
|
||||
setPullerRegulationMode,
|
||||
setPullerForward,
|
||||
};
|
||||
}
|
||||
@@ -50,7 +50,6 @@ export function Winder2ControlPage() {
|
||||
gotoTraverseHome,
|
||||
setSpoolAutomaticRequiredMeters,
|
||||
setSpoolAutomaticAction,
|
||||
startBuffering,
|
||||
isLoading,
|
||||
isDisabled,
|
||||
} = useWinder2();
|
||||
@@ -344,18 +343,6 @@ export function Winder2ControlPage() {
|
||||
/>
|
||||
</Label>
|
||||
</ControlCard>
|
||||
{state?.connected_machine_state.is_available && (
|
||||
<ControlCard>
|
||||
<TouchButton
|
||||
variant="outline"
|
||||
onClick={startBuffering}
|
||||
disabled={isDisabled}
|
||||
isLoading={isLoading}
|
||||
>
|
||||
Change Spool
|
||||
</TouchButton>
|
||||
</ControlCard>
|
||||
)}
|
||||
</ControlGrid>
|
||||
|
||||
{/* Reset Progress Confirmation Dialog */}
|
||||
|
||||
@@ -154,22 +154,22 @@ export function useWinder2() {
|
||||
const { request: requestSpoolResetProgress } = useMachineMutation(
|
||||
z.literal("ResetSpoolProgress"),
|
||||
);
|
||||
|
||||
const { request: requestSpoolAutomaticAction } = useMachineMutation(
|
||||
z.object({ SetSpoolAutomaticAction: spoolAutomaticActionModeSchema }),
|
||||
);
|
||||
|
||||
const { request: requestConnectedMachine } = useMachineMutation(
|
||||
z.object({
|
||||
SetConnectedMachine: machineIdentificationUnique,
|
||||
}),
|
||||
);
|
||||
|
||||
const { request: requestDisconnectedMachine } = useMachineMutation(
|
||||
z.object({
|
||||
DisconnectMachine: machineIdentificationUnique,
|
||||
}),
|
||||
);
|
||||
const { request: requestStartBuffering } = useMachineMutation(
|
||||
z.literal("StartBuffering"),
|
||||
);
|
||||
|
||||
// Helper function for optimistic updates using produce
|
||||
const updateStateOptimistically = (
|
||||
@@ -550,19 +550,6 @@ export function useWinder2() {
|
||||
);
|
||||
};
|
||||
|
||||
const startBuffering = () => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.buffer_state.buffering = true;
|
||||
},
|
||||
() =>
|
||||
requestStartBuffering({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: "StartBuffering",
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
// Calculate loading states
|
||||
const isLoading = stateOptimistic.isOptimistic;
|
||||
const isDisabled = !stateOptimistic.isInitialized;
|
||||
@@ -644,6 +631,5 @@ export function useWinder2() {
|
||||
setSpoolAdaptiveDeaccelerationUrgencyMultiplier,
|
||||
setConnectedMachine,
|
||||
disconnectMachine,
|
||||
startBuffering,
|
||||
};
|
||||
}
|
||||
@@ -133,13 +133,6 @@ export const connectedMachineStateSchema = z.object({
|
||||
is_available: z.boolean(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Buffer state schema
|
||||
*/
|
||||
export const bufferStateSchema = z.object({
|
||||
buffering: z.boolean(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Tension arm state schema
|
||||
*/
|
||||
@@ -173,7 +166,6 @@ export const stateEventDataSchema = z.object({
|
||||
spool_speed_controller_state: spoolSpeedControllerStateSchema,
|
||||
spool_automatic_action_state: spoolAutomaticActionStateSchema,
|
||||
connected_machine_state: connectedMachineStateSchema,
|
||||
buffer_state: bufferStateSchema,
|
||||
});
|
||||
|
||||
// ========== Event Schemas with Wrappers ==========
|
||||
|
||||
@@ -192,6 +192,12 @@ export const buffer1ControlRoute = createRoute({
|
||||
component: () => <Buffer1ControlPage />,
|
||||
});
|
||||
|
||||
export const mock1PresetsRoute = createRoute({
|
||||
getParentRoute: () => mock1SerialRoute,
|
||||
path: "presets",
|
||||
component: () => <Mock1PresetsPage />,
|
||||
});
|
||||
|
||||
export const aquapath1SerialRoute = createRoute({
|
||||
getParentRoute: () => machinesRoute,
|
||||
path: "aquapath1/$serial",
|
||||
@@ -216,12 +222,6 @@ export const buffer1SettingsRoute = createRoute({
|
||||
component: () => <Buffer1SettingsPage />,
|
||||
});
|
||||
|
||||
export const mock1PresetsRoute = createRoute({
|
||||
getParentRoute: () => mock1SerialRoute,
|
||||
path: "presets",
|
||||
component: () => <Mock1PresetsPage />,
|
||||
});
|
||||
|
||||
export const setupRoute = createRoute({
|
||||
getParentRoute: () => sidebarRoute,
|
||||
path: "setup",
|
||||
@@ -342,10 +342,7 @@ export const rootTree = RootRoute.addChildren([
|
||||
mock1PresetsRoute,
|
||||
]),
|
||||
|
||||
buffer1SerialRoute.addChildren([
|
||||
buffer1ControlRoute,
|
||||
buffer1SettingsRoute,
|
||||
]),
|
||||
buffer1SerialRoute.addChildren([buffer1ControlRoute]),
|
||||
]),
|
||||
]),
|
||||
]);
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::ethercat::config::{MAX_SUBDEVICES, PDI_LEN};
|
||||
use crate::performance_metrics::EthercatPerformanceMetrics;
|
||||
use crate::serial::registry::SERIAL_DEVICE_REGISTRY;
|
||||
use crate::socketio::main_namespace::machines_event::MachineObj;
|
||||
use crate::socketio::namespaces::Namespaces;
|
||||
use control_core::machines::Machine;
|
||||
use control_core::machines::identification::{DeviceIdentification, MachineIdentificationUnique};
|
||||
@@ -90,4 +91,22 @@ impl AppState {
|
||||
performance_metrics: Arc::new(RwLock::new(EthercatPerformanceMetrics::new())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_machine_objs(&self) -> Vec<MachineObj> {
|
||||
let machines = self.machines.read_blocking();
|
||||
machines
|
||||
.iter()
|
||||
.map(|machine| {
|
||||
let error = {
|
||||
let slot = machine.1.lock_blocking();
|
||||
slot.machine_connection.to_error().map(|e| e.to_string())
|
||||
};
|
||||
|
||||
MachineObj {
|
||||
machine_identification_unique: machine.0.clone(),
|
||||
error,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
@@ -235,7 +235,7 @@ pub async fn setup_loop(
|
||||
.write()
|
||||
.await
|
||||
.main_namespace;
|
||||
let event = MachinesEventBuilder().build(app_state_clone.clone()).await;
|
||||
let event = MachinesEventBuilder().build(app_state_clone.clone());
|
||||
main_namespace.emit(MainNamespaceEvents::MachinesEvent(event));
|
||||
});
|
||||
|
||||
|
||||
+3
-1
@@ -1,6 +1,7 @@
|
||||
use crate::app_state::AppState;
|
||||
use crate::panic::{PanicDetails, send_panic};
|
||||
use bitvec::prelude::*;
|
||||
use control_core::machines::connection::MachineConnection;
|
||||
use control_core::realtime::{set_core_affinity, set_realtime_priority};
|
||||
use smol::channel::Sender;
|
||||
use std::sync::Arc;
|
||||
@@ -144,7 +145,8 @@ pub async fn loop_once<'maindevice>(app_state: Arc<AppState>) -> Result<(), anyh
|
||||
let now = std::time::Instant::now();
|
||||
|
||||
for machine in machine_guard.iter() {
|
||||
if let Ok(machine) = machine.1 {
|
||||
let connection = &machine.1.lock_blocking().machine_connection;
|
||||
if let MachineConnection::Connected(machine) = connection {
|
||||
// if the machine is currenlty locked (likely processing API call)
|
||||
// we skip the machine
|
||||
if let Some(mut machine_guard) = machine.try_lock() {
|
||||
|
||||
@@ -13,8 +13,7 @@ use control_core::{
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use smol::channel::Sender;
|
||||
use socketioxide::extract::SocketRef;
|
||||
use smol::lock::Mutex;
|
||||
use tracing::instrument;
|
||||
|
||||
#[derive(Serialize, Debug, Clone, Default)]
|
||||
@@ -94,7 +93,7 @@ enum Mutation {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AquaPathV1Namespace {
|
||||
pub namespace: Namespace,
|
||||
pub namespace: Arc<Mutex<Namespace>>,
|
||||
}
|
||||
|
||||
impl NamespaceCacheingLogic<AquaPathV1Events> for AquaPathV1Namespace {
|
||||
@@ -102,15 +101,9 @@ impl NamespaceCacheingLogic<AquaPathV1Events> for AquaPathV1Namespace {
|
||||
fn emit(&mut self, events: AquaPathV1Events) {
|
||||
let event = Arc::new(events.event_value());
|
||||
let buffer_fn = events.event_cache_fn();
|
||||
self.namespace.emit(event, &buffer_fn);
|
||||
}
|
||||
}
|
||||
|
||||
impl AquaPathV1Namespace {
|
||||
pub fn new(socket_queue_tx: Sender<(SocketRef, Arc<GenericEvent>)>) -> Self {
|
||||
Self {
|
||||
namespace: Namespace::new(socket_queue_tx),
|
||||
}
|
||||
let mut namespace = self.namespace.lock_blocking();
|
||||
namespace.emit(event, &buffer_fn);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,7 +149,7 @@ impl MachineApi for AquaPathV1 {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn api_event_namespace(&mut self) -> &mut Namespace {
|
||||
&mut self.namespace.namespace
|
||||
fn api_event_namespace(&mut self) -> Arc<Mutex<Namespace>> {
|
||||
self.namespace.namespace.clone()
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
use control_core::{
|
||||
machines::identification::MachineIdentification, socketio::namespace::NamespaceCacheingLogic,
|
||||
machines::identification::{MachineIdentification, MachineIdentificationUnique},
|
||||
socketio::namespace::NamespaceCacheingLogic,
|
||||
};
|
||||
use control_core_derive::Machine;
|
||||
|
||||
use control_core_derive::Machine;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Instant;
|
||||
use uom::si::{
|
||||
@@ -79,12 +80,14 @@ impl Default for Flow {
|
||||
|
||||
#[derive(Debug, Machine)]
|
||||
pub struct AquaPathV1 {
|
||||
machine_identification_unique: MachineIdentificationUnique,
|
||||
namespace: AquaPathV1Namespace,
|
||||
mode: AquaPathV1Mode,
|
||||
last_measurement_emit: Instant,
|
||||
front_controller: Controller,
|
||||
back_controller: Controller,
|
||||
}
|
||||
|
||||
impl AquaPathV1 {
|
||||
pub const MACHINE_IDENTIFICATION: MachineIdentification = MachineIdentification {
|
||||
vendor: VENDOR_QITECH,
|
||||
|
||||
@@ -338,7 +338,10 @@ impl MachineNewTrait for AquaPathV1 {
|
||||
);
|
||||
|
||||
let mut water_cooling = Self {
|
||||
namespace: AquaPathV1Namespace::new(params.socket_queue_tx.clone()),
|
||||
machine_identification_unique: params.get_machine_identification_unique(),
|
||||
namespace: AquaPathV1Namespace {
|
||||
namespace: params.namespace.clone(),
|
||||
},
|
||||
mode: AquaPathV1Mode::Standby,
|
||||
last_measurement_emit: Instant::now(),
|
||||
front_controller,
|
||||
|
||||
@@ -2,25 +2,12 @@ use std::time::{Duration, Instant};
|
||||
|
||||
use super::BufferV1;
|
||||
use control_core::machines::new::MachineAct;
|
||||
|
||||
impl MachineAct for BufferV1 {
|
||||
fn act(&mut self, now: Instant) {
|
||||
// sync the lift speed
|
||||
self.sync_lift_speed(now);
|
||||
|
||||
// sync the puller speed
|
||||
self.sync_puller_speed(now);
|
||||
|
||||
// sync the puller speed of winder
|
||||
self.sync_winder_puller();
|
||||
|
||||
// check if lift state changed
|
||||
if self.buffer_lift_controller.did_change_state() {
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
// if last measurement is older than 1 second, emit a new measurement
|
||||
if now.duration_since(self.last_measurement_emit) > Duration::from_secs_f64(1.0 / 60.0) {
|
||||
// Emit live values at 60 FPS
|
||||
if now.duration_since(self.last_measurement_emit) > Duration::from_secs_f64(1.0 / 30.0) {
|
||||
// Emit live values at 30 FPS
|
||||
self.emit_live_values();
|
||||
|
||||
self.last_measurement_emit = now;
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use std::{sync::Arc, time::Duration};
|
||||
|
||||
use crate::machines::buffer1::puller_speed_controller::PullerRegulationMode;
|
||||
|
||||
use super::{BufferV1, BufferV1Mode};
|
||||
use control_core::{
|
||||
machines::{api::MachineApi, identification::MachineIdentificationUnique},
|
||||
machines::{
|
||||
api::MachineApi, connection::MachineCrossConnectionState,
|
||||
identification::MachineIdentificationUnique,
|
||||
},
|
||||
socketio::{
|
||||
event::{Event, GenericEvent},
|
||||
namespace::{
|
||||
@@ -15,17 +16,11 @@ use control_core::{
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use smol::channel::Sender;
|
||||
use socketioxide::extract::SocketRef;
|
||||
use smol::lock::Mutex;
|
||||
use tracing::instrument;
|
||||
|
||||
#[derive(Serialize, Debug, Clone, Default)]
|
||||
pub struct LiveValuesEvent {
|
||||
/// lift position in mm
|
||||
pub lift_position: Option<f64>,
|
||||
/// puller speed in m/min
|
||||
pub puller_speed: f64,
|
||||
}
|
||||
pub struct LiveValuesEvent {}
|
||||
|
||||
impl LiveValuesEvent {
|
||||
pub fn build(&self) -> Event<Self> {
|
||||
@@ -37,12 +32,8 @@ impl LiveValuesEvent {
|
||||
pub struct StateEvent {
|
||||
/// mode state
|
||||
pub mode_state: ModeState,
|
||||
/// lift state
|
||||
pub lift_state: LiftState,
|
||||
/// puller state
|
||||
pub puller_state: PullerState,
|
||||
/// connected machine state
|
||||
pub connected_machine_state: ConnectedMachineState,
|
||||
pub connected_machine_state: MachineCrossConnectionState,
|
||||
}
|
||||
|
||||
impl StateEvent {
|
||||
@@ -64,9 +55,8 @@ pub struct ModeState {
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Mode {
|
||||
Standby,
|
||||
Hold,
|
||||
Filling,
|
||||
Emptying,
|
||||
FillingBuffer,
|
||||
EmptyingBuffer,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
@@ -76,50 +66,6 @@ pub struct ConnectedMachineState {
|
||||
pub is_available: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct PullerState {
|
||||
/// regulation type
|
||||
pub regulation: PullerRegulationMode,
|
||||
/// target speed in m/min
|
||||
pub target_speed: f64,
|
||||
/// target diameter in mm
|
||||
pub target_diameter: f64,
|
||||
/// forward rotation direction
|
||||
pub forward: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct LiftState {
|
||||
/// min position in mm
|
||||
pub limit_top: f64,
|
||||
/// max position in mm
|
||||
pub limit_bottom: f64,
|
||||
/// position in mm
|
||||
pub position_in: f64,
|
||||
/// position out in mm
|
||||
pub position_out: f64,
|
||||
/// is going to position in
|
||||
pub is_going_up: bool,
|
||||
/// is going to position out
|
||||
pub is_going_down: bool,
|
||||
/// if is homed
|
||||
pub is_homed: bool,
|
||||
/// if is homing
|
||||
pub is_going_home: bool,
|
||||
/// if is buffering
|
||||
pub is_buffering: bool,
|
||||
/// step size in mm
|
||||
pub step_size: f64,
|
||||
/// padding in mm
|
||||
pub padding: f64,
|
||||
/// can go top (to top limit)
|
||||
pub can_go_top: bool,
|
||||
/// can go bottom (to bottom limit)
|
||||
pub can_go_bottom: bool,
|
||||
/// can home
|
||||
pub can_go_home: bool,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
enum Mutation {
|
||||
// Mode
|
||||
@@ -130,20 +76,11 @@ enum Mutation {
|
||||
|
||||
// Disconnect Machine
|
||||
DisconnectMachine(MachineIdentificationUnique),
|
||||
|
||||
// Lift
|
||||
GotoLiftHome,
|
||||
|
||||
// Puller
|
||||
SetPullerRegulationMode(PullerRegulationMode),
|
||||
SetPullerTargetSpeed(f64),
|
||||
SetPullerTargetDiameter(f64),
|
||||
SetPullerForward(bool),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Buffer1Namespace {
|
||||
pub namespace: Namespace,
|
||||
pub namespace: Arc<Mutex<Namespace>>,
|
||||
}
|
||||
|
||||
impl NamespaceCacheingLogic<BufferV1Events> for Buffer1Namespace {
|
||||
@@ -151,15 +88,9 @@ impl NamespaceCacheingLogic<BufferV1Events> for Buffer1Namespace {
|
||||
fn emit(&mut self, events: BufferV1Events) {
|
||||
let event = Arc::new(events.event_value());
|
||||
let buffer_fn = events.event_cache_fn();
|
||||
self.namespace.emit(event, &buffer_fn);
|
||||
}
|
||||
}
|
||||
|
||||
impl Buffer1Namespace {
|
||||
pub fn new(socket_queue_tx: Sender<(SocketRef, Arc<GenericEvent>)>) -> Self {
|
||||
Self {
|
||||
namespace: Namespace::new(socket_queue_tx),
|
||||
}
|
||||
let mut namespace = self.namespace.lock_blocking();
|
||||
namespace.emit(event, &buffer_fn);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,16 +124,11 @@ impl MachineApi for BufferV1 {
|
||||
Mutation::DisconnectMachine(machine_identification_unique) => {
|
||||
self.disconnect_winder(machine_identification_unique);
|
||||
}
|
||||
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::GotoLiftHome => self.buffer_lift_controller.goto_home(),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn api_event_namespace(&mut self) -> &mut Namespace {
|
||||
&mut self.namespace.namespace
|
||||
fn api_event_namespace(&mut self) -> Arc<Mutex<Namespace>> {
|
||||
self.namespace.namespace.clone()
|
||||
}
|
||||
}
|
||||
@@ -1,542 +0,0 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use control_core::{
|
||||
controllers::first_degree_motion::linear_acceleration_speed_controller::LinearAccelerationLimitingController,
|
||||
converters::linear_step_converter::LinearStepConverter,
|
||||
};
|
||||
use ethercat_hal::io::{
|
||||
digital_input::DigitalInput, stepper_velocity_el70x1::StepperVelocityEL70x1,
|
||||
};
|
||||
use tracing::info;
|
||||
use uom::{
|
||||
ConstZero,
|
||||
si::{
|
||||
acceleration::centimeter_per_second_squared,
|
||||
f64::{Acceleration, Length, Velocity},
|
||||
length::millimeter,
|
||||
velocity::millimeter_per_second,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BufferLiftController {
|
||||
/// Whether the speed controller is enabled or not
|
||||
enabled: bool,
|
||||
position: Length,
|
||||
limit_top: Length,
|
||||
limit_bottom: Length,
|
||||
step_size: Length,
|
||||
padding: Length,
|
||||
/// Stepper driver. Controls buffer stepper motor
|
||||
pub stepper_driver: StepperVelocityEL70x1,
|
||||
// Step Converter
|
||||
pub fullstep_converter: LinearStepConverter,
|
||||
pub microstep_converter: LinearStepConverter,
|
||||
|
||||
/// Linear acceleration controller to dampen speed change
|
||||
acceleration_controller: LinearAccelerationLimitingController,
|
||||
|
||||
/// Forward rotation direction. If false, applies negative sign to speed
|
||||
forward: bool,
|
||||
state: State,
|
||||
|
||||
/// Fixed constants
|
||||
spool_amount: u8,
|
||||
|
||||
/// Variables
|
||||
current_input_speed: Velocity,
|
||||
target_output_speed: Velocity,
|
||||
lift_speed: Velocity,
|
||||
|
||||
// A sticky flag if the [`State`] changed (not the sub states)
|
||||
// Needed to send state updates to the UI
|
||||
did_change_state: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum State {
|
||||
/// Initial state
|
||||
NotHomed,
|
||||
|
||||
/// Doing nothing
|
||||
/// Already homed
|
||||
Idle,
|
||||
|
||||
/// Going to upper limit
|
||||
///
|
||||
/// After reaching the upper limit, the state will change to [`State::Idle`]
|
||||
GoingUp,
|
||||
|
||||
/// Going to lower limit
|
||||
///
|
||||
/// After reaching the lower limit, the state will change to [`State::Idle`]
|
||||
GoingDown,
|
||||
|
||||
/// Homing in progress
|
||||
///
|
||||
/// After homing is done, the state will change to [`State::Idle`]
|
||||
Homing(HomingState),
|
||||
|
||||
/// Move at target speed to Buffer
|
||||
Buffering(BufferingState),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum BufferingState {
|
||||
GoingUp,
|
||||
Filling,
|
||||
Emptying,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum HomingState {
|
||||
/// In this state the lift is not moving but checks if the endstop is triggered
|
||||
/// If the endstop is triggered we go into [`HomingState::EscapeEndstop`]
|
||||
/// If the endstop is not triggered we go into [`HomingState::FindEndstop`]
|
||||
Initialize,
|
||||
|
||||
/// In this state the lift is moving out away from the endstop until it's not triggered anymore
|
||||
/// The it goes into [`HomingState::FindEnstopFineDistancing`]
|
||||
EscapeEndstop,
|
||||
|
||||
/// Moving out away from the endstop
|
||||
/// Then Transition into [`HomingState::FindEndtopFine`]
|
||||
FindEnstopFineDistancing,
|
||||
|
||||
/// In this state the lift is fast until it reaches the endstop
|
||||
FindEndstopCoarse,
|
||||
|
||||
/// In this state the lift is moving slowly until it reaches the endstop
|
||||
FindEndStopFine,
|
||||
|
||||
/// In this state we check if th current position is actually 0.0, if not we redo the homing routine
|
||||
Validate(Instant),
|
||||
}
|
||||
|
||||
impl BufferLiftController {
|
||||
pub fn new(
|
||||
driver: StepperVelocityEL70x1,
|
||||
limit_top: Length,
|
||||
limit_bot: Length,
|
||||
microsteps: u8,
|
||||
) -> Self {
|
||||
Self {
|
||||
enabled: false,
|
||||
position: Length::ZERO,
|
||||
limit_top,
|
||||
limit_bottom: limit_bot,
|
||||
step_size: Length::new::<millimeter>(1.75), // Default padding
|
||||
padding: Length::new::<millimeter>(0.9), // Default padding
|
||||
did_change_state: false,
|
||||
stepper_driver: driver,
|
||||
fullstep_converter: LinearStepConverter::from_diameter(
|
||||
200,
|
||||
Length::new::<millimeter>(32.22),
|
||||
),
|
||||
microstep_converter: LinearStepConverter::from_diameter(
|
||||
200 * microsteps as i16,
|
||||
Length::new::<millimeter>(32.22),
|
||||
),
|
||||
spool_amount: 13,
|
||||
state: State::NotHomed,
|
||||
forward: true,
|
||||
acceleration_controller: LinearAccelerationLimitingController::new_simple(
|
||||
Acceleration::new::<centimeter_per_second_squared>(1.0),
|
||||
Velocity::new::<millimeter_per_second>(5.0),
|
||||
),
|
||||
current_input_speed: Velocity::ZERO,
|
||||
target_output_speed: Velocity::ZERO,
|
||||
lift_speed: Velocity::ZERO,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BufferLiftController {
|
||||
/// Calculate the speed of the buffer lift from current input speed
|
||||
///
|
||||
/// Formula: input_speed / ( 2 * spool_amount - 1 )
|
||||
pub fn calculate_buffer_lift_speed(&self) -> Velocity {
|
||||
let lift_speed = Velocity::new::<millimeter_per_second>(
|
||||
(self.current_input_speed.get::<millimeter_per_second>()
|
||||
- self.target_output_speed.get::<millimeter_per_second>())
|
||||
/ (2.0 * self.spool_amount as f64 - 1.0),
|
||||
);
|
||||
lift_speed
|
||||
}
|
||||
|
||||
pub fn update_speed(
|
||||
&mut self,
|
||||
stepper_driver: &mut StepperVelocityEL70x1,
|
||||
lift_end_stop: &DigitalInput,
|
||||
t: Instant,
|
||||
) -> Velocity {
|
||||
let speed = match self.enabled {
|
||||
true => self.get_speed(stepper_driver, lift_end_stop),
|
||||
false => Velocity::ZERO,
|
||||
};
|
||||
|
||||
// Stepper is installed in reverse direction so we change to sign
|
||||
let speed = speed;
|
||||
|
||||
self.acceleration_controller.update(speed, t)
|
||||
}
|
||||
}
|
||||
|
||||
/// Getter & Setter
|
||||
impl BufferLiftController {
|
||||
pub fn set_enabled(&mut self, enabled: bool) {
|
||||
self.enabled = enabled;
|
||||
self.stepper_driver.set_enabled(enabled);
|
||||
}
|
||||
|
||||
pub fn set_limit_top(&mut self, limit: Length) {
|
||||
self.limit_top = limit;
|
||||
}
|
||||
|
||||
pub fn set_limit_bottom(&mut self, limit: Length) {
|
||||
self.limit_bottom = limit;
|
||||
}
|
||||
|
||||
pub fn set_step_size(&mut self, step_size: Length) {
|
||||
self.step_size = step_size;
|
||||
}
|
||||
|
||||
pub fn set_padding(&mut self, padding: Length) {
|
||||
self.padding = padding;
|
||||
}
|
||||
|
||||
pub fn get_limit_top(&self) -> Length {
|
||||
self.limit_top
|
||||
}
|
||||
|
||||
pub fn get_limit_bottom(&self) -> Length {
|
||||
self.limit_bottom
|
||||
}
|
||||
|
||||
pub fn get_step_size(&self) -> Length {
|
||||
self.step_size
|
||||
}
|
||||
|
||||
pub fn get_padding(&self) -> Length {
|
||||
self.padding
|
||||
}
|
||||
|
||||
pub fn set_forward(&mut self, forward: bool) {
|
||||
self.forward = forward;
|
||||
}
|
||||
|
||||
pub fn get_current_position(&self) -> Option<Length> {
|
||||
match self.is_homed() {
|
||||
true => Some(self.position),
|
||||
false => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn did_change_state(&mut self) -> bool {
|
||||
let did_change = self.did_change_state;
|
||||
// Reset the flag
|
||||
self.did_change_state = false;
|
||||
did_change
|
||||
}
|
||||
|
||||
pub fn set_current_input_speed(&mut self, speed: Velocity) {
|
||||
self.current_input_speed = speed;
|
||||
}
|
||||
pub fn set_target_output_speed(&mut self, speed: Velocity) {
|
||||
self.target_output_speed = speed;
|
||||
}
|
||||
pub fn get_current_input_speed(&self) -> Velocity {
|
||||
self.current_input_speed
|
||||
}
|
||||
pub fn get_target_output_speed(&self) -> Velocity {
|
||||
self.target_output_speed
|
||||
}
|
||||
pub fn get_lift_speed(&self) -> Velocity {
|
||||
self.lift_speed
|
||||
}
|
||||
}
|
||||
|
||||
/// State management
|
||||
impl BufferLiftController {
|
||||
pub fn goto_limit_top(&mut self) {
|
||||
self.state = State::GoingUp
|
||||
}
|
||||
|
||||
pub fn goto_limit_bottom(&mut self) {
|
||||
self.state = State::GoingDown
|
||||
}
|
||||
|
||||
pub fn goto_home(&mut self) {
|
||||
self.state = State::Homing(HomingState::Initialize);
|
||||
}
|
||||
|
||||
pub fn start_buffering(&mut self) {
|
||||
self.state = State::Buffering(BufferingState::GoingUp);
|
||||
}
|
||||
|
||||
pub fn is_going_up(&self) -> bool {
|
||||
// [`State::GoingUp`]
|
||||
matches!(self.state, State::GoingUp)
|
||||
}
|
||||
|
||||
pub fn is_going_down(&self) -> bool {
|
||||
// [`State::GoingDown`]
|
||||
matches!(self.state, State::GoingDown)
|
||||
}
|
||||
|
||||
pub fn is_homed(&self) -> bool {
|
||||
// if not [`State::NotHomed`], then it is homed
|
||||
!matches!(self.state, State::NotHomed)
|
||||
}
|
||||
|
||||
pub fn is_filling(&self) -> bool {
|
||||
// [`State::Filling`]
|
||||
matches!(self.state, State::Buffering(BufferingState::Filling))
|
||||
}
|
||||
|
||||
pub fn is_emptying(&self) -> bool {
|
||||
// [`State::Emptying`]
|
||||
matches!(self.state, State::Buffering(BufferingState::Emptying))
|
||||
}
|
||||
|
||||
pub fn is_going_home(&self) -> bool {
|
||||
// [`State::Homing`]
|
||||
matches!(self.state, State::Homing(_))
|
||||
}
|
||||
|
||||
pub fn is_buffering(&self) -> bool {
|
||||
// [`State::Buffering`]
|
||||
matches!(self.state, State::Buffering(_))
|
||||
}
|
||||
}
|
||||
|
||||
impl BufferLiftController {
|
||||
// If at inner limit within a tolerance
|
||||
fn is_at_position(&self, target_position: Length, tolerance: Length) -> bool {
|
||||
let upper_tolerance = target_position + tolerance.abs();
|
||||
let lower_tolerance = target_position - tolerance.abs();
|
||||
if self.position >= lower_tolerance && self.position <= upper_tolerance {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates distance to position
|
||||
fn distance_to_position(&self, target_position: Length) -> Length {
|
||||
if self.position > target_position {
|
||||
return self.position - target_position;
|
||||
} else if self.position < target_position {
|
||||
return target_position - self.position;
|
||||
} else {
|
||||
return Length::ZERO;
|
||||
}
|
||||
}
|
||||
|
||||
// Changes the direction of the speed based on the current position and target position
|
||||
fn speed_to_position(&self, target_position: Length, absolute_speed: Velocity) -> Velocity {
|
||||
// If we are over the target position we need to move negative
|
||||
if self.position > target_position {
|
||||
return -absolute_speed.abs();
|
||||
} else if self.position < target_position {
|
||||
return absolute_speed.abs();
|
||||
} else {
|
||||
return Velocity::ZERO;
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the current lift position as a [`Length`].
|
||||
pub fn sync_position(&mut self, stepper_driver: &StepperVelocityEL70x1) {
|
||||
let steps = stepper_driver.get_position();
|
||||
self.position = self.microstep_converter.steps_to_distance(steps as f64);
|
||||
}
|
||||
|
||||
/// Update the [`did_change_state`] flag
|
||||
/// Only consider the major state not the sub states
|
||||
fn update_did_change_state(&mut self, old_state: &State) -> bool {
|
||||
match self.state {
|
||||
State::NotHomed => !matches!(old_state, State::NotHomed),
|
||||
State::Idle => !matches!(old_state, State::Idle),
|
||||
State::GoingUp => !matches!(old_state, State::GoingUp),
|
||||
State::GoingDown => !matches!(old_state, State::GoingDown),
|
||||
State::Homing(_) => !matches!(old_state, State::Homing(_)),
|
||||
State::Buffering(_) => !matches!(old_state, State::Buffering(_)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates a desired speed based on the current state and the end stop status.
|
||||
///
|
||||
/// Positive speed moves Up, negative speed moves down
|
||||
fn get_speed(
|
||||
&mut self,
|
||||
stepper_driver: &mut StepperVelocityEL70x1,
|
||||
lift_end_stop: &DigitalInput,
|
||||
) -> Velocity {
|
||||
// Don't move if not enabled or in a state that doesn't result in movement
|
||||
if !self.enabled {
|
||||
return Velocity::ZERO;
|
||||
}
|
||||
|
||||
self.sync_position(stepper_driver);
|
||||
|
||||
// save state before
|
||||
let old_state = self.state.clone();
|
||||
|
||||
// Automatic Transitions
|
||||
match &self.state {
|
||||
State::NotHomed => {}
|
||||
State::Idle => {}
|
||||
State::GoingDown => {
|
||||
// If lower limit is reached
|
||||
if self.is_at_position(self.limit_bottom, Length::new::<millimeter>(0.01)) {
|
||||
// Put Into Idle
|
||||
self.state = State::Idle;
|
||||
}
|
||||
}
|
||||
State::GoingUp => {
|
||||
// If upper limit is reached
|
||||
if self.is_at_position(self.limit_top, Length::new::<millimeter>(0.01)) {
|
||||
// Put Into Idle
|
||||
self.state = State::Idle;
|
||||
}
|
||||
}
|
||||
State::Homing(homing_state) => match homing_state {
|
||||
HomingState::Initialize => {
|
||||
// If endstop is triggered, escape the endstop
|
||||
if lift_end_stop.get_value().unwrap_or(false) == true {
|
||||
self.state = State::Homing(HomingState::EscapeEndstop);
|
||||
} else {
|
||||
// If endstop is not triggered, move to the endstop
|
||||
self.state = State::Homing(HomingState::FindEndstopCoarse);
|
||||
}
|
||||
}
|
||||
HomingState::EscapeEndstop => {
|
||||
// Move out until endstop is not triggered anymore
|
||||
if lift_end_stop.get_value().unwrap_or(false) == false {
|
||||
self.state = State::Homing(HomingState::FindEnstopFineDistancing);
|
||||
}
|
||||
}
|
||||
HomingState::FindEnstopFineDistancing => {
|
||||
// Move out until endstop is not triggered anymore
|
||||
if lift_end_stop.get_value().unwrap_or(false) == false {
|
||||
// Find endstop fine
|
||||
self.state = State::Homing(HomingState::FindEndStopFine);
|
||||
}
|
||||
}
|
||||
HomingState::FindEndStopFine => {
|
||||
// If endstop is reached change to idle
|
||||
if lift_end_stop.get_value().unwrap_or(false) == true {
|
||||
// Set poition of lift to 0
|
||||
stepper_driver.set_position(0);
|
||||
// Put Into Idle
|
||||
self.state = State::Homing(HomingState::Validate(Instant::now()));
|
||||
}
|
||||
}
|
||||
HomingState::FindEndstopCoarse => {
|
||||
// Move to endstop
|
||||
if lift_end_stop.get_value().unwrap_or(false) == true {
|
||||
// Move awaiy from endstop
|
||||
self.state = State::Homing(HomingState::FindEnstopFineDistancing);
|
||||
}
|
||||
}
|
||||
HomingState::Validate(instant) => {
|
||||
// If 100ms have passed check if position is actually 0.0
|
||||
if instant.elapsed().as_millis() > 100 {
|
||||
if self.is_at_position(Length::ZERO, Length::new::<millimeter>(0.01)) {
|
||||
// If position is 0.0, put into idle
|
||||
self.state = State::Idle;
|
||||
} else {
|
||||
// If position is not 0.0, redo homing
|
||||
self.state = State::Homing(HomingState::Initialize);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// If state changed we
|
||||
State::Buffering(buffering_state) => match buffering_state {
|
||||
BufferingState::GoingUp => {}
|
||||
BufferingState::Emptying => {}
|
||||
BufferingState::Filling => {}
|
||||
},
|
||||
}
|
||||
|
||||
// Set the [`did_change_state`] flag
|
||||
if self.did_change_state == false {
|
||||
self.did_change_state = self.update_did_change_state(&old_state);
|
||||
info!("{:?}", self.state);
|
||||
}
|
||||
|
||||
// Speed
|
||||
let speed = match &self.state {
|
||||
State::NotHomed => Velocity::ZERO, // Not homed, no movement
|
||||
State::Idle => Velocity::ZERO, // No movement in idle state
|
||||
State::GoingDown => {
|
||||
// Move down at a speed of 10-100 mm/s
|
||||
self.speed_to_position(
|
||||
self.limit_bottom,
|
||||
match self.distance_to_position(self.limit_bottom).abs()
|
||||
> Length::new::<millimeter>(1.0)
|
||||
{
|
||||
true => Velocity::new::<millimeter_per_second>(100.0),
|
||||
false => Velocity::new::<millimeter_per_second>(10.0),
|
||||
},
|
||||
)
|
||||
}
|
||||
State::GoingUp => {
|
||||
// Move up at a speed of 10-100 mm/s
|
||||
self.speed_to_position(
|
||||
self.limit_top,
|
||||
match self.distance_to_position(self.limit_top).abs()
|
||||
> Length::new::<millimeter>(1.0)
|
||||
{
|
||||
true => Velocity::new::<millimeter_per_second>(100.0),
|
||||
false => Velocity::new::<millimeter_per_second>(10.0),
|
||||
},
|
||||
)
|
||||
}
|
||||
State::Homing(homing_state) => match homing_state {
|
||||
HomingState::Initialize => Velocity::ZERO,
|
||||
HomingState::EscapeEndstop => {
|
||||
// Move down at a speed of 10 mm/s
|
||||
Velocity::new::<millimeter_per_second>(10.0)
|
||||
}
|
||||
HomingState::FindEnstopFineDistancing => {
|
||||
// Move up at a speed of 2 mm/s
|
||||
Velocity::new::<millimeter_per_second>(2.0)
|
||||
}
|
||||
HomingState::FindEndstopCoarse => {
|
||||
// Move in at a speed of -100 mm/s
|
||||
Velocity::new::<millimeter_per_second>(-100.0)
|
||||
}
|
||||
HomingState::FindEndStopFine => {
|
||||
// move into the endstop at 2 mm/s
|
||||
Velocity::new::<millimeter_per_second>(-2.0)
|
||||
}
|
||||
HomingState::Validate(_) => {
|
||||
// We stand still until the validation cooldown has passed
|
||||
Velocity::ZERO
|
||||
}
|
||||
}, // Homing speed
|
||||
State::Buffering(buffering_state) => match buffering_state {
|
||||
BufferingState::GoingUp => {
|
||||
// Move top at a speed of 100 mm/s
|
||||
self.speed_to_position(
|
||||
self.limit_top - self.padding + Length::new::<millimeter>(0.01),
|
||||
Velocity::new::<millimeter_per_second>(100.0),
|
||||
)
|
||||
}
|
||||
BufferingState::Filling => self.speed_to_position(
|
||||
self.limit_top + self.padding - Length::new::<millimeter>(0.01),
|
||||
Self::calculate_buffer_lift_speed(self),
|
||||
),
|
||||
BufferingState::Emptying => self.speed_to_position(
|
||||
self.limit_bottom - self.padding + Length::new::<millimeter>(0.01),
|
||||
Self::calculate_buffer_lift_speed(self),
|
||||
),
|
||||
},
|
||||
};
|
||||
info!("{}", speed.get::<millimeter_per_second>());
|
||||
speed
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
use ethercat_hal::io::stepper_velocity_el70x1::StepperVelocityEL70x1;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BufferTowerController {
|
||||
enabled: bool,
|
||||
/// Stepper driver. Controls buffer stepper motor
|
||||
pub stepper_driver: StepperVelocityEL70x1,
|
||||
}
|
||||
|
||||
impl BufferTowerController {
|
||||
pub const fn new(driver: StepperVelocityEL70x1) -> Self {
|
||||
Self {
|
||||
enabled: false,
|
||||
stepper_driver: driver,
|
||||
}
|
||||
}
|
||||
pub fn set_enabled(&mut self, enabled: bool) {
|
||||
self.enabled = enabled;
|
||||
if enabled {
|
||||
self.stepper_driver.set_enabled(true);
|
||||
let _ = self.stepper_driver.set_speed(10.0);
|
||||
} else {
|
||||
self.stepper_driver.set_enabled(false);
|
||||
let _ = self.stepper_driver.set_speed(0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,62 +1,29 @@
|
||||
pub mod act;
|
||||
pub mod api;
|
||||
pub mod buffer_lift_controller;
|
||||
pub mod buffer_tower_controller;
|
||||
pub mod new;
|
||||
pub mod puller_speed_controller;
|
||||
|
||||
use api::{Buffer1Namespace, BufferV1Events, LiveValuesEvent, ModeState, StateEvent};
|
||||
use buffer_lift_controller::BufferLiftController;
|
||||
use buffer_tower_controller::BufferTowerController;
|
||||
use control_core::machines::connection::{CrossConnectableMachine, MachineCrossConnection};
|
||||
use control_core::{
|
||||
converters::linear_step_converter::LinearStepConverter,
|
||||
machines::{
|
||||
ConnectedMachine, ConnectedMachineData, downcast_machine,
|
||||
identification::{MachineIdentification, MachineIdentificationUnique},
|
||||
manager::MachineManager,
|
||||
},
|
||||
socketio::namespace::NamespaceCacheingLogic,
|
||||
uom_extensions::velocity::meter_per_minute,
|
||||
};
|
||||
use control_core_derive::Machine;
|
||||
use ethercat_hal::io::{
|
||||
digital_input::DigitalInput, stepper_velocity_el70x1::StepperVelocityEL70x1,
|
||||
};
|
||||
use futures::executor::block_on;
|
||||
use puller_speed_controller::PullerRegulationMode;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use smol::lock::{Mutex, RwLock};
|
||||
use std::{
|
||||
sync::{Arc, Weak},
|
||||
time::Instant,
|
||||
};
|
||||
use uom::{
|
||||
ConstZero,
|
||||
si::{
|
||||
f64::{Length, Velocity},
|
||||
length::millimeter,
|
||||
},
|
||||
};
|
||||
use smol::lock::RwLock;
|
||||
use std::{sync::Weak, time::Instant};
|
||||
|
||||
use crate::machines::{
|
||||
MACHINE_BUFFER_V1, VENDOR_QITECH,
|
||||
buffer1::{
|
||||
api::{ConnectedMachineState, LiftState, PullerState},
|
||||
puller_speed_controller::PullerSpeedController,
|
||||
},
|
||||
winder2::{BufferState, Winder2, Winder2Mode},
|
||||
};
|
||||
use crate::machines::{MACHINE_BUFFER_V1, VENDOR_QITECH, winder2::Winder2};
|
||||
|
||||
#[derive(Debug, Machine)]
|
||||
pub struct BufferV1 {
|
||||
// drivers
|
||||
pub lift: StepperVelocityEL70x1,
|
||||
pub lift_end_stop: DigitalInput,
|
||||
pub puller: StepperVelocityEL70x1,
|
||||
|
||||
// controllers
|
||||
pub buffer_lift_controller: BufferLiftController,
|
||||
pub puller_speed_controller: PullerSpeedController,
|
||||
|
||||
pub lift_step_converter: LinearStepConverter,
|
||||
pub buffer_tower_controller: BufferTowerController,
|
||||
|
||||
// socketio
|
||||
namespace: Buffer1Namespace,
|
||||
@@ -67,12 +34,18 @@ pub struct BufferV1 {
|
||||
pub machine_identification_unique: MachineIdentificationUnique,
|
||||
|
||||
// connected machines
|
||||
pub connected_winder: Option<ConnectedMachine<Weak<Mutex<Winder2>>>>,
|
||||
pub connected_winder: MachineCrossConnection<BufferV1, Winder2>,
|
||||
|
||||
// mode
|
||||
mode: BufferV1Mode,
|
||||
}
|
||||
|
||||
impl CrossConnectableMachine<BufferV1, Winder2> for BufferV1 {
|
||||
fn get_cross_connection(&mut self) -> &mut MachineCrossConnection<BufferV1, Winder2> {
|
||||
&mut self.connected_winder
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for BufferV1 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "BufferV1")
|
||||
@@ -84,28 +57,8 @@ impl BufferV1 {
|
||||
vendor: VENDOR_QITECH,
|
||||
machine: MACHINE_BUFFER_V1,
|
||||
};
|
||||
}
|
||||
|
||||
impl BufferV1 {
|
||||
pub fn emit_live_values(&mut self) {
|
||||
// Calculate puller speed from current motor steps
|
||||
let steps_per_second = self.puller.get_speed();
|
||||
let angular_velocity = self
|
||||
.puller_speed_controller
|
||||
.converter
|
||||
.steps_to_angular_velocity(steps_per_second as f64);
|
||||
let puller_speed = self
|
||||
.puller_speed_controller
|
||||
.angular_velocity_to_speed(angular_velocity);
|
||||
|
||||
// live values to be emittet
|
||||
let live_values = LiveValuesEvent {
|
||||
lift_position: self
|
||||
.buffer_lift_controller
|
||||
.get_current_position()
|
||||
.map(|x| x.get::<millimeter>()),
|
||||
puller_speed: puller_speed.get::<meter_per_minute>(),
|
||||
};
|
||||
let live_values = LiveValuesEvent {};
|
||||
|
||||
let event = live_values.build();
|
||||
self.namespace.emit(BufferV1Events::LiveValues(event));
|
||||
@@ -116,213 +69,52 @@ impl BufferV1 {
|
||||
mode_state: ModeState {
|
||||
mode: self.mode.clone(),
|
||||
},
|
||||
connected_machine_state: ConnectedMachineState {
|
||||
machine_identification_unique: self.connected_winder.as_ref().map(
|
||||
|connected_machine| {
|
||||
ConnectedMachineData::from(connected_machine).machine_identification_unique
|
||||
},
|
||||
),
|
||||
is_available: self
|
||||
.connected_winder
|
||||
.as_ref()
|
||||
.map(|connected_machine| {
|
||||
ConnectedMachineData::from(connected_machine).is_available
|
||||
})
|
||||
.unwrap_or(false),
|
||||
},
|
||||
lift_state: LiftState {
|
||||
limit_top: self
|
||||
.buffer_lift_controller
|
||||
.get_limit_top()
|
||||
.get::<millimeter>(),
|
||||
limit_bottom: self
|
||||
.buffer_lift_controller
|
||||
.get_limit_bottom()
|
||||
.get::<millimeter>(),
|
||||
position_in: self
|
||||
.buffer_lift_controller
|
||||
.get_limit_top()
|
||||
.get::<millimeter>(),
|
||||
position_out: self
|
||||
.buffer_lift_controller
|
||||
.get_limit_bottom()
|
||||
.get::<millimeter>(),
|
||||
is_going_up: self.buffer_lift_controller.is_going_up(),
|
||||
is_going_down: self.buffer_lift_controller.is_going_down(),
|
||||
is_homed: self.buffer_lift_controller.is_homed(),
|
||||
is_going_home: self.buffer_lift_controller.is_going_home(),
|
||||
is_buffering: self.buffer_lift_controller.is_buffering(),
|
||||
step_size: self
|
||||
.buffer_lift_controller
|
||||
.get_step_size()
|
||||
.get::<millimeter>(),
|
||||
padding: self
|
||||
.buffer_lift_controller
|
||||
.get_padding()
|
||||
.get::<millimeter>(),
|
||||
can_go_top: self.can_go_up(),
|
||||
can_go_bottom: self.can_go_down(),
|
||||
can_go_home: self.can_go_home(),
|
||||
},
|
||||
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,
|
||||
},
|
||||
connected_machine_state: self.connected_winder.to_state(),
|
||||
};
|
||||
|
||||
let event = state.build();
|
||||
self.namespace.emit(BufferV1Events::State(event));
|
||||
}
|
||||
|
||||
/// Can buffer capability check
|
||||
pub fn can_buffer(&self) -> bool {
|
||||
// Check if tension arm is zeroed and traverse is homed
|
||||
self.buffer_lift_controller.is_homed() && !self.buffer_lift_controller.is_going_home()
|
||||
}
|
||||
|
||||
/// Can go to upper limit capability check
|
||||
pub fn can_go_up(&self) -> bool {
|
||||
self.buffer_lift_controller.is_homed()
|
||||
&& self.mode != BufferV1Mode::Standby
|
||||
&& !self.buffer_lift_controller.is_going_up()
|
||||
&& !self.buffer_lift_controller.is_going_home()
|
||||
&& !self.buffer_lift_controller.is_buffering()
|
||||
&& self.mode != BufferV1Mode::Filling
|
||||
&& self.mode != BufferV1Mode::Emptying
|
||||
}
|
||||
|
||||
/// Can go to lower limit capability check
|
||||
pub fn can_go_down(&self) -> bool {
|
||||
self.buffer_lift_controller.is_homed()
|
||||
&& self.mode != BufferV1Mode::Standby
|
||||
&& !self.buffer_lift_controller.is_going_down()
|
||||
&& !self.buffer_lift_controller.is_going_home()
|
||||
&& !self.buffer_lift_controller.is_buffering()
|
||||
&& self.mode != BufferV1Mode::Filling
|
||||
&& self.mode != BufferV1Mode::Emptying
|
||||
}
|
||||
|
||||
/// Can go home capability check
|
||||
pub fn can_go_home(&self) -> bool {
|
||||
// Check if not in standby, not traversing
|
||||
// Allow going home even when going in or out
|
||||
self.mode != BufferV1Mode::Standby
|
||||
&& !self.buffer_lift_controller.is_going_home()
|
||||
&& !self.buffer_lift_controller.is_buffering()
|
||||
&& self.mode != BufferV1Mode::Filling
|
||||
&& self.mode != BufferV1Mode::Emptying
|
||||
}
|
||||
}
|
||||
|
||||
impl BufferV1 {
|
||||
// To be implemented
|
||||
fn fill_buffer(&mut self) {
|
||||
// stop the winder until the buffer is full
|
||||
self.update_winder2_mode(Winder2Mode::Hold);
|
||||
self.update_winder2_buffer_state(BufferState::Buffering);
|
||||
self.get_winder(|winder| {
|
||||
winder
|
||||
.puller_speed_controller
|
||||
.set_buffer_speed(Velocity::ZERO);
|
||||
});
|
||||
todo!();
|
||||
}
|
||||
|
||||
fn empty_buffer(&mut self) {
|
||||
// Set the winder2 to a mode where its faster than before to empty the buffer slowly
|
||||
self.update_winder2_mode(Winder2Mode::Wind);
|
||||
self.update_winder2_buffer_state(BufferState::Emptying);
|
||||
self.get_winder(|winder| {
|
||||
winder
|
||||
.puller_speed_controller
|
||||
.set_buffer_speed(self.puller_speed_controller.last_speed);
|
||||
});
|
||||
}
|
||||
|
||||
fn hold_buffer(&mut self) {
|
||||
// Hold buffer in current state input speed = output speed
|
||||
self.update_winder2_mode(Winder2Mode::Wind);
|
||||
self.update_winder2_buffer_state(BufferState::Hold);
|
||||
self.get_winder(|winder| {
|
||||
winder
|
||||
.puller_speed_controller
|
||||
.set_buffer_speed(self.puller_speed_controller.last_speed);
|
||||
});
|
||||
}
|
||||
|
||||
fn update_winder2_mode(&mut self, mode: Winder2Mode) {
|
||||
self.get_winder(|winder2| {
|
||||
if winder2.mode != mode {
|
||||
winder2.mode = mode;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn update_winder2_buffer_state(&mut self, state: BufferState) {
|
||||
self.get_winder(|winder2| {
|
||||
if winder2.buffer_state != state {
|
||||
winder2.set_buffer_state(state);
|
||||
}
|
||||
});
|
||||
todo!();
|
||||
}
|
||||
|
||||
// Turn off motor and do nothing
|
||||
fn switch_to_standby(&mut self) {
|
||||
match self.mode {
|
||||
BufferV1Mode::Standby => (),
|
||||
BufferV1Mode::Hold => {}
|
||||
BufferV1Mode::Filling => {}
|
||||
BufferV1Mode::Emptying => {}
|
||||
BufferV1Mode::FillingBuffer => {}
|
||||
BufferV1Mode::EmptyingBuffer => {}
|
||||
};
|
||||
self.mode = BufferV1Mode::Standby;
|
||||
self.buffer_lift_controller.set_enabled(false);
|
||||
let _ = self.buffer_lift_controller.stepper_driver.set_speed(0.0);
|
||||
self.update_winder2_buffer_state(BufferState::Hold);
|
||||
}
|
||||
|
||||
// hold motor
|
||||
fn switch_to_hold(&mut self) {
|
||||
match self.mode {
|
||||
BufferV1Mode::Standby => self.hold_buffer(),
|
||||
BufferV1Mode::Hold => (),
|
||||
BufferV1Mode::Filling => {}
|
||||
BufferV1Mode::Emptying => {}
|
||||
};
|
||||
self.mode = BufferV1Mode::Hold;
|
||||
self.buffer_lift_controller.set_enabled(true);
|
||||
let _ = self.buffer_lift_controller.stepper_driver.set_speed(0.0);
|
||||
self.buffer_tower_controller.set_enabled(false);
|
||||
}
|
||||
|
||||
// Turn on motor and fill buffer
|
||||
fn switch_to_filling(&mut self) {
|
||||
match self.mode {
|
||||
BufferV1Mode::Standby => self.fill_buffer(),
|
||||
BufferV1Mode::Hold => {}
|
||||
BufferV1Mode::Filling => (),
|
||||
BufferV1Mode::Emptying => {}
|
||||
BufferV1Mode::FillingBuffer => (),
|
||||
BufferV1Mode::EmptyingBuffer => {}
|
||||
};
|
||||
self.mode = BufferV1Mode::Filling;
|
||||
self.buffer_lift_controller.set_forward(true);
|
||||
self.buffer_lift_controller.set_enabled(true);
|
||||
self.mode = BufferV1Mode::FillingBuffer;
|
||||
self.buffer_tower_controller.set_enabled(true);
|
||||
}
|
||||
|
||||
// Turn on motor reverse and empty buffer
|
||||
fn switch_to_emptying(&mut self) {
|
||||
match self.mode {
|
||||
BufferV1Mode::Standby => self.empty_buffer(),
|
||||
BufferV1Mode::Hold => {}
|
||||
BufferV1Mode::Filling => {}
|
||||
BufferV1Mode::Emptying => (),
|
||||
BufferV1Mode::FillingBuffer => {}
|
||||
BufferV1Mode::EmptyingBuffer => (),
|
||||
};
|
||||
self.mode = BufferV1Mode::Emptying;
|
||||
self.buffer_lift_controller.set_forward(false);
|
||||
self.buffer_lift_controller.set_enabled(true);
|
||||
self.mode = BufferV1Mode::EmptyingBuffer;
|
||||
}
|
||||
|
||||
fn switch_mode(&mut self, mode: BufferV1Mode) {
|
||||
@@ -332,111 +124,17 @@ impl BufferV1 {
|
||||
|
||||
match mode {
|
||||
BufferV1Mode::Standby => self.switch_to_standby(),
|
||||
BufferV1Mode::Hold => self.switch_to_hold(),
|
||||
BufferV1Mode::Filling => self.switch_to_filling(),
|
||||
BufferV1Mode::Emptying => self.switch_to_emptying(),
|
||||
BufferV1Mode::FillingBuffer => self.switch_to_filling(),
|
||||
BufferV1Mode::EmptyingBuffer => self.switch_to_emptying(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BufferV1 {
|
||||
pub fn set_mode_state(&mut self, mode: BufferV1Mode) {
|
||||
fn set_mode_state(&mut self, mode: BufferV1Mode) {
|
||||
self.switch_mode(mode);
|
||||
self.emit_state();
|
||||
}
|
||||
}
|
||||
|
||||
// impl Winder
|
||||
impl BufferV1 {
|
||||
pub fn sync_winder_puller(&mut self) {
|
||||
self.get_winder(|winder| {
|
||||
winder
|
||||
.puller_speed_controller
|
||||
.set_buffer_speed(self.buffer_lift_controller.get_target_output_speed());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Implement Puller
|
||||
impl BufferV1 {
|
||||
/// called by `act`
|
||||
pub fn sync_puller_speed(&mut self, t: Instant) {
|
||||
let angular_velocity = self.puller_speed_controller.calc_angular_velocity(t);
|
||||
let steps_per_second = self
|
||||
.puller_speed_controller
|
||||
.converter
|
||||
.angular_velocity_to_steps(angular_velocity);
|
||||
let _ = self.puller.set_speed(steps_per_second);
|
||||
|
||||
// sync puller speed to lift input speed
|
||||
let linear_velocity = self
|
||||
.puller_speed_controller
|
||||
.converter
|
||||
.angular_velocity_to_velocity(angular_velocity);
|
||||
self.buffer_lift_controller
|
||||
.set_current_input_speed(linear_velocity);
|
||||
}
|
||||
|
||||
pub fn puller_set_regulation(&mut self, puller_regulation_mode: PullerRegulationMode) {
|
||||
self.puller_speed_controller
|
||||
.set_regulation_mode(puller_regulation_mode);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set target speed in m/min
|
||||
pub fn puller_set_target_speed(&mut self, target_speed: f64) {
|
||||
// Convert m/min to velocity
|
||||
let target_speed = Velocity::new::<meter_per_minute>(target_speed);
|
||||
self.puller_speed_controller.set_target_speed(target_speed);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set target diameter in mm
|
||||
pub fn puller_set_target_diameter(&mut self, target_diameter: f64) {
|
||||
// Convert mm to length
|
||||
let target_diameter = Length::new::<millimeter>(target_diameter);
|
||||
self.puller_speed_controller
|
||||
.set_target_diameter(target_diameter);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set forward direction
|
||||
pub fn puller_set_forward(&mut self, forward: bool) {
|
||||
self.puller_speed_controller.set_forward(forward);
|
||||
self.emit_state();
|
||||
}
|
||||
}
|
||||
|
||||
// Implement Lift
|
||||
impl BufferV1 {
|
||||
pub fn sync_lift_speed(&mut self, t: Instant) {
|
||||
let linear_velocity =
|
||||
self.buffer_lift_controller
|
||||
.update_speed(&mut self.lift, &self.lift_end_stop, t);
|
||||
if self.can_move() {
|
||||
let steps_per_second = self.lift_step_converter.velocity_to_steps(linear_velocity);
|
||||
let _ = self.lift.set_speed(steps_per_second);
|
||||
} else {
|
||||
let _ = self.lift.set_speed(0.0);
|
||||
}
|
||||
}
|
||||
|
||||
fn can_move(&mut self) -> bool {
|
||||
match self.lift_end_stop.get_value() {
|
||||
Ok(reached) => {
|
||||
if reached {
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Connecting/Disconnecting machine
|
||||
impl BufferV1 {
|
||||
/// Connecting/Disconnecting machine
|
||||
/// set connected winder
|
||||
pub fn set_connected_winder(
|
||||
&mut self,
|
||||
@@ -448,34 +146,13 @@ impl BufferV1 {
|
||||
) {
|
||||
return;
|
||||
}
|
||||
let machine_manager_arc = match self.machine_manager.upgrade() {
|
||||
Some(machine_manager_arc) => machine_manager_arc,
|
||||
None => return,
|
||||
};
|
||||
let machine_manager_guard = block_on(machine_manager_arc.read());
|
||||
let winder2_weak = machine_manager_guard.get_machine_weak(&machine_identification_unique);
|
||||
let winder2_weak = match winder2_weak {
|
||||
Some(winder2_weak) => winder2_weak,
|
||||
None => return,
|
||||
};
|
||||
let winder2_strong = match winder2_weak.upgrade() {
|
||||
Some(winder2_strong) => winder2_strong,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let winder2: Arc<Mutex<Winder2>> = block_on(downcast_machine::<Winder2>(winder2_strong))
|
||||
.expect("failed downcasting machine");
|
||||
|
||||
let machine = Arc::downgrade(&winder2);
|
||||
|
||||
self.connected_winder = Some(ConnectedMachine {
|
||||
machine_identification_unique,
|
||||
machine,
|
||||
});
|
||||
self.connected_winder
|
||||
.set_connected_machine(&machine_identification_unique);
|
||||
|
||||
self.emit_state();
|
||||
|
||||
self.reverse_connect();
|
||||
self.connected_winder.reverse_connect();
|
||||
}
|
||||
|
||||
/// disconnect winder
|
||||
@@ -489,65 +166,15 @@ impl BufferV1 {
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if let Some(connected) = &self.connected_winder {
|
||||
if let Some(winder2_arc) = connected.machine.upgrade() {
|
||||
let future = async move {
|
||||
let mut winder2 = winder2_arc.lock().await;
|
||||
if winder2.connected_buffer.is_some() {
|
||||
winder2.connected_buffer = None;
|
||||
winder2.emit_state();
|
||||
}
|
||||
};
|
||||
smol::spawn(future).detach();
|
||||
}
|
||||
}
|
||||
self.connected_winder = None;
|
||||
|
||||
self.connected_winder.disconnect();
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// initiate connection from winder to buffer
|
||||
pub fn reverse_connect(&mut self) {
|
||||
let machine_identification_unique = self.machine_identification_unique.clone();
|
||||
if let Some(connected) = &self.connected_winder {
|
||||
if let Some(winder2_arc) = connected.machine.upgrade() {
|
||||
let future = async move {
|
||||
let mut winder2 = winder2_arc.lock().await;
|
||||
if winder2.connected_buffer.is_none() {
|
||||
winder2.set_connected_buffer(machine_identification_unique);
|
||||
}
|
||||
};
|
||||
smol::spawn(future).detach();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This helper function provides an easy way
|
||||
/// to get the machine out of the Weak Reference
|
||||
///
|
||||
/// Usage:
|
||||
///
|
||||
/// self.get_winder(|winder2| {
|
||||
/// winder2.do_something // Use the Winder here as usual
|
||||
/// });
|
||||
fn get_winder<F, R>(&self, func: F) -> Option<R>
|
||||
where
|
||||
F: FnOnce(&mut Winder2) -> R,
|
||||
{
|
||||
self.connected_winder
|
||||
.as_ref()?
|
||||
.machine
|
||||
.upgrade()
|
||||
.map(|winder_arc| {
|
||||
let mut winder = block_on(winder_arc.lock());
|
||||
func(&mut winder)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
|
||||
pub enum BufferV1Mode {
|
||||
Standby,
|
||||
Hold,
|
||||
Filling,
|
||||
Emptying,
|
||||
FillingBuffer,
|
||||
EmptyingBuffer,
|
||||
}
|
||||
@@ -1,39 +1,37 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use anyhow::Error;
|
||||
use control_core::converters::linear_step_converter::LinearStepConverter;
|
||||
use control_core::machines::connection::MachineCrossConnection;
|
||||
use control_core::machines::new::{
|
||||
MachineNewHardware, MachineNewParams, MachineNewTrait, validate_no_role_dublicates,
|
||||
validate_same_machine_identification_unique,
|
||||
};
|
||||
use control_core::uom_extensions::velocity::meter_per_minute;
|
||||
use ethercat_hal::coe::ConfigurableDevice;
|
||||
use ethercat_hal::devices::EthercatDeviceUsed;
|
||||
use ethercat_hal::devices::ek1100::EK1100;
|
||||
use ethercat_hal::devices::ek1100::EK1100_IDENTITY_A;
|
||||
use ethercat_hal::devices::el7031_0030::coe::EL7031_0030Configuration;
|
||||
use ethercat_hal::devices::el7031_0030::pdo::EL7031_0030PredefinedPdoAssignment;
|
||||
use ethercat_hal::devices::el7031_0030::{
|
||||
EL7031_0030, EL7031_0030_IDENTITY_A, EL7031_0030StepperPort,
|
||||
use ethercat_hal::{
|
||||
coe::ConfigurableDevice,
|
||||
devices::el7031_0030::coe::EL7031_0030Configuration,
|
||||
devices::el7031_0030::pdo::EL7031_0030PredefinedPdoAssignment,
|
||||
devices::el7041_0052::coe::EL7041_0052Configuration,
|
||||
devices::{
|
||||
EthercatDeviceUsed,
|
||||
ek1100::{EK1100, EK1100_IDENTITY_A},
|
||||
el7031_0030::{EL7031_0030, EL7031_0030_IDENTITY_A},
|
||||
el7041_0052::{EL7041_0052, EL7041_0052_IDENTITY_A, EL7041_0052Port},
|
||||
},
|
||||
io::stepper_velocity_el70x1::StepperVelocityEL70x1,
|
||||
shared_config,
|
||||
shared_config::el70x1::{EL70x1OperationMode, StmMotorConfiguration},
|
||||
};
|
||||
use ethercat_hal::devices::el7041_0052::coe::EL7041_0052Configuration;
|
||||
use ethercat_hal::devices::el7041_0052::{EL7041_0052, EL7041_0052_IDENTITY_A, EL7041_0052Port};
|
||||
use ethercat_hal::io::digital_input::DigitalInput;
|
||||
use ethercat_hal::io::stepper_velocity_el70x1::StepperVelocityEL70x1;
|
||||
use ethercat_hal::shared_config;
|
||||
use ethercat_hal::shared_config::el70x1::{EL70x1OperationMode, StmMotorConfiguration};
|
||||
use uom::si::f64::{Length, Velocity};
|
||||
use uom::si::length::{centimeter, millimeter};
|
||||
|
||||
use crate::machines::buffer1::BufferV1Mode;
|
||||
use crate::machines::buffer1::buffer_lift_controller::BufferLiftController;
|
||||
use crate::machines::buffer1::puller_speed_controller::PullerSpeedController;
|
||||
use crate::machines::buffer1::buffer_tower_controller::BufferTowerController;
|
||||
use crate::machines::get_ethercat_device;
|
||||
|
||||
use super::{BufferV1, api::Buffer1Namespace};
|
||||
|
||||
impl MachineNewTrait for BufferV1 {
|
||||
fn new<'maindevice>(params: &MachineNewParams) -> Result<Self, Error> {
|
||||
fn new<'maindevice>(
|
||||
params: &MachineNewParams<'maindevice, '_, '_, '_, '_, '_, '_>,
|
||||
) -> Result<Self, Error> {
|
||||
// validate general stuff
|
||||
let device_identification = params.device_group.to_vec();
|
||||
validate_same_machine_identification_unique(&device_identification)?;
|
||||
@@ -81,7 +79,7 @@ impl MachineNewTrait for BufferV1 {
|
||||
el7041
|
||||
.write()
|
||||
.await
|
||||
.write_config(subdevice, &el7041_config)
|
||||
.write_config(&subdevice, &el7041_config)
|
||||
.await?;
|
||||
{
|
||||
let mut device_guard = el7041.write().await;
|
||||
@@ -116,7 +114,7 @@ impl MachineNewTrait for BufferV1 {
|
||||
el7031
|
||||
.write()
|
||||
.await
|
||||
.write_config(subdevice, &el7031_config)
|
||||
.write_config(&subdevice, &el7031_config)
|
||||
.await?;
|
||||
{
|
||||
let mut device_guard = el7031.write().await;
|
||||
@@ -124,44 +122,27 @@ impl MachineNewTrait for BufferV1 {
|
||||
}
|
||||
|
||||
// Controller
|
||||
let buffer_lift_controller = BufferLiftController::new(
|
||||
StepperVelocityEL70x1::new(el7041.clone(), EL7041_0052Port::STM1),
|
||||
Length::new::<centimeter>(135.0),
|
||||
Length::new::<centimeter>(0.0),
|
||||
64,
|
||||
);
|
||||
let puller_speed_controller = PullerSpeedController::new(
|
||||
Velocity::new::<meter_per_minute>(1.0),
|
||||
Length::new::<millimeter>(1.75),
|
||||
LinearStepConverter::from_diameter(200, Length::new::<centimeter>(8.0)),
|
||||
);
|
||||
let buffer_tower_controller = BufferTowerController::new(StepperVelocityEL70x1::new(
|
||||
el7041.clone(),
|
||||
EL7041_0052Port::STM1,
|
||||
));
|
||||
|
||||
// Get machine identification unique
|
||||
let machine_id = params
|
||||
.device_group
|
||||
.first()
|
||||
.expect("device group must have at least one device")
|
||||
.device_machine_identification
|
||||
.machine_identification_unique
|
||||
.clone();
|
||||
let machine_identification_unique = params.get_machine_identification_unique();
|
||||
|
||||
// create buffer instance
|
||||
let mut buffer = Self {
|
||||
lift: StepperVelocityEL70x1::new(el7041.clone(), EL7041_0052Port::STM1),
|
||||
lift_end_stop: DigitalInput::new(el7041, EL7041_0052Port::DI1),
|
||||
puller: StepperVelocityEL70x1::new(el7031.clone(), EL7031_0030StepperPort::STM1),
|
||||
lift_step_converter: LinearStepConverter::from_diameter(
|
||||
200,
|
||||
Length::new::<millimeter>(32.22),
|
||||
),
|
||||
namespace: Buffer1Namespace::new(params.socket_queue_tx.clone()),
|
||||
let mut buffer: BufferV1 = Self {
|
||||
namespace: Buffer1Namespace {
|
||||
namespace: params.namespace.clone(),
|
||||
},
|
||||
last_measurement_emit: Instant::now(),
|
||||
mode: BufferV1Mode::Standby,
|
||||
buffer_lift_controller,
|
||||
puller_speed_controller,
|
||||
buffer_tower_controller,
|
||||
machine_manager: params.machine_manager.clone(),
|
||||
machine_identification_unique: machine_id,
|
||||
connected_winder: None,
|
||||
machine_identification_unique: machine_identification_unique.clone(),
|
||||
connected_winder: MachineCrossConnection::new(
|
||||
params.machine_manager.clone(),
|
||||
&machine_identification_unique,
|
||||
),
|
||||
};
|
||||
buffer.emit_state();
|
||||
Ok(buffer)
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use control_core::{
|
||||
controllers::second_degree_motion::linear_jerk_speed_controller::LinearJerkSpeedController,
|
||||
converters::linear_step_converter::LinearStepConverter,
|
||||
uom_extensions::{
|
||||
acceleration::meter_per_minute_per_second, jerk::meter_per_minute_per_second_squared,
|
||||
velocity::meter_per_minute,
|
||||
},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uom::{
|
||||
ConstZero,
|
||||
si::f64::{Acceleration, AngularVelocity, Jerk, Length, Velocity},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PullerSpeedController {
|
||||
enabled: bool,
|
||||
pub target_speed: Velocity,
|
||||
pub target_diameter: Length,
|
||||
pub regulation_mode: PullerRegulationMode,
|
||||
/// Forward rotation direction. If false, applies negative sign to speed
|
||||
pub forward: bool,
|
||||
/// Linear acceleration controller to dampen speed change
|
||||
acceleration_controller: LinearJerkSpeedController,
|
||||
/// Converter for linear to angular transformations
|
||||
pub converter: LinearStepConverter,
|
||||
pub last_speed: Velocity,
|
||||
}
|
||||
|
||||
impl PullerSpeedController {
|
||||
pub fn new(
|
||||
target_speed: Velocity,
|
||||
target_diameter: Length,
|
||||
converter: LinearStepConverter,
|
||||
) -> Self {
|
||||
let acceleration = Acceleration::new::<meter_per_minute_per_second>(5.0);
|
||||
let jerk = Jerk::new::<meter_per_minute_per_second_squared>(10.0);
|
||||
let speed = Velocity::new::<meter_per_minute>(50.0);
|
||||
|
||||
Self {
|
||||
enabled: false,
|
||||
target_speed,
|
||||
target_diameter,
|
||||
regulation_mode: PullerRegulationMode::Speed,
|
||||
forward: true,
|
||||
acceleration_controller: LinearJerkSpeedController::new_simple(
|
||||
Some(speed),
|
||||
acceleration,
|
||||
jerk,
|
||||
),
|
||||
converter,
|
||||
last_speed: Velocity::ZERO,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_enabled(&mut self, enabled: bool) {
|
||||
self.enabled = enabled;
|
||||
}
|
||||
|
||||
pub fn set_target_speed(&mut self, target: Velocity) {
|
||||
self.target_speed = target;
|
||||
}
|
||||
|
||||
pub fn set_target_diameter(&mut self, target: Length) {
|
||||
self.target_diameter = target;
|
||||
}
|
||||
|
||||
pub fn set_regulation_mode(&mut self, regulation: PullerRegulationMode) {
|
||||
self.regulation_mode = regulation;
|
||||
}
|
||||
|
||||
pub fn set_forward(&mut self, forward: bool) {
|
||||
self.forward = forward;
|
||||
}
|
||||
|
||||
fn update_speed(&mut self, t: Instant) -> Velocity {
|
||||
let speed = match self.enabled {
|
||||
true => match self.regulation_mode {
|
||||
PullerRegulationMode::Speed => self.target_speed,
|
||||
PullerRegulationMode::Diameter => unimplemented!(),
|
||||
},
|
||||
false => Velocity::ZERO,
|
||||
};
|
||||
|
||||
let speed = if self.forward { speed } else { -speed };
|
||||
|
||||
let speed = self.acceleration_controller.update(speed, t);
|
||||
|
||||
self.last_speed = speed;
|
||||
speed
|
||||
}
|
||||
|
||||
pub fn speed_to_angular_velocity(&self, speed: Velocity) -> AngularVelocity {
|
||||
// Use the converter to transform from linear velocity to angular velocity
|
||||
self.converter.velocity_to_angular_velocity(speed)
|
||||
}
|
||||
|
||||
pub fn angular_velocity_to_speed(&self, angular_speed: AngularVelocity) -> Velocity {
|
||||
// Use the converter to transform from angular velocity to linear velocity
|
||||
self.converter.angular_velocity_to_velocity(angular_speed)
|
||||
}
|
||||
|
||||
pub fn calc_angular_velocity(&mut self, t: Instant) -> AngularVelocity {
|
||||
let speed = self.update_speed(t);
|
||||
self.speed_to_angular_velocity(speed)
|
||||
}
|
||||
|
||||
pub fn get_target_speed(&self) -> Velocity {
|
||||
self.target_speed
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub enum PullerRegulationMode {
|
||||
Speed,
|
||||
Diameter,
|
||||
}
|
||||
@@ -1,26 +1,25 @@
|
||||
use super::{ExtruderV2Mode, HeatingType, mitsubishi_cs80::MotorStatus};
|
||||
use super::{ExtruderV2Mode, mitsubishi_cs80::MotorStatus};
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use super::ExtruderV2;
|
||||
|
||||
#[cfg(feature = "mock-machine")]
|
||||
use super::mock::ExtruderV2;
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use crate::machines::extruder1::HeatingType;
|
||||
|
||||
use control_core::{
|
||||
machines::api::MachineApi,
|
||||
socketio::{
|
||||
event::{Event, GenericEvent},
|
||||
namespace::{
|
||||
CacheFn, CacheableEvents, Namespace, NamespaceCacheingLogic, cache_duration,
|
||||
cache_first_and_last_event,
|
||||
},
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use control_core::machines::api::MachineApi;
|
||||
use control_core::socketio::{
|
||||
event::{Event, GenericEvent},
|
||||
namespace::{
|
||||
CacheFn, CacheableEvents, Namespace, NamespaceCacheingLogic, cache_duration,
|
||||
cache_first_and_last_event,
|
||||
},
|
||||
};
|
||||
use control_core_derive::BuildEvent;
|
||||
use serde::{Deserialize, Serialize};
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use serde_json::Value;
|
||||
use smol::channel::Sender;
|
||||
use socketioxide::extract::SocketRef;
|
||||
use smol::lock::Mutex;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use tracing::instrument;
|
||||
use uom::si::{
|
||||
@@ -225,7 +224,7 @@ pub enum Mutation {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ExtruderV2Namespace {
|
||||
pub namespace: Namespace,
|
||||
pub namespace: Arc<Mutex<Namespace>>,
|
||||
}
|
||||
|
||||
impl NamespaceCacheingLogic<ExtruderV2Events> for ExtruderV2Namespace {
|
||||
@@ -233,15 +232,9 @@ impl NamespaceCacheingLogic<ExtruderV2Events> for ExtruderV2Namespace {
|
||||
fn emit(&mut self, events: ExtruderV2Events) {
|
||||
let event = Arc::new(events.event_value());
|
||||
let buffer_fn = events.event_cache_fn();
|
||||
self.namespace.emit(event, &buffer_fn);
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtruderV2Namespace {
|
||||
pub fn new(socket_queue_tx: Sender<(SocketRef, Arc<GenericEvent>)>) -> Self {
|
||||
Self {
|
||||
namespace: Namespace::new(socket_queue_tx),
|
||||
}
|
||||
let mut namespace = self.namespace.lock_blocking();
|
||||
namespace.emit(event, &buffer_fn);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -303,7 +296,7 @@ impl MachineApi for ExtruderV2 {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn api_event_namespace(&mut self) -> &mut Namespace {
|
||||
&mut self.namespace.namespace
|
||||
fn api_event_namespace(&mut self) -> Arc<Mutex<Namespace>> {
|
||||
self.namespace.namespace.clone()
|
||||
}
|
||||
}
|
||||
@@ -8,16 +8,21 @@ use crate::machines::extruder1::{
|
||||
RotationState, ScrewState, StateEvent,
|
||||
},
|
||||
};
|
||||
use control_core::{
|
||||
helpers::hasher_serializer::hash_with_serde_model,
|
||||
socketio::{event::BuildEvent, namespace::NamespaceCacheingLogic},
|
||||
};
|
||||
use uom::si::{
|
||||
angular_velocity::revolution_per_minute,
|
||||
f64::{AngularVelocity, Pressure, ThermodynamicTemperature},
|
||||
pressure::bar,
|
||||
thermodynamic_temperature::degree_celsius,
|
||||
};
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use control_core::helpers::hasher_serializer::hash_with_serde_model;
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use control_core::socketio::event::BuildEvent;
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use control_core::socketio::namespace::NamespaceCacheingLogic;
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use uom::si::{angular_velocity::revolution_per_minute, thermodynamic_temperature::degree_celsius};
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use uom::si::angular_velocity::AngularVelocity;
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use uom::si::pressure::{Pressure, bar};
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use uom::si::thermodynamic_temperature::ThermodynamicTemperature;
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
impl ExtruderV2 {
|
||||
@@ -199,7 +204,6 @@ impl ExtruderV2 {
|
||||
}
|
||||
|
||||
pub fn set_nozzle_pressure_limit(&mut self, pressure: f64) {
|
||||
use uom::si::pressure::bar;
|
||||
self.screw_speed_controller
|
||||
.set_nozzle_pressure_limit(Pressure::new::<bar>(pressure));
|
||||
self.emit_state();
|
||||
@@ -244,7 +248,6 @@ impl ExtruderV2 {
|
||||
}
|
||||
|
||||
pub fn set_target_rpm(&mut self, rpm: f64) {
|
||||
use uom::si::angular_velocity::revolution_per_minute;
|
||||
self.screw_speed_controller
|
||||
.set_target_screw_rpm(AngularVelocity::new::<revolution_per_minute>(rpm));
|
||||
self.emit_state();
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
use crate::machines::extruder1::{HeatingType, api::Mutation, mock::ExtruderV2};
|
||||
use control_core::machines::api::MachineApi;
|
||||
use control_core::socketio::namespace::Namespace;
|
||||
use smol::lock::Mutex;
|
||||
use std::sync::Arc;
|
||||
|
||||
impl MachineApi for ExtruderV2 {
|
||||
fn api_mutate(&mut self, request_body: serde_json::Value) -> Result<(), anyhow::Error> {
|
||||
@@ -38,7 +41,7 @@ impl MachineApi for ExtruderV2 {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn api_event_namespace(&mut self) -> &mut control_core::socketio::namespace::Namespace {
|
||||
&mut self.namespace.namespace
|
||||
fn api_event_namespace(&mut self) -> Arc<Mutex<Namespace>> {
|
||||
self.namespace.namespace.clone()
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
#[cfg(feature = "mock-machine")]
|
||||
use control_core::machines::identification::MachineIdentification;
|
||||
#[cfg(feature = "mock-machine")]
|
||||
use control_core::machines::identification::MachineIdentificationUnique;
|
||||
#[cfg(feature = "mock-machine")]
|
||||
use control_core_derive::Machine;
|
||||
#[cfg(feature = "mock-machine")]
|
||||
use std::time::Instant;
|
||||
@@ -31,6 +33,7 @@ pub mod new;
|
||||
#[cfg(feature = "mock-machine")]
|
||||
#[derive(Debug, Machine)]
|
||||
pub struct ExtruderV2 {
|
||||
machine_identification_unique: MachineIdentificationUnique,
|
||||
namespace: ExtruderV2Namespace,
|
||||
last_measurement_emit: Instant,
|
||||
last_status_hash: Option<u64>,
|
||||
|
||||
@@ -32,7 +32,10 @@ impl control_core::machines::new::MachineNewTrait for ExtruderV2 {
|
||||
let now = std::time::Instant::now();
|
||||
|
||||
let mut extruder_mock_machine = Self {
|
||||
namespace: ExtruderV2Namespace::new(params.socket_queue_tx.clone()),
|
||||
machine_identification_unique: params.get_machine_identification_unique(),
|
||||
namespace: ExtruderV2Namespace {
|
||||
namespace: params.namespace.clone(),
|
||||
},
|
||||
last_measurement_emit: now,
|
||||
|
||||
mode: ExtruderV2Mode::Standby, // Start in standby mode
|
||||
|
||||
@@ -1,15 +1,26 @@
|
||||
use crate::machines::{MACHINE_EXTRUDER_V1, VENDOR_QITECH};
|
||||
use api::ExtruderV2Namespace;
|
||||
use control_core::machines::identification::MachineIdentification;
|
||||
use control_core_derive::Machine;
|
||||
use screw_speed_controller::ScrewSpeedController;
|
||||
use serde::{Deserialize, Serialize};
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use std::time::Instant;
|
||||
use temperature_controller::TemperatureController;
|
||||
use uom::si::{
|
||||
electric_current::ampere, electric_potential::volt, f64::ThermodynamicTemperature,
|
||||
thermodynamic_temperature::degree_celsius,
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use control_core::machines::identification::{MachineIdentification, MachineIdentificationUnique};
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use control_core_derive::Machine;
|
||||
use serde::{Deserialize, Serialize};
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use uom::si::electric_current::ampere;
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use uom::si::electric_potential::volt;
|
||||
use uom::si::{f64::ThermodynamicTemperature, thermodynamic_temperature::degree_celsius};
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use crate::machines::{
|
||||
MACHINE_EXTRUDER_V1, VENDOR_QITECH,
|
||||
extruder1::{
|
||||
api::ExtruderV2Namespace, screw_speed_controller::ScrewSpeedController,
|
||||
temperature_controller::TemperatureController,
|
||||
},
|
||||
};
|
||||
|
||||
pub mod act;
|
||||
pub mod api;
|
||||
pub mod emit;
|
||||
@@ -52,9 +63,10 @@ pub enum HeatingType {
|
||||
Middle,
|
||||
}
|
||||
|
||||
#[derive(Debug, Machine)]
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
#[derive(Debug, Machine)]
|
||||
pub struct ExtruderV2 {
|
||||
machine_identification_unique: MachineIdentificationUnique,
|
||||
namespace: ExtruderV2Namespace,
|
||||
last_measurement_emit: Instant,
|
||||
last_status_hash: Option<u64>,
|
||||
@@ -126,6 +138,9 @@ impl ExtruderV2 {
|
||||
}
|
||||
|
||||
// Funktionen ohne emit_state bleiben hier
|
||||
|
||||
// Set all relais to ZERO
|
||||
// We dont need a function to enable again though, as the act Loop will detect the mode
|
||||
fn turn_heating_off(&mut self) {
|
||||
self.temperature_controller_back.disable();
|
||||
self.temperature_controller_front.disable();
|
||||
|
||||
@@ -1,47 +1,71 @@
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use super::{
|
||||
ExtruderV2, ExtruderV2Mode, Heating, api::ExtruderV2Namespace, mitsubishi_cs80::MitsubishiCS80,
|
||||
screw_speed_controller::ScrewSpeedController,
|
||||
};
|
||||
use crate::machines::{
|
||||
extruder1::temperature_controller::TemperatureController, get_ethercat_device,
|
||||
};
|
||||
use crate::machines::get_ethercat_device;
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use anyhow::Error;
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use control_core::machines::new::MachineNewHardware;
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use control_core::machines::new::{MachineNewParams, MachineNewTrait};
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use control_core::machines::new::{
|
||||
MachineNewHardware, MachineNewParams, MachineNewTrait, validate_no_role_dublicates,
|
||||
validate_same_machine_identification_unique,
|
||||
validate_no_role_dublicates, validate_same_machine_identification_unique,
|
||||
};
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use ethercat_hal::coe::ConfigurableDevice;
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use ethercat_hal::devices::{
|
||||
EthercatDeviceUsed,
|
||||
ek1100::EK1100,
|
||||
ek1100::EK1100_IDENTITY_A,
|
||||
el1002::EL1002,
|
||||
el1002::EL1002_IDENTITY_A,
|
||||
el6021::EL6021,
|
||||
el6021::EL6021Configuration,
|
||||
el6021::EL6021Port,
|
||||
el6021::{EL6021_IDENTITY_A, EL6021_IDENTITY_B, EL6021_IDENTITY_C, EL6021_IDENTITY_D},
|
||||
};
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use std::time::Duration;
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use std::time::Instant;
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use uom::si::angular_velocity::AngularVelocity;
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use uom::si::angular_velocity::revolution_per_minute;
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use uom::si::pressure::Pressure;
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use uom::si::pressure::bar;
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use ethercat_hal::{
|
||||
coe::ConfigurableDevice,
|
||||
devices::{
|
||||
EthercatDeviceUsed,
|
||||
ek1100::{EK1100, EK1100_IDENTITY_A},
|
||||
el1002::{EL1002, EL1002_IDENTITY_A},
|
||||
el2004::{EL2004, EL2004_IDENTITY_A, EL2004Port},
|
||||
el3021::{EL3021, EL3021_IDENTITY_A, EL3021Port},
|
||||
el3204::{EL3204, EL3204_IDENTITY_A, EL3204_IDENTITY_B, EL3204Port},
|
||||
el6021::{
|
||||
self, EL6021, EL6021_IDENTITY_A, EL6021_IDENTITY_B, EL6021_IDENTITY_C,
|
||||
EL6021_IDENTITY_D, EL6021Configuration,
|
||||
},
|
||||
},
|
||||
io::{
|
||||
analog_input::AnalogInput, digital_output::DigitalOutput,
|
||||
serial_interface::SerialInterface, temperature_input::TemperatureInput,
|
||||
},
|
||||
};
|
||||
use std::time::{Duration, Instant};
|
||||
use uom::si::{
|
||||
angular_velocity::revolution_per_minute,
|
||||
f64::{AngularVelocity, Pressure, ThermodynamicTemperature},
|
||||
pressure::bar,
|
||||
thermodynamic_temperature::degree_celsius,
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use uom::si::thermodynamic_temperature::{ThermodynamicTemperature, degree_celsius};
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use crate::machines::extruder1::temperature_controller::TemperatureController;
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use super::{
|
||||
ExtruderV2, ExtruderV2Mode, Heating, api::ExtruderV2Namespace, mitsubishi_cs80::MitsubishiCS80,
|
||||
screw_speed_controller::ScrewSpeedController,
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
impl MachineNewTrait for ExtruderV2 {
|
||||
fn new<'maindevice>(params: &MachineNewParams) -> Result<Self, Error> {
|
||||
// validate general stuff
|
||||
|
||||
let device_identification = params.device_group.to_vec();
|
||||
|
||||
validate_same_machine_identification_unique(&device_identification)?;
|
||||
@@ -189,8 +213,7 @@ impl MachineNewTrait for ExtruderV2 {
|
||||
0.95,
|
||||
);
|
||||
|
||||
let inverter =
|
||||
MitsubishiCS80::new(SerialInterface::new(el6021, el6021::EL6021Port::SI1));
|
||||
let inverter = MitsubishiCS80::new(SerialInterface::new(el6021, EL6021Port::SI1));
|
||||
|
||||
let target_pressure = Pressure::new::<bar>(0.0);
|
||||
let target_rpm = AngularVelocity::new::<revolution_per_minute>(0.0);
|
||||
@@ -198,8 +221,11 @@ impl MachineNewTrait for ExtruderV2 {
|
||||
let screw_speed_controller =
|
||||
ScrewSpeedController::new(inverter, target_pressure, target_rpm, pressure_sensor);
|
||||
|
||||
let mut extruder: Self = Self {
|
||||
namespace: ExtruderV2Namespace::new(params.socket_queue_tx.clone()),
|
||||
let mut extruder: ExtruderV2 = Self {
|
||||
machine_identification_unique: params.get_machine_identification_unique(),
|
||||
namespace: ExtruderV2Namespace {
|
||||
namespace: params.namespace.clone(),
|
||||
},
|
||||
last_measurement_emit: Instant::now(),
|
||||
mode: ExtruderV2Mode::Standby,
|
||||
total_energy_kwh: 0.0,
|
||||
|
||||
@@ -12,8 +12,7 @@ use control_core::{
|
||||
use control_core_derive::BuildEvent;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use smol::channel::Sender;
|
||||
use socketioxide::extract::SocketRef;
|
||||
use smol::lock::Mutex;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use tracing::instrument;
|
||||
|
||||
@@ -62,15 +61,7 @@ pub enum LaserEvents {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LaserMachineNamespace {
|
||||
pub namespace: Namespace,
|
||||
}
|
||||
|
||||
impl LaserMachineNamespace {
|
||||
pub fn new(socket_queue_tx: Sender<(SocketRef, Arc<GenericEvent>)>) -> Self {
|
||||
Self {
|
||||
namespace: Namespace::new(socket_queue_tx),
|
||||
}
|
||||
}
|
||||
pub namespace: Arc<Mutex<Namespace>>,
|
||||
}
|
||||
|
||||
impl CacheableEvents<Self> for LaserEvents {
|
||||
@@ -107,7 +98,9 @@ impl NamespaceCacheingLogic<LaserEvents> for LaserMachineNamespace {
|
||||
fn emit(&mut self, events: LaserEvents) {
|
||||
let event = Arc::new(events.event_value());
|
||||
let buffer_fn = events.event_cache_fn();
|
||||
self.namespace.emit(event, &buffer_fn);
|
||||
|
||||
let mut namespace = self.namespace.lock_blocking();
|
||||
namespace.emit(event, &buffer_fn);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,7 +121,7 @@ impl MachineApi for LaserMachine {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn api_event_namespace(&mut self) -> &mut Namespace {
|
||||
&mut self.namespace.namespace
|
||||
fn api_event_namespace(&mut self) -> Arc<Mutex<Namespace>> {
|
||||
self.namespace.namespace.clone()
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,8 @@ use crate::{
|
||||
};
|
||||
use api::{LaserEvents, LaserMachineNamespace, LaserState, LiveValuesEvent, StateEvent};
|
||||
use control_core::{
|
||||
machines::identification::MachineIdentification, socketio::namespace::NamespaceCacheingLogic,
|
||||
machines::identification::{MachineIdentification, MachineIdentificationUnique},
|
||||
socketio::namespace::NamespaceCacheingLogic,
|
||||
};
|
||||
use control_core_derive::Machine;
|
||||
use smol::lock::RwLock;
|
||||
@@ -17,6 +18,8 @@ pub mod new;
|
||||
|
||||
#[derive(Debug, Machine)]
|
||||
pub struct LaserMachine {
|
||||
machine_identification_unique: MachineIdentificationUnique,
|
||||
|
||||
// drivers
|
||||
laser: Arc<RwLock<Laser>>,
|
||||
|
||||
@@ -43,9 +46,7 @@ impl LaserMachine {
|
||||
vendor: VENDOR_QITECH,
|
||||
machine: MACHINE_LASER_V1,
|
||||
};
|
||||
}
|
||||
|
||||
impl LaserMachine {
|
||||
///diameter in mm
|
||||
pub fn emit_live_values(&mut self) {
|
||||
let diameter = self.diameter.get::<millimeter>();
|
||||
|
||||
@@ -4,11 +4,9 @@ use crate::serial::{devices::laser::Laser, registry::SERIAL_DEVICE_REGISTRY};
|
||||
|
||||
use super::{LaserMachine, LaserTarget, api::LaserMachineNamespace};
|
||||
use anyhow::Error;
|
||||
use control_core::machines::new::MachineNewTrait;
|
||||
use uom::{
|
||||
ConstZero,
|
||||
si::{f64::Length, length::millimeter},
|
||||
};
|
||||
use control_core::machines::new::{MachineNewHardware, MachineNewTrait};
|
||||
use uom::ConstZero;
|
||||
use uom::si::{f64::Length, length::millimeter};
|
||||
|
||||
impl MachineNewTrait for LaserMachine {
|
||||
fn new(
|
||||
@@ -18,7 +16,7 @@ impl MachineNewTrait for LaserMachine {
|
||||
Self: Sized,
|
||||
{
|
||||
let hardware_serial = match params.hardware {
|
||||
control_core::machines::new::MachineNewHardware::Serial(serial) => *serial,
|
||||
MachineNewHardware::Serial(serial) => *serial,
|
||||
_ => return Err(Error::msg("Invalid hardware type for LaserMachine")),
|
||||
};
|
||||
|
||||
@@ -37,8 +35,11 @@ impl MachineNewTrait for LaserMachine {
|
||||
diameter: Length::new::<millimeter>(1.75),
|
||||
};
|
||||
let mut laser_machine = Self {
|
||||
machine_identification_unique: params.get_machine_identification_unique(),
|
||||
laser,
|
||||
namespace: LaserMachineNamespace::new(params.socket_queue_tx.clone()),
|
||||
namespace: LaserMachineNamespace {
|
||||
namespace: params.namespace.clone(),
|
||||
},
|
||||
last_measurement_emit: Instant::now(),
|
||||
laser_target,
|
||||
emitted_default_state: false,
|
||||
|
||||
@@ -12,8 +12,7 @@ use control_core::{
|
||||
use control_core_derive::BuildEvent;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use smol::channel::Sender;
|
||||
use socketioxide::extract::SocketRef;
|
||||
use smol::lock::Mutex;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use tracing::instrument;
|
||||
|
||||
@@ -61,15 +60,7 @@ pub enum MockEvents {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MockMachineNamespace {
|
||||
pub namespace: Namespace,
|
||||
}
|
||||
|
||||
impl MockMachineNamespace {
|
||||
pub fn new(socket_queue_tx: Sender<(SocketRef, Arc<GenericEvent>)>) -> Self {
|
||||
Self {
|
||||
namespace: Namespace::new(socket_queue_tx),
|
||||
}
|
||||
}
|
||||
pub namespace: Arc<Mutex<Namespace>>,
|
||||
}
|
||||
|
||||
impl CacheableEvents<Self> for MockEvents {
|
||||
@@ -106,7 +97,9 @@ impl NamespaceCacheingLogic<MockEvents> for MockMachineNamespace {
|
||||
fn emit(&mut self, events: MockEvents) {
|
||||
let event = Arc::new(events.event_value());
|
||||
let buffer_fn = events.event_cache_fn();
|
||||
self.namespace.emit(event, &buffer_fn);
|
||||
|
||||
let mut namespace = self.namespace.lock_blocking();
|
||||
namespace.emit(event, &buffer_fn);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,7 +123,7 @@ impl MachineApi for MockMachine {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn api_event_namespace(&mut self) -> &mut Namespace {
|
||||
&mut self.namespace.namespace
|
||||
fn api_event_namespace(&mut self) -> Arc<Mutex<Namespace>> {
|
||||
self.namespace.namespace.clone()
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::machines::{MACHINE_MOCK, VENDOR_QITECH};
|
||||
use api::{LiveValuesEvent, MockEvents, MockMachineNamespace, Mode, ModeState, StateEvent};
|
||||
use control_core::machines::identification::MachineIdentificationUnique;
|
||||
use control_core::socketio::event::BuildEvent;
|
||||
use control_core::{
|
||||
machines::identification::MachineIdentification, socketio::namespace::NamespaceCacheingLogic,
|
||||
@@ -19,6 +20,8 @@ pub mod new;
|
||||
|
||||
#[derive(Debug, Machine)]
|
||||
pub struct MockMachine {
|
||||
machine_identification_unique: MachineIdentificationUnique,
|
||||
|
||||
// socketio
|
||||
namespace: MockMachineNamespace,
|
||||
last_measurement_emit: Instant,
|
||||
@@ -40,12 +43,10 @@ pub struct MockMachine {
|
||||
|
||||
impl MockMachine {
|
||||
pub const MACHINE_IDENTIFICATION: MachineIdentification = MachineIdentification {
|
||||
vendor: VENDOR_QITECH,
|
||||
machine: MACHINE_MOCK,
|
||||
vendor: VENDOR_QITECH,
|
||||
};
|
||||
}
|
||||
|
||||
impl MockMachine {
|
||||
/// Emit live values data event with the current sine wave amplitude
|
||||
pub fn emit_live_values(&mut self) {
|
||||
let now = Instant::now();
|
||||
|
||||
@@ -5,12 +5,12 @@ use super::{
|
||||
api::{MockMachineNamespace, Mode},
|
||||
};
|
||||
use anyhow::Error;
|
||||
use control_core::machines::new::{MachineNewHardware, MachineNewTrait};
|
||||
use control_core::machines::new::{MachineNewHardware, MachineNewParams, MachineNewTrait};
|
||||
use uom::si::{f64::Frequency, frequency::hertz};
|
||||
|
||||
impl MachineNewTrait for MockMachine {
|
||||
fn new(
|
||||
params: &control_core::machines::new::MachineNewParams<'_, '_, '_, '_, '_, '_, '_>,
|
||||
fn new<'maindevice, 'subdevices>(
|
||||
params: &MachineNewParams<'maindevice, 'subdevices, '_, '_, '_, '_, '_>,
|
||||
) -> Result<Self, Error>
|
||||
where
|
||||
Self: Sized,
|
||||
@@ -32,7 +32,10 @@ impl MachineNewTrait for MockMachine {
|
||||
let now = Instant::now();
|
||||
|
||||
let mut mock_machine = Self {
|
||||
namespace: MockMachineNamespace::new(params.socket_queue_tx.clone()),
|
||||
machine_identification_unique: params.get_machine_identification_unique(),
|
||||
namespace: MockMachineNamespace {
|
||||
namespace: params.namespace.clone(),
|
||||
},
|
||||
last_measurement_emit: now,
|
||||
t_0: now, // Initialize start time to current time
|
||||
frequency1: Frequency::new::<hertz>(0.1), // Default frequency1 of 100 mHz
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use super::Winder2;
|
||||
use control_core::machines::new::MachineAct;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
impl MachineAct for Winder2 {
|
||||
fn act(&mut self, now: Instant) {
|
||||
@@ -21,17 +20,10 @@ impl MachineAct for Winder2 {
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
// sync the puller speed to the buffer output speed
|
||||
self.sync_buffer_speed();
|
||||
|
||||
// more than 33ms have passed since last emit (30 "fps" target)
|
||||
if now.duration_since(self.last_measurement_emit) > Duration::from_secs_f64(1.0 / 30.0) {
|
||||
self.emit_live_values();
|
||||
self.last_measurement_emit = now;
|
||||
// check if traverse state changed
|
||||
if self.traverse_controller.did_change_state() {
|
||||
self.emit_state();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
use super::{Winder2, Winder2Mode, puller_speed_controller::PullerRegulationMode};
|
||||
use control_core::{
|
||||
machines::{api::MachineApi, identification::MachineIdentificationUnique},
|
||||
machines::{
|
||||
api::MachineApi, connection::MachineCrossConnectionState,
|
||||
identification::MachineIdentificationUnique,
|
||||
},
|
||||
socketio::{
|
||||
event::{Event, GenericEvent},
|
||||
namespace::{
|
||||
@@ -13,8 +16,7 @@ use control_core::{
|
||||
use control_core_derive::BuildEvent;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use smol::channel::Sender;
|
||||
use socketioxide::extract::SocketRef;
|
||||
use smol::lock::Mutex;
|
||||
use std::{
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
@@ -103,9 +105,6 @@ enum Mutation {
|
||||
|
||||
// Disconnect Machine
|
||||
DisconnectMachine(MachineIdentificationUnique),
|
||||
|
||||
// Connected Buffer
|
||||
StartBuffering(),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone, Default)]
|
||||
@@ -144,7 +143,7 @@ pub struct StateEvent {
|
||||
/// spool speed controller state
|
||||
pub spool_speed_controller_state: SpoolSpeedControllerState,
|
||||
/// connected machine state
|
||||
pub connected_machine_state: ConnectedMachineState,
|
||||
pub connected_machine_state: MachineCrossConnectionState,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
@@ -240,13 +239,6 @@ pub struct SpoolSpeedControllerState {
|
||||
pub adaptive_deacceleration_urgency_multiplier: f64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
pub struct ConnectedMachineState {
|
||||
/// Connected Machine
|
||||
pub machine_identification_unique: Option<MachineIdentificationUnique>,
|
||||
pub is_available: bool,
|
||||
}
|
||||
|
||||
pub enum Winder2Events {
|
||||
LiveValues(Event<LiveValuesEvent>),
|
||||
State(Event<StateEvent>),
|
||||
@@ -254,7 +246,7 @@ pub enum Winder2Events {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Winder2Namespace {
|
||||
pub namespace: Namespace,
|
||||
pub namespace: Arc<Mutex<Namespace>>,
|
||||
}
|
||||
|
||||
impl NamespaceCacheingLogic<Winder2Events> for Winder2Namespace {
|
||||
@@ -262,15 +254,9 @@ impl NamespaceCacheingLogic<Winder2Events> for Winder2Namespace {
|
||||
fn emit(&mut self, events: Winder2Events) {
|
||||
let event = Arc::new(events.event_value());
|
||||
let buffer_fn = events.event_cache_fn();
|
||||
self.namespace.emit(event, &buffer_fn);
|
||||
}
|
||||
}
|
||||
|
||||
impl Winder2Namespace {
|
||||
pub fn new(socket_queue_tx: Sender<(SocketRef, Arc<GenericEvent>)>) -> Self {
|
||||
Self {
|
||||
namespace: Namespace::new(socket_queue_tx),
|
||||
}
|
||||
let mut namespace = self.namespace.lock_blocking();
|
||||
namespace.emit(event, &buffer_fn);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -307,9 +293,7 @@ impl MachineApi for Winder2 {
|
||||
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::SetPullerTargetSpeed(value) => self.puller_set_target_speed(value),
|
||||
Mutation::SetPullerTargetDiameter(_) => todo!(),
|
||||
Mutation::SetPullerForward(value) => self.puller_set_forward(value),
|
||||
Mutation::SetSpoolRegulationMode(mode) => self.spool_set_regulation_mode(mode),
|
||||
@@ -342,12 +326,11 @@ impl MachineApi for Winder2 {
|
||||
Mutation::DisconnectMachine(machine_identification_unique) => {
|
||||
self.disconnect_buffer(machine_identification_unique)
|
||||
}
|
||||
Mutation::StartBuffering() => self.start_buffering(),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn api_event_namespace(&mut self) -> &mut Namespace {
|
||||
&mut self.namespace.namespace
|
||||
fn api_event_namespace(&mut self) -> Arc<Mutex<Namespace>> {
|
||||
self.namespace.namespace.clone()
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,9 @@ pub mod puller_speed_controller;
|
||||
pub mod spool_speed_controller;
|
||||
pub mod tension_arm;
|
||||
pub mod traverse_controller;
|
||||
|
||||
use std::{fmt::Debug, sync::Weak, time::Instant};
|
||||
|
||||
use api::{
|
||||
LiveValuesEvent, ModeState, PullerState, SpoolAutomaticActionMode, SpoolAutomaticActionState,
|
||||
SpoolSpeedControllerState, StateEvent, TensionArmState, TraverseState, Winder2Events,
|
||||
@@ -18,7 +21,7 @@ use control_core::socketio::event::BuildEvent;
|
||||
use control_core::{
|
||||
converters::angular_step_converter::AngularStepConverter,
|
||||
machines::{
|
||||
ConnectedMachine, ConnectedMachineData, downcast_machine,
|
||||
connection::{CrossConnectableMachine, MachineCrossConnection},
|
||||
identification::{MachineIdentification, MachineIdentificationUnique},
|
||||
manager::MachineManager,
|
||||
},
|
||||
@@ -30,15 +33,9 @@ use ethercat_hal::io::{
|
||||
digital_input::DigitalInput, digital_output::DigitalOutput,
|
||||
stepper_velocity_el70x1::StepperVelocityEL70x1,
|
||||
};
|
||||
use futures::executor::block_on;
|
||||
use puller_speed_controller::{PullerRegulationMode, PullerSpeedController};
|
||||
use smol::lock::{Mutex, RwLock};
|
||||
use smol::lock::RwLock;
|
||||
use spool_speed_controller::SpoolSpeedController;
|
||||
use std::{
|
||||
fmt::Debug,
|
||||
sync::{Arc, Weak},
|
||||
time::Instant,
|
||||
};
|
||||
use tension_arm::TensionArm;
|
||||
use traverse_controller::TraverseController;
|
||||
use uom::{
|
||||
@@ -52,9 +49,7 @@ use uom::{
|
||||
},
|
||||
};
|
||||
|
||||
use crate::machines::{
|
||||
MACHINE_WINDER_V1, VENDOR_QITECH, buffer1::BufferV1, winder2::api::ConnectedMachineState,
|
||||
};
|
||||
use crate::machines::{MACHINE_WINDER_V1, VENDOR_QITECH, buffer1::BufferV1};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SpoolAutomaticAction {
|
||||
@@ -84,11 +79,9 @@ pub struct Winder2 {
|
||||
// machine connection
|
||||
pub machine_manager: Weak<RwLock<MachineManager>>,
|
||||
pub machine_identification_unique: MachineIdentificationUnique,
|
||||
// Buffer State
|
||||
pub buffer_state: BufferState,
|
||||
|
||||
// connected machines
|
||||
pub connected_buffer: Option<ConnectedMachine<Weak<Mutex<BufferV1>>>>,
|
||||
pub connected_buffer: MachineCrossConnection<Winder2, BufferV1>,
|
||||
|
||||
// mode
|
||||
pub mode: Winder2Mode,
|
||||
@@ -111,15 +104,19 @@ pub struct Winder2 {
|
||||
emitted_default_state: bool,
|
||||
}
|
||||
|
||||
impl CrossConnectableMachine<Winder2, BufferV1> for Winder2 {
|
||||
fn get_cross_connection(&mut self) -> &mut MachineCrossConnection<Winder2, BufferV1> {
|
||||
&mut self.connected_buffer
|
||||
}
|
||||
}
|
||||
|
||||
impl Winder2 {
|
||||
pub const MACHINE_IDENTIFICATION: MachineIdentification = MachineIdentification {
|
||||
vendor: VENDOR_QITECH,
|
||||
machine: MACHINE_WINDER_V1,
|
||||
};
|
||||
}
|
||||
|
||||
/// Implement Traverse
|
||||
impl Winder2 {
|
||||
/// Implement Traverse
|
||||
fn set_laser(&mut self, value: bool) {
|
||||
self.laser.set(value);
|
||||
self.emit_state();
|
||||
@@ -313,20 +310,7 @@ impl Winder2 {
|
||||
spool_required_meters: self.spool_automatic_action.target_length.get::<meter>(),
|
||||
spool_automatic_action_mode: self.spool_automatic_action.mode.clone(),
|
||||
},
|
||||
connected_machine_state: ConnectedMachineState {
|
||||
machine_identification_unique: self.connected_buffer.as_ref().map(
|
||||
|connected_machine| {
|
||||
ConnectedMachineData::from(connected_machine).machine_identification_unique
|
||||
},
|
||||
),
|
||||
is_available: self
|
||||
.connected_buffer
|
||||
.as_ref()
|
||||
.map(|connected_machine| {
|
||||
ConnectedMachineData::from(connected_machine).is_available
|
||||
})
|
||||
.unwrap_or(false),
|
||||
},
|
||||
connected_machine_state: self.connected_buffer.to_state(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -387,13 +371,9 @@ impl Winder2 {
|
||||
&& !self.traverse_controller.is_traversing()
|
||||
&& self.mode != Winder2Mode::Wind
|
||||
}
|
||||
}
|
||||
|
||||
impl Winder2 {}
|
||||
|
||||
/// Implement Mode
|
||||
impl Winder2 {
|
||||
pub fn set_mode(&mut self, mode: &Winder2Mode) {
|
||||
/// Implement Mode
|
||||
fn set_mode(&mut self, mode: &Winder2Mode) {
|
||||
let should_update = *mode != Winder2Mode::Wind || self.can_wind();
|
||||
|
||||
if should_update {
|
||||
@@ -579,20 +559,16 @@ impl Winder2 {
|
||||
// Update the internal state
|
||||
self.puller_mode = mode;
|
||||
}
|
||||
}
|
||||
|
||||
/// Implement Tension Arm
|
||||
impl Winder2 {
|
||||
/// Implement Tension Arm
|
||||
fn tension_arm_zero(&mut self) {
|
||||
self.tension_arm.zero();
|
||||
self.emit_live_values(); // For angle update
|
||||
// For state update
|
||||
self.emit_state();
|
||||
}
|
||||
}
|
||||
|
||||
/// Implement Spool
|
||||
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(
|
||||
@@ -672,10 +648,8 @@ impl Winder2 {
|
||||
self.spool_automatic_action.progress += meters_pulled_this_interval;
|
||||
self.spool_automatic_action.progress_last_check = now;
|
||||
}
|
||||
}
|
||||
|
||||
/// Implement Puller
|
||||
impl Winder2 {
|
||||
/// 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);
|
||||
@@ -779,39 +753,8 @@ impl Winder2 {
|
||||
.set_adaptive_deacceleration_urgency_multiplier(deacceleration_urgency_multiplier);
|
||||
self.emit_state();
|
||||
}
|
||||
}
|
||||
|
||||
/// Buffer
|
||||
impl Winder2 {
|
||||
/// Sync Puller to Buffer
|
||||
pub fn sync_buffer_speed(&mut self) {
|
||||
// send puller speed to buffer, if connected
|
||||
self.get_buffer(|buffer| {
|
||||
buffer
|
||||
.buffer_lift_controller
|
||||
.set_target_output_speed(self.puller_speed_controller.get_target_speed());
|
||||
});
|
||||
}
|
||||
|
||||
/// Start Buffering Process
|
||||
pub fn start_buffering(&mut self) {
|
||||
// change winder's Buffer state to Buffering
|
||||
self.set_buffer_state(BufferState::Buffering);
|
||||
|
||||
self.puller_speed_controller
|
||||
.set_regulation_mode(PullerRegulationMode::Buffer);
|
||||
// change Buffer state to Filling
|
||||
self.get_buffer(|buffer| {
|
||||
buffer.set_mode_state(super::buffer1::BufferV1Mode::Filling);
|
||||
});
|
||||
}
|
||||
|
||||
/// Set Buffer State
|
||||
pub fn set_buffer_state(&mut self, state: BufferState) {
|
||||
self.buffer_state = state;
|
||||
}
|
||||
|
||||
/// Buffer Connection Logic
|
||||
/// implement machine connection
|
||||
/// set connected buffer
|
||||
pub fn set_connected_buffer(
|
||||
&mut self,
|
||||
@@ -823,33 +766,12 @@ impl Winder2 {
|
||||
) {
|
||||
return;
|
||||
}
|
||||
let machine_manager_arc = match self.machine_manager.upgrade() {
|
||||
Some(machine_manager_arc) => machine_manager_arc,
|
||||
None => return,
|
||||
};
|
||||
let machine_manager_guard = block_on(machine_manager_arc.read());
|
||||
let buffer_weak = machine_manager_guard.get_machine_weak(&machine_identification_unique);
|
||||
let buffer_weak = match buffer_weak {
|
||||
Some(buffer_weak) => buffer_weak,
|
||||
None => return,
|
||||
};
|
||||
let buffer_strong = match buffer_weak.upgrade() {
|
||||
Some(buffer_strong) => buffer_strong,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let buffer: Arc<Mutex<BufferV1>> = block_on(downcast_machine::<BufferV1>(buffer_strong))
|
||||
.expect("failed downcasting machine");
|
||||
self.connected_buffer
|
||||
.set_connected_machine(&machine_identification_unique);
|
||||
self.connected_buffer.reverse_connect();
|
||||
|
||||
let machine = Arc::downgrade(&buffer);
|
||||
|
||||
self.connected_buffer = Some(ConnectedMachine {
|
||||
machine_identification_unique,
|
||||
machine,
|
||||
});
|
||||
self.emit_state();
|
||||
|
||||
self.reverse_connect();
|
||||
}
|
||||
|
||||
/// disconnect buffer
|
||||
@@ -863,59 +785,12 @@ impl Winder2 {
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if let Some(connected) = &self.connected_buffer {
|
||||
if let Some(buffer_arc) = connected.machine.upgrade() {
|
||||
let future = async move {
|
||||
let mut buffer = buffer_arc.lock().await;
|
||||
if buffer.connected_winder.is_some() {
|
||||
buffer.connected_winder = None;
|
||||
buffer.emit_state();
|
||||
}
|
||||
};
|
||||
smol::spawn(future).detach();
|
||||
}
|
||||
}
|
||||
self.connected_buffer = None;
|
||||
|
||||
self.connected_buffer.reverse_disconnect();
|
||||
self.connected_buffer.disconnect();
|
||||
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// initiate connection from buffer to winder
|
||||
pub fn reverse_connect(&mut self) {
|
||||
let machine_identification_unique = self.machine_identification_unique.clone();
|
||||
if let Some(connected) = &self.connected_buffer {
|
||||
if let Some(buffer_arc) = connected.machine.upgrade() {
|
||||
let future = async move {
|
||||
let mut buffer = buffer_arc.lock().await;
|
||||
if buffer.connected_winder.is_none() {
|
||||
buffer.set_connected_winder(machine_identification_unique);
|
||||
}
|
||||
};
|
||||
smol::spawn(future).detach();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This helper function provides an easy way
|
||||
/// to get the machine out of the Weak Reference
|
||||
///
|
||||
/// Usage:
|
||||
///
|
||||
/// self.get_buffer(|buffer| {
|
||||
/// buffer.do_something // Use the Buffer here as usual
|
||||
/// });
|
||||
fn get_buffer<F, R>(&self, func: F) -> Option<R>
|
||||
where
|
||||
F: FnOnce(&mut BufferV1) -> R,
|
||||
{
|
||||
self.connected_buffer
|
||||
.as_ref()?
|
||||
.machine
|
||||
.upgrade()
|
||||
.map(|buffer_arc| {
|
||||
let mut buffer = block_on(buffer_arc.lock());
|
||||
func(&mut buffer)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
@@ -980,14 +855,6 @@ impl From<Winder2Mode> for PullerMode {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum BufferState {
|
||||
Disconnected,
|
||||
Hold,
|
||||
Buffering,
|
||||
Emptying,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Winder2 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Winder2")
|
||||
|
||||
@@ -4,13 +4,13 @@ use super::api::Winder2Namespace;
|
||||
use super::tension_arm::TensionArm;
|
||||
use super::{Winder2, Winder2Mode};
|
||||
use crate::machines::get_ethercat_device;
|
||||
use crate::machines::winder2::BufferState;
|
||||
use crate::machines::winder2::puller_speed_controller::PullerSpeedController;
|
||||
use crate::machines::winder2::spool_speed_controller::SpoolSpeedController;
|
||||
use crate::machines::winder2::traverse_controller::TraverseController;
|
||||
use anyhow::Error;
|
||||
use control_core::converters::angular_step_converter::AngularStepConverter;
|
||||
use control_core::converters::linear_step_converter::LinearStepConverter;
|
||||
use control_core::machines::connection::MachineCrossConnection;
|
||||
use control_core::machines::new::{
|
||||
MachineNewHardware, MachineNewParams, MachineNewTrait, validate_no_role_dublicates,
|
||||
validate_same_machine_identification_unique,
|
||||
@@ -198,7 +198,9 @@ impl MachineNewTrait for Winder2 {
|
||||
EL7031_0030AnalogInputPort::AI1,
|
||||
)),
|
||||
laser: DigitalOutput::new(el2002, EL2002Port::DO1),
|
||||
namespace: Winder2Namespace::new(params.socket_queue_tx.clone()),
|
||||
namespace: Winder2Namespace {
|
||||
namespace: params.namespace.clone(),
|
||||
},
|
||||
mode: mode.clone(),
|
||||
spool_step_converter: AngularStepConverter::new(200),
|
||||
spool_speed_controller: SpoolSpeedController::new(),
|
||||
@@ -228,8 +230,10 @@ impl MachineNewTrait for Winder2 {
|
||||
},
|
||||
machine_manager: params.machine_manager.clone(),
|
||||
machine_identification_unique: machine_id,
|
||||
connected_buffer: None,
|
||||
buffer_state: BufferState::Disconnected,
|
||||
connected_buffer: MachineCrossConnection::new(
|
||||
params.machine_manager.clone(),
|
||||
¶ms.get_machine_identification_unique(),
|
||||
),
|
||||
};
|
||||
|
||||
// initalize events
|
||||
|
||||
@@ -19,12 +19,11 @@ pub struct PullerSpeedController {
|
||||
enabled: bool,
|
||||
pub target_speed: Velocity,
|
||||
pub target_diameter: Length,
|
||||
pub buffer_speed: Velocity,
|
||||
pub regulation_mode: PullerRegulationMode,
|
||||
/// Forward rotation direction. If false, applies negative sign to speed
|
||||
pub forward: bool,
|
||||
/// Linear acceleration controller to dampen speed change
|
||||
pub acceleration_controller: LinearJerkSpeedController,
|
||||
acceleration_controller: LinearJerkSpeedController,
|
||||
/// Converter for linear to angular transformations
|
||||
pub converter: LinearStepConverter,
|
||||
pub last_speed: Velocity,
|
||||
@@ -44,7 +43,6 @@ impl PullerSpeedController {
|
||||
enabled: false,
|
||||
target_speed,
|
||||
target_diameter,
|
||||
buffer_speed: Velocity::ZERO,
|
||||
regulation_mode: PullerRegulationMode::Speed,
|
||||
forward: true,
|
||||
acceleration_controller: LinearJerkSpeedController::new_simple(
|
||||
@@ -69,10 +67,6 @@ impl PullerSpeedController {
|
||||
self.target_diameter = target;
|
||||
}
|
||||
|
||||
pub fn set_buffer_speed(&mut self, speed: Velocity) {
|
||||
self.buffer_speed = speed;
|
||||
}
|
||||
|
||||
pub const fn set_regulation_mode(&mut self, regulation: PullerRegulationMode) {
|
||||
self.regulation_mode = regulation;
|
||||
}
|
||||
@@ -86,7 +80,6 @@ impl PullerSpeedController {
|
||||
true => match self.regulation_mode {
|
||||
PullerRegulationMode::Speed => self.target_speed,
|
||||
PullerRegulationMode::Diameter => unimplemented!(),
|
||||
PullerRegulationMode::Buffer => self.buffer_speed,
|
||||
},
|
||||
false => Velocity::ZERO,
|
||||
};
|
||||
@@ -123,5 +116,4 @@ impl PullerSpeedController {
|
||||
pub enum PullerRegulationMode {
|
||||
Speed,
|
||||
Diameter,
|
||||
Buffer,
|
||||
}
|
||||
+2
-1
@@ -14,7 +14,6 @@ use app_state::AppState;
|
||||
use mock::init::init_mock;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
|
||||
use ethercat::init::init_ethercat;
|
||||
use r#loop::init_loop;
|
||||
use rest::init::init_api;
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
@@ -23,6 +22,8 @@ use serial::init::init_serial;
|
||||
#[cfg(all(not(target_env = "msvc"), not(feature = "dhat-heap")))]
|
||||
use jemalloc_stats::init_jemalloc_stats;
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use crate::ethercat::init::init_ethercat;
|
||||
use crate::panic::init_panic;
|
||||
use crate::socketio::queue::init_socketio_queue;
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ pub fn init_mock(app_state: Arc<AppState>) -> Result<(), anyhow::Error> {
|
||||
};
|
||||
|
||||
// Create the mock serial device
|
||||
match MockSerialDevice::new_serial(&serial_params) {
|
||||
let _ = match MockSerialDevice::new_serial(&serial_params) {
|
||||
Ok((device_identification, mock_serial_device)) => {
|
||||
// Add the mock device to the machine manager
|
||||
{
|
||||
@@ -47,7 +47,7 @@ pub fn init_mock(app_state: Arc<AppState>) -> Result<(), anyhow::Error> {
|
||||
.write()
|
||||
.await
|
||||
.main_namespace;
|
||||
let event = MachinesEventBuilder().build(app_state_event.clone()).await;
|
||||
let event = MachinesEventBuilder().build(app_state_event.clone());
|
||||
main_namespace.emit(MainNamespaceEvents::MachinesEvent(event));
|
||||
Ok::<(), anyhow::Error>(())
|
||||
}
|
||||
@@ -79,7 +79,7 @@ pub fn init_mock(app_state: Arc<AppState>) -> Result<(), anyhow::Error> {
|
||||
.write()
|
||||
.await
|
||||
.main_namespace;
|
||||
let event = MachinesEventBuilder().build(app_state_event.clone()).await;
|
||||
let event = MachinesEventBuilder().build(app_state_event.clone());
|
||||
main_namespace.emit(MainNamespaceEvents::MachinesEvent(event));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -3,7 +3,10 @@ use crate::{
|
||||
rest::util::{ResponseUtil, ResponseUtilError},
|
||||
};
|
||||
use axum::{Json, body::Body, extract::State, http::Response};
|
||||
use control_core::rest::mutation::{MachineMutationBody, MutationResponse};
|
||||
use control_core::{
|
||||
machines::connection::MachineConnection,
|
||||
rest::mutation::{MachineMutationBody, MutationResponse},
|
||||
};
|
||||
use serde_json::Value;
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -27,7 +30,7 @@ async fn _post_machine_mutate(
|
||||
let machines_guard = app_state.machines.read().await;
|
||||
|
||||
// find machine with given identification in hashmap
|
||||
let machine = machines_guard
|
||||
let slot = machines_guard
|
||||
.get(&body.machine_identification_unique)
|
||||
.ok_or(anyhow::anyhow!(
|
||||
"[{}::_post_machine_mutate] Machine not found {:?}",
|
||||
@@ -35,14 +38,21 @@ async fn _post_machine_mutate(
|
||||
body.machine_identification_unique,
|
||||
))?;
|
||||
|
||||
// check machine for error
|
||||
let machine = match machine {
|
||||
Ok(m) => m,
|
||||
Err(e) => {
|
||||
// check machine for valid connection
|
||||
let connection = &slot.lock_blocking().machine_connection;
|
||||
let machine = match connection {
|
||||
MachineConnection::Connected(m) => m,
|
||||
MachineConnection::Error(error) => {
|
||||
return Err(anyhow::anyhow!(
|
||||
"[{}::_post_machine_mutate] Machine has error: {}",
|
||||
module_path!(),
|
||||
e
|
||||
error
|
||||
));
|
||||
}
|
||||
MachineConnection::Disconnected => {
|
||||
return Err(anyhow::anyhow!(
|
||||
"[{}::_post_machine_mutate] Machine is disconnected",
|
||||
module_path!()
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -68,8 +68,7 @@ pub fn init_serial(
|
||||
.write()
|
||||
.await
|
||||
.main_namespace;
|
||||
let event =
|
||||
MachinesEventBuilder().build(app_state_event.clone()).await;
|
||||
let event = MachinesEventBuilder().build(app_state_event.clone());
|
||||
main_namespace.emit(MainNamespaceEvents::MachinesEvent(event));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -20,20 +20,8 @@ pub struct MachinesEventBuilder();
|
||||
impl MachinesEventBuilder {
|
||||
const NAME: &'static str = "MachinesEvent";
|
||||
|
||||
pub async fn build(&self, app_state: Arc<AppState>) -> Event<MachinesEvent> {
|
||||
let mut machine_objs: Vec<_> = vec![];
|
||||
// add machines
|
||||
let machines_guard = app_state.machines.read().await;
|
||||
for machine in machines_guard.iter() {
|
||||
machine_objs.push(MachineObj {
|
||||
machine_identification_unique: machine.0.clone(),
|
||||
error: match machine.1 {
|
||||
Ok(_) => None,
|
||||
Err(e) => Some(e.to_string()),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
pub fn build(&self, app_state: Arc<AppState>) -> Event<MachinesEvent> {
|
||||
let machine_objs = app_state.get_machine_objs();
|
||||
Event::new(
|
||||
Self::NAME,
|
||||
MachinesEvent {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use control_core::socketio::{
|
||||
event::GenericEvent, namespace::Namespace, namespace_id::NamespaceId,
|
||||
use control_core::{
|
||||
machines::connection::MachineConnection,
|
||||
socketio::{event::GenericEvent, namespace::Namespace, namespace_id::NamespaceId},
|
||||
};
|
||||
use smol::channel::Sender;
|
||||
use socketioxide::extract::SocketRef;
|
||||
@@ -34,8 +35,8 @@ impl Namespaces {
|
||||
let machines_guard = app_state.machines.read().await;
|
||||
|
||||
// get machine
|
||||
let machine = match machines_guard.get(&machine_identification_unique) {
|
||||
Some(machine) => machine,
|
||||
let slot = match machines_guard.get(&machine_identification_unique) {
|
||||
Some(slot) => slot,
|
||||
None => {
|
||||
callback(Err(anyhow::anyhow!(
|
||||
"[{}::Namespaces::appply_mut] Machine {:?} not found",
|
||||
@@ -45,24 +46,32 @@ impl Namespaces {
|
||||
return;
|
||||
}
|
||||
};
|
||||
let slot = slot.lock_blocking();
|
||||
|
||||
// check if machine has error
|
||||
let machine = match machine {
|
||||
Ok(machine) => machine,
|
||||
Err(err) => {
|
||||
let machine = match &slot.machine_connection {
|
||||
MachineConnection::Error(error) => {
|
||||
callback(Err(anyhow::anyhow!(
|
||||
"[{}::Namespaces::appply_mut] Machine {:?} has error: {}",
|
||||
module_path!(),
|
||||
machine_identification_unique,
|
||||
err
|
||||
error
|
||||
)));
|
||||
return;
|
||||
}
|
||||
MachineConnection::Disconnected => {
|
||||
callback(Err(anyhow::anyhow!(
|
||||
"[{}::Namespaces::appply_mut] Machine {:?} has disconnected",
|
||||
module_path!(),
|
||||
machine_identification_unique
|
||||
)));
|
||||
return;
|
||||
}
|
||||
MachineConnection::Connected(mutex) => mutex,
|
||||
};
|
||||
|
||||
let mut machine_guard = machine.lock().await;
|
||||
let namespace = machine_guard.api_event_namespace();
|
||||
callback(Ok(namespace));
|
||||
callback(Ok(&mut namespace.lock_blocking()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user