Compare commits

..
Author SHA1 Message Date
Robin Krämer 5e86988ac4 Merge pull request #907 from qitechgmbh/882-add-nozzle-docs-for-extruder
882 add nozzle docs for extruder
2025-11-19 11:38:07 +01:00
snacj 2d28c18b1a Added Heading for new Mechanical Diagram 2025-11-18 14:04:32 +01:00
snacj b19764281a Added Nozzle docs 2025-11-18 13:58:51 +01:00
snacj c25d63f781 Added Nozzle docs 2025-11-18 13:58:51 +01:00
Robin Krämer d79efc412f Merge pull request #904 from qitechgmbh/898-mock-machine-doesnt-work
898 mock machine doesnt work
2025-11-18 12:53:24 +01:00
rob 7ad74cf089 fixed formatting 2025-11-17 18:00:43 +01:00
rob 6073d37892 fixed remaining compile errors 2025-11-17 18:00:43 +01:00
rob c04937553f machines show up but dont get subscribed to 2025-11-17 18:00:43 +01:00
rob e08bdfa3c7 removed unnecessary and outdated docs 2025-11-17 18:00:43 +01:00
rob d282678b19 fixed compile errors for mock-machines 2025-11-17 18:00:43 +01:00
Robin Krämer 9ff4243350 Merge pull request #902 from qitechgmbh/2.12-releasenotes
updated Changelog
2025-11-17 16:38:48 +01:00
rob 6c79b33752 updated Changelog 2025-11-17 16:36:41 +01:00
rob 861b5da7a3 updated Changelog 2025-11-17 16:35:28 +01:00
Robin Krämer 7dd3d50653 Merge pull request #899 from qitechgmbh/add-debugging-utils-folder
added debugging utils and fixed memory leak
2025-11-14 23:30:55 +01:00
kraemr ba8e04c641 removed unused imports 2025-11-14 23:28:05 +01:00
kraemr cece245a55 removed one_hour caching on backend for apis 2025-11-14 21:26:38 +01:00
kraemr fab4d2810e fixed memory leak and added debugging utilities 2025-11-14 21:18:51 +01:00
Robin Krämer 5bff3ca929 Merge pull request #897 from qitechgmbh/fix-async-yield-timeout
Fix async yield timeout
2025-11-14 16:45:59 +01:00
kraemr afe1d3b80e fixed display of time taken on failing loop 2025-11-14 14:06:32 +01:00
kraemr 6c8005dba9 log cycle time on failure 2025-11-14 13:36:50 +01:00
kraemr 9f399ca6f3 fixed compile error 2025-11-14 13:25:44 +01:00
kraemr a7574dd348 better error handling for copy ethercat outputs and inputs 2025-11-14 13:22:27 +01:00
kraemr b39f3597c8 fixed loop 2025-11-14 12:20:53 +01:00
Robin Krämer bb9a86d52e Merge pull request #893 from qitechgmbh/fix-serial-hotplugging
fixed laser hotplug issue
2025-11-13 13:57:27 +01:00
kraemr e5329e40c6 fixed ci error 2025-11-13 13:55:44 +01:00
kraemr 319611f69d fixed laser hotplug issue 2025-11-13 13:55:44 +01:00
Robin Krämer 4b16253c8c Merge pull request #887 from qitechgmbh/841-remove-all-locking-and-as-much-async-code-as-possible
841 remove all locking and as much async code as possible
2025-11-13 10:01:47 +01:00
kraemr b60423fde6 removed unnecessary variable from shared_state and fixed serial hotplug 2025-11-13 09:33:40 +01:00
kraemr 5d3399c72e fixed formatting electron 2025-11-13 01:03:10 +01:00
kraemr d247bafcbb fixed formatting 2025-11-13 01:00:28 +01:00
kraemr 5227aad4c6 removed all warnings 2025-11-13 00:56:59 +01:00
kraemr da4c54a99e fixed compile errors 2025-11-13 00:04:15 +01:00
kraemr 8d42bd93f7 started refactoring so machines use channels instead of locks
backend works flawlessly now

added machine_connection and a main_thread actor

added back write_machine_device_ident

error handling for machines restored

correctly handles machine connections/disconnections now

removed unused deps
2025-11-12 17:54:11 +01:00
kraemr 37e467272b removed potential "deadlock" 2025-11-12 17:41:03 +01:00
Oshgnacknak ed0b9cd5ae Merge pull request #859 from qitechgmbh/789-create-minimal-set-of-uom-units
Use minimal set of units instead of full uom::si module
2025-11-11 15:47:45 +01:00
Oshgnacknak 8045f799f0 Use minimal set of units instead of full uom::si module 2025-11-10 17:15:51 +01:00
Robin Krämer ca6ea16f89 Merge pull request #881 from qitechgmbh/880-add-nozzle-pid-for-temperature-at-extruder
880 add nozzle pid for temperature at extruder
2025-11-06 12:16:58 +01:00
snacj 452614abf1 Fixed Frontend Formatting 2025-11-06 11:59:52 +01:00
snacj f63c797cd1 Fixed Formatting 2025-11-06 11:57:56 +01:00
snacj de791f6ea8 Added PID for extruder nozzle. 2025-11-06 11:57:34 +01:00
Robin Krämer 024f751ef1 Merge pull request #873 from qitechgmbh/872-add-cycle-target-time
872 add cycle target time
2025-11-04 16:19:15 +01:00
176 changed files with 3707 additions and 4171 deletions

No files matched your search

+16
View File
@@ -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
View File
File diff suppressed because it is too large. Load diff
+4 -1
View File
@@ -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"
+1 -3
View File
@@ -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,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,6 +1,6 @@
use std::time::Instant;
use uom::si::{
use units::{
acceleration::meter_per_second_squared,
f64::{Acceleration, Velocity},
velocity::meter_per_second,
@@ -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);
@@ -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
View File
@@ -1,2 +1 @@
pub mod eeprom_identification;
pub mod interface_discovery;
-6
View File
@@ -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;
-27
View File
@@ -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
}
}
-208
View File
@@ -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,
}
}
}
-113
View File
@@ -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,
}
-297
View File
@@ -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>,
}
-84
View File
@@ -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,
}
}
}
-23
View File
@@ -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;
}
-279
View File
@@ -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
View File
@@ -1 +0,0 @@
pub mod mutation;
+2 -2
View File
@@ -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
View File
@@ -1,3 +1,2 @@
pub mod event;
pub mod namespace;
pub mod namespace_id;
+2 -2
View File
@@ -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 -1
View File
@@ -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";
}
-7
View File
@@ -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";
}
-5
View File
@@ -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.
+12 -36
View File
@@ -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:
![QiTech Pro Extruder Electrical Diagram 2025 - Sheet 8](images/manuals/extruder/QiTech_Pro_Extruder_Electrical_Diagram_2025_8-008.png)
![QiTech Pro Extruder Electrical Diagram 2025 - Sheet 9](images/manuals/extruder/QiTech_Pro_Extruder_Electrical_Diagram_2025_9-009.png)
## Mechanical Diagrams
![QiTech Pro Extruder Nozzle 2025](images/manuals/extruder/Nozzle.png)
+10 -1
View File
@@ -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,
});
+24 -21
View File
@@ -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);
+17
View File
@@ -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,
) => {
+1 -1
View File
@@ -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"
+1 -1
View File
@@ -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 {
+2 -2
View File
@@ -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 {
+2 -2
View File
@@ -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 {
+1 -1
View File
@@ -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 {
+1 -1
View File
@@ -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::{
+4 -4
View File
@@ -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() {
+1 -1
View File
@@ -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::*;
+53
View File
@@ -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"]
+59
View File
@@ -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(),
+47
View File
@@ -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()
}
}
@@ -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 &params.hardware {
MachineNewHardware::Ethercat(x) => x,
_ => {
return Err(anyhow::anyhow!(
"[{}::MachineNewTrait/Buffer::new] MachineNewHardware is not Ethercat",
module_path!()
));
}
};
let hardware: &&MachineNewHardwareEthercat<'_, '_, '_> = match &params.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();
@@ -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
+45
View File
@@ -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()
}
}
@@ -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 &params.hardware {
MachineNewHardware::Ethercat(x) => x,
_ => {
return Err(anyhow::anyhow!(
"[{}::MachineNewTrait/Extruder2::new] MachineNewHardware is not Ethercat",
module_path!()
));
}
};
let hardware: &&MachineNewHardwareEthercat<'_, '_, '_> = match &params.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(),
@@ -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,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)]
+71
View File
@@ -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)
}
}
+400
View File
@@ -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))
}
@@ -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
};
}
@@ -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.
@@ -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
};
}
+66
View File
@@ -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();
}
}
}
}
@@ -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