Compare commits
41
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e86988ac4 | ||
|
|
2d28c18b1a | ||
|
|
b19764281a | ||
|
|
c25d63f781 | ||
|
|
d79efc412f | ||
|
|
7ad74cf089 | ||
|
|
6073d37892 | ||
|
|
c04937553f | ||
|
|
e08bdfa3c7 | ||
|
|
d282678b19 | ||
|
|
9ff4243350 | ||
|
|
6c79b33752 | ||
|
|
861b5da7a3 | ||
|
|
7dd3d50653 | ||
|
|
ba8e04c641 | ||
|
|
cece245a55 | ||
|
|
fab4d2810e | ||
|
|
5bff3ca929 | ||
|
|
afe1d3b80e | ||
|
|
6c8005dba9 | ||
|
|
9f399ca6f3 | ||
|
|
a7574dd348 | ||
|
|
b39f3597c8 | ||
|
|
bb9a86d52e | ||
|
|
e5329e40c6 | ||
|
|
319611f69d | ||
|
|
4b16253c8c | ||
|
|
b60423fde6 | ||
|
|
5d3399c72e | ||
|
|
d247bafcbb | ||
|
|
5227aad4c6 | ||
|
|
da4c54a99e | ||
|
|
8d42bd93f7 | ||
|
|
37e467272b | ||
|
|
ed0b9cd5ae | ||
|
|
8045f799f0 | ||
|
|
ca6ea16f89 | ||
|
|
452614abf1 | ||
|
|
f63c797cd1 | ||
|
|
de791f6ea8 | ||
|
|
024f751ef1 |
No files matched your search
@@ -1,3 +1,19 @@
|
||||
# `2.12.0`
|
||||
_17.11.2025_
|
||||
|
||||
## General
|
||||
- [#847](https://github.com/qitechgmbh/control/pull/847) Improved discovery and initialization
|
||||
- [#859](https://github.com/qitechgmbh/control/pull/859) Improved update speed
|
||||
- [#897](https://github.com/qitechgmbh/control/pull/897), [#899](https://github.com/qitechgmbh/control/pull/899), [#852](https://github.com/qitechgmbh/control/pull/852) Improved system stability
|
||||
- [#817](https://github.com/qitechgmbh/control/pull/817) Added an update bar
|
||||
|
||||
## Laser
|
||||
- [#857](https://github.com/qitechgmbh/control/pull/857) Fixed potential issues with 2 Axis Laser
|
||||
|
||||
## Extruder V2
|
||||
- [#881](https://github.com/qitechgmbh/control/pull/881) Extended Temperature PID Configuration
|
||||
|
||||
|
||||
# `2.11.0`
|
||||
_21.10.2025_
|
||||
|
||||
|
||||
Generated
+118
-714
File diff suppressed because it is too large.
Load diff
+4
-1
@@ -4,8 +4,11 @@ members = [
|
||||
"ethercat-hal",
|
||||
"ethercat-hal-derive",
|
||||
"control-core",
|
||||
"ethercat-eeprom-dump",
|
||||
"machines",
|
||||
#"ethercat-eeprom-dump",
|
||||
"control-core-derive",
|
||||
"units",
|
||||
"utils",
|
||||
]
|
||||
exclude = []
|
||||
resolver = "2"
|
||||
|
||||
@@ -12,17 +12,15 @@ anyhow = "1.0.100"
|
||||
axum = "0.8.6"
|
||||
|
||||
ethercat_hal = { path = "../ethercat-hal" }
|
||||
units = { path = "../units" }
|
||||
ethercrab = "0.6"
|
||||
interfaces = "0.0.9"
|
||||
serde = "1.0.219"
|
||||
serde_json = "1.0.143"
|
||||
serialport = "4.7.3"
|
||||
smol = "2.0.2"
|
||||
socketioxide = "0.17.2"
|
||||
uom = { version = "0.36.0", default-features = false, features = ["f64"] }
|
||||
bitvec = { version = "1.0.1", features = ["alloc"] }
|
||||
crc = "3.3.0"
|
||||
serial = "0.4.0"
|
||||
tracing = "0.1.41"
|
||||
erased-serde = "0.4.8"
|
||||
core_affinity = "0.8.3"
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use uom::si::{
|
||||
use units::{
|
||||
angular_acceleration::radian_per_second_squared,
|
||||
angular_velocity::radian_per_second,
|
||||
f64::{AngularAcceleration, AngularVelocity},
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use uom::si::{
|
||||
use units::{
|
||||
acceleration::meter_per_second_squared,
|
||||
f64::{Acceleration, Velocity},
|
||||
velocity::meter_per_second,
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use uom::si::{
|
||||
use units::{
|
||||
angle::radian,
|
||||
angular_acceleration::radian_per_second_squared,
|
||||
angular_velocity::radian_per_second,
|
||||
@@ -199,8 +199,8 @@ impl AngularAccelerationPositionController {
|
||||
/// # Example
|
||||
/// ```ignore
|
||||
/// use control_core::controllers::second_degree_motion::angular_acceleration_position_controller::AngularAccelerationPositionController;
|
||||
/// use uom::si::f64::Angle;
|
||||
/// use uom::si::angle::degree;
|
||||
/// use units::f64::Angle;
|
||||
/// use units::angle::degree;
|
||||
///
|
||||
/// let mut controller = AngularAccelerationPositionController::new_simple(
|
||||
/// Some(Angle::new::<degree>(180.0)),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use uom::si::{
|
||||
use units::{
|
||||
angular_acceleration::radian_per_second_squared,
|
||||
angular_jerk::radian_per_second_cubed,
|
||||
angular_velocity::radian_per_second,
|
||||
@@ -23,7 +23,7 @@ use super::jerk_speed_controller::JerkSpeedController;
|
||||
///
|
||||
/// # Example
|
||||
/// ```ignore
|
||||
/// use uom::si::{
|
||||
/// use units::{
|
||||
/// angular_velocity::revolution_per_minute,
|
||||
/// angular_acceleration::revolution_per_minute_per_second,
|
||||
/// angular_jerk::revolution_per_minute_per_second_squared,
|
||||
@@ -65,7 +65,7 @@ impl AngularJerkSpeedController {
|
||||
///
|
||||
/// # Example
|
||||
/// ```ignore
|
||||
/// use uom::si::{
|
||||
/// use units::{
|
||||
/// angular_velocity::revolution_per_minute,
|
||||
/// angular_acceleration::revolution_per_minute_per_second,
|
||||
/// angular_jerk::revolution_per_minute_per_second_squared,
|
||||
@@ -127,7 +127,7 @@ impl AngularJerkSpeedController {
|
||||
///
|
||||
/// # Example
|
||||
/// ```ignore
|
||||
/// use uom::si::{
|
||||
/// use units::{
|
||||
/// angular_velocity::revolution_per_minute,
|
||||
/// angular_acceleration::revolution_per_minute_per_second,
|
||||
/// angular_jerk::revolution_per_minute_per_second_squared,
|
||||
@@ -185,7 +185,7 @@ impl AngularJerkSpeedController {
|
||||
/// # Example
|
||||
/// ```ignore
|
||||
/// use std::time::Instant;
|
||||
/// use uom::si::{angular_velocity::revolution_per_minute, f64::AngularVelocity};
|
||||
/// use units::{angular_velocity::revolution_per_minute, f64::AngularVelocity};
|
||||
///
|
||||
/// let mut controller = AngularJerkSpeedController::new_simple(/* ... */);
|
||||
/// let target = AngularVelocity::new::<revolution_per_minute>(1500.0);
|
||||
|
||||
+7
-7
@@ -1,6 +1,6 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use uom::si::{
|
||||
use units::{
|
||||
acceleration::meter_per_second_squared,
|
||||
f64::{Acceleration, Length, Velocity},
|
||||
length::meter,
|
||||
@@ -22,8 +22,8 @@ use super::acceleration_position_controller::{
|
||||
///
|
||||
/// # Example
|
||||
/// ```ignore
|
||||
/// use uom::si::{length::meter, velocity::meter_per_second, acceleration::meter_per_second_squared};
|
||||
/// use uom::si::f64::{Length, Velocity, Acceleration};
|
||||
/// use units::{length::meter, velocity::meter_per_second, acceleration::meter_per_second_squared};
|
||||
/// use units::f64::{Length, Velocity, Acceleration};
|
||||
///
|
||||
/// let position_limit = Length::new::<meter>(10.0);
|
||||
/// let max_speed = Velocity::new::<meter_per_second>(2.0);
|
||||
@@ -101,8 +101,8 @@ impl LinearAccelerationPositionController {
|
||||
///
|
||||
/// # Example
|
||||
/// ```ignore
|
||||
/// use uom::si::{length::meter, velocity::meter_per_second, acceleration::meter_per_second_squared};
|
||||
/// use uom::si::f64::{Length, Velocity, Acceleration};
|
||||
/// use units::{length::meter, velocity::meter_per_second, acceleration::meter_per_second_squared};
|
||||
/// use units::f64::{Length, Velocity, Acceleration};
|
||||
///
|
||||
/// let position_limit = Length::new::<meter>(5.0); // Creates [-5.0, +5.0] meter range
|
||||
/// let max_speed = Velocity::new::<meter_per_second>(1.0); // Creates [-1.0, +1.0] m/s range
|
||||
@@ -150,7 +150,7 @@ impl LinearAccelerationPositionController {
|
||||
/// # Example
|
||||
/// ```ignore
|
||||
/// use std::time::Instant;
|
||||
/// use uom::si::{length::meter, f64::Length};
|
||||
/// use units::{length::meter, f64::Length};
|
||||
///
|
||||
/// let mut controller = LinearAccelerationPositionController::new_simple(
|
||||
/// Some(Length::new::<meter>(10.0)),
|
||||
@@ -375,7 +375,7 @@ impl LinearAccelerationPositionController {
|
||||
///
|
||||
/// # Example
|
||||
/// ```ignore
|
||||
/// use uom::si::{length::meter, velocity::meter_per_second, f64::{Length, Velocity}};
|
||||
/// use units::{length::meter, velocity::meter_per_second, f64::{Length, Velocity}};
|
||||
///
|
||||
/// let new_position = Length::new::<meter>(0.0);
|
||||
///
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use uom::si::{
|
||||
use units::{
|
||||
acceleration::meter_per_second_squared,
|
||||
f64::{Acceleration, Jerk, Velocity},
|
||||
jerk::meter_per_second_cubed,
|
||||
@@ -23,7 +23,7 @@ use super::jerk_speed_controller::JerkSpeedController;
|
||||
///
|
||||
/// # Example
|
||||
/// ```ignore
|
||||
/// use uom::si::{
|
||||
/// use units::{
|
||||
/// velocity::meter_per_second,
|
||||
/// acceleration::meter_per_second_squared,
|
||||
/// jerk::meter_per_second_cubed,
|
||||
@@ -65,7 +65,7 @@ impl LinearJerkSpeedController {
|
||||
///
|
||||
/// # Example
|
||||
/// ```ignore
|
||||
/// use uom::si::{
|
||||
/// use units::{
|
||||
/// velocity::meter_per_second,
|
||||
/// acceleration::meter_per_second_squared,
|
||||
/// jerk::meter_per_second_cubed,
|
||||
@@ -127,7 +127,7 @@ impl LinearJerkSpeedController {
|
||||
///
|
||||
/// # Example
|
||||
/// ```ignore
|
||||
/// use uom::si::{
|
||||
/// use units::{
|
||||
/// velocity::meter_per_second,
|
||||
/// acceleration::meter_per_second_squared,
|
||||
/// jerk::meter_per_second_cubed,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use uom::si::{angle::radian, f64::Angle};
|
||||
use units::{angle::radian, f64::Angle};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct AngleConverter {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use uom::si::{
|
||||
use units::{
|
||||
angle::revolution,
|
||||
angular_acceleration::radian_per_second_squared,
|
||||
angular_velocity::revolution_per_second,
|
||||
@@ -90,7 +90,7 @@ mod tests {
|
||||
use super::*;
|
||||
use approx::assert_relative_eq;
|
||||
use std::f64::EPSILON;
|
||||
use uom::si::{
|
||||
use units::{
|
||||
angle::degree, angular_acceleration::degree_per_second_squared,
|
||||
angular_velocity::degree_per_second,
|
||||
};
|
||||
|
||||
@@ -14,14 +14,14 @@
|
||||
//!
|
||||
//! ```rust
|
||||
//! use control_core::converters::circular_converter::CircularConverter;
|
||||
//! use uom::si::{f64::{Length, Velocity}, length::meter, velocity::meter_per_second};
|
||||
//! use units::{f64::{Length, Velocity}, length::meter, velocity::meter_per_second};
|
||||
//!
|
||||
//! let converter = CircularConverter::from_radius(Length::new::<meter>(0.1));
|
||||
//! let velocity = Velocity::new::<meter_per_second>(1.0);
|
||||
//! let angular_velocity = converter.linear_to_angular_velocity(velocity);
|
||||
//! ```
|
||||
|
||||
use uom::si::{
|
||||
use units::{
|
||||
acceleration::meter_per_second_squared,
|
||||
angle::radian,
|
||||
angular_acceleration::radian_per_second_squared,
|
||||
@@ -199,7 +199,7 @@ impl CircularConverter {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use uom::si::length::centimeter;
|
||||
use units::length::centimeter;
|
||||
|
||||
#[test]
|
||||
fn test_constructors() {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use uom::si::{
|
||||
use units::{
|
||||
angle::revolution,
|
||||
angular_velocity::revolution_per_second,
|
||||
f64::{Acceleration, Angle, AngularAcceleration, AngularVelocity, Length, Velocity},
|
||||
@@ -251,7 +251,7 @@ mod tests {
|
||||
use super::*;
|
||||
use approx::assert_relative_eq;
|
||||
use std::f64::EPSILON;
|
||||
use uom::si::{
|
||||
use units::{
|
||||
acceleration::meter_per_second_squared, angular_acceleration::radian_per_second_squared,
|
||||
length::meter, velocity::meter_per_second,
|
||||
};
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
use std::{process::Command, sync::Arc, time::Duration};
|
||||
|
||||
use ethercrab::{
|
||||
MainDevice, MainDeviceConfig, PduStorage, Timeouts,
|
||||
std::{ethercat_now, tx_rx_task},
|
||||
};
|
||||
use interfaces::Interface;
|
||||
use std::{process::Command, sync::Arc, time::Duration};
|
||||
|
||||
// Constants for EtherCAT configuration
|
||||
const IFACE_DISCOVERY_MAX_SUBDEVICES: usize = 16; // Must be power of 2 > 1
|
||||
@@ -92,9 +91,6 @@ pub async fn discover_ethercat_interface() -> Result<String, anyhow::Error> {
|
||||
let mut interface: Option<&str> = None;
|
||||
|
||||
for i in 0..interfaces.len() {
|
||||
// Avoid NetworkManager hangups
|
||||
set_interface_managed(&interfaces[i].name, false);
|
||||
|
||||
match test_interface(&interfaces[i].name) {
|
||||
Ok(_) => {
|
||||
// if interface found with ethercat Exit early, we expect only one interface with ethercat
|
||||
@@ -107,7 +103,7 @@ pub async fn discover_ethercat_interface() -> Result<String, anyhow::Error> {
|
||||
|
||||
for i in 0..interfaces.len() {
|
||||
if interface.is_some() && interface.unwrap() == &interfaces[i].name {
|
||||
continue;
|
||||
set_interface_managed(&interfaces[i].name, false);
|
||||
} else {
|
||||
set_interface_managed(&interfaces[i].name, true);
|
||||
}
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
pub mod eeprom_identification;
|
||||
pub mod interface_discovery;
|
||||
@@ -4,17 +4,11 @@ pub mod downcast;
|
||||
pub mod ethercat;
|
||||
pub mod helpers;
|
||||
pub mod irq_handling;
|
||||
pub mod machines;
|
||||
pub mod modbus;
|
||||
pub mod realtime;
|
||||
pub mod rest;
|
||||
pub mod serial;
|
||||
pub mod socketio;
|
||||
pub mod transmission;
|
||||
pub mod uom_extensions;
|
||||
|
||||
#[cfg(feature = "video-streaming")]
|
||||
pub mod video_streaming;
|
||||
|
||||
#[macro_use]
|
||||
extern crate uom;
|
||||
@@ -1,27 +0,0 @@
|
||||
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) -> Arc<Mutex<Namespace>>;
|
||||
|
||||
/// Returns a list of available video stream identifiers for this machine
|
||||
#[cfg(feature = "video-streaming")]
|
||||
fn api_video_streams(&self) -> Vec<String> {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
/// Get a video stream receiver for the specified stream ID
|
||||
/// Returns None if the stream doesn't exist
|
||||
#[cfg(feature = "video-streaming")]
|
||||
fn api_get_video_stream(
|
||||
&mut self,
|
||||
stream_id: &str,
|
||||
) -> Option<tokio::sync::broadcast::Receiver<Vec<u8>>> {
|
||||
let _ = stream_id;
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -1,208 +0,0 @@
|
||||
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, Default)]
|
||||
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,113 +0,0 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
/// Identifies a spacifi machine
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||||
pub struct MachineIdentificationUnique {
|
||||
pub machine_identification: MachineIdentification,
|
||||
pub serial: u16,
|
||||
}
|
||||
|
||||
impl MachineIdentificationUnique {
|
||||
/// Check if values are non-zero
|
||||
pub const fn is_valid(&self) -> bool {
|
||||
self.machine_identification.is_valid() && self.serial != 0
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for MachineIdentificationUnique {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}/{}/{}",
|
||||
self.machine_identification.vendor, self.machine_identification.machine, self.serial
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Identifies a machine
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||||
pub struct MachineIdentification {
|
||||
pub vendor: u16,
|
||||
pub machine: u16,
|
||||
}
|
||||
|
||||
impl MachineIdentification {
|
||||
/// Check if values are non-zero
|
||||
pub const fn is_valid(&self) -> bool {
|
||||
self.vendor != 0 && self.machine != 0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct DeviceMachineIdentification {
|
||||
pub machine_identification_unique: MachineIdentificationUnique,
|
||||
pub role: u16,
|
||||
}
|
||||
|
||||
impl DeviceMachineIdentification {
|
||||
/// Check if values are non-zero
|
||||
pub const fn is_valid(&self) -> bool {
|
||||
self.machine_identification_unique.is_valid()
|
||||
&& self
|
||||
.machine_identification_unique
|
||||
.machine_identification
|
||||
.machine
|
||||
!= 0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct DeviceIdentification {
|
||||
pub device_machine_identification: Option<DeviceMachineIdentification>,
|
||||
pub device_hardware_identification: DeviceHardwareIdentification,
|
||||
}
|
||||
|
||||
impl From<DeviceIdentificationIdentified> for DeviceIdentification {
|
||||
fn from(value: DeviceIdentificationIdentified) -> Self {
|
||||
Self {
|
||||
device_machine_identification: Some(value.device_machine_identification),
|
||||
device_hardware_identification: value.device_hardware_identification,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct DeviceIdentificationIdentified {
|
||||
pub device_machine_identification: DeviceMachineIdentification,
|
||||
pub device_hardware_identification: DeviceHardwareIdentification,
|
||||
}
|
||||
|
||||
impl TryFrom<DeviceIdentification> for DeviceIdentificationIdentified {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(value: DeviceIdentification) -> Result<Self, Self::Error> {
|
||||
let device_machine_identification =
|
||||
value.device_machine_identification.ok_or(anyhow::anyhow!(
|
||||
"[{}::DeviceIdentificationIdentified::try_from] No device machine identification",
|
||||
module_path!()
|
||||
))?;
|
||||
|
||||
Ok(Self {
|
||||
device_machine_identification,
|
||||
device_hardware_identification: value.device_hardware_identification,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub enum DeviceHardwareIdentification {
|
||||
Ethercat(DeviceHardwareIdentificationEthercat),
|
||||
Serial(DeviceHardwareIdentificationSerial),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct DeviceHardwareIdentificationEthercat {
|
||||
pub subdevice_index: usize,
|
||||
}
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct DeviceHardwareIdentificationSerial {
|
||||
pub path: String,
|
||||
}
|
||||
@@ -1,297 +0,0 @@
|
||||
use super::{
|
||||
Machine,
|
||||
identification::{DeviceIdentification, DeviceIdentificationIdentified},
|
||||
new::MachineNewHardwareSerial,
|
||||
registry::MachineRegistry,
|
||||
};
|
||||
use smol::{
|
||||
channel::Sender,
|
||||
lock::{Mutex, RwLock},
|
||||
};
|
||||
use socketioxide::extract::SocketRef;
|
||||
|
||||
use crate::{
|
||||
machines::{
|
||||
connection::{
|
||||
MachineConnection, MachineConnectionGeneric, MachineSlot, MachineSlotGeneric,
|
||||
},
|
||||
identification::MachineIdentificationUnique,
|
||||
new::{MachineNewHardware, MachineNewHardwareEthercat, MachineNewParams},
|
||||
},
|
||||
serial::SerialDevice,
|
||||
socketio::event::GenericEvent,
|
||||
};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{Arc, Weak},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MachineManager {
|
||||
pub ethercat_machines: HashMap<MachineIdentificationUnique, Arc<Mutex<MachineSlotGeneric>>>,
|
||||
pub serial_machines: HashMap<MachineIdentificationUnique, Arc<Mutex<MachineSlotGeneric>>>,
|
||||
}
|
||||
|
||||
impl Default for MachineManager {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl MachineManager {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
ethercat_machines: HashMap::new(),
|
||||
serial_machines: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_ethercat_devices<const MAX_SUBDEVICES: usize, const MAX_PDI: usize>(
|
||||
&mut self,
|
||||
device_identifications: &Vec<DeviceIdentification>,
|
||||
machine_registry: &MachineRegistry,
|
||||
hardware: &MachineNewHardwareEthercat,
|
||||
socket_queue_tx: Sender<(SocketRef, Arc<GenericEvent>)>,
|
||||
machine_manager: Weak<RwLock<Self>>,
|
||||
) {
|
||||
// group devices by machine device identification
|
||||
let device_grouping_result = group_devices_by_identification(device_identifications);
|
||||
|
||||
tracing::info!(
|
||||
"Device Groups {}",
|
||||
device_grouping_result.device_groups.len()
|
||||
);
|
||||
|
||||
let machine_new_hardware = MachineNewHardware::Ethercat(hardware);
|
||||
|
||||
// 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_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 {
|
||||
device_group,
|
||||
hardware: &machine_new_hardware,
|
||||
socket_queue_tx: socket_queue_tx.clone(),
|
||||
machine_manager: machine_manager.clone(),
|
||||
namespace: slot.namespace.clone(),
|
||||
});
|
||||
|
||||
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<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(
|
||||
&mut self,
|
||||
device_identification: &DeviceIdentification,
|
||||
device: Arc<RwLock<dyn SerialDevice>>,
|
||||
machine_registry: &MachineRegistry,
|
||||
socket_queue_tx: Sender<(SocketRef, Arc<GenericEvent>)>,
|
||||
machine_manager: Weak<RwLock<Self>>,
|
||||
) {
|
||||
let hardware = MachineNewHardwareSerial { device };
|
||||
|
||||
let device_identification_identified: DeviceIdentificationIdentified =
|
||||
device_identification
|
||||
.clone()
|
||||
.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.clone(),
|
||||
namespace: slot.namespace.clone(),
|
||||
});
|
||||
|
||||
slot.machine_connection = match new_machine {
|
||||
Err(err) => MachineConnectionGeneric::Error(err),
|
||||
Ok(machine) => MachineConnectionGeneric::Connected(machine),
|
||||
};
|
||||
|
||||
tracing::info!("Adding serial machine {:?}", slot);
|
||||
}
|
||||
|
||||
pub fn remove_serial_device(&mut self, device_identification: &DeviceIdentification) {
|
||||
let device_identification_identified: DeviceIdentificationIdentified =
|
||||
device_identification
|
||||
.clone()
|
||||
.try_into()
|
||||
.expect("Serial devices always have machine identification");
|
||||
|
||||
tracing::info!(
|
||||
"Removing serial machine {:?}",
|
||||
device_identification_identified
|
||||
);
|
||||
|
||||
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_ethercat_weak(
|
||||
&self,
|
||||
machine_identification: &MachineIdentificationUnique,
|
||||
) -> Option<Weak<Mutex<dyn Machine>>> {
|
||||
self.get_weak(&self.ethercat_machines, machine_identification)
|
||||
}
|
||||
|
||||
pub fn get_serial_weak(
|
||||
&self,
|
||||
machine_identification: &MachineIdentificationUnique,
|
||||
) -> Option<Weak<Mutex<dyn Machine>>> {
|
||||
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)),
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Groups devices by machine identification
|
||||
///
|
||||
/// Returns a DeviceGroupingResult containing:
|
||||
/// - device_groups: a vector of devices grouped by machine identification
|
||||
/// - unidentified_devices: a vector of devices that could not be identified
|
||||
pub fn group_devices_by_identification(
|
||||
device_identifications: &Vec<DeviceIdentification>,
|
||||
) -> DeviceGroupingResult {
|
||||
let mut device_groups: Vec<Vec<DeviceIdentificationIdentified>> = Vec::new();
|
||||
let mut unidentified_devices: Vec<DeviceIdentification> = Vec::new();
|
||||
|
||||
for device_identification in device_identifications {
|
||||
// if vendor or serial or machine is 0, it is not a valid machine device
|
||||
if let Some(device_machine_identification) =
|
||||
device_identification.device_machine_identification.as_ref()
|
||||
{
|
||||
if !device_machine_identification.is_valid() {
|
||||
unidentified_devices.push(device_identification.clone());
|
||||
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
unidentified_devices.push(device_identification.clone());
|
||||
continue;
|
||||
}
|
||||
|
||||
// scan over all deice groups
|
||||
// get the first DeviceMachineIdentification
|
||||
// compare and append to the group
|
||||
let mut found = false;
|
||||
for check_group in device_groups.iter_mut() {
|
||||
// get first device in group
|
||||
let first_device = check_group.first().expect("group to not be empty");
|
||||
let first_device_machine_identification = &first_device
|
||||
.device_machine_identification
|
||||
.machine_identification_unique;
|
||||
|
||||
// chek if it has machine identification
|
||||
if let Some(device_machine_identification) =
|
||||
device_identification.device_machine_identification.as_ref()
|
||||
{
|
||||
// compare with the current device
|
||||
if first_device_machine_identification
|
||||
== &device_machine_identification.machine_identification_unique
|
||||
{
|
||||
let device_identification_identified = device_identification
|
||||
.clone()
|
||||
.try_into()
|
||||
.expect("should have Some(DeviceMachineIdentification)");
|
||||
check_group.push(device_identification_identified);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
let device_identification_identified = device_identification
|
||||
.clone()
|
||||
.try_into()
|
||||
.expect("should have Some(DeviceMachineIdentification)");
|
||||
device_groups.push(vec![device_identification_identified]);
|
||||
}
|
||||
}
|
||||
|
||||
DeviceGroupingResult {
|
||||
device_groups,
|
||||
unidentified_devices,
|
||||
}
|
||||
}
|
||||
|
||||
/// Structure to hold the result of grouping devices by identification
|
||||
#[derive(Debug)]
|
||||
pub struct DeviceGroupingResult {
|
||||
/// Devices grouped by machine identification
|
||||
pub device_groups: Vec<Vec<DeviceIdentificationIdentified>>,
|
||||
/// Devices that could not be identified
|
||||
pub unidentified_devices: Vec<DeviceIdentification>,
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use smol::lock::Mutex;
|
||||
|
||||
use crate::machines::connection::MachineSlotGeneric;
|
||||
|
||||
use super::{identification::MachineIdentificationUnique, manager::MachineManager};
|
||||
|
||||
pub enum MachineManagerIter<'a> {
|
||||
EthercatMachines {
|
||||
iter: std::collections::hash_map::Iter<
|
||||
'a,
|
||||
MachineIdentificationUnique,
|
||||
Arc<Mutex<MachineSlotGeneric>>,
|
||||
>,
|
||||
},
|
||||
SerialMachines {
|
||||
iter: std::collections::hash_map::Iter<
|
||||
'a,
|
||||
MachineIdentificationUnique,
|
||||
Arc<Mutex<MachineSlotGeneric>>,
|
||||
>,
|
||||
},
|
||||
Done,
|
||||
}
|
||||
|
||||
pub struct MachineManagerIterator<'a> {
|
||||
iter: MachineManagerIter<'a>,
|
||||
serial_machines: &'a HashMap<MachineIdentificationUnique, Arc<Mutex<MachineSlotGeneric>>>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for MachineManagerIterator<'a> {
|
||||
type Item = (
|
||||
&'a MachineIdentificationUnique,
|
||||
&'a Arc<Mutex<MachineSlotGeneric>>,
|
||||
);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
loop {
|
||||
match &mut self.iter {
|
||||
MachineManagerIter::EthercatMachines { iter } => {
|
||||
// Try to get the next item from ethercat_machines
|
||||
if let Some(item) = iter.next() {
|
||||
return Some(item);
|
||||
} else {
|
||||
// If ethercat_machines is exhausted, move to serial_machines
|
||||
self.iter = MachineManagerIter::SerialMachines {
|
||||
iter: self.serial_machines.iter(),
|
||||
};
|
||||
// Continue to the next iteration of the loop
|
||||
continue;
|
||||
}
|
||||
}
|
||||
MachineManagerIter::SerialMachines { iter } => {
|
||||
// Return the next item from serial_machines or None if exhausted
|
||||
if let Some(item) = iter.next() {
|
||||
return Some(item);
|
||||
} else {
|
||||
// If serial_machines is also exhausted, we're done
|
||||
self.iter = MachineManagerIter::Done;
|
||||
return None;
|
||||
}
|
||||
}
|
||||
MachineManagerIter::Done => {
|
||||
// We've exhausted both collections
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MachineManager {
|
||||
// Returns an iterator over all machines (both ethercat and serial)
|
||||
pub fn iter(&self) -> MachineManagerIterator<'_> {
|
||||
MachineManagerIterator {
|
||||
iter: MachineManagerIter::EthercatMachines {
|
||||
iter: self.ethercat_machines.iter(),
|
||||
},
|
||||
serial_machines: &self.serial_machines,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
use new::MachineNewTrait;
|
||||
use std::any::Any;
|
||||
use std::fmt::Debug;
|
||||
|
||||
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 + 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;
|
||||
}
|
||||
@@ -1,279 +0,0 @@
|
||||
use crate::{
|
||||
machines::{identification::MachineIdentificationUnique, manager::MachineManager},
|
||||
serial::SerialDevice,
|
||||
socketio::namespace::Namespace,
|
||||
};
|
||||
|
||||
use super::identification::DeviceIdentificationIdentified;
|
||||
use anyhow::Error;
|
||||
use ethercat_hal::{
|
||||
devices::EthercatDevice, helpers::ethercrab_types::EthercrabSubDevicePreoperational,
|
||||
};
|
||||
use ethercrab::{SubDevice, SubDeviceRef};
|
||||
use smol::{
|
||||
channel::Sender,
|
||||
lock::{Mutex, RwLock},
|
||||
};
|
||||
use socketioxide::extract::SocketRef;
|
||||
use std::{
|
||||
sync::{Arc, Weak},
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
use crate::socketio::event::GenericEvent;
|
||||
|
||||
pub trait MachineNewTrait {
|
||||
fn new(params: &MachineNewParams<'_, '_, '_, '_, '_, '_, '_>) -> Result<Self, Error>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
pub trait MachineAct {
|
||||
fn act(&mut self, now: Instant);
|
||||
}
|
||||
|
||||
pub struct MachineNewParams<
|
||||
'maindevice,
|
||||
'subdevices,
|
||||
'device_identifications_identified,
|
||||
'ethercat_devices,
|
||||
'machine_new_hardware_etehrcat,
|
||||
'machine_new_hardware_serial,
|
||||
'machine_new_hardware,
|
||||
> where
|
||||
'maindevice: 'machine_new_hardware,
|
||||
'subdevices: 'machine_new_hardware,
|
||||
'ethercat_devices: 'machine_new_hardware,
|
||||
'machine_new_hardware_etehrcat: 'machine_new_hardware,
|
||||
{
|
||||
pub device_group: &'device_identifications_identified Vec<DeviceIdentificationIdentified>,
|
||||
pub hardware: &'machine_new_hardware MachineNewHardware<
|
||||
'maindevice,
|
||||
'subdevices,
|
||||
'ethercat_devices,
|
||||
'machine_new_hardware_etehrcat,
|
||||
'machine_new_hardware_serial,
|
||||
>,
|
||||
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<
|
||||
'maindevice,
|
||||
'subdevices,
|
||||
'ethercat_devices,
|
||||
'machine_new_hardware_etehrcat,
|
||||
'machine_new_hardware_serial,
|
||||
> where
|
||||
'maindevice: 'machine_new_hardware_etehrcat,
|
||||
'subdevices: 'machine_new_hardware_etehrcat,
|
||||
'ethercat_devices: 'machine_new_hardware_etehrcat,
|
||||
{
|
||||
Ethercat(
|
||||
&'machine_new_hardware_etehrcat MachineNewHardwareEthercat<
|
||||
'maindevice,
|
||||
'subdevices,
|
||||
'ethercat_devices,
|
||||
>,
|
||||
),
|
||||
Serial(&'machine_new_hardware_serial MachineNewHardwareSerial),
|
||||
}
|
||||
|
||||
pub struct MachineNewHardwareEthercat<'maindevice, 'subdevices, 'ethercat_devices> {
|
||||
pub subdevices:
|
||||
&'subdevices Vec<&'subdevices SubDeviceRef<'maindevice, &'subdevices SubDevice>>,
|
||||
pub ethercat_devices: &'ethercat_devices Vec<Arc<RwLock<dyn EthercatDevice>>>,
|
||||
}
|
||||
|
||||
pub struct MachineNewHardwareSerial {
|
||||
pub device: Arc<RwLock<dyn SerialDevice>>,
|
||||
}
|
||||
|
||||
// validates that all devices in the group have the same machine identification
|
||||
pub fn validate_same_machine_identification_unique(
|
||||
identified_device_group: &Vec<DeviceIdentificationIdentified>,
|
||||
) -> Result<(), Error> {
|
||||
let machine_identification_unique = &identified_device_group
|
||||
.first()
|
||||
.ok_or_else(|| {
|
||||
anyhow::anyhow!(
|
||||
"[{}::validate_same_machine_identification] No devices in group",
|
||||
module_path!()
|
||||
)
|
||||
})?
|
||||
.device_machine_identification
|
||||
.machine_identification_unique;
|
||||
for device in identified_device_group.iter() {
|
||||
if device
|
||||
.device_machine_identification
|
||||
.machine_identification_unique
|
||||
!= *machine_identification_unique
|
||||
{
|
||||
return Err(anyhow::anyhow!(
|
||||
"[{}::validate_same_machine_identification] Different machine identifications",
|
||||
module_path!()
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// validates that every role is unique
|
||||
pub fn validate_no_role_dublicates(
|
||||
identified_device_group: &Vec<DeviceIdentificationIdentified>,
|
||||
) -> Result<(), Error> {
|
||||
let mut roles = vec![];
|
||||
for device in identified_device_group.iter() {
|
||||
if roles.contains(&device.device_machine_identification.role) {
|
||||
return Err(anyhow::anyhow!(
|
||||
"[{}::validate_no_role_dublicates] Role dublicate",
|
||||
module_path!(),
|
||||
));
|
||||
}
|
||||
roles.push(device.device_machine_identification.role);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Inside control_core::machines::new module:
|
||||
pub fn get_device_identification_by_role(
|
||||
identified_device_group: &Vec<DeviceIdentificationIdentified>,
|
||||
role: u16,
|
||||
) -> Result<&DeviceIdentificationIdentified, Error> {
|
||||
for device in identified_device_group.iter() {
|
||||
if device.device_machine_identification.role == role {
|
||||
return Ok(device);
|
||||
}
|
||||
}
|
||||
Err(anyhow::anyhow!(
|
||||
"[{}::get_device_identification_by_role] Role {} not found",
|
||||
module_path!(),
|
||||
role
|
||||
))
|
||||
}
|
||||
|
||||
pub fn get_device_by_index<'maindevice>(
|
||||
devices: &Vec<Arc<RwLock<dyn EthercatDevice>>>,
|
||||
subdevice_index: usize,
|
||||
) -> Result<Arc<RwLock<dyn EthercatDevice>>, Error> {
|
||||
Ok(devices
|
||||
.get(subdevice_index)
|
||||
.ok_or(anyhow::anyhow!(
|
||||
"[{}::get_device_by_index] Index {} out of bounds for devices",
|
||||
module_path!(),
|
||||
subdevice_index
|
||||
))?
|
||||
.clone())
|
||||
}
|
||||
|
||||
pub fn get_subdevice_by_index<'subdevices, 'maindevice>(
|
||||
subdevices: &'subdevices Vec<&EthercrabSubDevicePreoperational<'maindevice>>,
|
||||
subdevice_index: usize,
|
||||
) -> Result<&'subdevices EthercrabSubDevicePreoperational<'maindevice>, Error> {
|
||||
Ok(subdevices.get(subdevice_index).ok_or(anyhow::anyhow!(
|
||||
"Index {} out of bounds for subdevices",
|
||||
subdevice_index
|
||||
))?)
|
||||
}
|
||||
|
||||
pub fn get_ethercat_device_by_index<'maindevice>(
|
||||
ethercat_devices: &Vec<Arc<RwLock<dyn EthercatDevice>>>,
|
||||
subdevice_index: usize,
|
||||
) -> Result<Arc<RwLock<dyn EthercatDevice>>, Error> {
|
||||
Ok(ethercat_devices
|
||||
.get(subdevice_index)
|
||||
.ok_or(anyhow::anyhow!(
|
||||
"[{}::get_ethercat_device_by_index] Index {} out of bounds for ethercat devices",
|
||||
module_path!(),
|
||||
subdevice_index
|
||||
))?
|
||||
.clone())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::machines::identification::{
|
||||
DeviceHardwareIdentification, DeviceHardwareIdentificationEthercat,
|
||||
DeviceMachineIdentification, MachineIdentification, MachineIdentificationUnique,
|
||||
};
|
||||
|
||||
pub use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_get_device_identification_by_role() {
|
||||
let device_identifications = vec![
|
||||
// role 0
|
||||
DeviceIdentificationIdentified {
|
||||
device_machine_identification: DeviceMachineIdentification {
|
||||
machine_identification_unique: MachineIdentificationUnique {
|
||||
machine_identification: MachineIdentification {
|
||||
vendor: 1,
|
||||
machine: 1,
|
||||
},
|
||||
serial: 1,
|
||||
},
|
||||
role: 0,
|
||||
},
|
||||
device_hardware_identification: DeviceHardwareIdentification::Ethercat(
|
||||
DeviceHardwareIdentificationEthercat { subdevice_index: 0 },
|
||||
),
|
||||
},
|
||||
// role 1
|
||||
DeviceIdentificationIdentified {
|
||||
device_machine_identification: DeviceMachineIdentification {
|
||||
machine_identification_unique: MachineIdentificationUnique {
|
||||
machine_identification: MachineIdentification {
|
||||
vendor: 2,
|
||||
machine: 2,
|
||||
},
|
||||
serial: 2,
|
||||
},
|
||||
role: 2,
|
||||
},
|
||||
device_hardware_identification: DeviceHardwareIdentification::Ethercat(
|
||||
DeviceHardwareIdentificationEthercat { subdevice_index: 1 },
|
||||
),
|
||||
},
|
||||
// role 2
|
||||
DeviceIdentificationIdentified {
|
||||
device_machine_identification: DeviceMachineIdentification {
|
||||
machine_identification_unique: MachineIdentificationUnique {
|
||||
machine_identification: MachineIdentification {
|
||||
vendor: 3,
|
||||
machine: 3,
|
||||
},
|
||||
serial: 3,
|
||||
},
|
||||
role: 1,
|
||||
},
|
||||
device_hardware_identification: DeviceHardwareIdentification::Ethercat(
|
||||
DeviceHardwareIdentificationEthercat { subdevice_index: 2 },
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
// search for role 0
|
||||
let result = get_device_identification_by_role(&device_identifications, 0);
|
||||
assert_eq!(result.unwrap().device_machine_identification.role, 0,);
|
||||
|
||||
// search for role 1
|
||||
let result = get_device_identification_by_role(&device_identifications, 1);
|
||||
assert_eq!(result.unwrap().device_machine_identification.role, 1,);
|
||||
// search for role 2
|
||||
let result = get_device_identification_by_role(&device_identifications, 2);
|
||||
assert_eq!(result.unwrap().device_machine_identification.role, 2,);
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
pub mod mutation;
|
||||
@@ -1,11 +1,10 @@
|
||||
use std::{any::Any, sync::Arc};
|
||||
/*use std::{any::Any, sync::Arc};
|
||||
|
||||
use smol::lock::RwLock;
|
||||
use std::fmt::Debug;
|
||||
|
||||
use crate::machines::identification::DeviceIdentification;
|
||||
|
||||
pub mod registry;
|
||||
pub mod serial_detection;
|
||||
|
||||
pub trait SerialDevice: Any + Send + Sync + SerialDeviceNew + Debug {}
|
||||
@@ -31,3 +30,4 @@ pub struct SerialDeviceIdentification {
|
||||
pub vendor_id: u16,
|
||||
pub product_id: u16,
|
||||
}
|
||||
*/
|
||||
@@ -1,3 +1,2 @@
|
||||
pub mod event;
|
||||
pub mod namespace;
|
||||
pub mod namespace_id;
|
||||
@@ -4,7 +4,7 @@ use socketioxide::extract::SocketRef;
|
||||
use std::{collections::HashMap, sync::Arc, time::Duration};
|
||||
use tracing::instrument;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Namespace {
|
||||
pub sockets: Vec<SocketRef>,
|
||||
pub events: HashMap<String, Vec<Arc<GenericEvent>>>,
|
||||
@@ -167,7 +167,7 @@ impl Namespace {
|
||||
|
||||
impl Drop for Namespace {
|
||||
fn drop(&mut self) {
|
||||
self.disconnect_all();
|
||||
//self.disconnect_all();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use uom::si::{
|
||||
use units::{
|
||||
angle::radian,
|
||||
angular_velocity::radian_per_second,
|
||||
f64::{Angle, AngularVelocity, Length, Velocity},
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
unit! {
|
||||
system: uom::si;
|
||||
quantity: uom::si::acceleration;
|
||||
|
||||
@meter_per_minute_per_second: 0.016_666_666_666_666_67; "m/min/s", "meters per minute per second",
|
||||
"meters per minute per second";
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
unit! {
|
||||
system: uom::si;
|
||||
quantity: uom::si::angular_acceleration;
|
||||
|
||||
@revolution_per_minute_per_second: 0.104_719_755_119_659_77; "rev/min/s", "revolutions per minute per second",
|
||||
"revolutions per minute per second";
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
unit! {
|
||||
system: uom::si;
|
||||
quantity: uom::si::angular_jerk;
|
||||
|
||||
@revolution_per_minute_per_second_squared: 0.104_719_755_119_659_77; "rev/min/s²", "revolution per minute per second squared",
|
||||
"revolution per minute per second squared";
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
unit! {
|
||||
system: uom::si;
|
||||
quantity: uom::si::jerk;
|
||||
|
||||
@meter_per_minute_per_second_squared: 0.016_666_666_666_666_666_67; "m/min/s²", "meters per minute per second squared",
|
||||
"meters per minute per second squared";
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
pub mod acceleration;
|
||||
pub mod angular_acceleration;
|
||||
pub mod angular_jerk;
|
||||
pub mod jerk;
|
||||
pub mod velocity;
|
||||
@@ -1,7 +0,0 @@
|
||||
unit! {
|
||||
system: uom::si;
|
||||
quantity: uom::si::velocity;
|
||||
|
||||
@meter_per_minute: 0.016_666_666_666_666_67; "m/min", "meters per minute",
|
||||
"meters per minute";
|
||||
}
|
||||
Binary file not shown.
Generated
+12
-36
@@ -175,7 +175,6 @@
|
||||
"version": "7.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz",
|
||||
"integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "^2.2.0",
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
@@ -639,7 +638,6 @@
|
||||
"url": "https://opencollective.com/csstools"
|
||||
}
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
@@ -662,7 +660,6 @@
|
||||
"url": "https://opencollective.com/csstools"
|
||||
}
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
@@ -1148,7 +1145,6 @@
|
||||
"integrity": "sha512-zx0EIq78WlY/lBb1uXlziZmDZI4ubcCXIMJ4uGjXzZW0nS19TjSPeXPAjzzTmKQlJUZm0SbmZhPKP7tuQ1SsEw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"chalk": "^4.1.1",
|
||||
"fs-extra": "^9.0.1",
|
||||
@@ -4095,7 +4091,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.130.17.tgz",
|
||||
"integrity": "sha512-F1tyMEbX7PJclnA25hAFXhLKq4jqeODvvaogsd0/NX02Kmz3QoT9GV+rgpv2TxamU6RwjRgihSH06Dn5hM6SOw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@tanstack/history": "1.130.12",
|
||||
"@tanstack/react-store": "^0.7.0",
|
||||
@@ -4177,7 +4172,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/router-core/-/router-core-1.130.17.tgz",
|
||||
"integrity": "sha512-0AH4s/52X2shYLifr9hR+6R6NpGsBp1M3LyjBTZm6Jlkud+nOhkkRxKxYS7l+JErRlffdxVHxMdoTnKcQ+duiQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@tanstack/history": "1.130.12",
|
||||
"@tanstack/store": "^0.7.0",
|
||||
@@ -4411,7 +4405,8 @@
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
|
||||
"integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@types/babel__core": {
|
||||
"version": "7.20.5",
|
||||
@@ -4568,7 +4563,6 @@
|
||||
"integrity": "sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~7.10.0"
|
||||
}
|
||||
@@ -4585,7 +4579,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.16.tgz",
|
||||
"integrity": "sha512-WBM/nDbEZmDUORKnh5i1bTnAz6vTohUf9b8esSMu+b24+srbaxa04UbJgWx78CVfNXA20sNu0odEIluZDFdCog==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
@@ -4595,7 +4588,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.7.tgz",
|
||||
"integrity": "sha512-i5ZzwYpqjmrKenzkoLM2Ibzt6mAsM7pxB6BCIouEVVmgiqaMj1TjaK7hnA36hbW5aZv20kx7Lw6hWzPWg0Rurw==",
|
||||
"devOptional": true,
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"@types/react": "^19.0.0"
|
||||
}
|
||||
@@ -4678,7 +4670,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.38.0.tgz",
|
||||
"integrity": "sha512-Zhy8HCvBUEfBECzIl1PKqF4p11+d0aUJS1GeUiuqK9WmOug8YCmC4h4bjyBvMyAMI9sbRczmrYL5lKg/YMbrcQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.38.0",
|
||||
"@typescript-eslint/types": "8.38.0",
|
||||
@@ -5033,7 +5024,6 @@
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -5481,7 +5471,6 @@
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"caniuse-lite": "^1.0.30001726",
|
||||
"electron-to-chromium": "^1.5.173",
|
||||
@@ -6174,8 +6163,7 @@
|
||||
"node_modules/csstype": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||
"peer": true
|
||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
|
||||
},
|
||||
"node_modules/data-urls": {
|
||||
"version": "5.0.0",
|
||||
@@ -6448,7 +6436,8 @@
|
||||
"version": "0.5.16",
|
||||
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
|
||||
"integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/dom-helpers": {
|
||||
"version": "5.2.1",
|
||||
@@ -7448,7 +7437,6 @@
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.32.0.tgz",
|
||||
"integrity": "sha512-LSehfdpgMeWcTZkWZVIJl+tkZ2nuSkyyB9C27MZqFWXuph7DvaowgcTvKqxvpLW1JZIk8PN7hFY3Rj9LQ7m7lg==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.2.0",
|
||||
"@eslint-community/regexpp": "^4.12.1",
|
||||
@@ -7509,7 +7497,6 @@
|
||||
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz",
|
||||
"integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"eslint-config-prettier": "bin/cli.js"
|
||||
},
|
||||
@@ -8816,7 +8803,6 @@
|
||||
"url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
|
||||
}
|
||||
],
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.6"
|
||||
},
|
||||
@@ -8879,7 +8865,6 @@
|
||||
"version": "10.1.1",
|
||||
"resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz",
|
||||
"integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/immer"
|
||||
@@ -9554,7 +9539,6 @@
|
||||
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz",
|
||||
"integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"cssstyle": "^4.2.1",
|
||||
"data-urls": "^5.0.0",
|
||||
@@ -10120,6 +10104,7 @@
|
||||
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
|
||||
"integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"lz-string": "bin/bin.js"
|
||||
}
|
||||
@@ -12064,7 +12049,6 @@
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
|
||||
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
},
|
||||
@@ -12178,6 +12162,7 @@
|
||||
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
|
||||
"integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1",
|
||||
"ansi-styles": "^5.0.0",
|
||||
@@ -12192,6 +12177,7 @@
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
|
||||
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
@@ -12320,7 +12306,6 @@
|
||||
"version": "19.1.1",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz",
|
||||
"integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -12329,7 +12314,6 @@
|
||||
"version": "19.1.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz",
|
||||
"integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"scheduler": "^0.26.0"
|
||||
},
|
||||
@@ -12341,7 +12325,6 @@
|
||||
"version": "7.62.0",
|
||||
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.62.0.tgz",
|
||||
"integrity": "sha512-7KWFejc98xqG/F4bAxpL41NB3o1nnvQO1RWZT3TqRZYL8RryQETGfEdVnJN2fy1crCiBLLjkRBVK05j24FxJGA==",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
@@ -12382,7 +12365,8 @@
|
||||
"version": "17.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/react-lifecycles-compat": {
|
||||
"version": "3.0.4",
|
||||
@@ -13133,7 +13117,6 @@
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/seroval/-/seroval-1.3.2.tgz",
|
||||
"integrity": "sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
@@ -13923,8 +13906,7 @@
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "4.1.11",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz",
|
||||
"integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==",
|
||||
"peer": true
|
||||
"integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA=="
|
||||
},
|
||||
"node_modules/tailwindcss-animate": {
|
||||
"version": "1.0.7",
|
||||
@@ -14026,8 +14008,7 @@
|
||||
"node_modules/tiny-invariant": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
|
||||
"integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
|
||||
"peer": true
|
||||
"integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="
|
||||
},
|
||||
"node_modules/tiny-warning": {
|
||||
"version": "1.0.3",
|
||||
@@ -14078,7 +14059,6 @@
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -14390,7 +14370,6 @@
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
|
||||
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
||||
"devOptional": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -14752,7 +14731,6 @@
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
|
||||
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"fdir": "^6.4.4",
|
||||
@@ -14874,7 +14852,6 @@
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -15418,7 +15395,6 @@
|
||||
"version": "3.25.76",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 74 KiB |
@@ -720,3 +720,7 @@ The following persons must not work on the machine:
|
||||

|
||||
|
||||

|
||||
|
||||
## Mechanical Diagrams
|
||||
|
||||

|
||||
@@ -145,8 +145,17 @@ export function mainMessageHandler(
|
||||
};
|
||||
}
|
||||
|
||||
// Create the store instance once and export it
|
||||
export const mainNamespaceStore: StoreApi<MainNamespaceStore> =
|
||||
createMainNamespaceStore();
|
||||
|
||||
// Message handler stays the same
|
||||
export const mainMessageHandlerInstance: EventHandler =
|
||||
mainMessageHandler(mainNamespaceStore);
|
||||
|
||||
// Hook that uses the exported store
|
||||
const mainRoomImplementation = createNamespaceHookImplementation({
|
||||
createStore: createMainNamespaceStore,
|
||||
createStore: () => mainNamespaceStore, // use the exported store
|
||||
createEventHandler: mainMessageHandler,
|
||||
});
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ import { z } from "zod";
|
||||
import { toastError, toastZodError } from "@/components/Toast";
|
||||
import { MachineIdentificationUnique } from "@/machines/types";
|
||||
import { FPS_30 } from "@/lib/constants";
|
||||
import { mainNamespaceStore, createMainNamespaceStore } from "./mainNamespace";
|
||||
import { softReloadMainNamespaceStore } from "./softReload";
|
||||
|
||||
/**
|
||||
* Simple buffer-based store updater to limit React re-renders to ~30 FPS
|
||||
@@ -305,27 +307,13 @@ const useSocketioStore = create<SocketioStore>()((set, get) => ({
|
||||
// add handlers
|
||||
socket.on("connect", () => {
|
||||
console.log(`Connected to ${namespace_path}`);
|
||||
|
||||
// reset the store
|
||||
resetStore(set);
|
||||
});
|
||||
socket.on("disconnect", (reason) => {
|
||||
socket.disconnect();
|
||||
console.warn(`Disconnected from ${namespace_path}, reason: ${reason}`);
|
||||
|
||||
// reset the store
|
||||
resetStore(set);
|
||||
|
||||
// Attempt to reconnect after a short delay for any disconnect reason
|
||||
console.log(
|
||||
`Disconnected from ${namespace_path}, attempting reconnect in 1s...`,
|
||||
);
|
||||
setTimeout(() => {
|
||||
if (get().hasNamespace(namespaceId) && !socket.connected) {
|
||||
console.log(`Reconnecting to ${namespace_path}...`);
|
||||
socket.connect();
|
||||
}
|
||||
}, 1000);
|
||||
// Its hacky but i do not care, electron + react is annoying ...
|
||||
window.location.reload();
|
||||
});
|
||||
|
||||
socket.on("event", (event: unknown) => {
|
||||
@@ -334,11 +322,9 @@ const useSocketioStore = create<SocketioStore>()((set, get) => ({
|
||||
if (!event_parsed.success) {
|
||||
toastZodError(event_parsed.error, "Invalid event");
|
||||
return;
|
||||
}
|
||||
// handle the event
|
||||
} // handle the event
|
||||
get().namespaces[namespace_path].handler(event_parsed.data);
|
||||
});
|
||||
|
||||
// store the namespace initally
|
||||
set(
|
||||
produce((state: SocketioStore) => {
|
||||
@@ -356,8 +342,25 @@ const useSocketioStore = create<SocketioStore>()((set, get) => ({
|
||||
}),
|
||||
);
|
||||
|
||||
// finally connect
|
||||
socket.connect();
|
||||
if (namespace_path !== "/main") {
|
||||
const intervalId = setInterval(() => {
|
||||
const mainState = mainNamespaceStore.getState();
|
||||
const machineExists =
|
||||
namespaceId.type === "machine" &&
|
||||
mainState.machines?.data?.machines.some(
|
||||
(m) =>
|
||||
m.machine_identification_unique.serial ===
|
||||
namespaceId.machine_identification_unique.serial,
|
||||
);
|
||||
//console.log(machineExists);
|
||||
if (machineExists && !socket.connected) {
|
||||
socket.connect();
|
||||
clearInterval(intervalId); // stop polling
|
||||
}
|
||||
}, 500);
|
||||
} else {
|
||||
socket.connect();
|
||||
}
|
||||
},
|
||||
incrementNamespace: (namespaceId: NamespaceId) => {
|
||||
const namespace_path = serializeNamespaceId(namespaceId);
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { mainNamespaceStore, mainMessageHandler } from "./mainNamespace";
|
||||
|
||||
// Soft reset function
|
||||
export function softReloadMainNamespaceStore() {
|
||||
// 1️⃣ Reset the store to initial state
|
||||
mainNamespaceStore.setState({
|
||||
ethercatDevices: null,
|
||||
machines: null,
|
||||
ethercatInterfaceDiscovery: null,
|
||||
});
|
||||
|
||||
// 2️⃣ Re-attach the message handler if needed
|
||||
// This ensures future socket events still update the store
|
||||
mainMessageHandler(mainNamespaceStore);
|
||||
|
||||
console.log("MainNamespaceStore soft reloaded");
|
||||
}
|
||||
@@ -62,7 +62,6 @@ export const connectedMachineStateSchema = z.object({
|
||||
/**
|
||||
* Consolidated state event schema (state changes only)
|
||||
*/
|
||||
|
||||
export const stateEventDataSchema = z.object({
|
||||
mode_state: modeStateSchema,
|
||||
connected_machine_state: connectedMachineStateSchema,
|
||||
|
||||
@@ -239,6 +239,50 @@ export function Extruder2SettingsPage() {
|
||||
/>
|
||||
</Label>
|
||||
</ControlCard>
|
||||
<ControlCard title="Temperature PID Settings (Nozzle) ">
|
||||
<Label label="Kp">
|
||||
<EditValue
|
||||
value={state?.pid_settings.temperature.nozzle.kp}
|
||||
defaultValue={
|
||||
defaultState?.pid_settings.temperature.nozzle.kp
|
||||
}
|
||||
min={0}
|
||||
max={100}
|
||||
step={0.001}
|
||||
renderValue={(v) => roundToDecimals(v, 3)}
|
||||
onChange={(v) => setTemperaturePidValue("nozzle", "kp", v)}
|
||||
title="Temperature PID KP"
|
||||
/>
|
||||
</Label>
|
||||
<Label label="Ki">
|
||||
<EditValue
|
||||
value={state?.pid_settings.temperature.nozzle.ki}
|
||||
defaultValue={
|
||||
defaultState?.pid_settings.temperature.nozzle.ki
|
||||
}
|
||||
min={0}
|
||||
max={100}
|
||||
step={0.001}
|
||||
renderValue={(v) => roundToDecimals(v, 3)}
|
||||
onChange={(v) => setTemperaturePidValue("nozzle", "ki", v)}
|
||||
title="Temperature PID KI"
|
||||
/>
|
||||
</Label>
|
||||
<Label label="Kd">
|
||||
<EditValue
|
||||
value={state?.pid_settings.temperature.nozzle.kd}
|
||||
defaultValue={
|
||||
defaultState?.pid_settings.temperature.nozzle.kd
|
||||
}
|
||||
min={0}
|
||||
max={100}
|
||||
step={0.001}
|
||||
renderValue={(v) => roundToDecimals(v, 3)}
|
||||
onChange={(v) => setTemperaturePidValue("nozzle", "kd", v)}
|
||||
title="Temperature PID KD"
|
||||
/>
|
||||
</Label>
|
||||
</ControlCard>
|
||||
</ControlGrid>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -149,6 +149,12 @@ export const pidSettingsSchema = z.object({
|
||||
kd: z.number(),
|
||||
zone: z.string(),
|
||||
}),
|
||||
nozzle: z.object({
|
||||
ki: z.number(),
|
||||
kp: z.number(),
|
||||
kd: z.number(),
|
||||
zone: z.string(),
|
||||
}),
|
||||
}),
|
||||
pressure: z.object({
|
||||
ki: z.number(),
|
||||
|
||||
@@ -294,7 +294,7 @@ export function useExtruder2() {
|
||||
};
|
||||
|
||||
const setTemperaturePidValue = (
|
||||
zone: "front" | "middle" | "back",
|
||||
zone: "front" | "middle" | "back" | "nozzle",
|
||||
key: "kp" | "ki" | "kd",
|
||||
value: number,
|
||||
) => {
|
||||
|
||||
@@ -10,9 +10,9 @@ workspace = true
|
||||
anyhow = "1.0.100"
|
||||
ethercrab = "0.6"
|
||||
ethercat_hal_derive = { version = "0.1.0", path = "../ethercat-hal-derive" }
|
||||
units = { path = "../units" }
|
||||
bitvec = { version = "1.0.1", features = ["alloc"] }
|
||||
smol = "2.0.2"
|
||||
uom = { version = "0.36.0", default-features = false, features = ["f64","si"] }
|
||||
rand = "0.9.2"
|
||||
futures = "0.3.31"
|
||||
tracing = "0.1.41"
|
||||
|
||||
@@ -14,7 +14,7 @@ use crate::{
|
||||
io::analog_input::{AnalogInputDevice, AnalogInputInput},
|
||||
};
|
||||
use ethercat_hal_derive::{EthercatDevice, RxPdo, TxPdo};
|
||||
use uom::si::{electric_potential::volt, f64::ElectricPotential};
|
||||
use units::{electric_potential::volt, f64::ElectricPotential};
|
||||
|
||||
#[derive(EthercatDevice)]
|
||||
pub struct EL3001 {
|
||||
|
||||
@@ -17,8 +17,8 @@ use crate::{
|
||||
io::analog_input::{AnalogInputDevice, AnalogInputInput},
|
||||
};
|
||||
use ethercat_hal_derive::{EthercatDevice, RxPdo, TxPdo};
|
||||
use uom::si::electric_current::milliampere;
|
||||
use uom::si::f64::ElectricCurrent;
|
||||
use units::electric_current::milliampere;
|
||||
use units::f64::ElectricCurrent;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EL3021Configuration {
|
||||
|
||||
@@ -17,8 +17,8 @@ use crate::{
|
||||
io::analog_input::{AnalogInputDevice, AnalogInputInput},
|
||||
};
|
||||
use ethercat_hal_derive::{EthercatDevice, RxPdo, TxPdo};
|
||||
use uom::si::electric_current::milliampere;
|
||||
use uom::si::f64::ElectricCurrent;
|
||||
use units::electric_current::milliampere;
|
||||
use units::f64::ElectricCurrent;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EL3024Configuration {
|
||||
|
||||
@@ -15,7 +15,7 @@ use crate::{
|
||||
io::analog_input::{AnalogInputDevice, AnalogInputInput},
|
||||
};
|
||||
use ethercat_hal_derive::EthercatDevice;
|
||||
use uom::si::{electric_potential::volt, f64::ElectricPotential};
|
||||
use units::{electric_potential::volt, f64::ElectricPotential};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EL3062_0030Configuration {
|
||||
|
||||
@@ -5,7 +5,7 @@ use anyhow::anyhow;
|
||||
use coe::EL7031_0030Configuration;
|
||||
use ethercat_hal_derive::EthercatDevice;
|
||||
use pdo::{EL7031_0030RxPdo, EL7031_0030TxPdo};
|
||||
use uom::si::{electric_potential::volt, f64::ElectricPotential};
|
||||
use units::{electric_potential::volt, f64::ElectricPotential};
|
||||
|
||||
use crate::{
|
||||
helpers::{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use uom::si::f64::{ElectricCurrent, ElectricPotential};
|
||||
use units::f64::{ElectricCurrent, ElectricPotential};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum AnalogInputValue {
|
||||
@@ -77,9 +77,9 @@ mod tests {
|
||||
|
||||
use super::*;
|
||||
use approx::assert_relative_eq;
|
||||
use uom::si::electric_current::milliampere;
|
||||
use uom::si::electric_potential::volt;
|
||||
use uom::si::f64::{ElectricCurrent, ElectricPotential};
|
||||
use units::electric_current::milliampere;
|
||||
use units::electric_potential::volt;
|
||||
use units::f64::{ElectricCurrent, ElectricPotential};
|
||||
|
||||
#[test]
|
||||
fn test_analog_input_getter_voltage() {
|
||||
|
||||
@@ -57,7 +57,7 @@ impl AnalogInputDummy {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use uom::si::{electric_potential::volt, f64::ElectricPotential};
|
||||
use units::{electric_potential::volt, f64::ElectricPotential};
|
||||
|
||||
use super::*;
|
||||
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
[package]
|
||||
name = "machines"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
ethercrab = "0.6"
|
||||
ethercat_hal = { path = "../ethercat-hal" }
|
||||
control_core = { path = "../control-core" }
|
||||
control_core_derive = { path = "../control-core-derive" }
|
||||
units = { path = "../units" }
|
||||
|
||||
serde = "1.0.217"
|
||||
anyhow = "1.0.100"
|
||||
|
||||
bitvec = "1.0.1"
|
||||
lazy_static = "1.5.0"
|
||||
euclid = "0.22.11"
|
||||
|
||||
# concurrency
|
||||
smol = "2.0.2"
|
||||
futures = "0.3.31"
|
||||
|
||||
# web
|
||||
serde_json = "1.0.143"
|
||||
socketioxide = { version = "0.17.2", features = ["msgpack"] }
|
||||
|
||||
# serial
|
||||
serialport = "4.7.3"
|
||||
|
||||
#tracing
|
||||
tracing = { version = "0.1.41", features = ["attributes"] }
|
||||
tonic = { version = "0.13.1", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
approx = "0.5.1"
|
||||
textplots = "0.8.7"
|
||||
|
||||
[features]
|
||||
default = ["tracing-fmt"]
|
||||
never_type = []
|
||||
tracing-fmt = []
|
||||
mock-machine = []
|
||||
memory-locking = []
|
||||
io-uring = []
|
||||
development-build = []
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[package.metadata.cargo-machete]
|
||||
ignored = ["tonic"]
|
||||
@@ -0,0 +1,59 @@
|
||||
use super::{AquaPathV1, AquaPathV1Mode};
|
||||
use crate::{MachineAct, MachineMessage};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
impl MachineAct for AquaPathV1 {
|
||||
fn act(&mut self, now_ts: Instant) {
|
||||
let msg = self.api_receiver.try_recv();
|
||||
match msg {
|
||||
Ok(msg) => {
|
||||
let _res = self.act_machine_message(msg);
|
||||
}
|
||||
Err(_) => (),
|
||||
};
|
||||
|
||||
match self.mode {
|
||||
AquaPathV1Mode::Standby => {
|
||||
self.switch_to_standby();
|
||||
}
|
||||
AquaPathV1Mode::Auto => {
|
||||
self.switch_to_auto();
|
||||
}
|
||||
}
|
||||
|
||||
let now = Instant::now();
|
||||
|
||||
self.front_controller.update(now_ts);
|
||||
self.back_controller.update(now_ts);
|
||||
|
||||
if now.duration_since(self.last_measurement_emit) > Duration::from_secs_f64(1.0 / 30.0) {
|
||||
self.emit_live_values();
|
||||
self.last_measurement_emit = now;
|
||||
}
|
||||
}
|
||||
|
||||
fn act_machine_message(&mut self, msg: MachineMessage) {
|
||||
match msg {
|
||||
MachineMessage::SubscribeNamespace(namespace) => {
|
||||
self.namespace.namespace = Some(namespace);
|
||||
self.emit_state();
|
||||
}
|
||||
MachineMessage::UnsubscribeNamespace => self.namespace.namespace = None,
|
||||
MachineMessage::HttpApiJsonRequest(value) => {
|
||||
use crate::MachineApi;
|
||||
|
||||
let _res = self.api_mutate(value);
|
||||
}
|
||||
MachineMessage::ConnectToMachine(_machine_connection) =>
|
||||
/*Doesnt connec to any Machine do nothing*/
|
||||
{
|
||||
()
|
||||
}
|
||||
MachineMessage::DisconnectMachine(_machine_connection) =>
|
||||
/*Doesnt connec to any Machine do nothing*/
|
||||
{
|
||||
()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,15 @@
|
||||
use std::{sync::Arc, time::Duration};
|
||||
|
||||
use super::{AquaPathV1, AquaPathV1Mode};
|
||||
use control_core::{
|
||||
machines::api::MachineApi,
|
||||
socketio::{
|
||||
event::{Event, GenericEvent},
|
||||
namespace::{
|
||||
CacheFn, CacheableEvents, Namespace, NamespaceCacheingLogic, cache_duration,
|
||||
cache_first_and_last_event,
|
||||
},
|
||||
use crate::{MachineApi, MachineMessage};
|
||||
use control_core::socketio::{
|
||||
event::{Event, GenericEvent},
|
||||
namespace::{
|
||||
CacheFn, CacheableEvents, Namespace, NamespaceCacheingLogic, cache_first_and_last_event,
|
||||
},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use smol::lock::Mutex;
|
||||
use smol::channel::Sender;
|
||||
use std::sync::Arc;
|
||||
use tracing::instrument;
|
||||
|
||||
#[derive(Serialize, Debug, Clone, Default)]
|
||||
@@ -91,9 +87,9 @@ enum Mutation {
|
||||
SetBackFlow(bool),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AquaPathV1Namespace {
|
||||
pub namespace: Arc<Mutex<Namespace>>,
|
||||
pub namespace: Option<Namespace>,
|
||||
}
|
||||
|
||||
impl NamespaceCacheingLogic<AquaPathV1Events> for AquaPathV1Namespace {
|
||||
@@ -101,9 +97,10 @@ 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();
|
||||
|
||||
let mut namespace = self.namespace.lock_blocking();
|
||||
namespace.emit(event, &buffer_fn);
|
||||
match &mut self.namespace {
|
||||
Some(ns) => ns.emit(event, &buffer_fn),
|
||||
None => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,17 +113,20 @@ impl CacheableEvents<AquaPathV1Events> for AquaPathV1Events {
|
||||
}
|
||||
|
||||
fn event_cache_fn(&self) -> CacheFn {
|
||||
let cache_one_hour = cache_duration(Duration::from_secs(60 * 60), Duration::from_secs(1));
|
||||
let cache_first_and_last = cache_first_and_last_event();
|
||||
|
||||
match self {
|
||||
AquaPathV1Events::LiveValues(_) => cache_one_hour,
|
||||
AquaPathV1Events::LiveValues(_) => cache_first_and_last,
|
||||
AquaPathV1Events::State(_) => cache_first_and_last,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MachineApi for AquaPathV1 {
|
||||
fn api_get_sender(&self) -> Sender<MachineMessage> {
|
||||
self.api_sender.clone()
|
||||
}
|
||||
|
||||
fn api_mutate(&mut self, request_body: Value) -> Result<(), anyhow::Error> {
|
||||
let control: Mutation = serde_json::from_value(request_body)?;
|
||||
match control {
|
||||
@@ -149,7 +149,7 @@ impl MachineApi for AquaPathV1 {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn api_event_namespace(&mut self) -> Arc<Mutex<Namespace>> {
|
||||
fn api_event_namespace(&mut self) -> Option<Namespace> {
|
||||
self.namespace.namespace.clone()
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,14 @@
|
||||
use crate::machines::aquapath1::VolumeRate;
|
||||
use crate::machines::aquapath1::{Flow, Temperature};
|
||||
use crate::aquapath1::VolumeRate;
|
||||
use crate::aquapath1::{Flow, Temperature};
|
||||
use control_core::controllers::pid::PidController;
|
||||
use ethercat_hal::io::encoder_input::EncoderInput;
|
||||
use ethercat_hal::io::{
|
||||
analog_output::AnalogOutput, digital_output::DigitalOutput, temperature_input::TemperatureInput,
|
||||
};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use uom::si::f64::ThermodynamicTemperature;
|
||||
use uom::si::thermodynamic_temperature::degree_celsius;
|
||||
use uom::si::volume_rate::liter_per_minute;
|
||||
use units::f64::ThermodynamicTemperature;
|
||||
use units::thermodynamic_temperature::degree_celsius;
|
||||
use units::volume_rate::liter_per_minute;
|
||||
#[derive(Debug)]
|
||||
|
||||
pub struct Controller {
|
||||
@@ -1,18 +1,11 @@
|
||||
use control_core::{
|
||||
machines::identification::{MachineIdentification, MachineIdentificationUnique},
|
||||
socketio::namespace::NamespaceCacheingLogic,
|
||||
};
|
||||
|
||||
use control_core_derive::Machine;
|
||||
use control_core::socketio::namespace::NamespaceCacheingLogic;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Instant;
|
||||
use uom::si::{
|
||||
f64::{ThermodynamicTemperature, VolumeRate},
|
||||
thermodynamic_temperature::degree_celsius,
|
||||
volume_rate::liter_per_minute,
|
||||
};
|
||||
use units::f64::*;
|
||||
use units::{thermodynamic_temperature::degree_celsius, volume_rate::liter_per_minute};
|
||||
|
||||
use crate::machines::{
|
||||
use crate::{AsyncThreadMessage, Machine, MachineMessage};
|
||||
use crate::{
|
||||
MACHINE_AQUAPATH_V1, VENDOR_QITECH,
|
||||
aquapath1::{
|
||||
api::{
|
||||
@@ -20,10 +13,13 @@ use crate::machines::{
|
||||
ModeState, StateEvent, TempState, TempStates,
|
||||
},
|
||||
controller::Controller,
|
||||
// flow_controller::FlowController,
|
||||
// temperature_controller::TemperatureController,
|
||||
},
|
||||
machine_identification::MachineIdentification,
|
||||
};
|
||||
|
||||
use smol::channel::{Receiver, Sender};
|
||||
|
||||
use super::machine_identification::MachineIdentificationUnique;
|
||||
pub mod act;
|
||||
pub mod api;
|
||||
pub mod controller;
|
||||
@@ -50,6 +46,16 @@ pub struct Temperature {
|
||||
pub target_temperature: ThermodynamicTemperature,
|
||||
}
|
||||
|
||||
impl Machine for AquaPathV1 {
|
||||
fn get_machine_identification_unique(&self) -> MachineIdentificationUnique {
|
||||
self.machine_identification_unique.clone()
|
||||
}
|
||||
|
||||
fn get_main_sender(&self) -> Option<Sender<AsyncThreadMessage>> {
|
||||
self.main_sender.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Temperature {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
@@ -78,14 +84,17 @@ impl Default for Flow {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Machine)]
|
||||
#[derive(Debug)]
|
||||
pub struct AquaPathV1 {
|
||||
api_receiver: Receiver<MachineMessage>,
|
||||
api_sender: Sender<MachineMessage>,
|
||||
machine_identification_unique: MachineIdentificationUnique,
|
||||
namespace: AquaPathV1Namespace,
|
||||
mode: AquaPathV1Mode,
|
||||
last_measurement_emit: Instant,
|
||||
front_controller: Controller,
|
||||
back_controller: Controller,
|
||||
main_sender: Option<Sender<AsyncThreadMessage>>,
|
||||
}
|
||||
|
||||
impl AquaPathV1 {
|
||||
@@ -1,13 +1,13 @@
|
||||
use super::{AquaPathV1, AquaPathV1Mode};
|
||||
use crate::machines::{
|
||||
aquapath1::{Flow, Temperature, api::AquaPathV1Namespace, controller::Controller},
|
||||
get_ethercat_device,
|
||||
use crate::{
|
||||
MachineNewHardware, MachineNewParams, MachineNewTrait, get_ethercat_device,
|
||||
get_subdevice_by_index, validate_no_role_dublicates,
|
||||
validate_same_machine_identification_unique,
|
||||
};
|
||||
|
||||
use super::{
|
||||
AquaPathV1, AquaPathV1Mode, Flow, Temperature, api::AquaPathV1Namespace, controller::Controller,
|
||||
};
|
||||
use anyhow::Error;
|
||||
use control_core::machines::new::{
|
||||
MachineNewHardware, MachineNewParams, MachineNewTrait, get_subdevice_by_index,
|
||||
validate_no_role_dublicates, validate_same_machine_identification_unique,
|
||||
};
|
||||
use ethercat_hal::{
|
||||
coe::ConfigurableDevice,
|
||||
devices::{
|
||||
@@ -26,7 +26,7 @@ use ethercat_hal::{
|
||||
},
|
||||
};
|
||||
use std::time::{Duration, Instant};
|
||||
use uom::si::{f64::ThermodynamicTemperature, thermodynamic_temperature::degree_celsius};
|
||||
use units::thermodynamic_temperature::{ThermodynamicTemperature, degree_celsius};
|
||||
|
||||
impl MachineNewTrait for AquaPathV1 {
|
||||
fn new<'maindevice>(params: &MachineNewParams) -> Result<Self, Error> {
|
||||
@@ -160,8 +160,11 @@ impl MachineNewTrait for AquaPathV1 {
|
||||
do2,
|
||||
enc2,
|
||||
);
|
||||
|
||||
let (sender, receiver) = smol::channel::unbounded();
|
||||
let mut water_cooling = Self {
|
||||
main_sender: params.main_thread_channel.clone(),
|
||||
api_receiver: receiver,
|
||||
api_sender: sender,
|
||||
machine_identification_unique: params.get_machine_identification_unique(),
|
||||
namespace: AquaPathV1Namespace {
|
||||
namespace: params.namespace.clone(),
|
||||
@@ -0,0 +1,47 @@
|
||||
use super::BufferV1;
|
||||
use crate::{MachineAct, MachineMessage};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
impl MachineAct for BufferV1 {
|
||||
fn act(&mut self, now: Instant) {
|
||||
let msg = self.api_receiver.try_recv();
|
||||
match msg {
|
||||
Ok(msg) => {
|
||||
let _res = self.act_machine_message(msg);
|
||||
}
|
||||
Err(_) => (),
|
||||
};
|
||||
// if last measurement is older than 1 second, emit a new measurement
|
||||
if now.duration_since(self.last_measurement_emit) > Duration::from_secs_f64(1.0 / 30.0) {
|
||||
// Emit live values at 30 FPS
|
||||
self.emit_live_values();
|
||||
|
||||
self.last_measurement_emit = now;
|
||||
}
|
||||
}
|
||||
|
||||
fn act_machine_message(&mut self, msg: MachineMessage) {
|
||||
match msg {
|
||||
MachineMessage::SubscribeNamespace(namespace) => {
|
||||
self.namespace.namespace = Some(namespace);
|
||||
self.emit_state();
|
||||
}
|
||||
MachineMessage::UnsubscribeNamespace => self.namespace.namespace = None,
|
||||
MachineMessage::HttpApiJsonRequest(value) => {
|
||||
use crate::MachineApi;
|
||||
|
||||
let _res = self.api_mutate(value);
|
||||
}
|
||||
MachineMessage::ConnectToMachine(_machine_connection) =>
|
||||
/*Doesnt connec to any Macine do nothing*/
|
||||
{
|
||||
()
|
||||
}
|
||||
MachineMessage::DisconnectMachine(_machine_connection) =>
|
||||
/*Doesnt connec to any Machine do nothing*/
|
||||
{
|
||||
()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,13 @@
|
||||
use std::{sync::Arc, time::Duration};
|
||||
|
||||
use super::{BufferV1, BufferV1Mode};
|
||||
use control_core::{
|
||||
machines::{
|
||||
api::MachineApi, connection::MachineCrossConnectionState,
|
||||
identification::MachineIdentificationUnique,
|
||||
},
|
||||
socketio::{
|
||||
event::{Event, GenericEvent},
|
||||
namespace::{
|
||||
CacheFn, CacheableEvents, Namespace, NamespaceCacheingLogic, cache_duration,
|
||||
cache_one_event,
|
||||
},
|
||||
},
|
||||
use crate::{MachineApi, MachineMessage, machine_identification::MachineIdentificationUnique};
|
||||
use control_core::socketio::{
|
||||
event::{Event, GenericEvent},
|
||||
namespace::{CacheFn, CacheableEvents, Namespace, NamespaceCacheingLogic, cache_one_event},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use smol::lock::Mutex;
|
||||
use smol::channel::Sender;
|
||||
use std::sync::Arc;
|
||||
use tracing::instrument;
|
||||
|
||||
#[derive(Serialize, Debug, Clone, Default)]
|
||||
@@ -31,9 +22,7 @@ impl LiveValuesEvent {
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
pub struct StateEvent {
|
||||
/// mode state
|
||||
pub mode_state: ModeState,
|
||||
/// connected machine state
|
||||
pub connected_machine_state: MachineCrossConnectionState,
|
||||
pub mode_state: ModeState, // connected machine state
|
||||
}
|
||||
|
||||
impl StateEvent {
|
||||
@@ -80,7 +69,7 @@ enum Mutation {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Buffer1Namespace {
|
||||
pub namespace: Arc<Mutex<Namespace>>,
|
||||
pub namespace: Option<Namespace>,
|
||||
}
|
||||
|
||||
impl NamespaceCacheingLogic<BufferV1Events> for Buffer1Namespace {
|
||||
@@ -89,8 +78,10 @@ impl NamespaceCacheingLogic<BufferV1Events> for Buffer1Namespace {
|
||||
let event = Arc::new(events.event_value());
|
||||
let buffer_fn = events.event_cache_fn();
|
||||
|
||||
let mut namespace = self.namespace.lock_blocking();
|
||||
namespace.emit(event, &buffer_fn);
|
||||
match &mut self.namespace {
|
||||
Some(ns) => ns.emit(event, &buffer_fn),
|
||||
None => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,32 +94,31 @@ impl CacheableEvents<Self> for BufferV1Events {
|
||||
}
|
||||
|
||||
fn event_cache_fn(&self) -> CacheFn {
|
||||
let cache_one_hour = cache_duration(Duration::from_secs(60 * 60), Duration::from_secs(1));
|
||||
let cache_one = cache_one_event();
|
||||
|
||||
match self {
|
||||
Self::LiveValues(_) => cache_one_hour,
|
||||
Self::LiveValues(_) => cache_one,
|
||||
Self::State(_) => cache_one,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MachineApi for BufferV1 {
|
||||
fn api_get_sender(&self) -> Sender<MachineMessage> {
|
||||
self.api_sender.clone()
|
||||
}
|
||||
|
||||
fn api_mutate(&mut self, request_body: Value) -> Result<(), anyhow::Error> {
|
||||
let mutation: Mutation = serde_json::from_value(request_body)?;
|
||||
match mutation {
|
||||
Mutation::SetBufferMode(mode) => self.set_mode_state(mode),
|
||||
Mutation::SetConnectedMachine(machine_identification_unique) => {
|
||||
self.set_connected_winder(machine_identification_unique);
|
||||
}
|
||||
Mutation::DisconnectMachine(machine_identification_unique) => {
|
||||
self.disconnect_winder(machine_identification_unique);
|
||||
}
|
||||
Mutation::SetConnectedMachine(_machine_identification_unique) => {}
|
||||
Mutation::DisconnectMachine(_machine_identification_unique) => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn api_event_namespace(&mut self) -> Arc<Mutex<Namespace>> {
|
||||
fn api_event_namespace(&mut self) -> Option<Namespace> {
|
||||
self.namespace.namespace.clone()
|
||||
}
|
||||
}
|
||||
File renamed without changes.
@@ -3,46 +3,37 @@ pub mod api;
|
||||
pub mod buffer_tower_controller;
|
||||
pub mod new;
|
||||
|
||||
use super::machine_identification::{MachineIdentification, MachineIdentificationUnique};
|
||||
use crate::{AsyncThreadMessage, Machine, MachineMessage};
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use crate::machines::{MACHINE_BUFFER_V1, VENDOR_QITECH, winder2::Winder2};
|
||||
use crate::{MACHINE_BUFFER_V1, VENDOR_QITECH};
|
||||
use api::{Buffer1Namespace, BufferV1Events, LiveValuesEvent, ModeState, StateEvent};
|
||||
use buffer_tower_controller::BufferTowerController;
|
||||
use control_core::machines::connection::{CrossConnectableMachine, MachineCrossConnection};
|
||||
use control_core::{
|
||||
machines::{
|
||||
identification::{MachineIdentification, MachineIdentificationUnique},
|
||||
manager::MachineManager,
|
||||
},
|
||||
socketio::namespace::NamespaceCacheingLogic,
|
||||
};
|
||||
use control_core_derive::Machine;
|
||||
use control_core::socketio::namespace::NamespaceCacheingLogic;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use smol::lock::RwLock;
|
||||
use std::{sync::Weak, time::Instant};
|
||||
use smol::channel::{Receiver, Sender};
|
||||
use std::time::Instant;
|
||||
|
||||
#[derive(Debug, Machine)]
|
||||
#[derive(Debug)]
|
||||
pub struct BufferV1 {
|
||||
// controllers
|
||||
pub buffer_tower_controller: BufferTowerController,
|
||||
api_receiver: Receiver<MachineMessage>,
|
||||
api_sender: Sender<MachineMessage>,
|
||||
|
||||
// socketio
|
||||
pub buffer_tower_controller: BufferTowerController,
|
||||
namespace: Buffer1Namespace,
|
||||
last_measurement_emit: Instant,
|
||||
|
||||
// machine connection
|
||||
pub machine_manager: Weak<RwLock<MachineManager>>,
|
||||
pub machine_identification_unique: MachineIdentificationUnique,
|
||||
|
||||
// connected machines
|
||||
pub connected_winder: MachineCrossConnection<BufferV1, Winder2>,
|
||||
|
||||
// mode
|
||||
mode: BufferV1Mode,
|
||||
main_sender: Option<Sender<AsyncThreadMessage>>,
|
||||
}
|
||||
|
||||
impl CrossConnectableMachine<BufferV1, Winder2> for BufferV1 {
|
||||
fn get_cross_connection(&mut self) -> &mut MachineCrossConnection<BufferV1, Winder2> {
|
||||
&mut self.connected_winder
|
||||
impl Machine for BufferV1 {
|
||||
fn get_machine_identification_unique(&self) -> MachineIdentificationUnique {
|
||||
self.machine_identification_unique.clone()
|
||||
}
|
||||
|
||||
fn get_main_sender(&self) -> Option<Sender<AsyncThreadMessage>> {
|
||||
self.main_sender.clone()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,13 +54,12 @@ impl BufferV1 {
|
||||
let event = live_values.build();
|
||||
self.namespace.emit(BufferV1Events::LiveValues(event));
|
||||
}
|
||||
|
||||
pub fn emit_state(&mut self) {
|
||||
let state = StateEvent {
|
||||
mode_state: ModeState {
|
||||
mode: self.mode.clone(),
|
||||
},
|
||||
connected_machine_state: self.connected_winder.to_state(),
|
||||
// connected_machine_state: self.connected_winder.to_state(),
|
||||
};
|
||||
|
||||
let event = state.build();
|
||||
@@ -133,43 +123,6 @@ impl BufferV1 {
|
||||
self.switch_mode(mode);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Connecting/Disconnecting machine
|
||||
/// set connected winder
|
||||
pub fn set_connected_winder(
|
||||
&mut self,
|
||||
machine_identification_unique: MachineIdentificationUnique,
|
||||
) {
|
||||
if !matches!(
|
||||
machine_identification_unique.machine_identification,
|
||||
Winder2::MACHINE_IDENTIFICATION
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.connected_winder
|
||||
.set_connected_machine(&machine_identification_unique);
|
||||
|
||||
self.emit_state();
|
||||
|
||||
self.connected_winder.reverse_connect();
|
||||
}
|
||||
|
||||
/// disconnect winder
|
||||
pub fn disconnect_winder(
|
||||
&mut self,
|
||||
machine_identification_unique: MachineIdentificationUnique,
|
||||
) {
|
||||
if !matches!(
|
||||
machine_identification_unique.machine_identification,
|
||||
Winder2::MACHINE_IDENTIFICATION
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.connected_winder.disconnect();
|
||||
self.emit_state();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
|
||||
@@ -1,11 +1,6 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use anyhow::Error;
|
||||
use control_core::machines::connection::MachineCrossConnection;
|
||||
use control_core::machines::new::{
|
||||
MachineNewHardware, MachineNewParams, MachineNewTrait, validate_no_role_dublicates,
|
||||
validate_same_machine_identification_unique,
|
||||
};
|
||||
use ethercat_hal::{
|
||||
coe::ConfigurableDevice,
|
||||
devices::el7031_0030::coe::EL7031_0030Configuration,
|
||||
@@ -22,9 +17,11 @@ use ethercat_hal::{
|
||||
shared_config::el70x1::{EL70x1OperationMode, StmMotorConfiguration},
|
||||
};
|
||||
|
||||
use crate::machines::buffer1::BufferV1Mode;
|
||||
use crate::machines::buffer1::buffer_tower_controller::BufferTowerController;
|
||||
use crate::machines::get_ethercat_device;
|
||||
use crate::{
|
||||
MachineNewHardware, MachineNewHardwareEthercat, MachineNewParams, MachineNewTrait,
|
||||
buffer1::BufferV1Mode, get_ethercat_device, validate_same_machine_identification_unique,
|
||||
};
|
||||
use crate::{buffer1::buffer_tower_controller::BufferTowerController, validate_no_role_dublicates};
|
||||
|
||||
use super::{BufferV1, api::Buffer1Namespace};
|
||||
|
||||
@@ -37,16 +34,15 @@ impl MachineNewTrait for BufferV1 {
|
||||
validate_same_machine_identification_unique(&device_identification)?;
|
||||
validate_no_role_dublicates(&device_identification)?;
|
||||
|
||||
let hardware: &&control_core::machines::new::MachineNewHardwareEthercat<'_, '_, '_> =
|
||||
match ¶ms.hardware {
|
||||
MachineNewHardware::Ethercat(x) => x,
|
||||
_ => {
|
||||
return Err(anyhow::anyhow!(
|
||||
"[{}::MachineNewTrait/Buffer::new] MachineNewHardware is not Ethercat",
|
||||
module_path!()
|
||||
));
|
||||
}
|
||||
};
|
||||
let hardware: &&MachineNewHardwareEthercat<'_, '_, '_> = match ¶ms.hardware {
|
||||
MachineNewHardware::Ethercat(x) => x,
|
||||
_ => {
|
||||
return Err(anyhow::anyhow!(
|
||||
"[{}::MachineNewTrait/Buffer::new] MachineNewHardware is not Ethercat",
|
||||
module_path!()
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
smol::block_on(async {
|
||||
// Role 0 - Buscoupler EK1100
|
||||
@@ -128,21 +124,20 @@ impl MachineNewTrait for BufferV1 {
|
||||
));
|
||||
|
||||
let machine_identification_unique = params.get_machine_identification_unique();
|
||||
let (sender, receiver) = smol::channel::unbounded();
|
||||
|
||||
// create buffer instance
|
||||
let mut buffer: BufferV1 = Self {
|
||||
main_sender: params.main_thread_channel.clone(),
|
||||
api_receiver: receiver,
|
||||
api_sender: sender,
|
||||
namespace: Buffer1Namespace {
|
||||
namespace: params.namespace.clone(),
|
||||
},
|
||||
last_measurement_emit: Instant::now(),
|
||||
mode: BufferV1Mode::Standby,
|
||||
buffer_tower_controller,
|
||||
machine_manager: params.machine_manager.clone(),
|
||||
machine_identification_unique: machine_identification_unique.clone(),
|
||||
connected_winder: MachineCrossConnection::new(
|
||||
params.machine_manager.clone(),
|
||||
&machine_identification_unique,
|
||||
),
|
||||
};
|
||||
buffer.emit_state();
|
||||
Ok(buffer)
|
||||
@@ -1,13 +1,21 @@
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use crate::machines::extruder1::ExtruderV2;
|
||||
use crate::extruder1::ExtruderV2;
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use control_core::machines::new::MachineAct;
|
||||
use crate::{MachineAct, MachineMessage};
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
impl MachineAct for ExtruderV2 {
|
||||
fn act(&mut self, now: Instant) {
|
||||
let msg = self.api_receiver.try_recv();
|
||||
match msg {
|
||||
Ok(msg) => {
|
||||
let _res = self.act_machine_message(msg);
|
||||
}
|
||||
Err(_) => (),
|
||||
};
|
||||
|
||||
self.temperature_controller_back.update(now);
|
||||
self.temperature_controller_nozzle.update(now);
|
||||
self.temperature_controller_front.update(now);
|
||||
@@ -39,4 +47,26 @@ impl MachineAct for ExtruderV2 {
|
||||
self.last_measurement_emit = now;
|
||||
}
|
||||
}
|
||||
|
||||
fn act_machine_message(&mut self, msg: MachineMessage) {
|
||||
match msg {
|
||||
MachineMessage::SubscribeNamespace(namespace) => {
|
||||
self.namespace.namespace = Some(namespace);
|
||||
self.emit_state();
|
||||
tracing::info!("extruder1 received subscribe");
|
||||
}
|
||||
MachineMessage::UnsubscribeNamespace => self.namespace.namespace = None,
|
||||
MachineMessage::HttpApiJsonRequest(value) => {
|
||||
use crate::MachineApi;
|
||||
|
||||
let _res = self.api_mutate(value);
|
||||
}
|
||||
MachineMessage::ConnectToMachine(_machine_connection) => (),
|
||||
MachineMessage::DisconnectMachine(_machine_connection) =>
|
||||
/*Doesnt connec to any Machine do nothing*/
|
||||
{
|
||||
()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,28 +4,28 @@ use super::{ExtruderV2Mode, mitsubishi_cs80::MotorStatus};
|
||||
use super::ExtruderV2;
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use crate::machines::extruder1::HeatingType;
|
||||
use crate::{MachineMessage, extruder1::HeatingType};
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use control_core::machines::api::MachineApi;
|
||||
use crate::MachineApi;
|
||||
use control_core::socketio::{
|
||||
event::{Event, GenericEvent},
|
||||
namespace::{
|
||||
CacheFn, CacheableEvents, Namespace, NamespaceCacheingLogic, cache_duration,
|
||||
cache_first_and_last_event,
|
||||
CacheFn, CacheableEvents, Namespace, NamespaceCacheingLogic, 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::lock::Mutex;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use smol::channel::Sender;
|
||||
use std::sync::Arc;
|
||||
use tracing::instrument;
|
||||
use uom::si::{
|
||||
angular_velocity::revolution_per_minute, electric_current::ampere, electric_potential::volt,
|
||||
frequency::hertz,
|
||||
};
|
||||
use units::angular_velocity::revolution_per_minute;
|
||||
use units::electric_current::ampere;
|
||||
use units::electric_potential::volt;
|
||||
use units::frequency::hertz;
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize)]
|
||||
pub struct MotorStatusValues {
|
||||
@@ -188,6 +188,7 @@ pub struct TemperaturePidStates {
|
||||
pub front: TemperaturePid,
|
||||
pub middle: TemperaturePid,
|
||||
pub back: TemperaturePid,
|
||||
pub nozzle: TemperaturePid,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
|
||||
@@ -240,7 +241,7 @@ pub enum Mutation {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ExtruderV2Namespace {
|
||||
pub namespace: Arc<Mutex<Namespace>>,
|
||||
pub namespace: Option<Namespace>,
|
||||
}
|
||||
|
||||
impl NamespaceCacheingLogic<ExtruderV2Events> for ExtruderV2Namespace {
|
||||
@@ -249,8 +250,10 @@ impl NamespaceCacheingLogic<ExtruderV2Events> for ExtruderV2Namespace {
|
||||
let event = Arc::new(events.event_value());
|
||||
let buffer_fn = events.event_cache_fn();
|
||||
|
||||
let mut namespace = self.namespace.lock_blocking();
|
||||
namespace.emit(event, &buffer_fn);
|
||||
match &mut self.namespace {
|
||||
Some(ns) => ns.emit(event, &buffer_fn),
|
||||
None => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,11 +266,9 @@ impl CacheableEvents<Self> for ExtruderV2Events {
|
||||
}
|
||||
|
||||
fn event_cache_fn(&self) -> CacheFn {
|
||||
let cache_one_hour = cache_duration(Duration::from_secs(60 * 60), Duration::from_secs(1));
|
||||
let cache_first_and_last = cache_first_and_last_event();
|
||||
|
||||
match self {
|
||||
Self::LiveValues(_) => cache_one_hour,
|
||||
Self::LiveValues(_) => cache_first_and_last,
|
||||
Self::State(_) => cache_first_and_last,
|
||||
}
|
||||
}
|
||||
@@ -275,6 +276,10 @@ impl CacheableEvents<Self> for ExtruderV2Events {
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
impl MachineApi for ExtruderV2 {
|
||||
fn api_get_sender(&self) -> Sender<MachineMessage> {
|
||||
self.api_sender.clone()
|
||||
}
|
||||
|
||||
fn api_mutate(&mut self, request_body: Value) -> Result<(), anyhow::Error> {
|
||||
// there are multiple Modbus Frames that are "prebuilt"
|
||||
let control: Mutation = serde_json::from_value(request_body)?;
|
||||
@@ -316,7 +321,7 @@ impl MachineApi for ExtruderV2 {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn api_event_namespace(&mut self) -> Arc<Mutex<Namespace>> {
|
||||
fn api_event_namespace(&mut self) -> Option<Namespace> {
|
||||
self.namespace.namespace.clone()
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
// Contains Implementations for All functions that use emit_state
|
||||
use crate::machines::extruder1::{
|
||||
use crate::extruder1::{
|
||||
ExtruderV2, ExtruderV2Mode, HeatingType,
|
||||
api::{
|
||||
ExtruderSettingsState, ExtruderV2Events, HeatingState, HeatingStates, InverterStatusState,
|
||||
@@ -15,19 +15,18 @@ 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};
|
||||
|
||||
use units::angular_velocity::AngularVelocity;
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use uom::si::angular_velocity::AngularVelocity;
|
||||
use units::pressure::{Pressure, bar};
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use uom::si::pressure::{Pressure, bar};
|
||||
use units::thermodynamic_temperature::ThermodynamicTemperature;
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use uom::si::thermodynamic_temperature::ThermodynamicTemperature;
|
||||
use units::{angular_velocity::revolution_per_minute, thermodynamic_temperature::degree_celsius};
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
impl ExtruderV2 {
|
||||
pub fn build_state_event(&mut self) -> StateEvent {
|
||||
use crate::machines::extruder1::api::{TemperaturePid, TemperaturePidStates};
|
||||
use crate::extruder1::api::{TemperaturePid, TemperaturePidStates};
|
||||
|
||||
StateEvent {
|
||||
is_default_state: !std::mem::replace(&mut self.emitted_default_state, true),
|
||||
@@ -127,6 +126,12 @@ impl ExtruderV2 {
|
||||
kd: self.temperature_controller_back.pid.get_kd(),
|
||||
zone: String::from("back"),
|
||||
},
|
||||
nozzle: TemperaturePid {
|
||||
ki: self.temperature_controller_nozzle.pid.get_ki(),
|
||||
kp: self.temperature_controller_nozzle.pid.get_kp(),
|
||||
kd: self.temperature_controller_nozzle.pid.get_kd(),
|
||||
zone: String::from("nozzle"),
|
||||
},
|
||||
},
|
||||
pressure: PidSettings {
|
||||
ki: self.screw_speed_controller.pid.get_ki(),
|
||||
@@ -320,6 +325,13 @@ impl ExtruderV2 {
|
||||
settings.kd,
|
||||
);
|
||||
}
|
||||
"nozzle" => {
|
||||
self.temperature_controller_nozzle.pid.configure(
|
||||
settings.ki,
|
||||
settings.kp,
|
||||
settings.kd,
|
||||
);
|
||||
}
|
||||
_ => tracing::warn!("Unknown zone: {}", settings.zone),
|
||||
}
|
||||
self.emit_state();
|
||||
+4
-6
@@ -6,12 +6,10 @@ use control_core::modbus::{
|
||||
use ethercat_hal::io::serial_interface::SerialInterface;
|
||||
use serde::Serialize;
|
||||
use std::time::{Duration, Instant};
|
||||
use uom::si::{
|
||||
electric_current::centiampere,
|
||||
electric_potential::centivolt,
|
||||
f64::{AngularVelocity, ElectricCurrent, ElectricPotential, Frequency},
|
||||
frequency::centihertz,
|
||||
};
|
||||
use units::electric_current::centiampere;
|
||||
use units::electric_potential::centivolt;
|
||||
use units::f64::*;
|
||||
use units::frequency::centihertz;
|
||||
|
||||
/// Specifies all System environment Variables
|
||||
/// Register addresses are calculated as follows: Register-value 40002 -> address: 40002-40001 -> actual address in request:0x1
|
||||
@@ -0,0 +1,45 @@
|
||||
use super::ExtruderV2;
|
||||
use crate::MachineAct;
|
||||
use crate::MachineMessage;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
impl MachineAct for ExtruderV2 {
|
||||
fn act(&mut self, now: Instant) {
|
||||
let msg = self.api_receiver.try_recv();
|
||||
match msg {
|
||||
Ok(msg) => {
|
||||
let _res = self.act_machine_message(msg);
|
||||
}
|
||||
Err(_) => (),
|
||||
};
|
||||
// more than 33ms have passed since last emit (30 "fps" target)
|
||||
if now.duration_since(self.last_measurement_emit) > Duration::from_secs_f64(1.0 / 30.0) {
|
||||
self.maybe_emit_state_event();
|
||||
// Emit live values at 30 FPS
|
||||
self.emit_live_values();
|
||||
self.last_measurement_emit = now;
|
||||
}
|
||||
}
|
||||
|
||||
fn act_machine_message(&mut self, msg: crate::MachineMessage) {
|
||||
match msg {
|
||||
MachineMessage::SubscribeNamespace(namespace) => {
|
||||
self.namespace.namespace = Some(namespace);
|
||||
self.emit_state();
|
||||
tracing::info!("extruder1 received subscribe");
|
||||
}
|
||||
MachineMessage::UnsubscribeNamespace => self.namespace.namespace = None,
|
||||
MachineMessage::HttpApiJsonRequest(value) => {
|
||||
use crate::MachineApi;
|
||||
|
||||
let _res = self.api_mutate(value);
|
||||
}
|
||||
MachineMessage::ConnectToMachine(_machine_connection) => (),
|
||||
MachineMessage::DisconnectMachine(_machine_connection) =>
|
||||
/*Doesnt connec to any Machine do nothing*/
|
||||
{
|
||||
()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +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;
|
||||
use super::ExtruderV2;
|
||||
use crate::{
|
||||
MachineApi,
|
||||
extruder1::{HeatingType, api::Mutation},
|
||||
};
|
||||
|
||||
impl MachineApi for ExtruderV2 {
|
||||
fn api_mutate(&mut self, request_body: serde_json::Value) -> Result<(), anyhow::Error> {
|
||||
@@ -45,7 +45,13 @@ impl MachineApi for ExtruderV2 {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn api_event_namespace(&mut self) -> Arc<Mutex<Namespace>> {
|
||||
fn api_event_namespace(
|
||||
&mut self,
|
||||
) -> std::option::Option<control_core::socketio::namespace::Namespace> {
|
||||
self.namespace.namespace.clone()
|
||||
}
|
||||
|
||||
fn api_get_sender(&self) -> smol::channel::Sender<crate::MachineMessage> {
|
||||
self.api_sender.clone()
|
||||
}
|
||||
}
|
||||
+7
-1
@@ -1,8 +1,9 @@
|
||||
use crate::machines::extruder1::{
|
||||
use crate::extruder1::{
|
||||
ExtruderV2Mode, HeatingType,
|
||||
api::{ExtruderV2Events, LiveValuesEvent, ModeState, PidSettings, StateEvent, TemperaturePid},
|
||||
mock::ExtruderV2,
|
||||
};
|
||||
|
||||
use control_core::{
|
||||
helpers::hasher_serializer::hash_with_serde_model,
|
||||
socketio::{event::BuildEvent, namespace::NamespaceCacheingLogic},
|
||||
@@ -151,6 +152,11 @@ impl ExtruderV2 {
|
||||
self.pid_settings.temperature.back.kp = settings.kp;
|
||||
self.pid_settings.temperature.back.kd = settings.kd;
|
||||
}
|
||||
"nozzle" => {
|
||||
self.pid_settings.temperature.nozzle.ki = settings.ki;
|
||||
self.pid_settings.temperature.nozzle.kp = settings.kp;
|
||||
self.pid_settings.temperature.nozzle.kd = settings.kd;
|
||||
}
|
||||
_ => tracing::warn!("Unknown zone: {}", settings.zone),
|
||||
}
|
||||
self.emit_state();
|
||||
@@ -1,14 +1,21 @@
|
||||
#[cfg(feature = "mock-machine")]
|
||||
use control_core::machines::identification::MachineIdentification;
|
||||
use smol::channel::Receiver;
|
||||
|
||||
#[cfg(feature = "mock-machine")]
|
||||
use control_core::machines::identification::MachineIdentificationUnique;
|
||||
use smol::channel::Sender;
|
||||
|
||||
#[cfg(feature = "mock-machine")]
|
||||
use control_core_derive::Machine;
|
||||
use crate::{AsyncThreadMessage, Machine};
|
||||
|
||||
#[cfg(feature = "mock-machine")]
|
||||
use crate::{
|
||||
MachineIdentificationUnique, MachineMessage, machine_identification::MachineIdentification,
|
||||
};
|
||||
#[cfg(feature = "mock-machine")]
|
||||
use std::time::Instant;
|
||||
|
||||
#[cfg(feature = "mock-machine")]
|
||||
use crate::machines::{
|
||||
use crate::{
|
||||
MACHINE_EXTRUDER_V1, VENDOR_QITECH,
|
||||
extruder1::{
|
||||
ExtruderV2Mode,
|
||||
@@ -31,9 +38,25 @@ pub mod mock_emit;
|
||||
pub mod new;
|
||||
|
||||
#[cfg(feature = "mock-machine")]
|
||||
#[derive(Debug, Machine)]
|
||||
impl Machine for ExtruderV2 {
|
||||
fn get_machine_identification_unique(&self) -> MachineIdentificationUnique {
|
||||
self.machine_identification_unique.clone()
|
||||
}
|
||||
|
||||
fn get_main_sender(&self) -> Option<Sender<AsyncThreadMessage>> {
|
||||
self.main_sender.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "mock-machine")]
|
||||
#[derive(Debug)]
|
||||
pub struct ExtruderV2 {
|
||||
api_receiver: Receiver<MachineMessage>,
|
||||
api_sender: Sender<MachineMessage>,
|
||||
main_sender: Option<Sender<AsyncThreadMessage>>,
|
||||
|
||||
machine_identification_unique: MachineIdentificationUnique,
|
||||
|
||||
namespace: ExtruderV2Namespace,
|
||||
last_measurement_emit: Instant,
|
||||
last_status_hash: Option<u64>,
|
||||
@@ -1,18 +1,19 @@
|
||||
use crate::machines::extruder1::{
|
||||
ExtruderV2Mode,
|
||||
api::{
|
||||
ExtruderSettingsState, ExtruderV2Namespace, HeatingState, HeatingStates,
|
||||
InverterStatusState, ModeState, MotorStatusValues, PidSettings, PidSettingsStates,
|
||||
PressureState, RegulationState, RotationState, ScrewState, TemperaturePid,
|
||||
TemperaturePidStates,
|
||||
use crate::{
|
||||
MachineNewHardware, MachineNewParams, MachineNewTrait,
|
||||
extruder1::{
|
||||
ExtruderV2Mode,
|
||||
api::{
|
||||
ExtruderSettingsState, ExtruderV2Namespace, HeatingState, HeatingStates,
|
||||
InverterStatusState, ModeState, MotorStatusValues, PidSettings, PidSettingsStates,
|
||||
PressureState, RegulationState, RotationState, ScrewState, TemperaturePid,
|
||||
TemperaturePidStates,
|
||||
},
|
||||
mock::ExtruderV2,
|
||||
},
|
||||
mock::ExtruderV2,
|
||||
};
|
||||
|
||||
impl control_core::machines::new::MachineNewTrait for ExtruderV2 {
|
||||
fn new(
|
||||
params: &control_core::machines::new::MachineNewParams<'_, '_, '_, '_, '_, '_, '_>,
|
||||
) -> Result<Self, anyhow::Error>
|
||||
impl MachineNewTrait for ExtruderV2 {
|
||||
fn new(params: &MachineNewParams<'_, '_, '_, '_, '_, '_, '_>) -> Result<Self, anyhow::Error>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
@@ -20,19 +21,23 @@ impl control_core::machines::new::MachineNewTrait for ExtruderV2 {
|
||||
// For the mock machine, we don't need to actually use the hardware
|
||||
// We just validate that we have the expected hardware type
|
||||
match params.hardware {
|
||||
control_core::machines::new::MachineNewHardware::Serial(_) => {
|
||||
MachineNewHardware::Serial(_) => {
|
||||
// For serial mode, we could potentially use the serial device if needed
|
||||
// but for a mock machine, we'll just note it and proceed
|
||||
}
|
||||
control_core::machines::new::MachineNewHardware::Ethercat(_) => {
|
||||
MachineNewHardware::Ethercat(_) => {
|
||||
// For ethercat mode, we could potentially use the ethercat devices
|
||||
// but for a mock machine, we'll just note it and proceed
|
||||
}
|
||||
}
|
||||
|
||||
let now = std::time::Instant::now();
|
||||
let (sender, receiver) = smol::channel::unbounded();
|
||||
|
||||
let mut extruder_mock_machine = Self {
|
||||
main_sender: params.main_thread_channel.clone(),
|
||||
api_receiver: receiver,
|
||||
api_sender: sender,
|
||||
machine_identification_unique: params.get_machine_identification_unique(),
|
||||
namespace: ExtruderV2Namespace {
|
||||
namespace: params.namespace.clone(),
|
||||
@@ -91,23 +96,29 @@ impl control_core::machines::new::MachineNewTrait for ExtruderV2 {
|
||||
pid_settings: PidSettingsStates {
|
||||
temperature: TemperaturePidStates {
|
||||
front: TemperaturePid {
|
||||
ki: 0.0,
|
||||
kp: 0.0,
|
||||
kd: 0.0,
|
||||
ki: 0.1,
|
||||
kp: 0.1,
|
||||
kd: 0.1,
|
||||
zone: String::from("front"),
|
||||
},
|
||||
middle: TemperaturePid {
|
||||
ki: 0.0,
|
||||
kp: 0.0,
|
||||
kd: 0.0,
|
||||
ki: 0.1,
|
||||
kp: 0.1,
|
||||
kd: 0.1,
|
||||
zone: String::from("middle"),
|
||||
},
|
||||
back: TemperaturePid {
|
||||
ki: 0.0,
|
||||
kp: 0.0,
|
||||
kd: 0.0,
|
||||
ki: 0.1,
|
||||
kp: 0.1,
|
||||
kd: 0.1,
|
||||
zone: String::from("back"),
|
||||
},
|
||||
nozzle: TemperaturePid {
|
||||
ki: 0.1,
|
||||
kp: 0.1,
|
||||
kd: 0.1,
|
||||
zone: String::from("nozzle"),
|
||||
},
|
||||
},
|
||||
pressure: PidSettings {
|
||||
ki: 0.0,
|
||||
@@ -2,23 +2,30 @@
|
||||
use std::time::Instant;
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use control_core::machines::identification::{MachineIdentification, MachineIdentificationUnique};
|
||||
use smol::channel::Receiver;
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use control_core_derive::Machine;
|
||||
use smol::channel::Sender;
|
||||
|
||||
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,
|
||||
use units::electric_current::ampere;
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use units::electric_potential::volt;
|
||||
|
||||
use crate::{AsyncThreadMessage, Machine};
|
||||
use units::f64::*;
|
||||
use units::thermodynamic_temperature::degree_celsius;
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use crate::{
|
||||
MACHINE_EXTRUDER_V1, MachineMessage, VENDOR_QITECH,
|
||||
extruder1::{
|
||||
api::ExtruderV2Namespace, screw_speed_controller::ScrewSpeedController,
|
||||
temperature_controller::TemperatureController,
|
||||
},
|
||||
machine_identification::{MachineIdentification, MachineIdentificationUnique},
|
||||
};
|
||||
|
||||
pub mod act;
|
||||
@@ -64,13 +71,19 @@ pub enum HeatingType {
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
#[derive(Debug, Machine)]
|
||||
#[derive(Debug)]
|
||||
pub struct ExtruderV2 {
|
||||
api_receiver: Receiver<MachineMessage>,
|
||||
api_sender: Sender<MachineMessage>,
|
||||
main_sender: Option<Sender<AsyncThreadMessage>>,
|
||||
|
||||
machine_identification_unique: MachineIdentificationUnique,
|
||||
namespace: ExtruderV2Namespace,
|
||||
|
||||
last_measurement_emit: Instant,
|
||||
last_status_hash: Option<u64>,
|
||||
mode: ExtruderV2Mode,
|
||||
|
||||
screw_speed_controller: ScrewSpeedController,
|
||||
temperature_controller_front: TemperatureController,
|
||||
temperature_controller_middle: TemperatureController,
|
||||
@@ -86,6 +99,17 @@ pub struct ExtruderV2 {
|
||||
emitted_default_state: bool,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
impl Machine for ExtruderV2 {
|
||||
fn get_machine_identification_unique(&self) -> MachineIdentificationUnique {
|
||||
self.machine_identification_unique.clone()
|
||||
}
|
||||
|
||||
fn get_main_sender(&self) -> Option<Sender<AsyncThreadMessage>> {
|
||||
self.main_sender.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
impl std::fmt::Display for ExtruderV2 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
@@ -1,15 +1,9 @@
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use crate::machines::get_ethercat_device;
|
||||
use crate::{MachineNewParams, MachineNewTrait, 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::{
|
||||
validate_no_role_dublicates, validate_same_machine_identification_unique,
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use ethercat_hal::coe::ConfigurableDevice;
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
@@ -29,13 +23,13 @@ use std::time::Duration;
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use std::time::Instant;
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use uom::si::angular_velocity::AngularVelocity;
|
||||
use units::angular_velocity::AngularVelocity;
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use uom::si::angular_velocity::revolution_per_minute;
|
||||
use units::angular_velocity::revolution_per_minute;
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use uom::si::pressure::Pressure;
|
||||
use units::pressure::Pressure;
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use uom::si::pressure::bar;
|
||||
use units::pressure::bar;
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use ethercat_hal::{
|
||||
@@ -50,10 +44,10 @@ use ethercat_hal::{
|
||||
},
|
||||
};
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use uom::si::thermodynamic_temperature::{ThermodynamicTemperature, degree_celsius};
|
||||
use units::thermodynamic_temperature::{ThermodynamicTemperature, degree_celsius};
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use crate::machines::extruder1::temperature_controller::TemperatureController;
|
||||
use crate::extruder1::temperature_controller::TemperatureController;
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use super::{
|
||||
@@ -66,21 +60,25 @@ impl MachineNewTrait for ExtruderV2 {
|
||||
fn new<'maindevice>(params: &MachineNewParams) -> Result<Self, Error> {
|
||||
// validate general stuff
|
||||
|
||||
use crate::{
|
||||
MachineNewHardware, MachineNewHardwareEthercat, validate_no_role_dublicates,
|
||||
validate_same_machine_identification_unique,
|
||||
};
|
||||
|
||||
let device_identification = params.device_group.to_vec();
|
||||
|
||||
validate_same_machine_identification_unique(&device_identification)?;
|
||||
validate_no_role_dublicates(&device_identification)?;
|
||||
|
||||
let hardware: &&control_core::machines::new::MachineNewHardwareEthercat<'_, '_, '_> =
|
||||
match ¶ms.hardware {
|
||||
MachineNewHardware::Ethercat(x) => x,
|
||||
_ => {
|
||||
return Err(anyhow::anyhow!(
|
||||
"[{}::MachineNewTrait/Extruder2::new] MachineNewHardware is not Ethercat",
|
||||
module_path!()
|
||||
));
|
||||
}
|
||||
};
|
||||
let hardware: &&MachineNewHardwareEthercat<'_, '_, '_> = match ¶ms.hardware {
|
||||
MachineNewHardware::Ethercat(x) => x,
|
||||
_ => {
|
||||
return Err(anyhow::anyhow!(
|
||||
"[{}::MachineNewTrait/Extruder2::new] MachineNewHardware is not Ethercat",
|
||||
module_path!()
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
// using block_on because making this funciton async creates a lifetime issue
|
||||
// if its async the compiler thinks &subdevices is persisted in the future which might never execute
|
||||
@@ -218,8 +216,12 @@ impl MachineNewTrait for ExtruderV2 {
|
||||
|
||||
let screw_speed_controller =
|
||||
ScrewSpeedController::new(inverter, target_pressure, target_rpm, pressure_sensor);
|
||||
let (sender, receiver) = smol::channel::unbounded();
|
||||
|
||||
let mut extruder: ExtruderV2 = Self {
|
||||
main_sender: params.main_thread_channel.clone(),
|
||||
api_receiver: receiver,
|
||||
api_sender: sender,
|
||||
machine_identification_unique: params.get_machine_identification_unique(),
|
||||
namespace: ExtruderV2Namespace {
|
||||
namespace: params.namespace.clone(),
|
||||
+6
-8
@@ -7,15 +7,13 @@ use control_core::{
|
||||
};
|
||||
use ethercat_hal::io::analog_input::AnalogInput;
|
||||
use futures::executor::block_on;
|
||||
use uom::si::{
|
||||
angular_velocity::revolution_per_minute,
|
||||
electric_current::milliampere,
|
||||
f64::{AngularVelocity, ElectricCurrent, Frequency, Pressure},
|
||||
frequency::{cycle_per_minute, hertz},
|
||||
pressure::bar,
|
||||
};
|
||||
use units::angular_velocity::revolution_per_minute;
|
||||
use units::electric_current::milliampere;
|
||||
use units::f64::*;
|
||||
use units::frequency::{cycle_per_minute, hertz};
|
||||
use units::pressure::bar;
|
||||
|
||||
use crate::machines::extruder1::mitsubishi_cs80::MitsubishiCS80Status;
|
||||
use crate::extruder1::mitsubishi_cs80::MitsubishiCS80Status;
|
||||
|
||||
use super::mitsubishi_cs80::{MitsubishiCS80, MotorStatus};
|
||||
|
||||
+2
-1
@@ -2,7 +2,8 @@ use super::Heating;
|
||||
use control_core::controllers::pid::PidController;
|
||||
use ethercat_hal::io::{digital_output::DigitalOutput, temperature_input::TemperatureInput};
|
||||
use std::time::{Duration, Instant};
|
||||
use uom::si::{f64::ThermodynamicTemperature, thermodynamic_temperature::degree_celsius};
|
||||
use units::f64::*;
|
||||
use units::thermodynamic_temperature::degree_celsius;
|
||||
|
||||
#[derive(Debug)]
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
use super::LaserMachine;
|
||||
use crate::MachineAct;
|
||||
use crate::MachineMessage;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
/// Implements the `MachineAct` trait for the `LaserMachine`.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `_now_ts`: The current timestamp of type `Instant`.
|
||||
///
|
||||
/// # Returns
|
||||
/// A pinned `Future` that resolves to `()` and is `Send`-safe. The future encapsulates the asynchronous behavior of the `act` method.
|
||||
///
|
||||
/// # Description
|
||||
/// This method is called to perform periodic actions for the `LaserMachine`. Specifically:
|
||||
/// - It checks if the time elapsed since the last measurement emission exceeds 20 milliseconds.
|
||||
/// - If the condition is met, it asynchronously emits live values at 60 FPS.
|
||||
///
|
||||
/// The method ensures that the diameter value is updated approximately 60 times per second.
|
||||
///
|
||||
impl MachineAct for LaserMachine {
|
||||
fn act(&mut self, now: Instant) {
|
||||
let msg = self.api_receiver.try_recv();
|
||||
match msg {
|
||||
Ok(msg) => {
|
||||
let _res = self.act_machine_message(msg);
|
||||
}
|
||||
Err(_) => (),
|
||||
};
|
||||
self.update();
|
||||
|
||||
// more than 33ms have passed since last emit (30 "fps" target)
|
||||
if now.duration_since(self.last_measurement_emit) > Duration::from_secs_f64(1.0 / 30.0) {
|
||||
self.emit_live_values();
|
||||
self.last_measurement_emit = now;
|
||||
}
|
||||
}
|
||||
|
||||
fn act_machine_message(&mut self, msg: MachineMessage) {
|
||||
tracing::info!("{:?}", msg);
|
||||
match msg {
|
||||
MachineMessage::SubscribeNamespace(namespace) => {
|
||||
self.namespace.namespace = Some(namespace);
|
||||
self.emit_state();
|
||||
}
|
||||
MachineMessage::UnsubscribeNamespace => match &mut self.namespace.namespace {
|
||||
Some(namespace) => {
|
||||
tracing::info!("UnsubscribeNamespace");
|
||||
namespace.socket_queue_tx.close();
|
||||
namespace.sockets.clear();
|
||||
namespace.events.clear();
|
||||
}
|
||||
None => todo!(),
|
||||
},
|
||||
MachineMessage::HttpApiJsonRequest(value) => {
|
||||
use crate::MachineApi;
|
||||
let _res = self.api_mutate(value);
|
||||
}
|
||||
MachineMessage::ConnectToMachine(_machine_connection) =>
|
||||
/*Doesnt connec to any Macine do nothing*/
|
||||
{
|
||||
()
|
||||
}
|
||||
MachineMessage::DisconnectMachine(_machine_connection) =>
|
||||
/*Doesnt connec to any Machine do nothing*/
|
||||
{
|
||||
()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,16 @@
|
||||
use crate::{MachineApi, MachineMessage};
|
||||
|
||||
use super::LaserMachine;
|
||||
use control_core::{
|
||||
machines::api::MachineApi,
|
||||
socketio::{
|
||||
event::{Event, GenericEvent},
|
||||
namespace::{
|
||||
CacheFn, CacheableEvents, Namespace, NamespaceCacheingLogic, cache_duration,
|
||||
cache_first_and_last_event,
|
||||
},
|
||||
use control_core::socketio::{
|
||||
event::{Event, GenericEvent},
|
||||
namespace::{
|
||||
CacheFn, CacheableEvents, Namespace, NamespaceCacheingLogic, cache_first_and_last_event,
|
||||
},
|
||||
};
|
||||
use control_core_derive::BuildEvent;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use smol::lock::Mutex;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use std::sync::Arc;
|
||||
use tracing::instrument;
|
||||
|
||||
#[derive(Serialize, Debug, Clone, Default)]
|
||||
@@ -61,7 +58,7 @@ pub enum LaserEvents {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LaserMachineNamespace {
|
||||
pub namespace: Arc<Mutex<Namespace>>,
|
||||
pub namespace: Option<Namespace>,
|
||||
}
|
||||
|
||||
impl CacheableEvents<Self> for LaserEvents {
|
||||
@@ -73,11 +70,10 @@ impl CacheableEvents<Self> for LaserEvents {
|
||||
}
|
||||
|
||||
fn event_cache_fn(&self) -> CacheFn {
|
||||
let cache_one_hour = cache_duration(Duration::from_secs(60 * 60), Duration::from_secs(1));
|
||||
let cache_first_and_last = cache_first_and_last_event();
|
||||
|
||||
match self {
|
||||
Self::LiveValues(_) => cache_one_hour,
|
||||
Self::LiveValues(_) => cache_first_and_last,
|
||||
Self::State(_) => cache_first_and_last,
|
||||
}
|
||||
}
|
||||
@@ -99,8 +95,10 @@ impl NamespaceCacheingLogic<LaserEvents> for LaserMachineNamespace {
|
||||
let event = Arc::new(events.event_value());
|
||||
let buffer_fn = events.event_cache_fn();
|
||||
|
||||
let mut namespace = self.namespace.lock_blocking();
|
||||
namespace.emit(event, &buffer_fn);
|
||||
match &mut self.namespace {
|
||||
Some(ns) => ns.emit(event, &buffer_fn),
|
||||
None => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,7 +119,11 @@ impl MachineApi for LaserMachine {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn api_event_namespace(&mut self) -> Arc<Mutex<Namespace>> {
|
||||
fn api_event_namespace(&mut self) -> Option<Namespace> {
|
||||
self.namespace.namespace.clone()
|
||||
}
|
||||
|
||||
fn api_get_sender(&self) -> smol::channel::Sender<MachineMessage> {
|
||||
self.api_sender.clone()
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,32 @@
|
||||
use crate::serial::devices::laser::Laser;
|
||||
use crate::{
|
||||
machines::{MACHINE_LASER_V1, VENDOR_QITECH},
|
||||
serial::devices::laser::Laser,
|
||||
MACHINE_LASER_V1, VENDOR_QITECH,
|
||||
machine_identification::{MachineIdentification, MachineIdentificationUnique},
|
||||
};
|
||||
use crate::{Machine, MachineMessage};
|
||||
use api::{LaserEvents, LaserMachineNamespace, LaserState, LiveValuesEvent, StateEvent};
|
||||
use control_core::{
|
||||
machines::identification::{MachineIdentification, MachineIdentificationUnique},
|
||||
socketio::namespace::NamespaceCacheingLogic,
|
||||
use control_core::socketio::namespace::NamespaceCacheingLogic;
|
||||
use smol::{
|
||||
channel::{Receiver, Sender},
|
||||
lock::RwLock,
|
||||
};
|
||||
use control_core_derive::Machine;
|
||||
use smol::lock::RwLock;
|
||||
use socketioxide::extract::SocketRef;
|
||||
|
||||
use crate::AsyncThreadMessage;
|
||||
use std::{sync::Arc, time::Instant};
|
||||
use uom::si::{f64::Length, length::millimeter};
|
||||
use units::f64::*;
|
||||
use units::length::millimeter;
|
||||
|
||||
pub mod act;
|
||||
pub mod api;
|
||||
pub mod new;
|
||||
|
||||
#[derive(Debug, Machine)]
|
||||
#[derive(Debug)]
|
||||
pub struct LaserMachine {
|
||||
api_receiver: Receiver<MachineMessage>,
|
||||
api_sender: Sender<MachineMessage>,
|
||||
machine_identification_unique: MachineIdentificationUnique,
|
||||
main_sender: Option<Sender<AsyncThreadMessage>>,
|
||||
|
||||
// drivers
|
||||
laser: Arc<RwLock<Laser>>,
|
||||
@@ -41,6 +49,42 @@ pub struct LaserMachine {
|
||||
emitted_default_state: bool,
|
||||
}
|
||||
|
||||
impl Machine for LaserMachine {
|
||||
fn get_machine_identification_unique(&self) -> MachineIdentificationUnique {
|
||||
self.machine_identification_unique.clone()
|
||||
}
|
||||
|
||||
fn get_main_sender(&self) -> Option<Sender<AsyncThreadMessage>> {
|
||||
self.main_sender.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl LaserMachineNamespace {
|
||||
pub async fn disconnect_all(&self) {
|
||||
for socket in self.connected_sockets().await {
|
||||
let _ = socket.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
async fn connected_sockets(&self) -> Vec<SocketRef> {
|
||||
if self.namespace.is_none() {
|
||||
return vec![];
|
||||
}
|
||||
let sockets = self.namespace.clone().unwrap().sockets.clone();
|
||||
sockets
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for LaserMachine {
|
||||
fn drop(&mut self) {
|
||||
tracing::info!(
|
||||
"[LaserMachine::{:?}] Dropping machine and disconnecting clients...",
|
||||
self.machine_identification_unique
|
||||
);
|
||||
smol::block_on(self.namespace.disconnect_all());
|
||||
}
|
||||
}
|
||||
|
||||
impl LaserMachine {
|
||||
pub const MACHINE_IDENTIFICATION: MachineIdentification = MachineIdentification {
|
||||
vendor: VENDOR_QITECH,
|
||||
@@ -60,6 +104,7 @@ impl LaserMachine {
|
||||
y_diameter,
|
||||
roundness,
|
||||
};
|
||||
|
||||
self.namespace
|
||||
.emit(LaserEvents::LiveValues(live_values.build()));
|
||||
}
|
||||
@@ -1,17 +1,15 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use crate::serial::{devices::laser::Laser, registry::SERIAL_DEVICE_REGISTRY};
|
||||
use crate::{MachineNewHardware, MachineNewTrait};
|
||||
|
||||
use super::{LaserMachine, LaserTarget, api::LaserMachineNamespace};
|
||||
use anyhow::Error;
|
||||
use control_core::machines::new::{MachineNewHardware, MachineNewTrait};
|
||||
use uom::ConstZero;
|
||||
use uom::si::{f64::Length, length::millimeter};
|
||||
use units::ConstZero;
|
||||
use units::length::{Length, millimeter};
|
||||
|
||||
impl MachineNewTrait for LaserMachine {
|
||||
fn new(
|
||||
params: &control_core::machines::new::MachineNewParams<'_, '_, '_, '_, '_, '_, '_>,
|
||||
) -> Result<Self, Error>
|
||||
fn new(params: &crate::MachineNewParams<'_, '_, '_, '_, '_, '_, '_>) -> Result<Self, Error>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
@@ -34,7 +32,12 @@ impl MachineNewTrait for LaserMachine {
|
||||
lower_tolerance: Length::new::<millimeter>(0.05),
|
||||
diameter: Length::new::<millimeter>(1.75),
|
||||
};
|
||||
let mut laser_machine = Self {
|
||||
let (sender, receiver) = smol::channel::unbounded();
|
||||
|
||||
let laser_machine = Self {
|
||||
main_sender: params.main_thread_channel.clone(),
|
||||
api_receiver: receiver,
|
||||
api_sender: sender,
|
||||
machine_identification_unique: params.get_machine_identification_unique(),
|
||||
laser,
|
||||
namespace: LaserMachineNamespace {
|
||||
@@ -49,9 +52,6 @@ impl MachineNewTrait for LaserMachine {
|
||||
roundness: None,
|
||||
};
|
||||
|
||||
// Emit initial state
|
||||
laser_machine.emit_state();
|
||||
|
||||
Ok(laser_machine)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,400 @@
|
||||
use anyhow::Error;
|
||||
use control_core::socketio::event::GenericEvent;
|
||||
use control_core::socketio::namespace::Namespace;
|
||||
use ethercat_hal::devices::{
|
||||
EthercatDevice, SubDeviceIdentityTuple, downcast_device, subdevice_identity_to_tuple,
|
||||
};
|
||||
use ethercat_hal::helpers::ethercrab_types::EthercrabSubDevicePreoperational;
|
||||
use ethercrab::{SubDevice, SubDeviceRef};
|
||||
use machine_identification::{
|
||||
DeviceHardwareIdentification, DeviceHardwareIdentificationEthercat, DeviceIdentification,
|
||||
DeviceIdentificationIdentified, MachineIdentificationUnique,
|
||||
};
|
||||
use serde::Serialize;
|
||||
use smol::channel::Sender;
|
||||
use socketioxide::extract::SocketRef;
|
||||
use std::fmt::Debug;
|
||||
use std::{any::Any, sync::Arc, time::Instant};
|
||||
pub mod aquapath1;
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
pub mod buffer1;
|
||||
pub mod extruder1;
|
||||
pub mod laser;
|
||||
pub mod machine_identification;
|
||||
pub mod mock;
|
||||
pub mod registry;
|
||||
pub mod serial;
|
||||
pub mod winder2;
|
||||
|
||||
pub const VENDOR_QITECH: u16 = 0x0001;
|
||||
pub const MACHINE_WINDER_V1: u16 = 0x0002;
|
||||
pub const MACHINE_EXTRUDER_V1: u16 = 0x0004;
|
||||
pub const MACHINE_LASER_V1: u16 = 0x0006;
|
||||
pub const MACHINE_MOCK: u16 = 0x0007;
|
||||
pub const MACHINE_AQUAPATH_V1: u16 = 0x0009;
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
pub const MACHINE_BUFFER_V1: u16 = 0x0008;
|
||||
|
||||
use serde_json::Value;
|
||||
use smol::lock::RwLock;
|
||||
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
pub struct MachineCrossConnectionState {
|
||||
machine_identification_unique: Option<MachineIdentificationUnique>,
|
||||
is_available: bool,
|
||||
}
|
||||
|
||||
pub struct CrossConnection {
|
||||
pub src: MachineIdentificationUnique,
|
||||
pub dest: MachineIdentificationUnique,
|
||||
}
|
||||
|
||||
pub enum AsyncThreadMessage {
|
||||
NoMsg,
|
||||
ConnectOneWayRequest(CrossConnection),
|
||||
DisconnectMachines(CrossConnection),
|
||||
}
|
||||
|
||||
pub struct MachineNewParams<
|
||||
'maindevice,
|
||||
'subdevices,
|
||||
'device_identifications_identified,
|
||||
'ethercat_devices,
|
||||
'machine_new_hardware_etehrcat,
|
||||
'machine_new_hardware_serial,
|
||||
'machine_new_hardware,
|
||||
> where
|
||||
'maindevice: 'machine_new_hardware,
|
||||
'subdevices: 'machine_new_hardware,
|
||||
'ethercat_devices: 'machine_new_hardware,
|
||||
'machine_new_hardware_etehrcat: 'machine_new_hardware,
|
||||
{
|
||||
pub device_group: &'device_identifications_identified Vec<DeviceIdentificationIdentified>,
|
||||
pub hardware: &'machine_new_hardware MachineNewHardware<
|
||||
'maindevice,
|
||||
'subdevices,
|
||||
'ethercat_devices,
|
||||
'machine_new_hardware_etehrcat,
|
||||
'machine_new_hardware_serial,
|
||||
>,
|
||||
pub socket_queue_tx: Sender<(SocketRef, Arc<GenericEvent>)>,
|
||||
pub main_thread_channel: Option<Sender<AsyncThreadMessage>>,
|
||||
pub namespace: Option<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<
|
||||
'maindevice,
|
||||
'subdevices,
|
||||
'ethercat_devices,
|
||||
'machine_new_hardware_etehrcat,
|
||||
'machine_new_hardware_serial,
|
||||
> where
|
||||
'maindevice: 'machine_new_hardware_etehrcat,
|
||||
'subdevices: 'machine_new_hardware_etehrcat,
|
||||
'ethercat_devices: 'machine_new_hardware_etehrcat,
|
||||
{
|
||||
Ethercat(
|
||||
&'machine_new_hardware_etehrcat MachineNewHardwareEthercat<
|
||||
'maindevice,
|
||||
'subdevices,
|
||||
'ethercat_devices,
|
||||
>,
|
||||
),
|
||||
Serial(&'machine_new_hardware_serial MachineNewHardwareSerial),
|
||||
}
|
||||
|
||||
pub struct MachineNewHardwareEthercat<'maindevice, 'subdevices, 'ethercat_devices> {
|
||||
pub subdevices:
|
||||
&'subdevices Vec<&'subdevices SubDeviceRef<'maindevice, &'subdevices SubDevice>>,
|
||||
pub ethercat_devices: &'ethercat_devices Vec<Arc<RwLock<dyn EthercatDevice>>>,
|
||||
}
|
||||
|
||||
pub trait SerialDevice: Any + Send + Sync + SerialDeviceNew + Debug {}
|
||||
|
||||
pub trait SerialDeviceNew {
|
||||
fn new_serial(
|
||||
params: &SerialDeviceNewParams,
|
||||
) -> Result<(DeviceIdentification, Arc<RwLock<Self>>), anyhow::Error>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
pub trait SerialDeviceThread {
|
||||
fn start_thread() -> Result<(), anyhow::Error>;
|
||||
}
|
||||
|
||||
pub struct SerialDeviceNewParams {
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
|
||||
pub struct SerialDeviceIdentification {
|
||||
pub vendor_id: u16,
|
||||
pub product_id: u16,
|
||||
}
|
||||
|
||||
pub struct MachineNewHardwareSerial {
|
||||
pub device: Arc<RwLock<dyn SerialDevice>>,
|
||||
}
|
||||
|
||||
// validates that all devices in the group have the same machine identification
|
||||
pub fn validate_same_machine_identification_unique(
|
||||
identified_device_group: &Vec<DeviceIdentificationIdentified>,
|
||||
) -> Result<(), Error> {
|
||||
let machine_identification_unique = &identified_device_group
|
||||
.first()
|
||||
.ok_or_else(|| {
|
||||
anyhow::anyhow!(
|
||||
"[{}::validate_same_machine_identification] No devices in group",
|
||||
module_path!()
|
||||
)
|
||||
})?
|
||||
.device_machine_identification
|
||||
.machine_identification_unique;
|
||||
for device in identified_device_group.iter() {
|
||||
if device
|
||||
.device_machine_identification
|
||||
.machine_identification_unique
|
||||
!= *machine_identification_unique
|
||||
{
|
||||
return Err(anyhow::anyhow!(
|
||||
"[{}::validate_same_machine_identification] Different machine identifications",
|
||||
module_path!()
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// validates that every role is unique
|
||||
pub fn validate_no_role_dublicates(
|
||||
identified_device_group: &Vec<DeviceIdentificationIdentified>,
|
||||
) -> Result<(), Error> {
|
||||
let mut roles = vec![];
|
||||
for device in identified_device_group.iter() {
|
||||
if roles.contains(&device.device_machine_identification.role) {
|
||||
return Err(anyhow::anyhow!(
|
||||
"[{}::validate_no_role_dublicates] Role dublicate",
|
||||
module_path!(),
|
||||
));
|
||||
}
|
||||
roles.push(device.device_machine_identification.role);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Inside control_core::machines::new module:
|
||||
pub fn get_device_identification_by_role(
|
||||
identified_device_group: &Vec<DeviceIdentificationIdentified>,
|
||||
role: u16,
|
||||
) -> Result<&DeviceIdentificationIdentified, Error> {
|
||||
for device in identified_device_group.iter() {
|
||||
if device.device_machine_identification.role == role {
|
||||
return Ok(device);
|
||||
}
|
||||
}
|
||||
Err(anyhow::anyhow!(
|
||||
"[{}::get_device_identification_by_role] Role {} not found",
|
||||
module_path!(),
|
||||
role
|
||||
))
|
||||
}
|
||||
|
||||
pub fn get_device_by_index<'maindevice>(
|
||||
devices: &Vec<Arc<RwLock<dyn EthercatDevice>>>,
|
||||
subdevice_index: usize,
|
||||
) -> Result<Arc<RwLock<dyn EthercatDevice>>, Error> {
|
||||
Ok(devices
|
||||
.get(subdevice_index)
|
||||
.ok_or(anyhow::anyhow!(
|
||||
"[{}::get_device_by_index] Index {} out of bounds for devices",
|
||||
module_path!(),
|
||||
subdevice_index
|
||||
))?
|
||||
.clone())
|
||||
}
|
||||
|
||||
pub fn get_subdevice_by_index<'subdevices, 'maindevice>(
|
||||
subdevices: &'subdevices Vec<&EthercrabSubDevicePreoperational<'maindevice>>,
|
||||
subdevice_index: usize,
|
||||
) -> Result<&'subdevices EthercrabSubDevicePreoperational<'maindevice>, Error> {
|
||||
Ok(subdevices.get(subdevice_index).ok_or(anyhow::anyhow!(
|
||||
"Index {} out of bounds for subdevices",
|
||||
subdevice_index
|
||||
))?)
|
||||
}
|
||||
|
||||
pub fn get_ethercat_device_by_index<'maindevice>(
|
||||
ethercat_devices: &Vec<Arc<RwLock<dyn EthercatDevice>>>,
|
||||
subdevice_index: usize,
|
||||
) -> Result<Arc<RwLock<dyn EthercatDevice>>, Error> {
|
||||
Ok(ethercat_devices
|
||||
.get(subdevice_index)
|
||||
.ok_or(anyhow::anyhow!(
|
||||
"[{}::get_ethercat_device_by_index] Index {} out of bounds for ethercat devices",
|
||||
module_path!(),
|
||||
subdevice_index
|
||||
))?
|
||||
.clone())
|
||||
}
|
||||
|
||||
pub trait MachineNewTrait {
|
||||
fn new(params: &MachineNewParams<'_, '_, '_, '_, '_, '_, '_>) -> Result<Self, anyhow::Error>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MachineConnection {
|
||||
pub ident: MachineIdentificationUnique,
|
||||
pub connection: Sender<MachineMessage>,
|
||||
}
|
||||
|
||||
pub trait MachineAct {
|
||||
fn act_machine_message(&mut self, msg: MachineMessage);
|
||||
fn act(&mut self, now: Instant);
|
||||
}
|
||||
|
||||
// generic MachineMessage allows us to implement actions
|
||||
// to manage or mutate machines with simple messages sent to the Recv Channel of the given Machine,
|
||||
// which the machine itself will handle to avoid locking
|
||||
// This also allows for simplified "CrossConnections"
|
||||
#[derive(Debug)]
|
||||
pub enum MachineMessage {
|
||||
SubscribeNamespace(Namespace),
|
||||
UnsubscribeNamespace,
|
||||
HttpApiJsonRequest(serde_json::Value),
|
||||
ConnectToMachine(MachineConnection),
|
||||
DisconnectMachine(MachineConnection),
|
||||
}
|
||||
|
||||
pub trait MachineApi {
|
||||
fn api_get_sender(&self) -> Sender<MachineMessage>;
|
||||
fn api_mutate(&mut self, value: Value) -> Result<(), anyhow::Error>;
|
||||
fn api_event_namespace(&mut self) -> Option<Namespace>;
|
||||
}
|
||||
|
||||
pub trait Machine: MachineAct + MachineNewTrait + MachineApi + Any + Debug + Send + Sync {
|
||||
fn get_machine_identification_unique(&self) -> MachineIdentificationUnique;
|
||||
fn get_main_sender(&self) -> Option<Sender<AsyncThreadMessage>>;
|
||||
}
|
||||
|
||||
pub trait AnyGetters: Any {
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any;
|
||||
}
|
||||
|
||||
async fn get_device_ident<
|
||||
'maindevice,
|
||||
'subdevices,
|
||||
'device_identifications_identified,
|
||||
'ethercat_devices,
|
||||
'machine_new_hardware_etehrcat,
|
||||
'machine_new_hardware_serial,
|
||||
'machine_new_hardware,
|
||||
>(
|
||||
params: &MachineNewParams<
|
||||
'maindevice,
|
||||
'subdevices,
|
||||
'device_identifications_identified,
|
||||
'ethercat_devices,
|
||||
'machine_new_hardware_etehrcat,
|
||||
'machine_new_hardware_serial,
|
||||
'machine_new_hardware,
|
||||
>,
|
||||
role: u16,
|
||||
) -> Result<DeviceHardwareIdentificationEthercat, anyhow::Error> {
|
||||
let device_identification = get_device_identification_by_role(params.device_group, role)?;
|
||||
let device_hardware_identification_ethercat =
|
||||
match &device_identification.device_hardware_identification {
|
||||
DeviceHardwareIdentification::Ethercat(device_hardware_identification_ethercat) => {
|
||||
device_hardware_identification_ethercat
|
||||
}
|
||||
_ => {
|
||||
return Err(anyhow::anyhow!(
|
||||
"[{}::MachineNewTrait/ExtruderV2::new] Device with role {} is not Ethercat",
|
||||
module_path!(),
|
||||
role
|
||||
));
|
||||
}
|
||||
};
|
||||
return Ok(device_hardware_identification_ethercat.clone());
|
||||
}
|
||||
|
||||
async fn get_ethercat_device<
|
||||
'maindevice,
|
||||
'subdevices,
|
||||
'device_identifications_identified,
|
||||
'ethercat_devices,
|
||||
'machine_new_hardware_etehrcat,
|
||||
'machine_new_hardware_serial,
|
||||
'machine_new_hardware,
|
||||
T,
|
||||
>(
|
||||
hardware: &&MachineNewHardwareEthercat<'maindevice, 'subdevices, 'ethercat_devices>,
|
||||
params: &MachineNewParams<
|
||||
'maindevice,
|
||||
'subdevices,
|
||||
'device_identifications_identified,
|
||||
'ethercat_devices,
|
||||
'machine_new_hardware_etehrcat,
|
||||
'machine_new_hardware_serial,
|
||||
'machine_new_hardware,
|
||||
>,
|
||||
role: u16,
|
||||
expected_identities: Vec<SubDeviceIdentityTuple>,
|
||||
) -> Result<
|
||||
(
|
||||
Arc<RwLock<T>>,
|
||||
&'subdevices SubDeviceRef<'subdevices, &'subdevices SubDevice>,
|
||||
),
|
||||
anyhow::Error,
|
||||
>
|
||||
where
|
||||
T: 'static + Send + Sync + EthercatDevice,
|
||||
{
|
||||
let device_hardware_identification_ethercat = get_device_ident(params, role).await?;
|
||||
let subdevice_index = device_hardware_identification_ethercat.subdevice_index;
|
||||
|
||||
let subdevice = get_subdevice_by_index(hardware.subdevices, subdevice_index)?;
|
||||
let subdevice_identity = subdevice.identity();
|
||||
|
||||
let actual_identity = subdevice_identity_to_tuple(&subdevice_identity);
|
||||
|
||||
let mut matched_any_identity = false;
|
||||
for identity in expected_identities.clone() {
|
||||
if actual_identity == identity {
|
||||
matched_any_identity = true;
|
||||
}
|
||||
}
|
||||
|
||||
if !matched_any_identity {
|
||||
return Err(anyhow::anyhow!(
|
||||
"[{}::MachineNewTrait/ExtruderV2::new] Device identity mismatch: expected {:?}",
|
||||
module_path!(),
|
||||
expected_identities
|
||||
));
|
||||
}
|
||||
|
||||
let ethercat_device =
|
||||
get_ethercat_device_by_index(&hardware.ethercat_devices, subdevice_index)?;
|
||||
let device = downcast_device::<T>(ethercat_device).await?;
|
||||
|
||||
{
|
||||
let mut device_guard = device.write().await;
|
||||
device_guard.set_used(true);
|
||||
}
|
||||
|
||||
Ok((device, subdevice))
|
||||
}
|
||||
+114
-4
@@ -1,3 +1,117 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
/// Identifies a spacifi machine
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||||
pub struct MachineIdentificationUnique {
|
||||
pub machine_identification: MachineIdentification,
|
||||
pub serial: u16,
|
||||
}
|
||||
|
||||
impl MachineIdentificationUnique {
|
||||
/// Check if values are non-zero
|
||||
pub const fn is_valid(&self) -> bool {
|
||||
self.machine_identification.is_valid() && self.serial != 0
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for MachineIdentificationUnique {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}/{}/{}",
|
||||
self.machine_identification.vendor, self.machine_identification.machine, self.serial
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Identifies a machine
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||||
pub struct MachineIdentification {
|
||||
pub vendor: u16,
|
||||
pub machine: u16,
|
||||
}
|
||||
|
||||
impl MachineIdentification {
|
||||
/// Check if values are non-zero
|
||||
pub const fn is_valid(&self) -> bool {
|
||||
self.vendor != 0 && self.machine != 0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct DeviceMachineIdentification {
|
||||
pub machine_identification_unique: MachineIdentificationUnique,
|
||||
pub role: u16,
|
||||
}
|
||||
|
||||
impl DeviceMachineIdentification {
|
||||
/// Check if values are non-zero
|
||||
pub const fn is_valid(&self) -> bool {
|
||||
self.machine_identification_unique.is_valid()
|
||||
&& self
|
||||
.machine_identification_unique
|
||||
.machine_identification
|
||||
.machine
|
||||
!= 0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct DeviceIdentification {
|
||||
pub device_machine_identification: Option<DeviceMachineIdentification>,
|
||||
pub device_hardware_identification: DeviceHardwareIdentification,
|
||||
}
|
||||
|
||||
impl From<DeviceIdentificationIdentified> for DeviceIdentification {
|
||||
fn from(value: DeviceIdentificationIdentified) -> Self {
|
||||
Self {
|
||||
device_machine_identification: Some(value.device_machine_identification),
|
||||
device_hardware_identification: value.device_hardware_identification,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct DeviceIdentificationIdentified {
|
||||
pub device_machine_identification: DeviceMachineIdentification,
|
||||
pub device_hardware_identification: DeviceHardwareIdentification,
|
||||
}
|
||||
|
||||
impl TryFrom<DeviceIdentification> for DeviceIdentificationIdentified {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(value: DeviceIdentification) -> Result<Self, Self::Error> {
|
||||
let device_machine_identification =
|
||||
value.device_machine_identification.ok_or(anyhow::anyhow!(
|
||||
"[{}::DeviceIdentificationIdentified::try_from] No device machine identification",
|
||||
module_path!()
|
||||
))?;
|
||||
|
||||
Ok(Self {
|
||||
device_machine_identification,
|
||||
device_hardware_identification: value.device_hardware_identification,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub enum DeviceHardwareIdentification {
|
||||
Ethercat(DeviceHardwareIdentificationEthercat),
|
||||
Serial(DeviceHardwareIdentificationSerial),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct DeviceHardwareIdentificationEthercat {
|
||||
pub subdevice_index: usize,
|
||||
}
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct DeviceHardwareIdentificationSerial {
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
use anyhow::Error;
|
||||
use anyhow::anyhow;
|
||||
use ethercat_hal::devices::ek1100::EK1100_IDENTITY_A;
|
||||
@@ -33,10 +147,6 @@ use ethercat_hal::helpers::ethercrab_types::{
|
||||
use ethercrab::MainDevice;
|
||||
use ethercrab::SubDeviceIdentity;
|
||||
|
||||
use crate::machines::identification::DeviceMachineIdentification;
|
||||
use crate::machines::identification::MachineIdentification;
|
||||
use crate::machines::identification::MachineIdentificationUnique;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MachineIdentificationAddresses {
|
||||
pub vendor_word: u16,
|
||||
@@ -1,6 +1,5 @@
|
||||
use control_core::machines::new::MachineAct;
|
||||
|
||||
use super::MockMachine;
|
||||
use crate::{MachineAct, MachineMessage};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
/// Implements the `MachineAct` trait for the `MockMachine`.
|
||||
@@ -21,6 +20,13 @@ use std::time::{Duration, Instant};
|
||||
///
|
||||
impl MachineAct for MockMachine {
|
||||
fn act(&mut self, now: Instant) {
|
||||
let msg = self.api_receiver.try_recv();
|
||||
match msg {
|
||||
Ok(msg) => {
|
||||
let _res = self.act_machine_message(msg);
|
||||
}
|
||||
Err(_) => (),
|
||||
};
|
||||
// Only emit live values if machine is in Running mode
|
||||
// The live values are updated approximately 30 times per second
|
||||
if now.duration_since(self.last_measurement_emit) > Duration::from_secs_f64(1.0 / 30.0) {
|
||||
@@ -28,4 +34,29 @@ impl MachineAct for MockMachine {
|
||||
self.last_measurement_emit = now;
|
||||
}
|
||||
}
|
||||
|
||||
fn act_machine_message(&mut self, msg: MachineMessage) {
|
||||
match msg {
|
||||
MachineMessage::SubscribeNamespace(namespace) => {
|
||||
self.namespace.namespace = Some(namespace);
|
||||
self.emit_state();
|
||||
}
|
||||
MachineMessage::UnsubscribeNamespace => self.namespace.namespace = None,
|
||||
MachineMessage::HttpApiJsonRequest(value) => {
|
||||
use crate::MachineApi;
|
||||
|
||||
let _res = self.api_mutate(value);
|
||||
}
|
||||
MachineMessage::ConnectToMachine(_machine_connection) =>
|
||||
/*Doesnt connec to any Macine do nothing*/
|
||||
{
|
||||
()
|
||||
}
|
||||
MachineMessage::DisconnectMachine(_machine_connection) =>
|
||||
/*Doesnt connec to any Machine do nothing*/
|
||||
{
|
||||
()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,17 @@
|
||||
use crate::{MachineApi, MachineMessage};
|
||||
|
||||
use super::MockMachine;
|
||||
use control_core::{
|
||||
machines::api::MachineApi,
|
||||
socketio::{
|
||||
event::{Event, GenericEvent},
|
||||
namespace::{
|
||||
CacheFn, CacheableEvents, Namespace, NamespaceCacheingLogic, cache_duration,
|
||||
cache_first_and_last_event,
|
||||
},
|
||||
use control_core::socketio::{
|
||||
event::{Event, GenericEvent},
|
||||
namespace::{
|
||||
CacheFn, CacheableEvents, Namespace, NamespaceCacheingLogic, cache_first_and_last_event,
|
||||
},
|
||||
};
|
||||
use control_core_derive::BuildEvent;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use smol::lock::Mutex;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use smol::channel::Sender;
|
||||
use std::sync::Arc;
|
||||
use tracing::instrument;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
@@ -60,7 +58,7 @@ pub enum MockEvents {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MockMachineNamespace {
|
||||
pub namespace: Arc<Mutex<Namespace>>,
|
||||
pub namespace: Option<Namespace>,
|
||||
}
|
||||
|
||||
impl CacheableEvents<Self> for MockEvents {
|
||||
@@ -72,11 +70,10 @@ impl CacheableEvents<Self> for MockEvents {
|
||||
}
|
||||
|
||||
fn event_cache_fn(&self) -> CacheFn {
|
||||
let cache_one_hour = cache_duration(Duration::from_secs(60 * 60), Duration::from_secs(1));
|
||||
let cache_first_and_last = cache_first_and_last_event();
|
||||
|
||||
match self {
|
||||
Self::LiveValues(_) => cache_one_hour,
|
||||
Self::LiveValues(_) => cache_first_and_last,
|
||||
Self::State(_) => cache_first_and_last,
|
||||
}
|
||||
}
|
||||
@@ -98,12 +95,18 @@ impl NamespaceCacheingLogic<MockEvents> for MockMachineNamespace {
|
||||
let event = Arc::new(events.event_value());
|
||||
let buffer_fn = events.event_cache_fn();
|
||||
|
||||
let mut namespace = self.namespace.lock_blocking();
|
||||
namespace.emit(event, &buffer_fn);
|
||||
match &mut self.namespace {
|
||||
Some(ns) => ns.emit(event, &buffer_fn),
|
||||
None => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MachineApi for MockMachine {
|
||||
fn api_get_sender(&self) -> Sender<MachineMessage> {
|
||||
self.api_sender.clone()
|
||||
}
|
||||
|
||||
fn api_mutate(&mut self, request_body: Value) -> Result<(), anyhow::Error> {
|
||||
let mutation: Mutation = serde_json::from_value(request_body)?;
|
||||
match mutation {
|
||||
@@ -123,7 +126,7 @@ impl MachineApi for MockMachine {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn api_event_namespace(&mut self) -> Arc<Mutex<Namespace>> {
|
||||
fn api_event_namespace(&mut self) -> Option<Namespace> {
|
||||
self.namespace.namespace.clone()
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,25 @@
|
||||
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,
|
||||
use crate::AsyncThreadMessage;
|
||||
use crate::{
|
||||
MACHINE_MOCK, Machine, MachineMessage, VENDOR_QITECH,
|
||||
machine_identification::{MachineIdentification, MachineIdentificationUnique},
|
||||
};
|
||||
use control_core_derive::Machine;
|
||||
|
||||
use api::{LiveValuesEvent, MockEvents, MockMachineNamespace, Mode, ModeState, StateEvent};
|
||||
use control_core::socketio::event::BuildEvent;
|
||||
use control_core::socketio::namespace::NamespaceCacheingLogic;
|
||||
use smol::channel::{Receiver, Sender};
|
||||
use std::time::Instant;
|
||||
use tracing::info;
|
||||
use uom::si::{
|
||||
f64::Frequency,
|
||||
frequency::{hertz, millihertz},
|
||||
};
|
||||
use units::f64::*;
|
||||
use units::frequency::{hertz, millihertz};
|
||||
|
||||
pub mod act;
|
||||
pub mod api;
|
||||
pub mod new;
|
||||
|
||||
#[derive(Debug, Machine)]
|
||||
#[derive(Debug)]
|
||||
pub struct MockMachine {
|
||||
machine_identification_unique: MachineIdentificationUnique,
|
||||
main_sender: Option<Sender<AsyncThreadMessage>>,
|
||||
|
||||
// socketio
|
||||
namespace: MockMachineNamespace,
|
||||
@@ -39,6 +38,18 @@ pub struct MockMachine {
|
||||
/// Will be initialized as false and set to true by emit_state
|
||||
/// This way we can signal to the client that the first state emission is a default state
|
||||
emitted_default_state: bool,
|
||||
api_sender: Sender<MachineMessage>,
|
||||
api_receiver: Receiver<MachineMessage>,
|
||||
}
|
||||
|
||||
impl Machine for MockMachine {
|
||||
fn get_machine_identification_unique(&self) -> MachineIdentificationUnique {
|
||||
self.machine_identification_unique.clone()
|
||||
}
|
||||
|
||||
fn get_main_sender(&self) -> Option<Sender<AsyncThreadMessage>> {
|
||||
self.main_sender.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl MockMachine {
|
||||
@@ -4,9 +4,10 @@ use super::{
|
||||
MockMachine,
|
||||
api::{MockMachineNamespace, Mode},
|
||||
};
|
||||
use crate::{MachineNewHardware, MachineNewParams, MachineNewTrait};
|
||||
use anyhow::Error;
|
||||
use control_core::machines::new::{MachineNewHardware, MachineNewParams, MachineNewTrait};
|
||||
use uom::si::{f64::Frequency, frequency::hertz};
|
||||
use units::f64::Frequency;
|
||||
use units::frequency::hertz;
|
||||
|
||||
impl MachineNewTrait for MockMachine {
|
||||
fn new<'maindevice, 'subdevices>(
|
||||
@@ -30,8 +31,11 @@ impl MachineNewTrait for MockMachine {
|
||||
}
|
||||
|
||||
let now = Instant::now();
|
||||
|
||||
let (sender, receiver) = smol::channel::unbounded();
|
||||
let mut mock_machine = Self {
|
||||
main_sender: params.main_thread_channel.clone(),
|
||||
api_receiver: receiver,
|
||||
api_sender: sender,
|
||||
machine_identification_unique: params.get_machine_identification_unique(),
|
||||
namespace: MockMachineNamespace {
|
||||
namespace: params.namespace.clone(),
|
||||
@@ -1,10 +1,20 @@
|
||||
use super::{Machine, identification::MachineIdentification, new::MachineNewParams};
|
||||
#[cfg(feature = "mock-machine")]
|
||||
use crate::{extruder1::mock::ExtruderV2, mock::MockMachine, winder2::mock::Winder2};
|
||||
|
||||
use crate::{Machine, MachineNewParams, machine_identification::MachineIdentification};
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use crate::{
|
||||
aquapath1::AquaPathV1, buffer1::BufferV1, extruder1::ExtruderV2, laser::LaserMachine,
|
||||
winder2::Winder2,
|
||||
};
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
use anyhow::Error;
|
||||
use smol::lock::Mutex;
|
||||
use std::{any::TypeId, collections::HashMap, sync::Arc};
|
||||
use std::{any::TypeId, collections::HashMap};
|
||||
|
||||
pub type MachineNewClosure =
|
||||
Box<dyn Fn(&MachineNewParams) -> Result<Arc<Mutex<dyn Machine>>, Error> + Send + Sync>;
|
||||
Box<dyn Fn(&MachineNewParams) -> Result<Box<dyn Machine>, Error> + Send + Sync>;
|
||||
|
||||
pub struct MachineRegistry {
|
||||
type_map: HashMap<TypeId, (MachineIdentification, MachineNewClosure)>,
|
||||
@@ -32,9 +42,7 @@ impl MachineRegistry {
|
||||
(
|
||||
machine_identficiation.clone(),
|
||||
// create a machine construction closure
|
||||
Box::new(|machine_new_params| {
|
||||
Ok(Arc::new(Mutex::new(T::new(machine_new_params)?)))
|
||||
}),
|
||||
Box::new(|machine_new_params| Ok(Box::new(T::new(machine_new_params)?))),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -42,7 +50,7 @@ impl MachineRegistry {
|
||||
pub fn new_machine(
|
||||
&self,
|
||||
machine_new_params: &MachineNewParams,
|
||||
) -> Result<Arc<Mutex<dyn Machine>>, anyhow::Error> {
|
||||
) -> Result<Box<dyn Machine>, anyhow::Error> {
|
||||
// get machiine identification
|
||||
let device_identification =
|
||||
&machine_new_params
|
||||
@@ -72,3 +80,24 @@ impl MachineRegistry {
|
||||
(machine_new_closure)(machine_new_params)
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
pub static ref MACHINE_REGISTRY: MachineRegistry = {
|
||||
let mut mc = MachineRegistry::new();
|
||||
mc.register::<Winder2>(Winder2::MACHINE_IDENTIFICATION);
|
||||
mc.register::<ExtruderV2>(ExtruderV2::MACHINE_IDENTIFICATION);
|
||||
|
||||
#[cfg(feature = "mock-machine")]
|
||||
mc.register::<MockMachine>(MockMachine::MACHINE_IDENTIFICATION);
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
mc.register::<LaserMachine>(LaserMachine::MACHINE_IDENTIFICATION);
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
mc.register::<BufferV1>(BufferV1::MACHINE_IDENTIFICATION);
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
mc.register::<AquaPathV1>(AquaPathV1::MACHINE_IDENTIFICATION);
|
||||
mc
|
||||
};
|
||||
}
|
||||
+6
-7
@@ -1,16 +1,15 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use control_core::{
|
||||
helpers::hashing::{byte_folding_u16, hash_djb2},
|
||||
machines::identification::{
|
||||
use control_core::helpers::hashing::{byte_folding_u16, hash_djb2};
|
||||
use smol::lock::RwLock;
|
||||
|
||||
use crate::{
|
||||
MACHINE_EXTRUDER_V1, SerialDevice, SerialDeviceNew, SerialDeviceNewParams, VENDOR_QITECH,
|
||||
machine_identification::{
|
||||
DeviceHardwareIdentification, DeviceHardwareIdentificationSerial, DeviceIdentification,
|
||||
DeviceMachineIdentification, MachineIdentification, MachineIdentificationUnique,
|
||||
},
|
||||
serial::{SerialDevice, SerialDeviceNew, SerialDeviceNewParams},
|
||||
};
|
||||
use smol::lock::RwLock;
|
||||
|
||||
use crate::machines::{MACHINE_EXTRUDER_V1, VENDOR_QITECH};
|
||||
|
||||
/// Mock serial device for testing MockMachine
|
||||
/// This provides a minimal SerialDevice implementation that doesn't require actual hardware
|
||||
@@ -1,34 +1,35 @@
|
||||
use anyhow::anyhow;
|
||||
use control_core::helpers::hashing::{byte_folding_u16, hash_djb2};
|
||||
use control_core::helpers::retry::retry_n_times;
|
||||
use control_core::modbus::ModbusResponse;
|
||||
use control_core::modbus::{self, ModbusRequest};
|
||||
use serialport::SerialPort;
|
||||
use serialport::{ClearBuffer, DataBits, FlowControl, Parity, StopBits};
|
||||
use smol::lock::RwLock;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::{
|
||||
io::Write,
|
||||
sync::Arc,
|
||||
thread,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use units::f64::Length;
|
||||
|
||||
use crate::machines::{MACHINE_LASER_V1, VENDOR_QITECH};
|
||||
use anyhow::anyhow;
|
||||
use control_core::{
|
||||
helpers::{
|
||||
hashing::{byte_folding_u16, hash_djb2},
|
||||
retry::retry_n_times,
|
||||
},
|
||||
machines::identification::{
|
||||
DeviceHardwareIdentification, DeviceHardwareIdentificationSerial, DeviceIdentification,
|
||||
DeviceMachineIdentification, MachineIdentification, MachineIdentificationUnique,
|
||||
},
|
||||
modbus::{self, ModbusRequest, ModbusResponse},
|
||||
serial::{SerialDevice, SerialDeviceNew, SerialDeviceNewParams},
|
||||
use crate::machine_identification::{
|
||||
DeviceHardwareIdentification, DeviceHardwareIdentificationSerial, DeviceIdentification,
|
||||
DeviceMachineIdentification, MachineIdentification, MachineIdentificationUnique,
|
||||
};
|
||||
use serialport::SerialPort;
|
||||
use serialport::{ClearBuffer, DataBits, FlowControl, Parity, StopBits};
|
||||
use smol::lock::RwLock;
|
||||
use uom::si::f64::Length;
|
||||
use crate::{
|
||||
MACHINE_LASER_V1, SerialDevice, SerialDeviceNew, SerialDeviceNewParams, VENDOR_QITECH,
|
||||
};
|
||||
use units::length::millimeter;
|
||||
|
||||
/// The struct of Laser Device
|
||||
#[derive(Debug)]
|
||||
pub struct Laser {
|
||||
pub data: Option<LaserData>,
|
||||
pub path: String,
|
||||
pub shutdown_flag: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl SerialDevice for Laser {}
|
||||
@@ -59,15 +60,15 @@ impl TryFrom<ModbusResponse> for LaserDiameterResponse {
|
||||
let x = u16::from_be_bytes([value.data[3], value.data[4]]) as f64 / 1000.0;
|
||||
let y = u16::from_be_bytes([value.data[5], value.data[6]]) as f64 / 1000.0;
|
||||
(
|
||||
Some(Length::new::<uom::si::length::millimeter>(x)),
|
||||
Some(Length::new::<uom::si::length::millimeter>(y)),
|
||||
Some(Length::new::<millimeter>(x)),
|
||||
Some(Length::new::<millimeter>(y)),
|
||||
)
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
diameter: Length::new::<uom::si::length::millimeter>(diameter),
|
||||
diameter: Length::new::<millimeter>(diameter),
|
||||
x_axis,
|
||||
y_axis,
|
||||
})
|
||||
@@ -94,7 +95,7 @@ impl SerialDeviceNew for Laser {
|
||||
params: &SerialDeviceNewParams,
|
||||
) -> Result<(DeviceIdentification, Arc<RwLock<Self>>), anyhow::Error> {
|
||||
let laser_data = Some(LaserData {
|
||||
diameter: Length::new::<uom::si::length::millimeter>(0.0),
|
||||
diameter: Length::new::<millimeter>(0.0),
|
||||
x_axis: None,
|
||||
y_axis: None,
|
||||
last_timestamp: Instant::now(),
|
||||
@@ -119,15 +120,17 @@ impl SerialDeviceNew for Laser {
|
||||
),
|
||||
};
|
||||
|
||||
let shutdown_flag: Arc<AtomicBool> = AtomicBool::new(false).into();
|
||||
// Create a new Laser instance
|
||||
let _self = Arc::new(RwLock::new(Self {
|
||||
data: laser_data,
|
||||
path: params.path.clone(),
|
||||
shutdown_flag: shutdown_flag.clone(),
|
||||
}));
|
||||
|
||||
//// Spawn the device thread
|
||||
let _self_clone = _self.clone();
|
||||
thread::Builder::new()
|
||||
|
||||
let _ = thread::Builder::new()
|
||||
.name("laser".to_owned())
|
||||
.spawn(move || {
|
||||
smol::block_on(async {
|
||||
@@ -139,6 +142,14 @@ impl SerialDeviceNew for Laser {
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Laser {
|
||||
fn drop(&mut self) {
|
||||
// Signal shutdown
|
||||
self.shutdown_flag.store(true, Ordering::SeqCst);
|
||||
println!("Laser struct dropped, thread stopped");
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LaserData {
|
||||
pub diameter: Length,
|
||||
@@ -197,7 +208,7 @@ impl Laser {
|
||||
|
||||
port.clear(ClearBuffer::All).ok();
|
||||
|
||||
loop {
|
||||
while !_self.read().await.shutdown_flag.load(Ordering::SeqCst) {
|
||||
// send diameter request
|
||||
let response = retry_n_times(10, || {
|
||||
if let Err(e) = port.write_all(&request_buffer) {
|
||||
@@ -211,7 +222,6 @@ impl Laser {
|
||||
38400,
|
||||
8,
|
||||
));
|
||||
|
||||
modbus::receive_data_modbus(&mut *port)?
|
||||
.map(ModbusResponse::try_from)
|
||||
.transpose()
|
||||
@@ -230,5 +240,6 @@ impl Laser {
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,15 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use control_core::{
|
||||
helpers::hashing::{byte_folding_u16, hash_djb2},
|
||||
machines::identification::{
|
||||
use control_core::helpers::hashing::{byte_folding_u16, hash_djb2};
|
||||
use smol::lock::RwLock;
|
||||
|
||||
use crate::{
|
||||
MACHINE_MOCK, SerialDevice, SerialDeviceNew, SerialDeviceNewParams, VENDOR_QITECH,
|
||||
machine_identification::{
|
||||
DeviceHardwareIdentification, DeviceHardwareIdentificationSerial, DeviceIdentification,
|
||||
DeviceMachineIdentification, MachineIdentification, MachineIdentificationUnique,
|
||||
},
|
||||
serial::{SerialDevice, SerialDeviceNew, SerialDeviceNewParams},
|
||||
};
|
||||
use smol::lock::RwLock;
|
||||
|
||||
use crate::machines::{MACHINE_MOCK, VENDOR_QITECH};
|
||||
|
||||
/// Mock serial device for testing MockMachine
|
||||
/// This provides a minimal SerialDevice implementation that doesn't require actual hardware
|
||||
File renamed without changes.
+5
-8
@@ -1,16 +1,13 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use control_core::{
|
||||
helpers::hashing::{byte_folding_u16, hash_djb2},
|
||||
machines::identification::{
|
||||
use crate::{
|
||||
MACHINE_WINDER_V1, SerialDevice, SerialDeviceNew, SerialDeviceNewParams, VENDOR_QITECH,
|
||||
machine_identification::{
|
||||
DeviceHardwareIdentification, DeviceHardwareIdentificationSerial, DeviceIdentification,
|
||||
DeviceMachineIdentification, MachineIdentification, MachineIdentificationUnique,
|
||||
},
|
||||
serial::{SerialDevice, SerialDeviceNew, SerialDeviceNewParams},
|
||||
};
|
||||
use control_core::helpers::hashing::{byte_folding_u16, hash_djb2};
|
||||
use smol::lock::RwLock;
|
||||
|
||||
use crate::machines::{MACHINE_WINDER_V1, VENDOR_QITECH};
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Mock serial device for testing MockMachine
|
||||
/// This provides a minimal SerialDevice implementation that doesn't require actual hardware
|
||||
@@ -1,26 +1,84 @@
|
||||
use control_core::{
|
||||
machines::identification::MachineIdentificationUnique,
|
||||
serial::{
|
||||
SerialDeviceIdentification, SerialDeviceNew, SerialDeviceNewParams,
|
||||
serial_detection::SerialDetection,
|
||||
},
|
||||
socketio::namespace::NamespaceCacheingLogic,
|
||||
};
|
||||
use serialport::UsbPortInfo;
|
||||
use smol::Task;
|
||||
use std::{collections::HashMap, sync::Arc, time::Duration};
|
||||
use std::{collections::HashMap, time::Duration};
|
||||
/*
|
||||
*@author: Alisher Darmenov
|
||||
*@company: QiTech
|
||||
*@created: 20.04.2025
|
||||
*
|
||||
*@description: This module is responsible for usb detection and validation, specially made with serialport to avoid complexity and size of tokio_serial
|
||||
*/
|
||||
use crate::SerialDeviceIdentification;
|
||||
use serialport::{SerialPortInfo, SerialPortType};
|
||||
/// Handles scanning and caching of connected USB serial devices.
|
||||
pub struct SerialDetection {}
|
||||
|
||||
use crate::{
|
||||
app_state::AppState,
|
||||
machines::{laser::LaserMachine, registry::MACHINE_REGISTRY},
|
||||
socketio::main_namespace::{MainNamespaceEvents, machines_event::MachinesEventBuilder},
|
||||
};
|
||||
impl SerialDetection {
|
||||
/// Returns all serial ports on the system (minus macOS "cu" noise).
|
||||
fn get_ports() -> Vec<SerialPortInfo> {
|
||||
match serialport::available_ports() {
|
||||
Ok(ports) => ports
|
||||
.into_iter()
|
||||
.filter(|port| !port.port_name.contains("cu"))
|
||||
.collect(),
|
||||
Err(_) => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
use super::devices::laser::Laser;
|
||||
/// Extracts only USB serial devices from the port list.
|
||||
fn extract_usb_serial_devices(port_list: Vec<SerialPortInfo>) -> HashMap<String, UsbPortInfo> {
|
||||
let mut map = HashMap::new();
|
||||
|
||||
for port in port_list {
|
||||
if let SerialPortType::UsbPort(usb_info) = port.port_type {
|
||||
map.insert(port.port_name, usb_info);
|
||||
}
|
||||
}
|
||||
map
|
||||
}
|
||||
|
||||
/// Scans for connected USB serial devices and updates the internal map.
|
||||
/// Returns a clone of the map for quick access.
|
||||
pub fn detect_devices() -> HashMap<String, UsbPortInfo> {
|
||||
let ports = Self::get_ports();
|
||||
let usb_devices = Self::extract_usb_serial_devices(ports);
|
||||
usb_devices
|
||||
}
|
||||
|
||||
pub fn device_port_exists(device_path: &str) -> bool {
|
||||
let ports = Self::get_ports();
|
||||
return ports.iter().any(|p| p.port_name == device_path);
|
||||
}
|
||||
|
||||
pub fn device_id_exists(sdevid: SerialDeviceIdentification) -> bool {
|
||||
let ports = Self::get_ports();
|
||||
for port in ports {
|
||||
if let SerialPortType::UsbPort(usb_info) = port.port_type {
|
||||
if usb_info.vid == sdevid.vendor_id && usb_info.pid == sdevid.product_id {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn get_path_by_id(
|
||||
sdevid: SerialDeviceIdentification,
|
||||
map: HashMap<String, UsbPortInfo>,
|
||||
) -> Option<String> {
|
||||
for (port, usbport) in map.iter() {
|
||||
if usbport.vid == sdevid.vendor_id && usbport.pid == sdevid.product_id {
|
||||
return Some(port.clone());
|
||||
}
|
||||
}
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
// Async function that runs until at least one serial device is found.
|
||||
pub async fn find_serial() -> Option<HashMap<String, UsbPortInfo>> {
|
||||
smol::Timer::after(Duration::from_secs(1)).await;
|
||||
|
||||
let devices = SerialDetection::detect_devices();
|
||||
if !devices.is_empty() {
|
||||
return Some(devices);
|
||||
@@ -34,11 +92,10 @@ pub fn start_serial_discovery() -> Task<Option<HashMap<String, UsbPortInfo>>> {
|
||||
smol::spawn(find_serial())
|
||||
}
|
||||
|
||||
// This function handles a disconnection and or reconnection of Laser
|
||||
// You can easily rewrite it when we have more serialdevices
|
||||
// TODO: Add A static list of known SeriadeviceIdents
|
||||
/*
|
||||
|
||||
pub async fn handle_serial_device_hotplug(
|
||||
app_state: Arc<AppState>,
|
||||
app_state: Arc<SharedState>,
|
||||
map: Option<HashMap<String, UsbPortInfo>>,
|
||||
) {
|
||||
let laser_ident = SerialDeviceIdentification {
|
||||
@@ -137,3 +194,6 @@ pub async fn handle_serial_device_hotplug(
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
*/
|
||||
File renamed without changes.
@@ -1,8 +1,14 @@
|
||||
use super::SerialDeviceNewParams;
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
use crate::{
|
||||
machines::identification::DeviceIdentification,
|
||||
serial::{SerialDevice, SerialDeviceIdentification},
|
||||
SerialDevice, SerialDeviceIdentification, machine_identification::DeviceIdentification,
|
||||
serial::devices::laser::Laser,
|
||||
};
|
||||
|
||||
#[cfg(feature = "mock-machine")]
|
||||
use crate::serial::devices::mock::MockSerialDevice;
|
||||
|
||||
use crate::SerialDeviceNewParams;
|
||||
use anyhow::{Error, Result};
|
||||
use smol::lock::RwLock;
|
||||
use std::{any::TypeId, collections::HashMap, sync::Arc};
|
||||
@@ -94,3 +100,22 @@ impl SerialDeviceRegistry {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
pub static ref SERIAL_DEVICE_REGISTRY: SerialDeviceRegistry = {
|
||||
let mut sdr = SerialDeviceRegistry::new();
|
||||
sdr.register::<Laser>(SerialDeviceIdentification {
|
||||
vendor_id: 0x0403,
|
||||
product_id: 0x6001,
|
||||
});
|
||||
|
||||
// Register MockSerialDevice when mock-machine feature is enabled
|
||||
#[cfg(feature = "mock-machine")]
|
||||
sdr.register::<MockSerialDevice>(SerialDeviceIdentification {
|
||||
vendor_id: 0x0001, // VENDOR_QITECH
|
||||
product_id: 0x0007, // MACHINE_MOCK
|
||||
});
|
||||
|
||||
sdr
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use super::Winder2;
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use crate::{MachineAct, MachineMessage};
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use std::time::{Duration, Instant};
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
|
||||
impl MachineAct for Winder2 {
|
||||
fn act(&mut self, now: Instant) {
|
||||
let machine_message = self.api_receiver.try_recv();
|
||||
match machine_message {
|
||||
Ok(machine_message) => self.act_machine_message(machine_message),
|
||||
Err(_e) => (),
|
||||
};
|
||||
// sync the spool speed
|
||||
self.sync_spool_speed(now);
|
||||
|
||||
// sync the puller speed
|
||||
self.sync_puller_speed(now);
|
||||
|
||||
// sync the traverse speed
|
||||
self.sync_traverse_speed();
|
||||
|
||||
// automatically stops or pulls after N Meters if enabled
|
||||
self.stop_or_pull_spool(now);
|
||||
|
||||
if self.traverse_controller.did_change_state() {
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
// more than 33ms have passed since last emit (30 "fps" target)
|
||||
if now.duration_since(self.last_measurement_emit) > Duration::from_secs_f64(1.0 / 30.0) {
|
||||
self.emit_live_values();
|
||||
self.last_measurement_emit = now;
|
||||
}
|
||||
}
|
||||
|
||||
fn act_machine_message(&mut self, msg: MachineMessage) {
|
||||
match msg {
|
||||
MachineMessage::SubscribeNamespace(namespace) => {
|
||||
self.namespace.namespace = Some(namespace);
|
||||
self.emit_state();
|
||||
}
|
||||
MachineMessage::UnsubscribeNamespace => self.namespace.namespace = None,
|
||||
MachineMessage::HttpApiJsonRequest(value) => {
|
||||
use crate::MachineApi;
|
||||
let _res = self.api_mutate(value);
|
||||
}
|
||||
MachineMessage::ConnectToMachine(machine_connection) => {
|
||||
if self.connected_machines.len() >= self.max_connected_machines {
|
||||
tracing::debug!(
|
||||
"Refusing to add Machine Connection {:?}, since self.connected_machines would be over the limit of {:?}",
|
||||
machine_connection,
|
||||
self.max_connected_machines
|
||||
);
|
||||
return;
|
||||
}
|
||||
self.connected_machines.push(machine_connection);
|
||||
}
|
||||
MachineMessage::DisconnectMachine(_machine_connection) => {
|
||||
self.connected_machines.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
-12
@@ -1,4 +1,4 @@
|
||||
use crate::machines::winder2::{
|
||||
use crate::winder2::{
|
||||
clamp_revolution::clamp_revolution_uom, filament_tension::FilamentTensionCalculator,
|
||||
puller_speed_controller::PullerSpeedController,
|
||||
};
|
||||
@@ -10,17 +10,13 @@ use control_core::{
|
||||
};
|
||||
use core::f64;
|
||||
use std::time::Instant;
|
||||
use uom::{
|
||||
ConstZero,
|
||||
si::{
|
||||
angle::degree,
|
||||
angular_acceleration::radian_per_second_squared,
|
||||
angular_velocity::{radian_per_second, revolution_per_minute},
|
||||
f64::{Angle, AngularAcceleration, AngularVelocity, Length},
|
||||
length::{centimeter, meter},
|
||||
velocity::meter_per_second,
|
||||
},
|
||||
};
|
||||
use units::ConstZero;
|
||||
use units::angle::degree;
|
||||
use units::angular_acceleration::radian_per_second_squared;
|
||||
use units::angular_velocity::{radian_per_second, revolution_per_minute};
|
||||
use units::f64::*;
|
||||
use units::length::{centimeter, meter};
|
||||
use units::velocity::meter_per_second;
|
||||
|
||||
/// Adaptive spool speed controller that automatically adjusts to maintain optimal filament tension.
|
||||
///
|
||||
@@ -2,43 +2,11 @@
|
||||
mod winder2_imports {
|
||||
pub use super::super::Winder2Mode;
|
||||
pub use super::super::puller_speed_controller::{GearRatio, PullerRegulationMode};
|
||||
pub use serde::Deserialize;
|
||||
pub use serde::Serialize;
|
||||
|
||||
pub use std::{sync::Arc, time::Duration};
|
||||
|
||||
pub use control_core::{
|
||||
machines::{
|
||||
connection::MachineCrossConnectionState, identification::MachineIdentificationUnique,
|
||||
},
|
||||
socketio::{
|
||||
event::{Event, GenericEvent},
|
||||
namespace::{
|
||||
CacheFn, CacheableEvents, Namespace, NamespaceCacheingLogic, cache_duration,
|
||||
cache_first_and_last_event,
|
||||
},
|
||||
},
|
||||
};
|
||||
pub use control_core_derive::BuildEvent;
|
||||
pub use smol::lock::Mutex;
|
||||
pub use tracing::instrument;
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
mod winder2_imports {
|
||||
pub use super::super::puller_speed_controller::{GearRatio, PullerRegulationMode};
|
||||
pub use super::super::{Winder2, Winder2Mode};
|
||||
pub use control_core::{
|
||||
machines::{
|
||||
api::MachineApi, connection::MachineCrossConnectionState,
|
||||
identification::MachineIdentificationUnique,
|
||||
},
|
||||
socketio::{
|
||||
event::{Event, GenericEvent},
|
||||
namespace::{
|
||||
CacheFn, CacheableEvents, Namespace, NamespaceCacheingLogic, cache_duration,
|
||||
cache_first_and_last_event,
|
||||
},
|
||||
pub use control_core::socketio::{
|
||||
event::{Event, GenericEvent},
|
||||
namespace::{
|
||||
CacheFn, CacheableEvents, Namespace, NamespaceCacheingLogic, cache_duration,
|
||||
cache_first_and_last_event,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -53,8 +21,37 @@ mod winder2_imports {
|
||||
pub use tracing::instrument;
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
mod winder2_imports {
|
||||
pub use super::super::puller_speed_controller::{GearRatio, PullerRegulationMode};
|
||||
pub use super::super::{Winder2, Winder2Mode};
|
||||
pub use control_core::socketio::{
|
||||
event::{Event, GenericEvent},
|
||||
namespace::{
|
||||
CacheFn, CacheableEvents, Namespace, NamespaceCacheingLogic, cache_duration,
|
||||
cache_first_and_last_event,
|
||||
},
|
||||
};
|
||||
|
||||
pub use control_core_derive::BuildEvent;
|
||||
pub use serde::{Deserialize, Serialize};
|
||||
pub use serde_json::Value;
|
||||
pub use smol::lock::Mutex;
|
||||
pub use std::{
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
pub use tracing::instrument;
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use smol::channel::Sender;
|
||||
pub use winder2_imports::*;
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use crate::{MachineApi, MachineMessage};
|
||||
use crate::{MachineCrossConnectionState, machine_identification::MachineIdentificationUnique};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||
pub enum Mode {
|
||||
#[default]
|
||||
@@ -177,7 +174,7 @@ pub struct StateEvent {
|
||||
pub tension_arm_state: TensionArmState,
|
||||
/// spool speed controller state
|
||||
pub spool_speed_controller_state: SpoolSpeedControllerState,
|
||||
/// connected machine state
|
||||
/// Is a Machine Connected?
|
||||
pub connected_machine_state: MachineCrossConnectionState,
|
||||
}
|
||||
|
||||
@@ -286,7 +283,7 @@ pub enum Winder2Events {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Winder2Namespace {
|
||||
pub namespace: Arc<Mutex<Namespace>>,
|
||||
pub namespace: Option<Namespace>,
|
||||
}
|
||||
|
||||
impl NamespaceCacheingLogic<Winder2Events> for Winder2Namespace {
|
||||
@@ -294,9 +291,10 @@ 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();
|
||||
|
||||
let mut namespace = self.namespace.lock_blocking();
|
||||
namespace.emit(event, &buffer_fn);
|
||||
match &mut self.namespace {
|
||||
Some(ns) => ns.emit(event, &buffer_fn),
|
||||
None => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -309,11 +307,9 @@ impl CacheableEvents<Self> for Winder2Events {
|
||||
}
|
||||
|
||||
fn event_cache_fn(&self) -> CacheFn {
|
||||
let cache_one_hour = cache_duration(Duration::from_secs(60 * 60), Duration::from_secs(1));
|
||||
let cache_first_and_last = cache_first_and_last_event();
|
||||
|
||||
match self {
|
||||
Self::LiveValues(_) => cache_one_hour,
|
||||
Self::LiveValues(_) => cache_first_and_last,
|
||||
Self::State(_) => cache_first_and_last,
|
||||
}
|
||||
}
|
||||
@@ -321,7 +317,13 @@ impl CacheableEvents<Self> for Winder2Events {
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
impl MachineApi for Winder2 {
|
||||
fn api_get_sender(&self) -> Sender<MachineMessage> {
|
||||
self.api_sender.clone()
|
||||
}
|
||||
|
||||
fn api_mutate(&mut self, request_body: Value) -> Result<(), anyhow::Error> {
|
||||
use crate::Machine;
|
||||
|
||||
let mutation: Mutation = serde_json::from_value(request_body)?;
|
||||
match mutation {
|
||||
Mutation::EnableTraverseLaserpointer(enable) => self.set_laser(enable),
|
||||
@@ -364,16 +366,37 @@ impl MachineApi for Winder2 {
|
||||
Mutation::ResetSpoolProgress => self.stop_or_pull_spool_reset(Instant::now()),
|
||||
Mutation::ZeroTensionArmAngle => self.tension_arm_zero(),
|
||||
Mutation::SetConnectedMachine(machine_identification_unique) => {
|
||||
self.set_connected_buffer(machine_identification_unique)
|
||||
let main_sender = match &self.main_sender {
|
||||
Some(sender) => sender,
|
||||
None => {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Machine cannot connect to others! {:?}",
|
||||
self.get_machine_identification_unique()
|
||||
));
|
||||
}
|
||||
};
|
||||
main_sender.try_send(crate::AsyncThreadMessage::ConnectOneWayRequest(
|
||||
crate::CrossConnection {
|
||||
src: self.get_machine_identification_unique(),
|
||||
dest: machine_identification_unique,
|
||||
},
|
||||
))?;
|
||||
self.emit_state();
|
||||
}
|
||||
Mutation::DisconnectMachine(machine_identification_unique) => {
|
||||
self.disconnect_buffer(machine_identification_unique)
|
||||
Mutation::DisconnectMachine(_machine_identification_unique) => {
|
||||
self.connected_machines.clear();
|
||||
/*let main_sender = match &self.main_sender {
|
||||
Some(sender) => sender,
|
||||
None => return Err(anyhow::anyhow!("[DisconnectMachine] Machine cannot connect to others! {:?}", self.machine_identification_unique)),
|
||||
};*/
|
||||
//main_sender.try_send(crate::AsyncThreadMessage::ConnectOneWayRequest(crate::CrossConnection { src: self.get_machine_identification_unique(), dest: machine_identification_unique }))?;
|
||||
self.emit_state();
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn api_event_namespace(&mut self) -> Arc<Mutex<Namespace>> {
|
||||
fn api_event_namespace(&mut self) -> Option<Namespace> {
|
||||
self.namespace.namespace.clone()
|
||||
}
|
||||
}
|
||||
Loaded 100 of 176 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user