Compare commits

..
178 changed files with 1994 additions and 1774 deletions

No files matched your search

+2 -3
View File
@@ -1,6 +1,5 @@
{
"typescript.inlayHints.enumMemberValues.enabled": false,
"git.detectSubmodules": true,
"git.detectSubmodulesLimit": 10,
"rust-analyzer.check.command": "clippy",
"git.detectSubmodules": true,
"git.detectSubmodulesLimit": 10,
}
+1 -18
View File
@@ -4,24 +4,7 @@ members = [
"ethercat-hal",
"ethercat-hal-derive",
"control-core",
"ethercat-eeprom-dump",
"control-core-derive",
"ethercat-eeprom-dump", "control-core-derive",
]
exclude = []
resolver = "2"
[workspace.lints.clippy]
# Individual lints (priority 0)
# don't lint poissbilities for let chains because they were introduces after Rust 1.86
collapsible_if = "allow"
# Lint groups (priority -1)
correctness = { level = "warn", priority = -1 }
suspicious = { level = "warn", priority = -1 }
complexity = { level = "warn", priority = -1 }
perf = { level = "warn", priority = -1 }
style = { level = "warn", priority = -1 }
pendantic = { level = "warn", priority = -1 }
cargo = { level = "warn", priority = -1 }
nursery = { level = "warn", priority = -1 }
+1 -1
View File
@@ -24,7 +24,7 @@ if [ "$1" != "mock-machine" ]; then
sudo setcap 'cap_net_raw,cap_sys_nice=eip' ./target/debug/server
echo "Setting permissions for serial ports"
sudo chown "root:$(groups | cut -f1 -d' ')" /dev/ttyUSB*
sudo chown "root:$USER" /dev/ttyUSB*
fi
# run
+1 -4
View File
@@ -3,13 +3,10 @@ name = "control_core_derive"
version = "0.1.0"
edition = "2024"
[lints]
workspace = true
[lib]
proc-macro = true
[dependencies]
proc-macro2 = "1.0"
quote = "1.0"
syn = { version = "2.0", features = ["full"] }
syn = { version = "2.0", features = ["full"] }
-29
View File
@@ -29,32 +29,3 @@ fn build_event_derive2(item: TokenStream2) -> Result<TokenStream2, Error> {
Ok(expanded)
}
#[proc_macro_derive(Machine)]
pub fn machine_derive(item: TokenStream) -> TokenStream {
machine_derive2(item.into()).unwrap().into()
}
fn machine_derive2(item: TokenStream2) -> Result<TokenStream2, Error> {
// Parse the input tokens into a syntax tree
let ast: DeriveInput = syn::parse2(item)?;
let ident = &ast.ident;
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
let expanded = quote! {
impl #impl_generics control_core::machines::Machine for #ident #ty_generics #where_clause {}
impl #impl_generics control_core::machines::AnyGetters for #ident #ty_generics #where_clause {
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
}
};
Ok(expanded)
}
+1 -4
View File
@@ -3,9 +3,6 @@ name = "control_core"
version = "0.1.0"
edition = "2024"
[lints]
workspace = true
[dependencies]
anyhow = "1.0.99"
axum = "0.8.4"
@@ -13,7 +10,7 @@ ethercat_hal = { path = "../ethercat-hal" }
ethercrab = "0.6"
interfaces = "0.0.9"
serde = "1.0.219"
serde_json = "1.0.143"
serde_json = "1.0.143"
serialport = "4.7.3"
smol = "2.0.2"
socketioxide = "0.17.2"
@@ -33,7 +33,7 @@ pub struct ClampingTimeagnosticPidController {
}
impl ClampingTimeagnosticPidController {
pub const fn new(
pub fn new(
kp: f64,
ki: f64,
kd: f64,
@@ -65,7 +65,7 @@ impl ClampingTimeagnosticPidController {
}
}
pub const fn simple_new(kp: f64, ki: f64, kd: f64) -> Self {
pub fn simple_new(kp: f64, ki: f64, kd: f64) -> Self {
Self {
kp,
ki,
@@ -85,26 +85,26 @@ impl ClampingTimeagnosticPidController {
}
}
pub const fn get_kp(&self) -> f64 {
pub fn get_kp(&self) -> f64 {
self.kp
}
pub const fn get_ki(&self) -> f64 {
pub fn get_ki(&self) -> f64 {
self.ki
}
pub const fn get_kd(&self) -> f64 {
pub fn get_kd(&self) -> f64 {
self.kd
}
pub const fn configure(&mut self, ki: f64, kp: f64, kd: f64) {
pub fn configure(&mut self, ki: f64, kp: f64, kd: f64) {
self.reset();
self.kp = kp;
self.ki = ki;
self.kd = kd;
}
pub const fn optional_clamp(value: f64, min: Option<f64>, max: Option<f64>) -> f64 {
pub fn optional_clamp(value: f64, min: Option<f64>, max: Option<f64>) -> f64 {
match (min, max) {
(Some(min), Some(max)) => value.clamp(min, max),
(Some(min), None) => value.max(min),
@@ -114,7 +114,7 @@ impl ClampingTimeagnosticPidController {
}
pub fn update(&mut self, error: f64, t: Instant) -> f64 {
match self.last {
let signal = match self.last {
// First update
None => {
// Calculate error
@@ -122,10 +122,15 @@ impl ClampingTimeagnosticPidController {
// Calculate signal
let signal = self.kp * ep;
let clamped_signal = Self::optional_clamp(signal, self.min_signal, self.max_signal);
let clamped_signal = ClampingTimeagnosticPidController::optional_clamp(
signal,
self.min_signal,
self.max_signal,
);
// Set values
self.ep = Self::optional_clamp(ep, self.min_ep, self.max_ep);
self.ep =
ClampingTimeagnosticPidController::optional_clamp(ep, self.min_ep, self.max_ep);
self.ei = 0.0;
self.ed = 0.0;
self.last = Some(t);
@@ -138,11 +143,23 @@ impl ClampingTimeagnosticPidController {
let dt = t.duration_since(last).as_secs_f64();
// Calculate errors
let ep = Self::optional_clamp(error, self.min_ep, self.max_ep);
let ep = ClampingTimeagnosticPidController::optional_clamp(
error,
self.min_ep,
self.max_ep,
);
let ei = Self::optional_clamp(ep.mul_add(dt, self.ei), self.min_ei, self.max_ei);
let ei = ClampingTimeagnosticPidController::optional_clamp(
self.ei + ep * dt,
self.min_ei,
self.max_ei,
);
let ed = Self::optional_clamp((ep - self.ep) / dt, self.min_ed, self.max_ed);
let ed = ClampingTimeagnosticPidController::optional_clamp(
(ep - self.ep) / dt,
self.min_ed,
self.max_ed,
);
// Make factors timeagnostic
let kp = self.kp * dt;
@@ -150,8 +167,12 @@ impl ClampingTimeagnosticPidController {
let kd = self.kd * dt;
// Calculate signal
let signal = kd.mul_add(ed, kp.mul_add(ep, ki * ei));
let clamped_signal = Self::optional_clamp(signal, self.min_signal, self.max_signal);
let signal = kp * ep + ki * ei + kd * ed;
let clamped_signal = ClampingTimeagnosticPidController::optional_clamp(
signal,
self.min_signal,
self.max_signal,
);
// Set values
self.ep = ep;
@@ -161,10 +182,12 @@ impl ClampingTimeagnosticPidController {
clamped_signal
}
}
};
signal
}
pub const fn reset(&mut self) {
pub fn reset(&mut self) {
self.ep = 0.0;
self.ei = 0.0;
self.ed = 0.0;
@@ -22,7 +22,7 @@ pub struct AccelerationSpeedController {
}
impl AccelerationSpeedController {
pub const fn new(
pub fn new(
min_speed: Option<f64>,
max_speed: Option<f64>,
min_acceleration: f64,
@@ -98,16 +98,16 @@ impl AccelerationSpeedController {
new_speed
}
pub const fn reset(&mut self, initial_speed: f64) {
pub fn reset(&mut self, initial_speed: f64) {
self.last_speed = initial_speed;
self.last_t = None; // Reset the last update time
}
pub const fn set_max_acceleration(&mut self, acceleration: f64) {
pub fn set_max_acceleration(&mut self, acceleration: f64) {
self.max_acceleration = acceleration;
}
const fn apply_speed_limits(&self, speed: f64) -> f64 {
fn apply_speed_limits(&self, speed: f64) -> f64 {
let mut limited_speed = speed;
if let Some(min) = self.min_speed {
@@ -121,23 +121,23 @@ impl AccelerationSpeedController {
limited_speed
}
pub const fn get_min_speed(&self) -> Option<f64> {
pub fn get_min_speed(&self) -> Option<f64> {
self.min_speed
}
pub const fn get_max_speed(&self) -> Option<f64> {
pub fn get_max_speed(&self) -> Option<f64> {
self.max_speed
}
pub const fn set_min_speed(&mut self, min_speed: Option<f64>) {
pub fn set_min_speed(&mut self, min_speed: Option<f64>) {
self.min_speed = min_speed;
}
pub const fn set_max_speed(&mut self, max_speed: Option<f64>) {
pub fn set_max_speed(&mut self, max_speed: Option<f64>) {
self.max_speed = max_speed;
}
pub const fn set_min_acceleration(&mut self, deceleration: f64) {
pub fn set_min_acceleration(&mut self, deceleration: f64) {
self.min_acceleration = deceleration;
}
}
@@ -53,7 +53,7 @@ impl AngularAccelerationSpeedController {
pub fn update(&mut self, target_speed: AngularVelocity, t: Instant) -> AngularVelocity {
let target_speed = target_speed.get::<radian_per_second>();
let new_speed = self.controller.update(target_speed, t);
AngularVelocity::new::<radian_per_second>(new_speed)
return AngularVelocity::new::<radian_per_second>(new_speed);
}
pub fn reset(&mut self, initial_speed: AngularVelocity) {
@@ -73,13 +73,13 @@ impl AngularAccelerationSpeedController {
pub fn get_min_speed(&self) -> Option<AngularVelocity> {
self.controller
.get_min_speed()
.map(AngularVelocity::new::<radian_per_second>)
.map(|s| AngularVelocity::new::<radian_per_second>(s))
}
pub fn get_max_speed(&self) -> Option<AngularVelocity> {
self.controller
.get_max_speed()
.map(AngularVelocity::new::<radian_per_second>)
.map(|s| AngularVelocity::new::<radian_per_second>(s))
}
pub fn set_min_speed(&mut self, min_speed: Option<AngularVelocity>) {
@@ -48,7 +48,7 @@ impl LinearAccelerationLimitingController {
pub fn update(&mut self, target_speed: Velocity, t: Instant) -> Velocity {
let target_speed = target_speed.get::<meter_per_second>();
let new_speed = self.controller.update(target_speed, t);
Velocity::new::<meter_per_second>(new_speed)
return Velocity::new::<meter_per_second>(new_speed);
}
pub fn reset(&mut self, initial_speed: Velocity) {
let initial_speed = initial_speed.get::<meter_per_second>();
@@ -65,12 +65,12 @@ impl LinearAccelerationLimitingController {
pub fn get_min_speed(&self) -> Option<Velocity> {
self.controller
.get_min_speed()
.map(Velocity::new::<meter_per_second>)
.map(|s| Velocity::new::<meter_per_second>(s))
}
pub fn get_max_speed(&self) -> Option<Velocity> {
self.controller
.get_max_speed()
.map(Velocity::new::<meter_per_second>)
.map(|s| Velocity::new::<meter_per_second>(s))
}
pub fn set_min_speed(&mut self, min_speed: Option<Velocity>) {
self.controller
+12 -10
View File
@@ -21,7 +21,7 @@ pub struct PidController {
}
impl PidController {
pub const fn new(kp: f64, ki: f64, kd: f64) -> Self {
pub fn new(kp: f64, ki: f64, kd: f64) -> Self {
Self {
kp,
ki,
@@ -33,27 +33,27 @@ impl PidController {
}
}
pub const fn configure(&mut self, ki: f64, kp: f64, kd: f64) {
pub fn configure(&mut self, ki: f64, kp: f64, kd: f64) {
self.reset();
self.kp = kp;
self.ki = ki;
self.kd = kd;
}
pub const fn get_kp(&self) -> f64 {
pub fn get_kp(&self) -> f64 {
self.kp
}
pub const fn get_ki(&self) -> f64 {
pub fn get_ki(&self) -> f64 {
self.ki
}
pub const fn get_kd(&self) -> f64 {
pub fn get_kd(&self) -> f64 {
self.kd
}
pub fn update(&mut self, error: f64, t: Instant) -> f64 {
match self.last {
let signal = match self.last {
// First update
None => {
// Calculate error
@@ -78,11 +78,11 @@ impl PidController {
// Calculate errors
let ep = error;
let ei = ep.mul_add(dt, self.ei);
let ei = self.ei + ep * dt;
let ed = (ep - self.ep) / dt;
// Calculate signal
let signal = self.kd.mul_add(ed, self.kp.mul_add(ep, self.ki * ei));
let signal = self.kp * ep + self.ki * ei + self.kd * ed;
// Set values
self.ep = ep;
@@ -92,10 +92,12 @@ impl PidController {
signal
}
}
};
signal
}
pub const fn reset(&mut self) {
pub fn reset(&mut self) {
self.ep = 0.0;
self.ei = 0.0;
self.ed = 0.0;
@@ -3,7 +3,7 @@ use std::fmt;
// Remove the anyhow::Ok import as it conflicts with std::result::Ok
/// Errors that can occur when creating or using the motion controller
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq)]
pub enum MotionControllerError {
InvalidSpeedLimits,
InvalidAccelerationLimits,
@@ -14,25 +14,25 @@ pub enum MotionControllerError {
impl fmt::Display for MotionControllerError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidSpeedLimits => {
MotionControllerError::InvalidSpeedLimits => {
write!(
f,
"Invalid speed limits: min_speed must be ≤ 0 and max_speed must be ≥ 0"
)
}
Self::InvalidAccelerationLimits => {
MotionControllerError::InvalidAccelerationLimits => {
write!(
f,
"Invalid acceleration limits: min_acceleration must be < 0 and max_acceleration must be > 0"
)
}
Self::InvalidPositionLimits => {
MotionControllerError::InvalidPositionLimits => {
write!(
f,
"Invalid position limits: min_position must be ≤ max_position"
)
}
Self::ZeroDeceleration => {
MotionControllerError::ZeroDeceleration => {
write!(
f,
"Zero deceleration rate: cannot plan motion without deceleration capability"
@@ -148,7 +148,7 @@ struct MotionConstants {
}
/// Represents the current phase of motion
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum MotionPhase {
Idle,
IncreasingSpeed,
@@ -170,7 +170,7 @@ pub struct ControllerBuilder {
impl ControllerBuilder {
/// Create a new builder
pub const fn new() -> Self {
pub fn new() -> Self {
Self {
min_speed: None,
max_speed: None,
@@ -184,36 +184,28 @@ impl ControllerBuilder {
}
/// Set speed limits (min_speed ≤ 0, max_speed ≥ 0)
pub const fn speed_limits(mut self, min_speed: f64, max_speed: f64) -> Self {
pub fn speed_limits(mut self, min_speed: f64, max_speed: f64) -> Self {
self.min_speed = Some(min_speed);
self.max_speed = Some(max_speed);
self
}
/// Set acceleration limits (min_acceleration < 0, max_acceleration > 0)
pub const fn acceleration_limits(
mut self,
min_acceleration: f64,
max_acceleration: f64,
) -> Self {
pub fn acceleration_limits(mut self, min_acceleration: f64, max_acceleration: f64) -> Self {
self.min_acceleration = Some(min_acceleration);
self.max_acceleration = Some(max_acceleration);
self
}
/// Set position limits (optional)
pub const fn position_limits(
mut self,
min_position: Option<f64>,
max_position: Option<f64>,
) -> Self {
pub fn position_limits(mut self, min_position: Option<f64>, max_position: Option<f64>) -> Self {
self.min_position = min_position;
self.max_position = max_position;
self
}
/// Set tolerances for position and speed comparisons
pub const fn tolerances(mut self, position_tolerance: f64, speed_tolerance: f64) -> Self {
pub fn tolerances(mut self, position_tolerance: f64, speed_tolerance: f64) -> Self {
self.position_tolerance = position_tolerance;
self.speed_tolerance = speed_tolerance;
self
@@ -330,7 +322,7 @@ impl AccelerationPositionController {
let motion_constants = Self::calculate_motion_constants(&config);
Ok(Self {
Ok(AccelerationPositionController {
config,
current_position: 0.0,
current_speed: 0.0,
@@ -374,19 +366,19 @@ impl AccelerationPositionController {
}
Self::new(
-speed, // min_speed
speed, // max_speed
-acceleration, // min_acceleration
acceleration, // max_acceleration
position.map(|p| -p.abs()), // min_position
position.map(|p| p.abs()), // max_position
1e-6, // position_tolerance (default)
1e-6, // speed_tolerance (default)
-speed, // min_speed
speed, // max_speed
-acceleration, // min_acceleration
acceleration, // max_acceleration
position.map(|p| -p), // min_position
position.map(|p| p), // max_position
1e-6, // position_tolerance (default)
1e-6, // speed_tolerance (default)
)
}
/// Create a new builder for the controller
pub const fn builder() -> ControllerBuilder {
pub fn builder() -> ControllerBuilder {
ControllerBuilder::new()
}
@@ -572,10 +564,8 @@ impl AccelerationPositionController {
// Calculate distance needed to reach cruise speed from current speed
let speed_change_distance = if current_speed_in_direction < cruise_speed.abs() {
current_speed_in_direction.max(0.0).mul_add(
-current_speed_in_direction.max(0.0),
cruise_speed.abs().powi(2),
) / (2.0 * acceleration_rate)
(cruise_speed.abs().powi(2) - current_speed_in_direction.max(0.0).powi(2))
/ (2.0 * acceleration_rate)
} else {
0.0
};
@@ -589,10 +579,8 @@ impl AccelerationPositionController {
// Using corrected kinematic equation: v_peak² = v_current² + 2*a*d_total*a_accel*a_decel/(a_accel + a_decel)
let accel_decel_factor =
(acceleration_rate * deceleration_rate) / (acceleration_rate + deceleration_rate);
let discriminant = current_speed_in_direction.max(0.0).mul_add(
current_speed_in_direction.max(0.0),
2.0 * abs_position_change * accel_decel_factor,
);
let discriminant = current_speed_in_direction.max(0.0).powi(2)
+ 2.0 * abs_position_change * accel_decel_factor;
if discriminant >= 0.0 {
self.peak_speed = discriminant.sqrt() * cruise_speed.signum();
@@ -646,7 +634,7 @@ impl AccelerationPositionController {
self.current_acceleration = self.config.max_acceleration * self.direction as f64;
// Update speed
let new_speed = self.current_acceleration.mul_add(dt, self.current_speed);
let new_speed = self.current_speed + self.current_acceleration * dt;
// Check if we've reached peak speed
if (self.direction > 0 && new_speed >= self.peak_speed)
@@ -677,7 +665,7 @@ impl AccelerationPositionController {
self.current_acceleration = self.config.min_acceleration * self.direction as f64;
// Update speed
let new_speed = self.current_acceleration.mul_add(dt, self.current_speed);
let new_speed = self.current_speed + self.current_acceleration * dt;
// Check if we've reached zero speed or target
if (self.current_speed > 0.0 && new_speed <= 0.0)
@@ -701,7 +689,7 @@ impl AccelerationPositionController {
}
// Update position based on current speed
let new_position = self.current_speed.mul_add(dt, self.current_position);
let new_position = self.current_position + self.current_speed * dt;
// Check position limits and handle violations
let mut position_limited = false;
@@ -754,7 +742,7 @@ impl AccelerationPositionController {
///
/// Returns the current position of the controlled object.
/// This value is updated with each call to `update()`.
pub const fn get_position(&self) -> f64 {
pub fn get_position(&self) -> f64 {
self.current_position
}
@@ -763,7 +751,7 @@ impl AccelerationPositionController {
/// Returns the current speed (velocity) of the controlled object.
/// Positive values indicate motion towards increasing position,
/// negative values indicate motion towards decreasing position.
pub const fn get_speed(&self) -> f64 {
pub fn get_speed(&self) -> f64 {
self.current_speed
}
@@ -771,32 +759,32 @@ impl AccelerationPositionController {
///
/// Returns the current acceleration of the controlled object.
/// Positive values indicate increasing speed, negative values indicate decreasing speed.
pub const fn get_acceleration(&self) -> f64 {
pub fn get_acceleration(&self) -> f64 {
self.current_acceleration
}
/// Get the target position
pub const fn get_target_position(&self) -> f64 {
pub fn get_target_position(&self) -> f64 {
self.target_position
}
/// Get the current motion phase
pub const fn get_motion_phase(&self) -> MotionPhase {
pub fn get_motion_phase(&self) -> MotionPhase {
self.motion_phase
}
/// Get the direction of motion
pub const fn get_direction(&self) -> i8 {
pub fn get_direction(&self) -> i8 {
self.direction
}
/// Get the calculated peak speed for current motion
pub const fn get_peak_speed(&self) -> f64 {
pub fn get_peak_speed(&self) -> f64 {
self.peak_speed
}
/// Get the position at which deceleration will begin
pub const fn get_deceleration_position(&self) -> f64 {
pub fn get_deceleration_position(&self) -> f64 {
self.deceleration_position
}
@@ -849,7 +837,7 @@ impl AccelerationPositionController {
}
/// Force the controller to stop at current position
pub const fn emergency_stop(&mut self) {
pub fn emergency_stop(&mut self) {
self.target_position = self.current_position;
self.current_speed = 0.0;
self.current_acceleration = 0.0;
@@ -1018,32 +1006,32 @@ impl AccelerationPositionController {
}
/// Get the minimum speed limit
pub const fn get_min_speed(&self) -> f64 {
pub fn get_min_speed(&self) -> f64 {
self.config.min_speed
}
/// Get the maximum speed limit
pub const fn get_max_speed(&self) -> f64 {
pub fn get_max_speed(&self) -> f64 {
self.config.max_speed
}
/// Get the minimum acceleration limit
pub const fn get_min_acceleration(&self) -> f64 {
pub fn get_min_acceleration(&self) -> f64 {
self.config.min_acceleration
}
/// Get the maximum acceleration limit
pub const fn get_max_acceleration(&self) -> f64 {
pub fn get_max_acceleration(&self) -> f64 {
self.config.max_acceleration
}
/// Get the minimum position limit
pub const fn get_min_position(&self) -> Option<f64> {
pub fn get_min_position(&self) -> Option<f64> {
self.config.min_position
}
/// Get the maximum position limit
pub const fn get_max_position(&self) -> Option<f64> {
pub fn get_max_position(&self) -> Option<f64> {
self.config.max_position
}
@@ -1080,17 +1068,17 @@ impl AccelerationPositionController {
}
/// Get the position tolerance
pub const fn get_position_tolerance(&self) -> f64 {
pub fn get_position_tolerance(&self) -> f64 {
self.config.position_tolerance
}
/// Get the speed tolerance
pub const fn get_speed_tolerance(&self) -> f64 {
pub fn get_speed_tolerance(&self) -> f64 {
self.config.speed_tolerance
}
/// Set tolerances for position and speed comparisons
pub const fn set_tolerances(&mut self, position_tolerance: f64, speed_tolerance: f64) {
pub fn set_tolerances(&mut self, position_tolerance: f64, speed_tolerance: f64) {
self.config.position_tolerance = position_tolerance;
self.config.speed_tolerance = speed_tolerance;
}
@@ -102,12 +102,16 @@ impl AngularAccelerationPositionController {
/// Get the minimum angle position limit
pub fn get_min_position(&self) -> Option<Angle> {
self.controller.get_min_position().map(Angle::new::<radian>)
self.controller
.get_min_position()
.map(|angle| Angle::new::<radian>(angle))
}
/// Get the maximum angle position limit
pub fn get_max_position(&self) -> Option<Angle> {
self.controller.get_max_position().map(Angle::new::<radian>)
self.controller
.get_max_position()
.map(|angle| Angle::new::<radian>(angle))
}
/// Set the minimum angle position limit
@@ -236,7 +236,7 @@ impl AngularJerkSpeedController {
pub fn get_min_speed(&self) -> Option<AngularVelocity> {
self.controller
.get_min_speed()
.map(AngularVelocity::new::<radian_per_second>)
.map(|speed| AngularVelocity::new::<radian_per_second>(speed))
}
/// Get the maximum angular velocity limit
@@ -248,7 +248,7 @@ impl AngularJerkSpeedController {
pub fn get_max_speed(&self) -> Option<AngularVelocity> {
self.controller
.get_max_speed()
.map(AngularVelocity::new::<radian_per_second>)
.map(|speed| AngularVelocity::new::<radian_per_second>(speed))
}
/// Set the minimum angular velocity limit
@@ -88,7 +88,7 @@ impl JerkSpeedController {
)
.expect("Failed to create AccelerationPositionController");
Self { base_controller }
JerkSpeedController { base_controller }
}
/// Create a new speed controller with simple symmetric limits
@@ -183,7 +183,7 @@ impl JerkSpeedController {
///
/// # Returns
/// Current speed as a floating-point value
pub const fn get_speed(&self) -> f64 {
pub fn get_speed(&self) -> f64 {
self.base_controller.get_position()
}
@@ -193,7 +193,7 @@ impl JerkSpeedController {
///
/// # Returns
/// Current acceleration as a floating-point value
pub const fn get_acceleration(&self) -> f64 {
pub fn get_acceleration(&self) -> f64 {
self.base_controller.get_speed()
}
@@ -203,7 +203,7 @@ impl JerkSpeedController {
///
/// # Returns
/// Current jerk as a floating-point value
pub const fn get_jerk(&self) -> f64 {
pub fn get_jerk(&self) -> f64 {
self.base_controller.get_acceleration()
}
@@ -213,7 +213,7 @@ impl JerkSpeedController {
///
/// # Returns
/// Target speed as a floating-point value
pub const fn get_target_speed(&self) -> f64 {
pub fn get_target_speed(&self) -> f64 {
self.base_controller.get_target_position()
}
@@ -223,7 +223,7 @@ impl JerkSpeedController {
///
/// # Returns
/// Minimum speed limit as an Option<f64>, None if no limit is set
pub const fn get_min_speed(&self) -> Option<f64> {
pub fn get_min_speed(&self) -> Option<f64> {
self.base_controller.get_min_position()
}
@@ -233,7 +233,7 @@ impl JerkSpeedController {
///
/// # Returns
/// Maximum speed limit as an Option<f64>, None if no limit is set
pub const fn get_max_speed(&self) -> Option<f64> {
pub fn get_max_speed(&self) -> Option<f64> {
self.base_controller.get_max_position()
}
@@ -205,7 +205,9 @@ impl LinearAccelerationPositionController {
/// # Returns
/// Minimum position limit as an Option<Length>, None if no limit is set
pub fn get_min_position(&self) -> Option<Length> {
self.controller.get_min_position().map(Length::new::<meter>)
self.controller
.get_min_position()
.map(|length| Length::new::<meter>(length))
}
/// Get the maximum position limit
@@ -215,7 +217,9 @@ impl LinearAccelerationPositionController {
/// # Returns
/// Maximum position limit as an Option<Length>, None if no limit is set
pub fn get_max_position(&self) -> Option<Length> {
self.controller.get_max_position().map(Length::new::<meter>)
self.controller
.get_max_position()
.map(|length| Length::new::<meter>(length))
}
/// Set the minimum position limit
@@ -195,14 +195,14 @@ impl LinearJerkSpeedController {
pub fn get_min_speed(&self) -> Option<Velocity> {
self.controller
.get_min_speed()
.map(Velocity::new::<meter_per_second>)
.map(|speed| Velocity::new::<meter_per_second>(speed))
}
/// Get the maximum velocity limit
pub fn get_max_speed(&self) -> Option<Velocity> {
self.controller
.get_max_speed()
.map(Velocity::new::<meter_per_second>)
.map(|speed| Velocity::new::<meter_per_second>(speed))
}
/// Set the minimum velocity limit
+26 -26
View File
@@ -1,6 +1,8 @@
use std::f32::consts::PI;
use uom::si::angle::radian;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct AngleConverter {
pub flip_x: bool, // true to flip X-axis (mirror horizontally)
pub flip_y: bool, // true to flip Y-axis (mirror vertically)
@@ -9,7 +11,7 @@ pub struct AngleConverter {
impl AngleConverter {
/// Create a custom coordinate system
pub const fn new(flip_x: bool, flip_y: bool, is_cw: bool) -> Self {
pub fn new(flip_x: bool, flip_y: bool, is_cw: bool) -> Self {
Self {
flip_x,
flip_y,
@@ -18,47 +20,47 @@ impl AngleConverter {
}
/// Standard mathematical coordinate system (CCW positive, 0° at positive X)
pub const fn mathematical() -> Self {
pub fn mathematical() -> Self {
Self::new(false, false, false)
}
/// Screen/graphics coordinate system (CW positive, 0° at positive X, Y-flipped)
pub const fn screen() -> Self {
pub fn screen() -> Self {
Self::new(false, true, false)
}
/// System where 0° points up, clockwise positive
pub const fn y_up_cw() -> Self {
pub fn y_up_cw() -> Self {
Self::new(false, false, true)
}
/// System where 0° points down, counter-clockwise positive
pub const fn y_down_ccw() -> Self {
pub fn y_down_ccw() -> Self {
Self::new(false, true, true)
}
/// System where 0° points left, clockwise positive
pub const fn x_left_cw() -> Self {
pub fn x_left_cw() -> Self {
Self::new(true, false, false)
}
/// System where 0° points right, counter-clockwise positive (same as mathematical)
pub const fn x_right_ccw() -> Self {
pub fn x_right_ccw() -> Self {
Self::mathematical()
}
/// System where 0° points up, counter-clockwise positive
pub const fn y_up_ccw() -> Self {
pub fn y_up_ccw() -> Self {
Self::new(false, false, false)
}
/// System where 0° points down, clockwise positive
pub const fn y_down_cw() -> Self {
pub fn y_down_cw() -> Self {
Self::new(false, true, false)
}
/// System where 0° points left, counter-clockwise positive
pub const fn x_left_ccw() -> Self {
pub fn x_left_ccw() -> Self {
Self::new(true, false, true)
}
@@ -73,7 +75,7 @@ impl AngleConverter {
(false, false) => {} // No change
(true, false) => result = 180.0 - result, // X-flip: mirror across Y-axis
(false, true) => result = -result, // Y-flip: mirror across X-axis
(true, true) => result += 180.0, // Both flips: 180° rotation
(true, true) => result = 180.0 + result, // Both flips: 180° rotation
}
// Handle rotation direction
@@ -100,7 +102,7 @@ impl AngleConverter {
(false, false) => {} // No change
(true, false) => result = 180.0 - result, // X-flip: mirror across Y-axis
(false, true) => result = -result, // Y-flip: mirror across X-axis
(true, true) => result -= 180.0, // Both flips: inverse 180° rotation
(true, true) => result = result - 180.0, // Both flips: inverse 180° rotation
}
self.normalize_angle(result)
@@ -108,16 +110,16 @@ impl AngleConverter {
/// Convert angle in radians from mathematical system to this system (f32)
pub fn radians_encode(&self, math_angle_radians: f32) -> f32 {
let degrees = math_angle_radians.to_degrees();
let degrees = math_angle_radians * 180.0 / PI;
let converted_degrees = self.degrees_encode(degrees);
converted_degrees.to_radians()
converted_degrees * PI / 180.0
}
/// Convert angle in radians from this system to mathematical system (f32)
pub fn radians_decode(&self, system_angle_radians: f32) -> f32 {
let degrees = system_angle_radians.to_degrees();
let degrees = system_angle_radians * 180.0 / PI;
let converted_degrees = self.degrees_decode(degrees);
converted_degrees.to_radians()
converted_degrees * PI / 180.0
}
/// Convert angle from mathematical system to this system (f64)
@@ -131,7 +133,7 @@ impl AngleConverter {
(false, false) => {} // No change
(true, false) => result = 180.0 - result, // X-flip: mirror across Y-axis
(false, true) => result = -result, // Y-flip: mirror across X-axis
(true, true) => result += 180.0, // Both flips: 180° rotation
(true, true) => result = 180.0 + result, // Both flips: 180° rotation
}
// Handle rotation direction
@@ -158,7 +160,7 @@ impl AngleConverter {
(false, false) => {} // No change
(true, false) => result = 180.0 - result, // X-flip: mirror across Y-axis
(false, true) => result = -result, // Y-flip: mirror across X-axis
(true, true) => result -= 180.0, // Both flips: inverse 180° rotation
(true, true) => result = result - 180.0, // Both flips: inverse 180° rotation
}
self.normalize_angle_f64(result)
@@ -166,16 +168,16 @@ impl AngleConverter {
/// Convert angle in radians from mathematical system to this system (f64)
pub fn radians_encode_f64(&self, math_angle_radians: f64) -> f64 {
let degrees = math_angle_radians.to_degrees();
let degrees = math_angle_radians * 180.0 / std::f64::consts::PI;
let converted_degrees = self.degrees_encode_f64(degrees);
converted_degrees.to_radians()
converted_degrees * std::f64::consts::PI / 180.0
}
/// Convert angle in radians from this system to mathematical system (f64)
pub fn radians_decode_f64(&self, system_angle_radians: f64) -> f64 {
let degrees = system_angle_radians.to_degrees();
let degrees = system_angle_radians * 180.0 / std::f64::consts::PI;
let converted_degrees = self.degrees_decode_f64(degrees);
converted_degrees.to_radians()
converted_degrees * std::f64::consts::PI / 180.0
}
/// Normalize angle to [0, 360) range (f32)
@@ -203,7 +205,7 @@ pub struct AngleConverterUom {
}
impl AngleConverterUom {
pub const fn new(angle_converter: AngleConverter) -> Self {
pub fn new(angle_converter: AngleConverter) -> Self {
Self { angle_converter }
}
@@ -236,8 +238,6 @@ impl AngleConverterUom {
#[cfg(test)]
mod tests {
use std::f32::consts::PI;
use super::*;
#[test]
@@ -18,7 +18,7 @@ pub struct AngularStepConverter {
impl AngularStepConverter {
/// Create a new converter with the specified steps per revolution
pub const fn new(steps_per_revolution: i16) -> Self {
pub fn new(steps_per_revolution: i16) -> Self {
Self {
steps_per_revolution,
}
@@ -60,7 +60,7 @@ impl LinearStepConverter {
}
/// Get the steps per revolution
pub const fn steps_per_revolution(&self) -> i16 {
pub fn steps_per_revolution(&self) -> i16 {
self.angular_step_converter.steps_per_revolution
}
}
@@ -45,12 +45,7 @@ pub struct MachineIdentificationAddresses {
}
impl MachineIdentificationAddresses {
pub const fn new(
vendor_word: u16,
serial_word: u16,
machine_word: u16,
device_word: u16,
) -> Self {
pub fn new(vendor_word: u16, serial_word: u16, machine_word: u16, device_word: u16) -> Self {
Self {
vendor_word,
serial_word,
@@ -80,7 +75,7 @@ pub async fn read_device_identifications<'maindevice>(
) -> Vec<Result<DeviceMachineIdentification, Error>> {
let mut result = Vec::new();
for subdevice in subdevices.iter() {
let identification = machine_device_identification(subdevice, maindevice).await;
let identification = machine_device_identification(&subdevice, maindevice).await;
result.push(identification);
}
result
@@ -227,11 +222,11 @@ pub async fn write_machine_device_identification<'maindevice, const MAX_PDI: usi
/// Returns the EEPROM addresses for the machine device identification
/// based on the subdevice's identity
pub fn get_identification_addresses(
pub fn get_identification_addresses<'maindevice>(
subdevice_identity: &SubDeviceIdentity,
subdevice_name: &str,
) -> Result<MachineIdentificationAddresses, Error> {
let identity_tuple = subdevice_identity_to_tuple(subdevice_identity);
let identity_tuple = subdevice_identity_to_tuple(&subdevice_identity);
Ok(match identity_tuple {
EK1100_IDENTITY_A => MachineIdentificationAddresses::default(),
@@ -281,8 +276,8 @@ async fn u16dump<'maindevice>(
words.push(subdevice.eeprom_read(maindevice, word).await?);
}
println!(
"EEPROM dump for {} from 0x{:04x} to 0x{:04x}",
print!(
"EEPROM dump for {} from 0x{:04x} to 0x{:04x}\n",
subdevice.name(),
start_byte / 2,
end_byte / 2
@@ -297,7 +292,7 @@ fn u16print(start_byte: u16, end_byte: u16, data: Vec<u16>) {
let table_start_word = start_byte & 0xfff0;
let table_end_word = (end_byte & 0xfff0_u16) + 0x10_u16;
let rows = (table_end_word - table_start_word) >> 4;
let rows = table_end_word - table_start_word >> 4;
for row in 0..rows {
print!("0x{:04x} | ", (table_start_word + row * 0x10) / 2);
@@ -314,7 +309,7 @@ fn u16print(start_byte: u16, end_byte: u16, data: Vec<u16>) {
}
}
}
println!();
print!("\n");
}
}
@@ -42,7 +42,7 @@ pub async fn discover_ethercat_interface() -> Result<String, anyhow::Error> {
// Get eligible interfaces
let mut interfaces = Interface::get_all()
.map_err(|e| anyhow::anyhow!("Failed to get network interfaces: {}", e))?
.or_else(|e| Err(anyhow::anyhow!("Failed to get network interfaces: {}", e)))?
.into_iter()
.filter(|iface| {
iface.is_up()
@@ -90,7 +90,10 @@ pub async fn discover_ethercat_interface() -> Result<String, anyhow::Error> {
// Wait for all tasks to complete and collect successful results
let successful_interfaces: Vec<String> = tasks
.into_iter()
.filter_map(|task| task.join().expect("Should join thread"))
.filter_map(|task| match task.join().expect("Should join thread") {
Some(name) => Some(name),
None => None,
})
.collect();
// Return the first successful interface (they're sorted by name)
+34 -11
View File
@@ -25,12 +25,12 @@ impl serde::ser::Error for HashSerializerError {
where
T: Display,
{
Self {}
HashSerializerError {}
}
}
pub struct HashSerializer<'a, H: Hasher>(&'a mut H);
impl<H: Hasher> SerializeSeq for HashSerializer<'_, H> {
impl<'a, H: Hasher> SerializeSeq for HashSerializer<'a, H> {
type Ok = ();
type Error = HashSerializerError;
@@ -46,7 +46,7 @@ impl<H: Hasher> SerializeSeq for HashSerializer<'_, H> {
}
}
impl<H: Hasher> SerializeTuple for HashSerializer<'_, H> {
impl<'a, H: Hasher> SerializeTuple for HashSerializer<'a, H> {
type Ok = ();
type Error = HashSerializerError;
@@ -62,7 +62,7 @@ impl<H: Hasher> SerializeTuple for HashSerializer<'_, H> {
}
}
impl<H: Hasher> SerializeTupleStruct for HashSerializer<'_, H> {
impl<'a, H: Hasher> SerializeTupleStruct for HashSerializer<'a, H> {
type Ok = ();
type Error = HashSerializerError;
@@ -78,7 +78,7 @@ impl<H: Hasher> SerializeTupleStruct for HashSerializer<'_, H> {
}
}
impl<H: Hasher> SerializeTupleVariant for HashSerializer<'_, H> {
impl<'a, H: Hasher> SerializeTupleVariant for HashSerializer<'a, H> {
type Ok = ();
type Error = HashSerializerError;
@@ -94,7 +94,7 @@ impl<H: Hasher> SerializeTupleVariant for HashSerializer<'_, H> {
}
}
impl<H: Hasher> SerializeMap for HashSerializer<'_, H> {
impl<'a, H: Hasher> SerializeMap for HashSerializer<'a, H> {
type Ok = ();
type Error = HashSerializerError;
@@ -117,7 +117,7 @@ impl<H: Hasher> SerializeMap for HashSerializer<'_, H> {
}
}
impl<H: Hasher> SerializeStruct for HashSerializer<'_, H> {
impl<'a, H: Hasher> SerializeStruct for HashSerializer<'a, H> {
type Ok = ();
type Error = HashSerializerError;
@@ -135,7 +135,7 @@ impl<H: Hasher> SerializeStruct for HashSerializer<'_, H> {
}
}
impl<H: Hasher> SerializeStructVariant for HashSerializer<'_, H> {
impl<'a, H: Hasher> SerializeStructVariant for HashSerializer<'a, H> {
type Ok = ();
type Error = HashSerializerError;
@@ -152,7 +152,7 @@ impl<H: Hasher> SerializeStructVariant for HashSerializer<'_, H> {
}
}
impl<H: Hasher> Serializer for HashSerializer<'_, H> {
impl<'a, H: Hasher> Serializer for HashSerializer<'a, H> {
type Ok = ();
type Error = HashSerializerError;
type SerializeSeq = Self;
@@ -329,9 +329,32 @@ impl<H: Hasher> Serializer for HashSerializer<'_, H> {
}
// A helper that uses Serialize to feed into a Hasher without allocating a buffer:
pub fn hash_with_serde_model<T: Serialize>(value: T) -> u64 {
let mut h = DefaultHasher::new();
pub fn hash_with_serde_model<T: Serialize, H: Hasher>(value: T, hasher: H) -> u64 {
let mut h = hasher;
let ser = HashSerializer(&mut h);
value.serialize(ser).expect("serialize to hasher failed");
h.finish()
}
/// if hash of a and b are different this function returns true
/// a and b need to impl Serialize
pub fn check_hash_different<T: Serialize>(a: T, b: T) -> bool {
// Yes DefaultHasher has to be defined multiple times
// If you use one for both the results will never match even if they are the same value
let hasher = DefaultHasher::new();
let hash = hash_with_serde_model(a, hasher);
let hasher = DefaultHasher::new();
let hash_old = hash_with_serde_model(b, hasher);
return hash != hash_old;
}
#[test]
fn test_same_values() {
let a = 10;
let b = 10;
assert_eq!(
check_hash_different(a, b),
false,
"Hashes should be equal for identical values"
);
}
+7 -7
View File
@@ -63,11 +63,11 @@ pub fn scale<T>(normalized_value: f64, new_min: T, new_max: T) -> T
where
T: Into<f64> + From<f64>,
{
let normalized_value: f64 = normalized_value;
let normalized_value: f64 = normalized_value.into();
let new_min: f64 = new_min.into();
let new_max: f64 = new_max.into();
let result = normalized_value.mul_add(new_max - new_min, new_min);
let result = normalized_value * (new_max - new_min) + new_min;
T::from(result)
}
@@ -128,11 +128,11 @@ where
// If we are under X we scale linearly from 0 to y
// If we are over X we scale linearly from y to 1
if value_f64 < x_f64 {
normalize(value_f64, 0.0, x_f64) * y_f64
return normalize(value_f64, 0.0, x_f64) * y_f64;
} else if value_f64 > x_f64 {
normalize(value_f64, x_f64, 1.0).mul_add(1.0 - y_f64, y_f64)
return normalize(value_f64, x_f64, 1.0) * (1.0 - y_f64) + y_f64;
} else {
y_f64
return y_f64;
}
}
@@ -273,7 +273,7 @@ pub fn interpolate_exponential(normalized_value: f64, steepness: f64) -> f64 {
return normalized_value;
}
let numerator = steepness.exp().powf(normalized_value) - 1.0;
let denominator = steepness.exp_m1();
let denominator = steepness.exp() - 1.0;
clip(numerator / denominator)
}
@@ -414,7 +414,7 @@ pub fn interpolate_inflected_exponential(x: f64, steepness: f64) -> f64 {
// Use standard exponential interpolation
let result = interpolate_exponential(x_norm, steepness);
0.5f64.mul_add(result, 0.5)
0.5 + 0.5 * result
}
}
+189
View File
@@ -0,0 +1,189 @@
use smol::Timer;
use std::time::{Duration, Instant};
use tracing::instrument;
pub struct LoopThrottle {
target_cycle_time: Duration,
update_interval_cycles: usize,
window_start: Instant,
total_cycles: usize,
delay: Duration,
last_avg_cycle_time: Duration,
on_update: Option<Box<dyn Fn(LoopThrottleUpdate)>>,
}
pub struct LoopThrottleUpdate {
pub delay: Duration,
pub avg_cycle_time: Duration,
pub error: i128, // Error in nanoseconds
}
impl LoopThrottle {
pub fn new(
target_cycle_time: Duration,
update_interval_cycles: usize,
on_update: Option<Box<dyn Fn(LoopThrottleUpdate)>>,
) -> Self {
Self {
target_cycle_time,
update_interval_cycles,
window_start: Instant::now(),
total_cycles: 0,
delay: target_cycle_time,
last_avg_cycle_time: target_cycle_time,
on_update,
}
}
#[instrument(skip_all)]
pub async fn sleep(&mut self) {
self.total_cycles += 1;
// Check if it's time to update (every N cycles)
if self.total_cycles % self.update_interval_cycles == 0 {
let actual_total_time = self.window_start.elapsed();
// Use integer arithmetic where possible
let avg_actual_cycle_time_nanos =
actual_total_time.as_nanos() / self.update_interval_cycles as u128;
let target_cycle_time_nanos = self.target_cycle_time.as_nanos();
// Calculate error in nanoseconds (signed)
let error_nanos = target_cycle_time_nanos as i128 - avg_actual_cycle_time_nanos as i128;
// Update current delay, allowing zero or negative values to become zero
let new_delay_nanos = (self.delay.as_nanos() as i128 + error_nanos).max(0) as u64;
self.delay = Duration::from_nanos(new_delay_nanos);
self.last_avg_cycle_time = Duration::from_nanos(avg_actual_cycle_time_nanos as u64);
// Reset for next measurement window
self.window_start = Instant::now();
self.total_cycles = 0; // Reset to prevent overflow
// if we have an update callback, call it
if let Some(on_update) = &self.on_update {
on_update(LoopThrottleUpdate {
delay: self.delay,
avg_cycle_time: self.last_avg_cycle_time,
error: error_nanos,
});
}
}
Timer::after(self.delay).await;
}
pub fn get_cycle_time(&self) -> Duration {
self.last_avg_cycle_time
}
pub fn get_delay(&self) -> Duration {
self.delay
}
}
#[cfg(test)]
mod tests {
use super::*;
use smol::block_on;
#[test]
fn test_adaptive_throttle_timing() {
block_on(async {
let update_interval_cycles = 100;
let target_cycle_time = Duration::from_micros(5);
let mut throttle = LoopThrottle::new(target_cycle_time, update_interval_cycles, None);
let test_start = Instant::now();
let mut cycle_times = Vec::new();
// Run for several cycles to trigger multiple updates
for _ in 0..10000 {
let cycle_start = Instant::now();
// Simulate some work
Timer::after(Duration::from_micros(5)).await;
throttle.sleep().await;
let cycle_time = cycle_start.elapsed();
cycle_times.push(cycle_time);
}
let total_test_time = test_start.elapsed();
let avg_cycle_time = cycle_times.iter().sum::<Duration>() / cycle_times.len() as u32;
println!("Test completed in: {:?}", total_test_time);
println!("Average cycle time: {:?}", avg_cycle_time);
println!("Target cycle time: {:?}", target_cycle_time);
println!("Last measured avg: {:?}", throttle.get_cycle_time());
// Assert that we're reasonably close to target timing
let tolerance = Duration::from_millis(10);
assert!(
avg_cycle_time >= target_cycle_time.saturating_sub(tolerance)
&& avg_cycle_time <= target_cycle_time + tolerance,
"Average cycle time {:?} not within tolerance of target {:?}",
avg_cycle_time,
target_cycle_time
);
});
}
#[test]
fn test_throttle_adaptation() {
block_on(async {
let target_cycle_time = Duration::from_millis(100);
let update_interval_cycles = 3;
let mut throttle = LoopThrottle::new(target_cycle_time, update_interval_cycles, None);
let initial_delay = throttle.get_delay();
// Run enough cycles to trigger multiple updates
for _ in 0..12 {
// Add variable work to test adaptation
Timer::after(Duration::from_millis(10)).await;
throttle.sleep().await;
}
println!("Initial delay: {:?}", initial_delay);
println!("Final delay: {:?}", throttle.get_delay());
println!("Last avg cycle time: {:?}", throttle.get_cycle_time());
assert!(throttle.get_cycle_time() > Duration::ZERO);
});
}
#[test]
fn test_throttle_initialization() {
let update_interval_cycles = 10;
let target_cycle_time = Duration::from_millis(75);
let throttle = LoopThrottle::new(target_cycle_time, update_interval_cycles, None);
assert_eq!(throttle.get_delay(), target_cycle_time);
assert_eq!(throttle.get_cycle_time(), target_cycle_time);
assert_eq!(throttle.total_cycles, 0);
}
#[test]
fn test_overflow_protection() {
block_on(async {
let target_cycle_time = Duration::from_micros(1);
let update_interval_cycles = 5;
let mut throttle = LoopThrottle::new(target_cycle_time, update_interval_cycles, None);
// Run many cycles to ensure total_cycles resets properly
for i in 0..50 {
throttle.sleep().await;
// Verify that total_cycles never exceeds update_interval_cycles
assert!(
throttle.total_cycles <= update_interval_cycles,
"Cycle {}: total_cycles ({}) exceeded update_interval_cycles ({})",
i,
throttle.total_cycles,
update_interval_cycles
);
}
});
}
}
+1
View File
@@ -2,5 +2,6 @@ pub mod compare_lists;
pub mod hasher_serializer;
pub mod hashing;
pub mod interpolation;
pub mod loop_trottle;
pub mod moving_time_window;
pub mod retry;
+30 -12
View File
@@ -11,40 +11,56 @@ pub trait DivideByCount {
impl DivideByCount for f64 {
fn divide_by_count(self, count: usize) -> Self {
self / (count as Self)
self / (count as f64)
}
}
impl DivideByCount for f32 {
fn divide_by_count(self, count: usize) -> Self {
self / (count as Self)
self / (count as f32)
}
}
impl DivideByCount for i32 {
fn divide_by_count(self, count: usize) -> Self {
self / (count as Self)
self / (count as i32)
}
}
impl DivideByCount for i64 {
fn divide_by_count(self, count: usize) -> Self {
self / (count as Self)
self / (count as i64)
}
}
impl DivideByCount for u32 {
fn divide_by_count(self, count: usize) -> Self {
self / (count as Self)
self / (count as u32)
}
}
impl DivideByCount for u64 {
fn divide_by_count(self, count: usize) -> Self {
self / (count as Self)
self / (count as u64)
}
}
/// A generic moving time window that works with numeric types.
///
/// # Examples
///
/// ```rust
/// use std::time::{Duration, Instant};
/// use control_core::helpers::moving_time_window::MovingTimeWindow;
///
/// let mut window = MovingTimeWindow::new(Duration::from_secs(10), 100);
/// window.update(1.5, Instant::now());
/// window.update(2.5, Instant::now());
///
/// let average = window.average();
/// println!("Average: {}", average);
/// ```
// Type aliases for common use cases
pub type F64Window = MovingTimeWindow<f64>;
pub type F32Window = MovingTimeWindow<f32>;
@@ -178,14 +194,15 @@ where
.samples
.iter()
.map(|(v, _)| *v)
.fold(None, |acc, val| {
acc.map_or(Some(val), |current_max| {
.fold(None, |acc, val| match acc {
None => Some(val),
Some(current_max) => {
if val > current_max {
Some(val)
} else {
Some(current_max)
}
})
}
})
.unwrap_or_default();
@@ -212,14 +229,15 @@ where
.samples
.iter()
.map(|(v, _)| *v)
.fold(None, |acc, val| {
acc.map_or(Some(val), |current_min| {
.fold(None, |acc, val| match acc {
None => Some(val),
Some(current_min) => {
if val < current_min {
Some(val)
} else {
Some(current_min)
}
})
}
})
.unwrap_or_default();
-113
View File
@@ -1,113 +0,0 @@
#[cfg(target_os = "linux")]
use libc;
use std::ffi::CString;
use std::io;
use std::str;
#[cfg(target_os = "linux")]
fn read_proc_interrupts() -> Result<String, io::Error> {
let path = CString::new("/proc/interrupts").unwrap();
// O_RDONLY
let fd = unsafe { libc::open(path.as_ptr(), libc::O_RDONLY) };
if fd < 0 {
return Err(io::Error::from_raw_os_error(-fd));
}
let mut buf: Vec<u8> = Vec::new();
let mut chunk = [0u8; 8192];
loop {
let n = unsafe { libc::read(fd, chunk.as_mut_ptr() as *mut _, chunk.len()) };
if n < 0 {
let e = io::Error::from_raw_os_error(-n as i32);
unsafe { libc::close(fd) };
return Err(e);
} else if n == 0 {
break;
} else {
buf.extend_from_slice(&chunk[..n as usize]);
}
}
unsafe { libc::close(fd) };
Ok(String::from_utf8_lossy(&buf).into_owned())
}
#[cfg(target_os = "linux")]
fn get_interface_irq(proc_content: &str, interface_name: &str) -> Option<u32> {
for line in proc_content.lines() {
if line.contains(interface_name) {
if let Some(colon_pos) = line.find(':') {
let head = &line[..colon_pos].trim();
// try parse head as integer IRQ number
if let Ok(irq) = head.parse::<u32>() {
return Some(irq);
}
}
}
}
None
}
/// Since kernel 3.0 it’s possible to use the /proc/irq/<IRQ-NUMBER>/smp_affinity_list
/// With comma seperated values
/// This function takes the irq identifier and writes the cpu string
/// into /proc/irq/irq_number/smp_affinity_list
#[cfg(target_os = "linux")]
fn set_irq_affinity_raw(irq: u32, cpu: &str) -> Result<(), io::Error> {
let path: String = format!("/proc/irq/{}/smp_affinity_list", irq);
let cpath = CString::new(path.clone()).unwrap();
// We want to completely overwrite the smp affinities, so that we guarentee execution on the specified cores
let file_descriptor = unsafe { libc::open(cpath.as_ptr(), libc::O_WRONLY) };
if file_descriptor < 0 {
tracing::info!("Permission denied to write: {}", path);
return Err(io::Error::new(
io::ErrorKind::PermissionDenied,
format!("failed to open {}: errno {}", path, -file_descriptor),
));
}
// convert to "raw bytes"
// cpu needs to be a string because we need the raw ascii code 48 = '0' and so on as bytes
let bytes = cpu.as_bytes();
let mut bytes_written_total = 0usize;
while bytes_written_total < bytes.len() {
let to_write = &bytes[bytes_written_total..];
let bytes_written = unsafe {
libc::write(
file_descriptor,
to_write.as_ptr() as *const _,
to_write.len(),
)
};
if bytes_written < 0 {
let e = io::Error::from_raw_os_error(-bytes_written as i32);
unsafe { libc::close(file_descriptor) };
return Err(e);
}
bytes_written_total += bytes_written as usize;
}
unsafe { libc::close(file_descriptor) };
Ok(())
}
/// This is used to make sure that a given irq handler runs on the expected cpu
/// On Realtime systems this is the make or break fix for certain usecases like ethercrab
/// On our machines the 99.99th percentile of cycle times was very bad ~5-11 ms while ethercat is supposed to be in the microseconds
/// After pinning the ethernet irq to the core that the ethercat code ran on 99.99th percentile went down to ~200us (microseconds)
/// Example input: irq_name: "eno1" , cpu: 2
/// Remember that cpu cores are counted from 0
#[cfg(target_os = "linux")]
pub fn set_irq_affinity(irq_name: &str, cpu: u32) -> Result<(), anyhow::Error> {
let proc_contents = read_proc_interrupts()?;
let irq = get_interface_irq(&proc_contents, irq_name);
if irq.is_none() {
return Err(anyhow::anyhow!("Couldnt find irq number!"));
}
let res = set_irq_affinity_raw(irq.unwrap(), &cpu.to_string());
if res.is_err() {
return Err(anyhow::anyhow!("Couldnt write affinity list!"));
} else {
return Ok(());
}
}
-1
View File
@@ -2,7 +2,6 @@ pub mod controllers;
pub mod converters;
pub mod ethercat;
pub mod helpers;
pub mod irq_handling;
pub mod machines;
pub mod modbus;
pub mod realtime;
+11 -11
View File
@@ -12,7 +12,7 @@ pub struct MachineIdentificationUnique {
impl MachineIdentificationUnique {
/// Check if values are non-zero
pub const fn is_valid(&self) -> bool {
pub fn is_valid(&self) -> bool {
self.machine_identification.is_valid() && self.serial != 0
}
}
@@ -36,12 +36,12 @@ pub struct MachineIdentification {
impl MachineIdentification {
/// Check if values are non-zero
pub const fn is_valid(&self) -> bool {
pub fn is_valid(&self) -> bool {
self.vendor != 0 && self.machine != 0
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct DeviceMachineIdentification {
pub machine_identification_unique: MachineIdentificationUnique,
pub role: u16,
@@ -49,7 +49,7 @@ pub struct DeviceMachineIdentification {
impl DeviceMachineIdentification {
/// Check if values are non-zero
pub const fn is_valid(&self) -> bool {
pub fn is_valid(&self) -> bool {
self.machine_identification_unique.is_valid()
&& self
.machine_identification_unique
@@ -59,7 +59,7 @@ impl DeviceMachineIdentification {
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct DeviceIdentification {
pub device_machine_identification: Option<DeviceMachineIdentification>,
pub device_hardware_identification: DeviceHardwareIdentification,
@@ -67,14 +67,14 @@ pub struct DeviceIdentification {
impl From<DeviceIdentificationIdentified> for DeviceIdentification {
fn from(value: DeviceIdentificationIdentified) -> Self {
Self {
DeviceIdentification {
device_machine_identification: Some(value.device_machine_identification),
device_hardware_identification: value.device_hardware_identification,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct DeviceIdentificationIdentified {
pub device_machine_identification: DeviceMachineIdentification,
pub device_hardware_identification: DeviceHardwareIdentification,
@@ -90,24 +90,24 @@ impl TryFrom<DeviceIdentification> for DeviceIdentificationIdentified {
module_path!()
))?;
Ok(Self {
Ok(DeviceIdentificationIdentified {
device_machine_identification,
device_hardware_identification: value.device_hardware_identification,
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum DeviceHardwareIdentification {
Ethercat(DeviceHardwareIdentificationEthercat),
Serial(DeviceHardwareIdentificationSerial),
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct DeviceHardwareIdentificationEthercat {
pub subdevice_index: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct DeviceHardwareIdentificationSerial {
pub path: String,
}
+5 -11
View File
@@ -31,12 +31,6 @@ pub struct MachineManager {
HashMap<MachineIdentificationUnique, Result<Arc<Mutex<dyn Machine>>, anyhow::Error>>,
}
impl Default for MachineManager {
fn default() -> Self {
Self::new()
}
}
impl MachineManager {
pub fn new() -> Self {
Self {
@@ -51,7 +45,7 @@ impl MachineManager {
machine_registry: &MachineRegistry,
hardware: &MachineNewHardwareEthercat,
socket_queue_tx: Sender<(SocketRef, Arc<GenericEvent>)>,
machine_manager: Weak<RwLock<Self>>,
machine_manager: Weak<RwLock<MachineManager>>,
) {
// empty ethercat machines
self.ethercat_machines.clear();
@@ -105,7 +99,7 @@ impl MachineManager {
device: Arc<RwLock<dyn SerialDevice>>,
machine_registry: &MachineRegistry,
socket_queue_tx: Sender<(SocketRef, Arc<GenericEvent>)>,
machine_manager: Weak<RwLock<Self>>,
machine_manager: Weak<RwLock<MachineManager>>,
) {
let hardware = MachineNewHardwareSerial { device };
@@ -119,7 +113,7 @@ impl MachineManager {
device_group: &vec![device_identification_identified.clone()],
hardware: &MachineNewHardware::Serial(&hardware),
socket_queue_tx,
machine_manager,
machine_manager: machine_manager.clone(),
});
tracing::info!("Adding serial machine {:?}", new_machine);
@@ -169,7 +163,7 @@ impl MachineManager {
}
};
let machine_weak = Arc::downgrade(machine);
Some(machine_weak)
return Some(machine_weak);
}
pub fn get_serial_weak(
@@ -190,7 +184,7 @@ impl MachineManager {
}
};
let machine_weak = Arc::downgrade(machine);
Some(machine_weak)
return Some(machine_weak);
}
}
+1 -1
View File
@@ -71,7 +71,7 @@ impl<'a> Iterator for MachineManagerIterator<'a> {
impl MachineManager {
// Returns an iterator over all machines (both ethercat and serial)
pub fn iter(&'_ self) -> MachineManagerIterator<'_> {
pub fn iter(&self) -> MachineManagerIterator {
MachineManagerIterator {
iter: MachineManagerIter::EthercatMachines {
iter: self.ethercat_machines.iter(),
+2 -5
View File
@@ -16,12 +16,9 @@ pub mod manager_iter;
pub mod new;
pub mod registry;
pub trait Machine: MachineAct + MachineNewTrait + MachineApi + AnyGetters + Debug + Send {}
pub trait AnyGetters: Any {
pub trait Machine: MachineAct + MachineNewTrait + MachineApi + Any + Debug + Send {
fn as_any(&self) -> &dyn Any;
fn as_any_mut(&mut self) -> &mut dyn Any;
}
/// Casts a `dyn Machine` to a specific machine type
pub async fn downcast_machine<T: Machine>(
machine: Arc<Mutex<dyn Machine>>,
@@ -64,7 +61,7 @@ pub struct ConnectedMachine<T: GetStrongCount> {
pub machine: T,
}
#[derive(Serialize, Debug, Clone, PartialEq, Eq)]
#[derive(Serialize, Debug, Clone, PartialEq)]
pub struct ConnectedMachineData {
pub machine_identification_unique: MachineIdentificationUnique,
pub is_available: bool,
+5 -2
View File
@@ -9,6 +9,7 @@ use ethercrab::{SubDevice, SubDeviceRef};
use smol::{channel::Sender, lock::RwLock};
use socketioxide::extract::SocketRef;
use std::{
pin::Pin,
sync::{Arc, Weak},
time::Instant,
};
@@ -16,13 +17,15 @@ use std::{
use crate::socketio::event::GenericEvent;
pub trait MachineNewTrait {
fn new(params: &MachineNewParams<'_, '_, '_, '_, '_, '_, '_>) -> Result<Self, Error>
fn new<'maindevice, 'subdevices>(
params: &MachineNewParams<'maindevice, 'subdevices, '_, '_, '_, '_, '_>,
) -> Result<Self, Error>
where
Self: Sized;
}
pub trait MachineAct {
fn act(&mut self, now: Instant);
fn act(&mut self, now: Instant) -> Pin<Box<dyn Future<Output = ()> + Send + '_>>;
}
pub struct MachineNewParams<
-6
View File
@@ -10,12 +10,6 @@ pub struct MachineRegistry {
type_map: HashMap<TypeId, (MachineIdentification, MachineNewClosure)>,
}
impl Default for MachineRegistry {
fn default() -> Self {
Self::new()
}
}
impl MachineRegistry {
pub fn new() -> Self {
Self {
+34 -28
View File
@@ -62,17 +62,17 @@ impl From<ModbusExceptionCode> for u8 {
impl From<u8> for ModbusExceptionCode {
fn from(value: u8) -> Self {
match value {
0 => Self::None,
1 => Self::IllegalFunction,
2 => Self::IllegalDataAddress,
3 => Self::IllegalDataValue,
4 => Self::SlaveDeviceFailure,
5 => Self::Acknowledge,
6 => Self::SlaveDeviceBusy,
8 => Self::MemoryParityError,
10 => Self::GatewayPathUnavailable,
11 => Self::GatewayTargetDeviceFailedToRespond,
other => Self::Unknown(other),
0 => ModbusExceptionCode::None,
1 => ModbusExceptionCode::IllegalFunction,
2 => ModbusExceptionCode::IllegalDataAddress,
3 => ModbusExceptionCode::IllegalDataValue,
4 => ModbusExceptionCode::SlaveDeviceFailure,
5 => ModbusExceptionCode::Acknowledge,
6 => ModbusExceptionCode::SlaveDeviceBusy,
8 => ModbusExceptionCode::MemoryParityError,
10 => ModbusExceptionCode::GatewayPathUnavailable,
11 => ModbusExceptionCode::GatewayTargetDeviceFailedToRespond,
other => ModbusExceptionCode::Unknown(other),
}
}
}
@@ -97,10 +97,10 @@ impl TryFrom<u8> for ModbusFunctionCode {
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0x03 => Ok(Self::ReadHoldingRegister),
0x04 => Ok(Self::ReadInputRegister),
0x06 => Ok(Self::PresetHoldingRegister),
0x08 => Ok(Self::DiagnoseFunction),
0x03 => Ok(ModbusFunctionCode::ReadHoldingRegister),
0x04 => Ok(ModbusFunctionCode::ReadInputRegister),
0x06 => Ok(ModbusFunctionCode::PresetHoldingRegister),
0x08 => Ok(ModbusFunctionCode::DiagnoseFunction),
_ => Err(anyhow::anyhow!("Error: Modbus Function Code doesnt exist!")),
}
}
@@ -119,7 +119,7 @@ impl From<ModbusFunctionCode> for u8 {
impl From<ModbusRequest> for Vec<u8> {
fn from(request: ModbusRequest) -> Self {
let mut buffer = Self::new();
let mut buffer = Vec::new();
buffer.push(request.slave_id);
buffer.push(request.function_code.into()); // convert functioncode into u8 and push it
@@ -132,7 +132,7 @@ impl From<ModbusRequest> for Vec<u8> {
// convert u16 value to le_bytes and add it to the end of the frame
buffer.extend_from_slice(&result_crc.to_le_bytes());
buffer
return buffer;
}
}
/// Reads data from a Modbus device via a serial port.
@@ -153,7 +153,7 @@ pub fn receive_data_modbus(port: &mut dyn SerialPort) -> Result<Option<Vec<u8>>,
}
/// Computes Modbus CRC-16 using `crc` crate.
pub const fn modbus_crc16(data: &[u8]) -> u16 {
pub fn modbus_crc16(data: &[u8]) -> u16 {
let modbus: Crc<u16> = Crc::<u16>::new(&CRC_16_MODBUS);
modbus.checksum(data)
}
@@ -204,17 +204,23 @@ fn extract_crc(raw_data: &Vec<u8>) -> Result<u16, Error> {
}
impl TryFrom<Vec<u8>> for ModbusResponse {
type Error = anyhow::Error;
fn try_from(value: Vec<u8>) -> Result<Self, Error> {
let crc = extract_crc(&value)?;
fn try_from(value: Vec<u8>) -> Result<ModbusResponse, Error> {
let crc = match extract_crc(&value) {
Ok(crc_value) => crc_value,
Err(err) => return Err(err),
};
let function_code_res = ModbusFunctionCode::try_from(value[1]);
let function_code = function_code_res?;
let function_code = match function_code_res {
Ok(code) => code,
Err(err) => return Err(err),
};
Ok(Self {
Ok(ModbusResponse {
slave_id: value[0],
function_code,
function_code: function_code,
data: value[2..value.len() - 2].to_vec(), // get data without the crc
crc,
crc: crc,
})
}
}
@@ -226,23 +232,23 @@ impl TryFrom<Vec<u8>> for ModbusResponse {
/// machine_operation_delay_nano: Delay for the given operation in nanoseconds as specified by the slaves datasheet (example: mitsubishi csfr84 has 12ms for read write in RAM)
/// baudrate: bits per second
/// message_size: size of original message in bytes
pub const fn calculate_modbus_rtu_timeout(
pub fn calculate_modbus_rtu_timeout(
bits: u8,
machine_operation_delay_nano: Duration,
baudrate: u32,
message_size: usize,
) -> Duration {
let nanoseconds_per_bit: u64 = (1000000 / baudrate) as u64;
let nanoseconds_per_byte: u64 = bits as u64 * nanoseconds_per_bit;
let nanoseconds_per_byte: u64 = bits as u64 * nanoseconds_per_bit as u64;
let transmission_timeout: u64 = nanoseconds_per_byte * message_size as u64;
let silent_time: u64 = (nanoseconds_per_byte * (35)) / 10_u64; // silent_time is 3.5x of character length,which is 11 bit for 8E1
let silent_time: u64 = (nanoseconds_per_byte * (35)) / 10 as u64; // silent_time is 3.5x of character length,which is 11 bit for 8E1
let mut full_timeout: u64 = transmission_timeout;
full_timeout += machine_operation_delay_nano.as_nanos() as u64;
full_timeout += silent_time;
Duration::from_nanos(full_timeout)
return Duration::from_nanos(full_timeout);
}
#[cfg(test)]
@@ -66,7 +66,7 @@ impl ModbusSerialInterface {
}
}
pub const fn is_initialized(&self) -> bool {
pub fn is_initialized(&self) -> bool {
!matches!(self.state, State::Uninitialized)
}
@@ -199,7 +199,7 @@ impl ModbusSerialInterface {
Some(Duration::from_nanos(total_timeout))
}
pub const fn get_response(&self) -> Option<&ModbusResponse> {
pub fn get_response(&self) -> Option<&ModbusResponse> {
self.response.as_ref()
}
+2 -2
View File
@@ -11,13 +11,13 @@ pub struct MutationResponse {
}
impl MutationResponse {
pub const fn success() -> Self {
pub fn success() -> Self {
Self {
success: true,
error: None,
}
}
pub const fn error(error: String) -> Self {
pub fn error(error: String) -> Self {
Self {
success: false,
error: Some(error),
+1 -1
View File
@@ -30,7 +30,7 @@ pub struct SerialDeviceNewParams {
pub device_thread_panic_tx: Sender<SerialDeviceRemoval<String>>,
}
#[derive(PartialEq, Eq, Clone, Debug)]
#[derive(PartialEq, Clone, Debug)]
pub struct SerialDeviceIdentification {
pub vendor_id: u16,
pub product_id: u16,
-6
View File
@@ -20,12 +20,6 @@ pub struct SerialDeviceRegistry {
pub type_map: HashMap<TypeId, (SerialDeviceIdentification, SerialDeviceNewClosure)>,
}
impl Default for SerialDeviceRegistry {
fn default() -> Self {
Self::new()
}
}
impl SerialDeviceRegistry {
pub fn new() -> Self {
Self {
+6 -3
View File
@@ -82,12 +82,15 @@ impl<'serialdeviceregistry> SerialDetection<'serialdeviceregistry> {
let mut usb_ports: Vec<(String, UsbPortInfo)> = Vec::new();
for port in port_list {
if let SerialPortType::UsbPort(usb_info) = &port.port_type {
usb_ports.push((port.port_name.to_string(), usb_info.clone()));
match &port.port_type {
SerialPortType::UsbPort(usb_info) => {
usb_ports.push((port.port_name.to_string(), usb_info.clone()));
}
_ => {}
};
}
usb_ports
return usb_ports;
}
pub async fn check_ports(&mut self) -> CheckPortsResult {
+12 -8
View File
@@ -30,7 +30,7 @@ impl Namespace {
#[instrument(skip_all)]
pub fn subscribe(&mut self, socket: SocketRef) {
// add the socket to the list
self.sockets.push(socket);
self.sockets.push(socket.clone());
}
/// Removes a socket from the namespace.
@@ -81,7 +81,7 @@ impl Namespace {
for (_event_name, events) in event_groups {
for event in events {
// Send to global queue instead of per-socket queue
self.send_to_queue(&socket, event, "reemit");
self.send_to_queue(&socket, &event, "reemit");
}
}
}
@@ -97,10 +97,13 @@ impl Namespace {
fn cache(
&mut self,
event: Arc<GenericEvent>,
buffer_fn: &Box<dyn Fn(&mut Vec<Arc<GenericEvent>>, &Arc<GenericEvent>)>,
buffer_fn: &Box<dyn Fn(&mut Vec<Arc<GenericEvent>>, &Arc<GenericEvent>) -> ()>,
) {
let cached_events_for_key = self.events.entry(event.name.clone()).or_default();
buffer_fn(cached_events_for_key, &event);
let mut cached_events_for_key = self
.events
.entry(event.name.clone())
.or_insert_with(Vec::new);
buffer_fn(&mut cached_events_for_key, &event);
}
/// Emits an event to all sockets in the namespace and caches it.
@@ -114,10 +117,10 @@ impl Namespace {
pub fn emit(
&mut self,
event: Arc<GenericEvent>,
buffer_fn: &Box<dyn Fn(&mut Vec<Arc<GenericEvent>>, &Arc<GenericEvent>)>,
buffer_fn: &Box<dyn Fn(&mut Vec<Arc<GenericEvent>>, &Arc<GenericEvent>) -> ()>,
) {
// cache the event
self.cache(event.clone(), buffer_fn);
self.cache(event.clone(), &buffer_fn);
// emit the event - inlined from emit function
// Send to global queue for each socket in the namespace
@@ -188,7 +191,7 @@ pub trait CacheableEvents<Events> {
fn event_cache_fn(&self) -> CacheFn;
}
pub type CacheFn = Box<dyn Fn(&mut Vec<Arc<GenericEvent>>, &Arc<GenericEvent>)>;
pub type CacheFn = Box<dyn Fn(&mut Vec<Arc<GenericEvent>>, &Arc<GenericEvent>) -> ()>;
/// [`BufferFn`] that stores the last n events
pub fn cache_n_events(n: usize) -> CacheFn {
@@ -220,6 +223,7 @@ pub fn cache_first_and_last_event() -> CacheFn {
if events.len() == 2 {
events.remove(1);
events.push(event.clone());
return;
}
})
}
+7 -7
View File
@@ -17,8 +17,8 @@ impl Serialize for NamespaceId {
S: Serializer,
{
match self {
Self::Main => serializer.serialize_str("/main"),
Self::Machine(id) => {
NamespaceId::Main => serializer.serialize_str("/main"),
NamespaceId::Machine(id) => {
let path = format!(
"/machine/{}/{}/{}",
id.machine_identification.vendor, id.machine_identification.machine, id.serial
@@ -36,7 +36,7 @@ impl<'de> Deserialize<'de> for NamespaceId {
{
struct NamespaceIdVisitor;
impl Visitor<'_> for NamespaceIdVisitor {
impl<'de> Visitor<'de> for NamespaceIdVisitor {
type Value = NamespaceId;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
@@ -85,7 +85,7 @@ impl FromStr for NamespaceId {
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s == "/main" {
return Ok(Self::Main);
return Ok(NamespaceId::Main);
}
if let Some(machine_path) = s.strip_prefix("/machine/") {
@@ -101,7 +101,7 @@ impl FromStr for NamespaceId {
.parse::<u16>()
.map_err(|_| "Invalid serial id".to_string())?;
return Ok(Self::Machine(MachineIdentificationUnique {
return Ok(NamespaceId::Machine(MachineIdentificationUnique {
machine_identification: MachineIdentification { vendor, machine },
serial,
}));
@@ -116,8 +116,8 @@ impl FromStr for NamespaceId {
impl fmt::Display for NamespaceId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Main => write!(f, "/main"),
Self::Machine(id) => {
NamespaceId::Main => write!(f, "/main"),
NamespaceId::Machine(id) => {
write!(
f,
"/machine/{}/{}/{}",
+1 -1
View File
@@ -21,7 +21,7 @@ impl FixedTransmission {
/// # Returns
///
/// A new instance of FixedTransmission.
pub const fn new(ratio: f64) -> Self {
pub fn new(ratio: f64) -> Self {
Self { ratio }
}
}
+18 -51
View File
@@ -11,7 +11,7 @@
"@hookform/resolvers": "^4.1.2",
"@icons-pack/react-simple-icons": "^11.2.0",
"@jub3i/tree-kill": "^0.0.15",
"@radix-ui/react-collapsible": "^1.1.12",
"@radix-ui/react-collapsible": "^1.1.8",
"@radix-ui/react-dialog": "^1.1.6",
"@radix-ui/react-dropdown-menu": "^2.1.15",
"@radix-ui/react-label": "^2.1.2",
@@ -54,7 +54,7 @@
"xlsx": "^0.18.5",
"zod": "^3.24.2",
"zod-validation-error": "^3.4.0",
"zustand": "^5.0.8"
"zustand": "^5.0.3"
},
"devDependencies": {
"@electron-forge/cli": "^7.6.1",
@@ -79,7 +79,7 @@
"@types/node": "^24.2.1",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
"electron": "^37.3.1",
"electron": "^37.2.6",
"electron-playwright-helpers": "^1.7.1",
"eslint": "^9.20.1",
"eslint-config-prettier": "^10.1.2",
@@ -95,7 +95,7 @@
"ts-node": "^10.9.2",
"typescript": "^5.7.3",
"typescript-eslint": "^8.24.1",
"vite": "^6.3.6",
"vite": "^6.2.6",
"vitest": "^3.0.6"
}
},
@@ -2323,16 +2323,15 @@
}
},
"node_modules/@radix-ui/react-collapsible": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.12.tgz",
"integrity": "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==",
"license": "MIT",
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.11.tgz",
"integrity": "sha512-2qrRsVGSCYasSz1RFOorXwl0H7g7J1frQtgpQgYrt+MOidtPAINHn9CPovQXb83r8ahapdx3Tu0fa/pdFFSdPg==",
"dependencies": {
"@radix-ui/primitive": "1.1.3",
"@radix-ui/primitive": "1.1.2",
"@radix-ui/react-compose-refs": "1.1.2",
"@radix-ui/react-context": "1.1.2",
"@radix-ui/react-id": "1.1.1",
"@radix-ui/react-presence": "1.1.5",
"@radix-ui/react-presence": "1.1.4",
"@radix-ui/react-primitive": "2.1.3",
"@radix-ui/react-use-controllable-state": "1.2.2",
"@radix-ui/react-use-layout-effect": "1.1.1"
@@ -2352,36 +2351,6 @@
}
}
},
"node_modules/@radix-ui/react-collapsible/node_modules/@radix-ui/primitive": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz",
"integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==",
"license": "MIT"
},
"node_modules/@radix-ui/react-collapsible/node_modules/@radix-ui/react-presence": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz",
"integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.2",
"@radix-ui/react-use-layout-effect": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-collection": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz",
@@ -6316,9 +6285,9 @@
"dev": true
},
"node_modules/electron": {
"version": "37.3.1",
"resolved": "https://registry.npmjs.org/electron/-/electron-37.3.1.tgz",
"integrity": "sha512-7DhktRLqhe6OJh/Bo75bTI0puUYEmIwSzMinocgO63mx3MVjtIn2tYMzLmAleNIlud2htkjpsMG2zT4PiTCloA==",
"version": "37.2.6",
"resolved": "https://registry.npmjs.org/electron/-/electron-37.2.6.tgz",
"integrity": "sha512-Ns6xyxE+hIK5UlujtRlw7w4e2Ju/ImCWXf1Q/PoOhc0N3/6SN6YW7+ujCarsHbxWnolbW+1RlkHtdklUJpjbPA==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
@@ -14552,10 +14521,9 @@
}
},
"node_modules/vite": {
"version": "6.3.6",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.6.tgz",
"integrity": "sha512-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA==",
"license": "MIT",
"version": "6.3.5",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz",
"integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==",
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.4.4",
@@ -15236,10 +15204,9 @@
}
},
"node_modules/zustand": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.8.tgz",
"integrity": "sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw==",
"license": "MIT",
"version": "5.0.7",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.7.tgz",
"integrity": "sha512-Ot6uqHDW/O2VdYsKLLU8GQu8sCOM1LcoE8RwvLv9uuRT9s6SOHCKs0ZEOhxg+I1Ld+A1Q5lwx+UlKXXUoCZITg==",
"engines": {
"node": ">=12.20.0"
},
+4 -4
View File
@@ -44,7 +44,7 @@
"@types/node": "^24.2.1",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
"electron": "^37.3.1",
"electron": "^37.2.6",
"electron-playwright-helpers": "^1.7.1",
"eslint": "^9.20.1",
"eslint-config-prettier": "^10.1.2",
@@ -60,14 +60,14 @@
"ts-node": "^10.9.2",
"typescript": "^5.7.3",
"typescript-eslint": "^8.24.1",
"vite": "^6.3.6",
"vite": "^6.2.6",
"vitest": "^3.0.6"
},
"dependencies": {
"@hookform/resolvers": "^4.1.2",
"@icons-pack/react-simple-icons": "^11.2.0",
"@jub3i/tree-kill": "^0.0.15",
"@radix-ui/react-collapsible": "^1.1.12",
"@radix-ui/react-collapsible": "^1.1.8",
"@radix-ui/react-dialog": "^1.1.6",
"@radix-ui/react-dropdown-menu": "^2.1.15",
"@radix-ui/react-label": "^2.1.2",
@@ -110,6 +110,6 @@
"xlsx": "^0.18.5",
"zod": "^3.24.2",
"zod-validation-error": "^3.4.0",
"zustand": "^5.0.8"
"zustand": "^5.0.3"
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 850 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 325 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 664 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 298 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 226 KiB

@@ -697,26 +697,3 @@ The following persons must not work on the machine:
| Cleaning, maintenance | |
| Maintenance | Trained and instructed specialised personnel of the operator or service technicians of the manufacturer |
| Decommissioning | Trained and instructed specialised personnel of the operator |
<br>
<br>
## Electrical Diagrams
![QiTech Pro Extruder Electrical Diagram 2025 - Sheet 1](images/manuals/extruder/QiTech_Pro_Extruder_Electrical_Diagram_2025-001.png)
![QiTech Pro Extruder Electrical Diagram 2025 - Sheet 2](images/manuals/extruder/QiTech_Pro_Extruder_Electrical_Diagram_2025_2-002.png)
![QiTech Pro Extruder Electrical Diagram 2025 - Sheet 3](images/manuals/extruder/QiTech_Pro_Extruder_Electrical_Diagram_2025_3-003.png)
![QiTech Pro Extruder Electrical Diagram 2025 - Sheet 4](images/manuals/extruder/QiTech_Pro_Extruder_Electrical_Diagram_2025_4-004.png)
![QiTech Pro Extruder Electrical Diagram 2025 - Sheet 5](images/manuals/extruder/QiTech_Pro_Extruder_Electrical_Diagram_2025_5-005.png)
![QiTech Pro Extruder Electrical Diagram 2025 - Sheet 6](images/manuals/extruder/QiTech_Pro_Extruder_Electrical_Diagram_2025_6-006.png)
![QiTech Pro Extruder Electrical Diagram 2025 - Sheet 7](images/manuals/extruder/QiTech_Pro_Extruder_Electrical_Diagram_2025_7-007.png)
![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)
@@ -687,18 +687,3 @@ The following persons may not work on the machine:
| K Cleaning, maintenance | |
| L Maintenance | Trained and instructed specialized personnel of the operator or service technicians of the manufacturer |
| M Decommissioning | Trained and instructed specialized personnel of the operator |
<br>
<br>
## Electrical Diagrams
![QiTech Pro Winder Electrical Diagram - Sheet 1](images/manuals/winder/QiTech_Pro_Winder_Electrical_Diagram-001.png)
![QiTech Pro Winder Electrical Diagram - Sheet 2](images/manuals/winder/QiTech_Pro_Winder_Electrical_Diagram_2-002.png)
![QiTech Pro Winder Electrical Diagram - Sheet 3](images/manuals/winder/QiTech_Pro_Winder_Electrical_Diagram_3-003.png)
![QiTech Pro Winder Electrical Diagram - Sheet 4](images/manuals/winder/QiTech_Pro_Winder_Electrical_Diagram_4-004.png)
![QiTech Pro Winder Electrical Diagram - Sheet 5](images/manuals/winder/QiTech_Pro_Winder_Electrical_Diagram_5-005.png)
-7
View File
@@ -31,8 +31,6 @@ export function getUnitIcon(unit: Unit): IconName {
return "lu:Zap";
case "A":
return "lu:Zap";
case "kWh":
return "lu:BatteryFull";
default:
return "lu:ChartNoAxesColumn";
}
@@ -66,8 +64,6 @@ export function renderUnitSymbol(unit: Unit | undefined): string {
return "V";
case "A":
return "A";
case "kWh":
return "kWh";
default:
return "";
}
@@ -123,8 +119,6 @@ export function renderUnitSymbolLong(unit: Unit): string {
return "volts";
case "A":
return "amperes";
case "kWh":
return "kilowatt-hours";
default:
return "";
}
@@ -144,7 +138,6 @@ export const units = [
"W",
"V",
"A",
"kWh",
] as const;
export type Unit = (typeof units)[number];
@@ -145,7 +145,7 @@ async function deleteNixOSGeneration(generationId: string): Promise<{
const process = spawn("sudo", [
"sh",
"-c",
`nix-env --delete-generations ${generationId} -p /nix/var/nix/profiles/system && nix store gc && /nix/var/nix/profiles/system/bin/switch-to-configuration boot`,
`nix-env --delete-generations ${generationId} -p /nix/var/nix/profiles/system && /nix/var/nix/profiles/system/bin/switch-to-configuration boot`,
]);
let stderr = "";
@@ -50,11 +50,12 @@ export function useBuffer1() {
// Single optimistic state for all state management
const stateOptimistic = useStateOptimistic<StateEvent>();
// Update optimistic state when real state changes
useEffect(() => {
if (state) {
stateOptimistic.setReal(state);
}
}, [state]);
}, [state, stateOptimistic]);
// Helper function for optimistic updates using produce
const updateStateOptimistically = (
@@ -62,7 +63,7 @@ export function useBuffer1() {
serverRequest: () => void,
) => {
const currentState = stateOptimistic.value;
if (currentState && !stateOptimistic.isOptimistic) {
if (currentState) {
stateOptimistic.setOptimistic(produce(currentState, producer));
}
serverRequest();
@@ -30,8 +30,6 @@ export function Extruder2ControlPage() {
motorScrewRpm,
motorPower,
motorCurrent,
totalEnergyKWh,
combinedPower,
setExtruderMode,
@@ -142,6 +140,7 @@ export function Extruder2ControlPage() {
onChange={setInverterRegulation}
disabled={isDisabled}
loading={isLoading}
reverse
/>
</Label>
<div className="flex flex-row flex-wrap gap-4">
@@ -226,30 +225,12 @@ export function Extruder2ControlPage() {
/>
</ControlCard>
<ControlCard className="bg-blue" title="Power Consumption">
<ControlCard className="bg-blue" title="Total Power Consumption">
<TimeSeriesValueNumeric
label="Total Power"
label=""
unit="W"
renderValue={(value) => roundToDecimals(value, 0)}
timeseries={combinedPower}
/>
<TimeSeriesValueNumeric
label="Motor Power"
unit="W"
renderValue={(value) => roundToDecimals(value, 0)}
timeseries={motorPower}
/>
<TimeSeriesValueNumeric
label="Motor Current"
unit="A"
renderValue={(value) => roundToDecimals(value, 1)}
timeseries={motorCurrent}
/>
<TimeSeriesValueNumeric
label="Total Energy Consumption"
unit="kWh"
renderValue={(value) => roundToDecimals(value, 3)}
timeseries={totalEnergyKWh}
timeseries={combinedPower}
/>
</ControlCard>
</ControlGrid>
@@ -43,8 +43,6 @@ export const liveValuesEventDataSchema = z.object({
front_power: z.number(),
back_power: z.number(),
middle_power: z.number(),
combined_power: z.number(),
total_energy_kwh: z.number(),
});
/**
@@ -197,9 +195,8 @@ export type Extruder2NamespaceStore = {
middlePower: TimeSeries;
backPower: TimeSeries;
// Combined power consumption and energy
// Combined power consumption
combinedPower: TimeSeries;
totalEnergyKWh: TimeSeries;
};
// Constants for time durations
@@ -208,6 +205,13 @@ const ONE_SECOND = 1000;
const FIVE_SECOND = 5 * ONE_SECOND;
const ONE_HOUR = 60 * 60 * ONE_SECOND;
const { initialTimeSeries: screwRpm, insert: addScrewRpm } = createTimeSeries(
TWENTY_MILLISECOND,
ONE_SECOND,
FIVE_SECOND,
ONE_HOUR,
);
const { initialTimeSeries: pressure, insert: addPressure } = createTimeSeries(
TWENTY_MILLISECOND,
ONE_SECOND,
@@ -246,9 +250,6 @@ const { initialTimeSeries: backPower, insert: addBackPower } = createTimeSeries(
const { initialTimeSeries: combinedPower, insert: addCombinedPower } =
createTimeSeries(TWENTY_MILLISECOND, ONE_SECOND, FIVE_SECOND, ONE_HOUR);
const { initialTimeSeries: totalEnergyKWh, insert: addTotalEnergyKWh } =
createTimeSeries(TWENTY_MILLISECOND, ONE_SECOND, FIVE_SECOND, ONE_HOUR);
const { initialTimeSeries: motorCurrent, insert: addMotorCurrent } =
createTimeSeries(TWENTY_MILLISECOND, ONE_SECOND, FIVE_SECOND, ONE_HOUR);
@@ -345,11 +346,12 @@ export function extruder2MessageHandler(
timestamp,
}),
combinedPower: addCombinedPower(state.combinedPower, {
value: liveValuesEvent.data.combined_power,
timestamp,
}),
totalEnergyKWh: addTotalEnergyKWh(state.totalEnergyKWh, {
value: liveValuesEvent.data.total_energy_kwh,
value:
liveValuesEvent.data.motor_status.power +
liveValuesEvent.data.nozzle_power +
liveValuesEvent.data.front_power +
liveValuesEvent.data.middle_power +
liveValuesEvent.data.back_power,
timestamp,
}),
}));
@@ -385,7 +387,6 @@ export const createExtruder2NamespaceStore =
backPower,
middlePower,
combinedPower,
totalEnergyKWh,
};
});
@@ -56,17 +56,17 @@ export function useExtruder2() {
middlePower,
backPower,
combinedPower,
totalEnergyKWh,
} = useExtruder2Namespace(machineIdentification);
// Single optimistic state for all state management
const stateOptimistic = useStateOptimistic<StateEvent>();
// Update optimistic state when real state changes
useEffect(() => {
if (state) {
stateOptimistic.setReal(state);
}
}, [state]);
}, [state, stateOptimistic]);
// Helper function for optimistic updates using produce
const updateStateOptimistically = (
@@ -74,7 +74,7 @@ export function useExtruder2() {
serverRequest: () => void,
) => {
const currentState = stateOptimistic.value;
if (currentState && !stateOptimistic.isOptimistic) {
if (currentState) {
stateOptimistic.setOptimistic(produce(currentState, producer));
}
serverRequest();
@@ -360,6 +360,9 @@ export function useExtruder2() {
z.object({ ResetInverter: z.boolean() }),
);
const isLoading = stateOptimistic.isOptimistic;
const isDisabled = !stateOptimistic.isInitialized;
return {
// Consolidated state
state: stateOptimistic.value?.data,
@@ -383,11 +386,10 @@ export function useExtruder2() {
middlePower,
backPower,
combinedPower,
totalEnergyKWh,
// Loading states
isLoading: stateOptimistic.isOptimistic,
isDisabled: !stateOptimistic.isInitialized,
isLoading,
isDisabled,
// Action functions (verb-first)
setInverterRotationDirection,
@@ -18,6 +18,7 @@ function useLaser(machine_identification_unique: MachineIdentificationUnique) {
// Single optimistic state for all state management
const stateOptimistic = useStateOptimistic<StateEvent>();
// Update optimistic state when real state changes
useEffect(() => {
if (state) {
stateOptimistic.setReal(state);
@@ -30,7 +31,7 @@ function useLaser(machine_identification_unique: MachineIdentificationUnique) {
serverRequest: () => void,
) => {
const currentState = stateOptimistic.value;
if (currentState && !stateOptimistic.isOptimistic) {
if (currentState) {
stateOptimistic.setOptimistic(produce(currentState, producer));
}
serverRequest();
+2 -1
View File
@@ -17,6 +17,7 @@ function useMock(machine_identification_unique: MachineIdentificationUnique) {
// Single optimistic state for all state management
const stateOptimistic = useStateOptimistic<StateEvent>();
// Update optimistic state when real state changes
useEffect(() => {
if (state) {
stateOptimistic.setReal(state);
@@ -29,7 +30,7 @@ function useMock(machine_identification_unique: MachineIdentificationUnique) {
serverRequest: () => Promise<any>,
) => {
const currentState = stateOptimistic.value;
if (currentState && !stateOptimistic.isOptimistic) {
if (currentState) {
stateOptimistic.setOptimistic(produce(currentState, producer));
}
serverRequest()
@@ -36,6 +36,7 @@ export function Winder2ControlPage() {
tensionArmAngle,
zeroTensionArmAngle,
spoolRpm,
spoolDiameter,
setMode,
pullerSpeed,
spoolProgress,
@@ -79,6 +80,12 @@ export function Winder2ControlPage() {
timeseries={spoolRpm}
renderValue={(value) => roundToDecimals(value, 0)}
/>
<TimeSeriesValueNumeric
label="Estimated Diameter"
unit="cm"
timeseries={spoolDiameter}
renderValue={(value) => roundToDecimals(value, 1)}
/>
</ControlCard>
<ControlCard className="bg-red" height={2} title="Traverse">
@@ -159,7 +166,7 @@ export function Winder2ControlPage() {
<Label label="Laserpointer">
<SelectionGroupBoolean
value={state?.traverse_state.laserpointer}
disabled={isLoading || isDisabled}
disabled={isDisabled}
loading={isLoading}
optionFalse={{ children: "Off", icon: "lu:LightbulbOff" }}
optionTrue={{ children: "On", icon: "lu:Lightbulb" }}
@@ -16,6 +16,7 @@ export function Winder2GraphsPage() {
const {
state,
spoolRpm,
spoolDiameter,
traversePosition,
tensionArmAngle,
pullerSpeed,
@@ -59,6 +60,13 @@ export function Winder2GraphsPage() {
renderValue={(value) => roundToDecimals(value, 1)}
/>
<SpoolDiameterGraph
syncHook={syncHook}
newData={spoolDiameter}
unit="cm"
renderValue={(value) => roundToDecimals(value, 1)}
/>
<SpoolProgressGraph
syncHook={syncHook}
newData={spoolProgress}
@@ -288,3 +296,37 @@ export function PullerSpeedGraph({
/>
);
}
export function SpoolDiameterGraph({
syncHook,
newData,
unit,
renderValue,
}: {
syncHook: ReturnType<typeof useGraphSync>;
newData: TimeSeries | null;
unit?: Unit;
renderValue?: (value: number) => string;
}) {
const config: GraphConfig = {
title: "Estimated Spool Diameter",
icon: "lu:Circle",
colors: {
primary: "#ec4899",
grid: "#e2e8f0",
background: "#ffffff",
},
exportFilename: "spool_diameter_data",
};
return (
<AutoSyncedBigGraph
syncHook={syncHook}
newData={{ newData }}
unit={unit}
renderValue={renderValue}
config={config}
graphId="spool-diameter"
/>
);
}
@@ -61,6 +61,7 @@ export function useWinder2() {
traversePosition,
pullerSpeed,
spoolRpm,
spoolDiameter,
tensionArmAngle,
spoolProgress,
} = useWinder2Namespace(machineIdentification);
@@ -171,19 +172,21 @@ export function useWinder2() {
}),
);
// Helper function for optimistic updates using produce
const updateStateOptimistically = (
const updateStateOptimistically = <T>(
producer: (current: StateEvent) => void,
serverRequest: () => void,
shouldSkip?: (current: StateEvent) => boolean,
) => {
const currentState = stateOptimistic.value;
if (currentState && !stateOptimistic.isOptimistic) {
stateOptimistic.setOptimistic(produce(currentState, producer));
}
if (!currentState) return;
// Skip if nothing changes
if (shouldSkip?.(currentState)) return;
stateOptimistic.setOptimistic(produce(currentState, producer));
serverRequest();
};
// Action functions
// Action functions with value checks to skip no-op updates
const zeroTensionArmAngle = () => {
updateStateOptimistically(
(current) => {
@@ -194,6 +197,7 @@ export function useWinder2() {
machine_identification_unique: machineIdentification,
data: "ZeroTensionArmAngle",
}),
(current) => current.data.tension_arm_state.zeroed === true,
);
};
@@ -207,6 +211,7 @@ export function useWinder2() {
machine_identification_unique: machineIdentification,
data: { SetTraverseLimitInner: limitInner },
}),
(current) => current.data.traverse_state.limit_inner === limitInner,
);
};
@@ -220,6 +225,7 @@ export function useWinder2() {
machine_identification_unique: machineIdentification,
data: { SetTraverseLimitOuter: limitOuter },
}),
(current) => current.data.traverse_state.limit_outer === limitOuter,
);
};
@@ -233,6 +239,7 @@ export function useWinder2() {
machine_identification_unique: machineIdentification,
data: "GotoTraverseLimitInner",
}),
(current) => current.data.traverse_state.is_going_in === true,
);
};
@@ -246,6 +253,7 @@ export function useWinder2() {
machine_identification_unique: machineIdentification,
data: "GotoTraverseLimitOuter",
}),
(current) => current.data.traverse_state.is_going_out === true,
);
};
@@ -259,6 +267,7 @@ export function useWinder2() {
machine_identification_unique: machineIdentification,
data: "GotoTraverseHome",
}),
(current) => current.data.traverse_state.is_going_home === true,
);
};
@@ -272,6 +281,7 @@ export function useWinder2() {
machine_identification_unique: machineIdentification,
data: { EnableTraverseLaserpointer: enabled },
}),
(current) => current.data.traverse_state.laserpointer === enabled,
);
};
@@ -285,6 +295,7 @@ export function useWinder2() {
machine_identification_unique: machineIdentification,
data: { SetMode: mode },
}),
(current) => current.data.mode_state.mode === mode,
);
};
@@ -298,6 +309,7 @@ export function useWinder2() {
machine_identification_unique: machineIdentification,
data: { SetTraverseStepSize: stepSize },
}),
(current) => current.data.traverse_state.step_size === stepSize,
);
};
@@ -311,6 +323,7 @@ export function useWinder2() {
machine_identification_unique: machineIdentification,
data: { SetTraversePadding: padding },
}),
(current) => current.data.traverse_state.padding === padding,
);
};
@@ -324,6 +337,7 @@ export function useWinder2() {
machine_identification_unique: machineIdentification,
data: { SetPullerTargetSpeed: targetSpeed },
}),
(current) => current.data.puller_state.target_speed === targetSpeed,
);
};
@@ -337,6 +351,7 @@ export function useWinder2() {
machine_identification_unique: machineIdentification,
data: { SetPullerTargetDiameter: targetDiameter },
}),
(current) => current.data.puller_state.target_diameter === targetDiameter,
);
};
@@ -350,6 +365,7 @@ export function useWinder2() {
machine_identification_unique: machineIdentification,
data: { SetPullerRegulationMode: regulationMode },
}),
(current) => current.data.puller_state.regulation === regulationMode,
);
};
@@ -363,6 +379,7 @@ export function useWinder2() {
machine_identification_unique: machineIdentification,
data: { SetPullerForward: forward },
}),
(current) => current.data.puller_state.forward === forward,
);
};
@@ -377,6 +394,9 @@ export function useWinder2() {
machine_identification_unique: machineIdentification,
data: { SetSpoolAutomaticRequiredMeters: meters },
}),
(current) =>
current.data.spool_automatic_action_state.spool_required_meters ===
meters,
);
};
@@ -391,6 +411,9 @@ export function useWinder2() {
machine_identification_unique: machineIdentification,
data: { SetSpoolAutomaticAction: mode },
}),
(current) =>
current.data.spool_automatic_action_state
.spool_automatic_action_mode === mode,
);
};
@@ -411,6 +434,8 @@ export function useWinder2() {
machine_identification_unique: machineIdentification,
data: { SetSpoolRegulationMode: mode },
}),
(current) =>
current.data.spool_speed_controller_state.regulation_mode === mode,
);
};
@@ -424,6 +449,8 @@ export function useWinder2() {
machine_identification_unique: machineIdentification,
data: { SetSpoolMinMaxMinSpeed: speed },
}),
(current) =>
current.data.spool_speed_controller_state.minmax_min_speed === speed,
);
};
@@ -437,6 +464,8 @@ export function useWinder2() {
machine_identification_unique: machineIdentification,
data: { SetSpoolMinMaxMaxSpeed: speed },
}),
(current) =>
current.data.spool_speed_controller_state.minmax_max_speed === speed,
);
};
@@ -451,6 +480,9 @@ export function useWinder2() {
machine_identification_unique: machineIdentification,
data: { SetSpoolAdaptiveTensionTarget: value },
}),
(current) =>
current.data.spool_speed_controller_state.adaptive_tension_target ===
value,
);
};
@@ -465,6 +497,9 @@ export function useWinder2() {
machine_identification_unique: machineIdentification,
data: { SetSpoolAdaptiveRadiusLearningRate: value },
}),
(current) =>
current.data.spool_speed_controller_state
.adaptive_radius_learning_rate === value,
);
};
@@ -479,6 +514,9 @@ export function useWinder2() {
machine_identification_unique: machineIdentification,
data: { SetSpoolAdaptiveMaxSpeedMultiplier: value },
}),
(current) =>
current.data.spool_speed_controller_state
.adaptive_max_speed_multiplier === value,
);
};
@@ -493,6 +531,9 @@ export function useWinder2() {
machine_identification_unique: machineIdentification,
data: { SetSpoolAdaptiveAccelerationFactor: value },
}),
(current) =>
current.data.spool_speed_controller_state
.adaptive_acceleration_factor === value,
);
};
@@ -507,14 +548,14 @@ export function useWinder2() {
machine_identification_unique: machineIdentification,
data: { SetSpoolAdaptiveDeaccelerationUrgencyMultiplier: value },
}),
(current) =>
current.data.spool_speed_controller_state
.adaptive_deacceleration_urgency_multiplier === value,
);
};
const setConnectedMachine = (machineIdentificationUnique: {
machine_identification: {
vendor: number;
machine: number;
};
machine_identification: { vendor: number; machine: number };
serial: number;
}) => {
updateStateOptimistically(
@@ -527,14 +568,14 @@ export function useWinder2() {
machine_identification_unique,
data: { SetConnectedMachine: machineIdentificationUnique },
}),
(current) =>
current.data.connected_machine_state.machine_identification_unique ===
machineIdentificationUnique,
);
};
const disconnectMachine = (machineIdentificationUnique: {
machine_identification: {
vendor: number;
machine: number;
};
machine_identification: { vendor: number; machine: number };
serial: number;
}) => {
updateStateOptimistically(
@@ -547,6 +588,9 @@ export function useWinder2() {
machine_identification_unique,
data: { DisconnectMachine: machineIdentificationUnique },
}),
(current) =>
current.data.connected_machine_state.machine_identification_unique ===
null,
);
};
@@ -596,6 +640,7 @@ export function useWinder2() {
traversePosition,
pullerSpeed,
spoolRpm,
spoolDiameter,
tensionArmAngle,
spoolProgress,
@@ -22,6 +22,7 @@ import {
TimeSeries,
TimeSeriesValue,
} from "@/lib/timeseries";
import { connected } from "process";
// ========== Event Schema Definitions ==========
@@ -32,6 +33,7 @@ export const liveValuesEventDataSchema = z.object({
traverse_position: z.number().nullable(),
puller_speed: z.number(),
spool_rpm: z.number(),
spool_diameter: z.number(),
tension_arm_angle: z.number(),
spool_progress: z.number(),
});
@@ -188,6 +190,7 @@ export type Winder2NamespaceStore = {
traversePosition: TimeSeries;
pullerSpeed: TimeSeries;
spoolRpm: TimeSeries;
spoolDiameter: TimeSeries;
tensionArmAngle: TimeSeries;
spoolProgress: TimeSeries;
};
@@ -210,6 +213,8 @@ const { initialTimeSeries: spoolRpm, insert: addSpoolRpm } = createTimeSeries(
FIVE_SECOND,
ONE_HOUR,
);
const { initialTimeSeries: spoolDiameter, insert: addSpoolDiameter } =
createTimeSeries(TWENTY_MILLISECOND, ONE_SECOND, FIVE_SECOND, ONE_HOUR);
const { initialTimeSeries: tensionArmAngle, insert: addTensionArmAngle } =
createTimeSeries(TWENTY_MILLISECOND, ONE_SECOND, FIVE_SECOND, ONE_HOUR);
@@ -229,6 +234,7 @@ export const createWinder2NamespaceStore =
traversePosition,
pullerSpeed,
spoolRpm,
spoolDiameter,
tensionArmAngle,
spoolProgress,
};
@@ -279,6 +285,7 @@ export function winder2MessageHandler(
traverse_position,
puller_speed,
spool_rpm,
spool_diameter,
tension_arm_angle,
spool_progress,
} = liveValuesEvent.data;
@@ -326,6 +333,16 @@ export function winder2MessageHandler(
};
newState.spoolRpm = addSpoolRpm(state.spoolRpm, spoolRpmValue);
// Add spool diameter
const spoolDiameterValue: TimeSeriesValue = {
value: spool_diameter,
timestamp,
};
newState.spoolDiameter = addSpoolDiameter(
state.spoolDiameter,
spoolDiameterValue,
);
// Add tension arm angle
const tensionArmAngleValue: TimeSeriesValue = {
value: tension_arm_angle,
-3
View File
@@ -3,9 +3,6 @@ name = "ethercat-eeprom-dump"
version = "0.1.0"
edition = "2024"
[lints]
workspace = true
[dependencies]
clap = { version = "4.5.40", features = ["derive"] }
ethercrab = "0.6.0"
+2 -2
View File
@@ -34,7 +34,7 @@ pub fn ls(
let driver = device_from_subdevice_identity(&identity);
let identification_adresses = get_identification_addresses(&identity, device.name());
let identification = match identification_adresses {
Ok(_) => block_on(machine_device_identification(&device, maindevice)),
Ok(_) => block_on(machine_device_identification(&device, &maindevice)),
Err(_) => Err(anyhow!("")),
};
@@ -122,7 +122,7 @@ pub fn ls(
print_markdown_table(&table, true);
// Put group into op state
match block_on(group.into_op(maindevice)) {
match block_on(group.into_op(&maindevice)) {
Ok(_) => println!("Successfully put group into OP state"),
Err(e) => println!("Failed to put group into OP state: {}", e),
}
+6 -4
View File
@@ -1,7 +1,7 @@
use std::sync::Arc;
use dump::dump_eeprom;
use ethercrab::{MainDevice, MainDeviceConfig, PduStorage, std::ethercat_now};
use ethercrab::{MainDevice, MainDeviceConfig, PduStorage, Timeouts, std::ethercat_now};
use futures::executor::block_on;
use read::read_eeprom;
use restore::restore_eeoprom;
@@ -36,19 +36,21 @@ async fn main() {
// Setup Maindevice
let maindevice = Arc::new(MainDevice::new(
pdu_loop,
Default::default(),
Timeouts {
..Default::default()
},
MainDeviceConfig::default(),
));
// Setup TX/RX task
#[cfg(not(target_os = "windows"))]
tokio::spawn(ethercrab::std::tx_rx_task(interface, tx, rx).expect("spawn TX/RX task"));
tokio::spawn(ethercrab::std::tx_rx_task(&interface, tx, rx).expect("spawn TX/RX task"));
// Init ethercat
let group = maindevice
.init_single_group::<MAX_SUBDEVICES, PDI_LEN>(ethercat_now)
.await
.unwrap_or_else(|_| panic!("{}", "Failed to initalize group".to_string()));
.expect(format!("Failed to initalize group").as_str());
match matches.subcommand() {
Some(("ls", _)) => ls::ls(group, &maindevice),
+2 -8
View File
@@ -52,7 +52,7 @@ pub fn print_markdown_table(table: &Vec<Vec<String>>, header_line: bool) {
if row_idx == 0 && header_line && table.len() > 1 {
print!("|");
for (i, width) in col_widths.iter().enumerate() {
print!(":{}", "-".repeat(*width + 1));
print!("{}:{}", "", "-".repeat(*width + 1));
if i < max_cols - 1 {
print!("|");
}
@@ -107,13 +107,7 @@ pub fn print_buffer(buffer: &[u8]) {
// Add ASCII representation
let ascii: String = chunk
.iter()
.map(|&b| {
if (32..=126).contains(&b) {
b as char
} else {
'.'
}
})
.map(|&b| if b >= 32 && b <= 126 { b as char } else { '.' })
.collect();
row.push(ascii);
+6 -6
View File
@@ -139,7 +139,7 @@ fn extract_string_sections(bytes: &[u8]) -> Vec<String> {
let mut in_string = false;
for &byte in bytes {
if (32..127).contains(&byte) {
if byte >= 32 && byte < 127 {
// Printable ASCII character
current_string.push(byte);
in_string = true;
@@ -184,7 +184,7 @@ fn extract_string_sections(bytes: &[u8]) -> Vec<String> {
if s.len() > 30 {
// Try to split at common separators
let parts: Vec<&str> = s
.split(['!', '(', ')', '.', ','])
.split(|c| c == '!' || c == '(' || c == ')' || c == '.' || c == ',')
.map(|p| p.trim())
.filter(|p| !p.is_empty() && p.len() >= MIN_STRING_LENGTH)
.collect();
@@ -215,7 +215,7 @@ impl std::fmt::Display for EepromData {
let device_name = if let Some(repo) = &self.extended_information.string_repository {
repo.strings
.iter()
.find(|s| s.contains("EL") && s.chars().any(|c| c.is_ascii_digit()))
.find(|s| s.contains("EL") && s.chars().any(|c| c.is_digit(10)))
.cloned()
.unwrap_or_else(|| "Unknown Device".to_string())
} else {
@@ -732,11 +732,11 @@ impl From<&[u8]> for EepromData {
};
// Construct the final EEPROM data object with bounds checking
Self {
EepromData {
device_machine_identification: device_info,
identity,
esc_configuration: EscConfiguratoin {
pdi_control: if !words.is_empty() { words[0x0] } else { 0 },
pdi_control: if words.len() > 0x0 { words[0x0] } else { 0 },
pdi_configuration: if words.len() > 0x1 { words[0x1] } else { 0 },
sync_impulse_length: if words.len() > 0x2 { words[0x2] } else { 0 },
pdi_configuration2: if words.len() > 0x3 { words[0x3] } else { 0 },
@@ -788,7 +788,7 @@ pub async fn read_eeprom(file: &str) -> Result<(), Box<dyn std::error::Error>> {
}
/// turn &[u16, u16] little endian into u32
pub const fn u32_from_u16(start_word: u16, end_word: u16) -> u32 {
pub fn u32_from_u16(start_word: u16, end_word: u16) -> u32 {
let start_bytes = start_word.to_le_bytes();
let end_bytes = end_word.to_le_bytes();
u32::from_le_bytes([start_bytes[0], start_bytes[1], end_bytes[0], end_bytes[1]])
-3
View File
@@ -3,9 +3,6 @@ name = "ethercat_hal_derive"
version = "0.1.0"
edition = "2024"
[lints]
workspace = true
[lib]
proc-macro = true
-3
View File
@@ -3,9 +3,6 @@ name = "ethercat_hal"
version = "0.1.0"
edition = "2024"
[lints]
workspace = true
[dependencies]
anyhow = "1.0.99"
ethercrab = "0.6"
+2 -2
View File
@@ -40,9 +40,9 @@ where
/// }
/// }
/// ```
fn write_config(
fn write_config<'maindevice>(
&mut self,
device: &EthercrabSubDevicePreoperational<'_>,
device: &EthercrabSubDevicePreoperational<'maindevice>,
config: &C,
) -> impl Future<Output = Result<(), anyhow::Error>> + Send;
+5 -5
View File
@@ -52,10 +52,10 @@ pub enum EL1002Port {
}
impl EL1002Port {
pub const fn to_bit_index(&self) -> usize {
pub fn to_bit_index(&self) -> usize {
match self {
Self::DI1 => 0,
Self::DI2 => 1,
EL1002Port::DI1 => 0,
EL1002Port::DI2 => 1,
}
}
}
@@ -85,14 +85,14 @@ pub enum EL1002PredefinedPdoAssignment {
impl PredefinedPdoAssignment<EL1002TxPdo, ()> for EL1002PredefinedPdoAssignment {
fn txpdo_assignment(&self) -> EL1002TxPdo {
match self {
Self::All => EL1002TxPdo {
EL1002PredefinedPdoAssignment::All => EL1002TxPdo {
channel1: Some(BoolPdoObject::default()),
channel2: Some(BoolPdoObject::default()),
},
}
}
fn rxpdo_assignment(&self) {
fn rxpdo_assignment(&self) -> () {
unreachable!()
}
}
+11 -11
View File
@@ -65,16 +65,16 @@ pub enum EL1008Port {
}
impl EL1008Port {
pub const fn to_bit_index(&self) -> usize {
pub fn to_bit_index(&self) -> usize {
match self {
Self::DI1 => 0,
Self::DI2 => 1,
Self::DI3 => 2,
Self::DI4 => 3,
Self::DI5 => 4,
Self::DI6 => 5,
Self::DI7 => 6,
Self::DI8 => 7,
EL1008Port::DI1 => 0,
EL1008Port::DI2 => 1,
EL1008Port::DI3 => 2,
EL1008Port::DI4 => 3,
EL1008Port::DI5 => 4,
EL1008Port::DI6 => 5,
EL1008Port::DI7 => 6,
EL1008Port::DI8 => 7,
}
}
}
@@ -122,7 +122,7 @@ pub enum EL1008PredefinedPdoAssignment {
impl PredefinedPdoAssignment<EL1008TxPdo, ()> for EL1008PredefinedPdoAssignment {
fn txpdo_assignment(&self) -> EL1008TxPdo {
match self {
Self::All => EL1008TxPdo {
EL1008PredefinedPdoAssignment::All => EL1008TxPdo {
channel1: Some(BoolPdoObject::default()),
channel2: Some(BoolPdoObject::default()),
channel3: Some(BoolPdoObject::default()),
@@ -135,7 +135,7 @@ impl PredefinedPdoAssignment<EL1008TxPdo, ()> for EL1008PredefinedPdoAssignment
}
}
fn rxpdo_assignment(&self) {
fn rxpdo_assignment(&self) -> () {
unreachable!()
}
}
+4 -4
View File
@@ -35,10 +35,10 @@ impl DigitalOutputDevice<EL2002Port> for EL2002 {
let expect_text = "All channels should be Some(_)";
match port {
EL2002Port::DO1 => {
self.rxpdo.channel1.as_mut().expect(expect_text).value = value.into()
self.rxpdo.channel1.as_mut().expect(&expect_text).value = value.into()
}
EL2002Port::DO2 => {
self.rxpdo.channel2.as_mut().expect(expect_text).value = value.into()
self.rxpdo.channel2.as_mut().expect(&expect_text).value = value.into()
}
}
}
@@ -46,8 +46,8 @@ impl DigitalOutputDevice<EL2002Port> for EL2002 {
fn get_output(&self, port: EL2002Port) -> DigitalOutputOutput {
let expect_text = "All channels should be Some(_)";
DigitalOutputOutput(match port {
EL2002Port::DO1 => self.rxpdo.channel1.as_ref().expect(expect_text).value,
EL2002Port::DO2 => self.rxpdo.channel2.as_ref().expect(expect_text).value,
EL2002Port::DO1 => self.rxpdo.channel1.as_ref().expect(&expect_text).value,
EL2002Port::DO2 => self.rxpdo.channel2.as_ref().expect(&expect_text).value,
})
}
}
+8 -8
View File
@@ -33,16 +33,16 @@ impl DigitalOutputDevice<EL2004Port> for EL2004 {
let expect_text = "All channels should be Some(_)";
match port {
EL2004Port::DO1 => {
self.rxpdo.channel1.as_mut().expect(expect_text).value = value.into()
self.rxpdo.channel1.as_mut().expect(&expect_text).value = value.into()
}
EL2004Port::DO2 => {
self.rxpdo.channel2.as_mut().expect(expect_text).value = value.into()
self.rxpdo.channel2.as_mut().expect(&expect_text).value = value.into()
}
EL2004Port::DO3 => {
self.rxpdo.channel3.as_mut().expect(expect_text).value = value.into()
self.rxpdo.channel3.as_mut().expect(&expect_text).value = value.into()
}
EL2004Port::DO4 => {
self.rxpdo.channel4.as_mut().expect(expect_text).value = value.into()
self.rxpdo.channel4.as_mut().expect(&expect_text).value = value.into()
}
}
}
@@ -50,10 +50,10 @@ impl DigitalOutputDevice<EL2004Port> for EL2004 {
fn get_output(&self, port: EL2004Port) -> DigitalOutputOutput {
let expect_text = "All channels should be Some(_)";
DigitalOutputOutput(match port {
EL2004Port::DO1 => self.rxpdo.channel1.as_ref().expect(expect_text).value,
EL2004Port::DO2 => self.rxpdo.channel2.as_ref().expect(expect_text).value,
EL2004Port::DO3 => self.rxpdo.channel3.as_ref().expect(expect_text).value,
EL2004Port::DO4 => self.rxpdo.channel4.as_ref().expect(expect_text).value,
EL2004Port::DO1 => self.rxpdo.channel1.as_ref().expect(&expect_text).value,
EL2004Port::DO2 => self.rxpdo.channel2.as_ref().expect(&expect_text).value,
EL2004Port::DO3 => self.rxpdo.channel3.as_ref().expect(&expect_text).value,
EL2004Port::DO4 => self.rxpdo.channel4.as_ref().expect(&expect_text).value,
})
}
}
+16 -16
View File
@@ -35,28 +35,28 @@ impl DigitalOutputDevice<EL2008Port> for EL2008 {
let expect_text = "All channels should be Some(_)";
match port {
EL2008Port::DO1 => {
self.rxpdo.channel1.as_mut().expect(expect_text).value = value.into()
self.rxpdo.channel1.as_mut().expect(&expect_text).value = value.into()
}
EL2008Port::DO2 => {
self.rxpdo.channel2.as_mut().expect(expect_text).value = value.into()
self.rxpdo.channel2.as_mut().expect(&expect_text).value = value.into()
}
EL2008Port::DO3 => {
self.rxpdo.channel3.as_mut().expect(expect_text).value = value.into()
self.rxpdo.channel3.as_mut().expect(&expect_text).value = value.into()
}
EL2008Port::DO4 => {
self.rxpdo.channel4.as_mut().expect(expect_text).value = value.into()
self.rxpdo.channel4.as_mut().expect(&expect_text).value = value.into()
}
EL2008Port::DO5 => {
self.rxpdo.channel5.as_mut().expect(expect_text).value = value.into()
self.rxpdo.channel5.as_mut().expect(&expect_text).value = value.into()
}
EL2008Port::DO6 => {
self.rxpdo.channel6.as_mut().expect(expect_text).value = value.into()
self.rxpdo.channel6.as_mut().expect(&expect_text).value = value.into()
}
EL2008Port::DO7 => {
self.rxpdo.channel7.as_mut().expect(expect_text).value = value.into()
self.rxpdo.channel7.as_mut().expect(&expect_text).value = value.into()
}
EL2008Port::DO8 => {
self.rxpdo.channel8.as_mut().expect(expect_text).value = value.into()
self.rxpdo.channel8.as_mut().expect(&expect_text).value = value.into()
}
}
}
@@ -64,14 +64,14 @@ impl DigitalOutputDevice<EL2008Port> for EL2008 {
fn get_output(&self, port: EL2008Port) -> DigitalOutputOutput {
let expect_text = "All channels should be Some(_)";
DigitalOutputOutput(match port {
EL2008Port::DO1 => self.rxpdo.channel1.as_ref().expect(expect_text).value,
EL2008Port::DO2 => self.rxpdo.channel2.as_ref().expect(expect_text).value,
EL2008Port::DO3 => self.rxpdo.channel3.as_ref().expect(expect_text).value,
EL2008Port::DO4 => self.rxpdo.channel4.as_ref().expect(expect_text).value,
EL2008Port::DO5 => self.rxpdo.channel5.as_ref().expect(expect_text).value,
EL2008Port::DO6 => self.rxpdo.channel6.as_ref().expect(expect_text).value,
EL2008Port::DO7 => self.rxpdo.channel7.as_ref().expect(expect_text).value,
EL2008Port::DO8 => self.rxpdo.channel8.as_ref().expect(expect_text).value,
EL2008Port::DO1 => self.rxpdo.channel1.as_ref().expect(&expect_text).value,
EL2008Port::DO2 => self.rxpdo.channel2.as_ref().expect(&expect_text).value,
EL2008Port::DO3 => self.rxpdo.channel3.as_ref().expect(&expect_text).value,
EL2008Port::DO4 => self.rxpdo.channel4.as_ref().expect(&expect_text).value,
EL2008Port::DO5 => self.rxpdo.channel5.as_ref().expect(&expect_text).value,
EL2008Port::DO6 => self.rxpdo.channel6.as_ref().expect(&expect_text).value,
EL2008Port::DO7 => self.rxpdo.channel7.as_ref().expect(&expect_text).value,
EL2008Port::DO8 => self.rxpdo.channel8.as_ref().expect(&expect_text).value,
})
}
}
+8 -8
View File
@@ -36,16 +36,16 @@ impl DigitalOutputDevice<EL2024Port> for EL2024 {
let expect_text = "All channels should be Some(_)";
match port {
EL2024Port::DO1 => {
self.rxpdo.channel1.as_mut().expect(expect_text).value = value.into()
self.rxpdo.channel1.as_mut().expect(&expect_text).value = value.into()
}
EL2024Port::DO2 => {
self.rxpdo.channel2.as_mut().expect(expect_text).value = value.into()
self.rxpdo.channel2.as_mut().expect(&expect_text).value = value.into()
}
EL2024Port::DO3 => {
self.rxpdo.channel3.as_mut().expect(expect_text).value = value.into()
self.rxpdo.channel3.as_mut().expect(&expect_text).value = value.into()
}
EL2024Port::DO4 => {
self.rxpdo.channel4.as_mut().expect(expect_text).value = value.into()
self.rxpdo.channel4.as_mut().expect(&expect_text).value = value.into()
}
}
}
@@ -53,10 +53,10 @@ impl DigitalOutputDevice<EL2024Port> for EL2024 {
fn get_output(&self, port: EL2024Port) -> DigitalOutputOutput {
let expect_text = "All channels should be Some(_)";
DigitalOutputOutput(match port {
EL2024Port::DO1 => self.rxpdo.channel1.as_ref().expect(expect_text).value,
EL2024Port::DO2 => self.rxpdo.channel2.as_ref().expect(expect_text).value,
EL2024Port::DO3 => self.rxpdo.channel3.as_ref().expect(expect_text).value,
EL2024Port::DO4 => self.rxpdo.channel4.as_ref().expect(expect_text).value,
EL2024Port::DO1 => self.rxpdo.channel1.as_ref().expect(&expect_text).value,
EL2024Port::DO2 => self.rxpdo.channel2.as_ref().expect(&expect_text).value,
EL2024Port::DO3 => self.rxpdo.channel3.as_ref().expect(&expect_text).value,
EL2024Port::DO4 => self.rxpdo.channel4.as_ref().expect(&expect_text).value,
})
}
}
+3 -3
View File
@@ -37,7 +37,7 @@ impl NewEthercatDevice for EL2521 {
let txpdo = configuration.pdo_assignment.txpdo_assignment();
let rxpdo = configuration.pdo_assignment.rxpdo_assignment();
Self {
configuration,
configuration: configuration,
txpdo,
rxpdo,
is_used: false,
@@ -354,7 +354,7 @@ pub enum EL2521PredefinedPdoAssignment {
impl PredefinedPdoAssignment<EL2521TxPdo, EL2521RxPdo> for EL2521PredefinedPdoAssignment {
fn txpdo_assignment(&self) -> EL2521TxPdo {
match self {
Self::EnhancedOperatingMode32Bit => EL2521TxPdo {
EL2521PredefinedPdoAssignment::EnhancedOperatingMode32Bit => EL2521TxPdo {
pto_status: Some(PtoStatus::default()),
enc_status: Some(EncStatus::default()),
},
@@ -363,7 +363,7 @@ impl PredefinedPdoAssignment<EL2521TxPdo, EL2521RxPdo> for EL2521PredefinedPdoAs
fn rxpdo_assignment(&self) -> EL2521RxPdo {
match self {
Self::EnhancedOperatingMode32Bit => EL2521RxPdo {
EL2521PredefinedPdoAssignment::EnhancedOperatingMode32Bit => EL2521RxPdo {
pto_control: Some(PtoControl::default()),
pto_target: Some(PtoTarget::default()),
enc_control: Some(EncControl::default()),
+6 -6
View File
@@ -37,7 +37,7 @@ impl NewEthercatDevice for EL2522 {
let txpdo = configuration.pdo_assignment.txpdo_assignment();
let rxpdo = configuration.pdo_assignment.rxpdo_assignment();
Self {
configuration,
configuration: configuration,
txpdo,
rxpdo,
is_used: false,
@@ -104,7 +104,7 @@ impl PulseTrainOutputDevice<EL2522Port> for EL2522 {
}
impl EL2522 {
const fn get_txpdo(&self, port: EL2522Port) -> (&PtoStatus, &EncStatus) {
fn get_txpdo(&self, port: EL2522Port) -> (&PtoStatus, &EncStatus) {
match port {
EL2522Port::PTO1 => (
self.txpdo.pto_status_channel1.as_ref().unwrap(),
@@ -117,7 +117,7 @@ impl EL2522 {
}
}
const fn get_rxpdo(&self, port: EL2522Port) -> (&PtoControl, &PtoTarget, &EncControl) {
fn get_rxpdo(&self, port: EL2522Port) -> (&PtoControl, &PtoTarget, &EncControl) {
match port {
EL2522Port::PTO1 => (
self.rxpdo.pto_control_channel1.as_ref().unwrap(),
@@ -131,7 +131,7 @@ impl EL2522 {
),
}
}
const fn get_rxpdo_mut(
fn get_rxpdo_mut(
&mut self,
port: EL2522Port,
) -> (&mut PtoControl, &mut PtoTarget, &mut EncControl) {
@@ -478,7 +478,7 @@ pub enum EL2522PredefinedPdoAssignment {
impl PredefinedPdoAssignment<EL2522TxPdo, EL2522RxPdo> for EL2522PredefinedPdoAssignment {
fn txpdo_assignment(&self) -> EL2522TxPdo {
match self {
Self::Standart32Bit => EL2522TxPdo {
EL2522PredefinedPdoAssignment::Standart32Bit => EL2522TxPdo {
pto_status_channel1: Some(PtoStatus::default()),
pto_status_channel2: Some(PtoStatus::default()),
enc_status_channel1: Some(EncStatus::default()),
@@ -489,7 +489,7 @@ impl PredefinedPdoAssignment<EL2522TxPdo, EL2522RxPdo> for EL2522PredefinedPdoAs
fn rxpdo_assignment(&self) -> EL2522RxPdo {
match self {
Self::Standart32Bit => EL2522RxPdo {
EL2522PredefinedPdoAssignment::Standart32Bit => EL2522RxPdo {
pto_control_channel1: Some(PtoControl::default()),
pto_target_channel1: Some(PtoTarget::default()),
enc_control_channel1: Some(EncControl::default()),
+16 -8
View File
@@ -35,20 +35,28 @@ impl DigitalOutputDevice<EL2634Port> for EL2634 {
fn set_output(&mut self, port: EL2634Port, value: DigitalOutputOutput) {
let expect_text = "All channels should be Some(_)";
match port {
EL2634Port::R1 => self.rxpdo.channel1.as_mut().expect(expect_text).value = value.into(),
EL2634Port::R2 => self.rxpdo.channel2.as_mut().expect(expect_text).value = value.into(),
EL2634Port::R3 => self.rxpdo.channel3.as_mut().expect(expect_text).value = value.into(),
EL2634Port::R4 => self.rxpdo.channel4.as_mut().expect(expect_text).value = value.into(),
EL2634Port::R1 => {
self.rxpdo.channel1.as_mut().expect(&expect_text).value = value.into()
}
EL2634Port::R2 => {
self.rxpdo.channel2.as_mut().expect(&expect_text).value = value.into()
}
EL2634Port::R3 => {
self.rxpdo.channel3.as_mut().expect(&expect_text).value = value.into()
}
EL2634Port::R4 => {
self.rxpdo.channel4.as_mut().expect(&expect_text).value = value.into()
}
}
}
fn get_output(&self, port: EL2634Port) -> DigitalOutputOutput {
let expect_text = "All channels should be Some(_)";
DigitalOutputOutput(match port {
EL2634Port::R1 => self.rxpdo.channel1.as_ref().expect(expect_text).value,
EL2634Port::R2 => self.rxpdo.channel2.as_ref().expect(expect_text).value,
EL2634Port::R3 => self.rxpdo.channel3.as_ref().expect(expect_text).value,
EL2634Port::R4 => self.rxpdo.channel4.as_ref().expect(expect_text).value,
EL2634Port::R1 => self.rxpdo.channel1.as_ref().expect(&expect_text).value,
EL2634Port::R2 => self.rxpdo.channel2.as_ref().expect(&expect_text).value,
EL2634Port::R3 => self.rxpdo.channel3.as_ref().expect(&expect_text).value,
EL2634Port::R4 => self.rxpdo.channel4.as_ref().expect(&expect_text).value,
})
}
}
+32 -32
View File
@@ -36,52 +36,52 @@ impl DigitalOutputDevice<EL2809Port> for EL2809 {
let expect_text = "All channels should be Some(_)";
match port {
EL2809Port::DO1 => {
self.rxpdo.channel1.as_mut().expect(expect_text).value = value.into()
self.rxpdo.channel1.as_mut().expect(&expect_text).value = value.into()
}
EL2809Port::DO2 => {
self.rxpdo.channel2.as_mut().expect(expect_text).value = value.into()
self.rxpdo.channel2.as_mut().expect(&expect_text).value = value.into()
}
EL2809Port::DO3 => {
self.rxpdo.channel3.as_mut().expect(expect_text).value = value.into()
self.rxpdo.channel3.as_mut().expect(&expect_text).value = value.into()
}
EL2809Port::DO4 => {
self.rxpdo.channel4.as_mut().expect(expect_text).value = value.into()
self.rxpdo.channel4.as_mut().expect(&expect_text).value = value.into()
}
EL2809Port::DO5 => {
self.rxpdo.channel5.as_mut().expect(expect_text).value = value.into()
self.rxpdo.channel5.as_mut().expect(&expect_text).value = value.into()
}
EL2809Port::DO6 => {
self.rxpdo.channel6.as_mut().expect(expect_text).value = value.into()
self.rxpdo.channel6.as_mut().expect(&expect_text).value = value.into()
}
EL2809Port::DO7 => {
self.rxpdo.channel7.as_mut().expect(expect_text).value = value.into()
self.rxpdo.channel7.as_mut().expect(&expect_text).value = value.into()
}
EL2809Port::DO8 => {
self.rxpdo.channel8.as_mut().expect(expect_text).value = value.into()
self.rxpdo.channel8.as_mut().expect(&expect_text).value = value.into()
}
EL2809Port::DO9 => {
self.rxpdo.channel9.as_mut().expect(expect_text).value = value.into()
self.rxpdo.channel9.as_mut().expect(&expect_text).value = value.into()
}
EL2809Port::DO10 => {
self.rxpdo.channel10.as_mut().expect(expect_text).value = value.into()
self.rxpdo.channel10.as_mut().expect(&expect_text).value = value.into()
}
EL2809Port::DO11 => {
self.rxpdo.channel11.as_mut().expect(expect_text).value = value.into()
self.rxpdo.channel11.as_mut().expect(&expect_text).value = value.into()
}
EL2809Port::DO12 => {
self.rxpdo.channel12.as_mut().expect(expect_text).value = value.into()
self.rxpdo.channel12.as_mut().expect(&expect_text).value = value.into()
}
EL2809Port::DO13 => {
self.rxpdo.channel13.as_mut().expect(expect_text).value = value.into()
self.rxpdo.channel13.as_mut().expect(&expect_text).value = value.into()
}
EL2809Port::DO14 => {
self.rxpdo.channel14.as_mut().expect(expect_text).value = value.into()
self.rxpdo.channel14.as_mut().expect(&expect_text).value = value.into()
}
EL2809Port::DO15 => {
self.rxpdo.channel15.as_mut().expect(expect_text).value = value.into()
self.rxpdo.channel15.as_mut().expect(&expect_text).value = value.into()
}
EL2809Port::DO16 => {
self.rxpdo.channel16.as_mut().expect(expect_text).value = value.into()
self.rxpdo.channel16.as_mut().expect(&expect_text).value = value.into()
}
}
}
@@ -89,22 +89,22 @@ impl DigitalOutputDevice<EL2809Port> for EL2809 {
fn get_output(&self, port: EL2809Port) -> DigitalOutputOutput {
let expect_text = "All channels should be Some(_)";
DigitalOutputOutput(match port {
EL2809Port::DO1 => self.rxpdo.channel1.as_ref().expect(expect_text).value,
EL2809Port::DO2 => self.rxpdo.channel2.as_ref().expect(expect_text).value,
EL2809Port::DO3 => self.rxpdo.channel3.as_ref().expect(expect_text).value,
EL2809Port::DO4 => self.rxpdo.channel4.as_ref().expect(expect_text).value,
EL2809Port::DO5 => self.rxpdo.channel5.as_ref().expect(expect_text).value,
EL2809Port::DO6 => self.rxpdo.channel6.as_ref().expect(expect_text).value,
EL2809Port::DO7 => self.rxpdo.channel7.as_ref().expect(expect_text).value,
EL2809Port::DO8 => self.rxpdo.channel8.as_ref().expect(expect_text).value,
EL2809Port::DO9 => self.rxpdo.channel9.as_ref().expect(expect_text).value,
EL2809Port::DO10 => self.rxpdo.channel10.as_ref().expect(expect_text).value,
EL2809Port::DO11 => self.rxpdo.channel11.as_ref().expect(expect_text).value,
EL2809Port::DO12 => self.rxpdo.channel12.as_ref().expect(expect_text).value,
EL2809Port::DO13 => self.rxpdo.channel13.as_ref().expect(expect_text).value,
EL2809Port::DO14 => self.rxpdo.channel14.as_ref().expect(expect_text).value,
EL2809Port::DO15 => self.rxpdo.channel15.as_ref().expect(expect_text).value,
EL2809Port::DO16 => self.rxpdo.channel16.as_ref().expect(expect_text).value,
EL2809Port::DO1 => self.rxpdo.channel1.as_ref().expect(&expect_text).value,
EL2809Port::DO2 => self.rxpdo.channel2.as_ref().expect(&expect_text).value,
EL2809Port::DO3 => self.rxpdo.channel3.as_ref().expect(&expect_text).value,
EL2809Port::DO4 => self.rxpdo.channel4.as_ref().expect(&expect_text).value,
EL2809Port::DO5 => self.rxpdo.channel5.as_ref().expect(&expect_text).value,
EL2809Port::DO6 => self.rxpdo.channel6.as_ref().expect(&expect_text).value,
EL2809Port::DO7 => self.rxpdo.channel7.as_ref().expect(&expect_text).value,
EL2809Port::DO8 => self.rxpdo.channel8.as_ref().expect(&expect_text).value,
EL2809Port::DO9 => self.rxpdo.channel9.as_ref().expect(&expect_text).value,
EL2809Port::DO10 => self.rxpdo.channel10.as_ref().expect(&expect_text).value,
EL2809Port::DO11 => self.rxpdo.channel11.as_ref().expect(&expect_text).value,
EL2809Port::DO12 => self.rxpdo.channel12.as_ref().expect(&expect_text).value,
EL2809Port::DO13 => self.rxpdo.channel13.as_ref().expect(&expect_text).value,
EL2809Port::DO14 => self.rxpdo.channel14.as_ref().expect(&expect_text).value,
EL2809Port::DO15 => self.rxpdo.channel15.as_ref().expect(&expect_text).value,
EL2809Port::DO16 => self.rxpdo.channel16.as_ref().expect(&expect_text).value,
})
}
}
+4 -4
View File
@@ -155,11 +155,11 @@ pub enum EL3001PredefinedPdoAssignment {
impl PredefinedPdoAssignment<EL3001TxPdo, EL3001RxPdo> for EL3001PredefinedPdoAssignment {
fn txpdo_assignment(&self) -> EL3001TxPdo {
match self {
Self::Standard => EL3001TxPdo {
EL3001PredefinedPdoAssignment::Standard => EL3001TxPdo {
ai_standard: Some(AiStandard::default()),
ai_compact: None,
},
Self::Compact => EL3001TxPdo {
EL3001PredefinedPdoAssignment::Compact => EL3001TxPdo {
ai_standard: None,
ai_compact: Some(AiCompact::default()),
},
@@ -168,8 +168,8 @@ impl PredefinedPdoAssignment<EL3001TxPdo, EL3001RxPdo> for EL3001PredefinedPdoAs
fn rxpdo_assignment(&self) -> EL3001RxPdo {
match self {
Self::Standard => EL3001RxPdo {},
Self::Compact => EL3001RxPdo {},
EL3001PredefinedPdoAssignment::Standard => EL3001RxPdo {},
EL3001PredefinedPdoAssignment::Compact => EL3001RxPdo {},
}
}
}
+4 -4
View File
@@ -175,11 +175,11 @@ pub enum EL3021PredefinedPdoAssignment {
impl PredefinedPdoAssignment<EL3021TxPdo, EL3021RxPdo> for EL3021PredefinedPdoAssignment {
fn txpdo_assignment(&self) -> EL3021TxPdo {
match self {
Self::Standard => EL3021TxPdo {
EL3021PredefinedPdoAssignment::Standard => EL3021TxPdo {
ai_standard_channel1: Some(AiStandard::default()),
ai_compact_channel1: None,
},
Self::Compact => EL3021TxPdo {
EL3021PredefinedPdoAssignment::Compact => EL3021TxPdo {
ai_standard_channel1: None,
ai_compact_channel1: Some(AiCompact::default()),
},
@@ -188,8 +188,8 @@ impl PredefinedPdoAssignment<EL3021TxPdo, EL3021RxPdo> for EL3021PredefinedPdoAs
fn rxpdo_assignment(&self) -> EL3021RxPdo {
match self {
Self::Standard => EL3021RxPdo {},
Self::Compact => EL3021RxPdo {},
EL3021PredefinedPdoAssignment::Standard => EL3021RxPdo {},
EL3021PredefinedPdoAssignment::Compact => EL3021RxPdo {},
}
}
}
+4 -4
View File
@@ -246,7 +246,7 @@ pub enum EL3024PredefinedPdoAssignment {
impl PredefinedPdoAssignment<EL3024TxPdo, EL3024RxPdo> for EL3024PredefinedPdoAssignment {
fn txpdo_assignment(&self) -> EL3024TxPdo {
match self {
Self::Standard => EL3024TxPdo {
EL3024PredefinedPdoAssignment::Standard => EL3024TxPdo {
ai_standard_channel1: Some(AiStandard::default()),
ai_compact_channel1: None,
ai_standard_channel2: Some(AiStandard::default()),
@@ -256,7 +256,7 @@ impl PredefinedPdoAssignment<EL3024TxPdo, EL3024RxPdo> for EL3024PredefinedPdoAs
ai_standard_channel4: Some(AiStandard::default()),
ai_compact_channel4: None,
},
Self::Compact => EL3024TxPdo {
EL3024PredefinedPdoAssignment::Compact => EL3024TxPdo {
ai_standard_channel1: None,
ai_compact_channel1: Some(AiCompact::default()),
ai_standard_channel2: None,
@@ -271,8 +271,8 @@ impl PredefinedPdoAssignment<EL3024TxPdo, EL3024RxPdo> for EL3024PredefinedPdoAs
fn rxpdo_assignment(&self) -> EL3024RxPdo {
match self {
Self::Standard => EL3024RxPdo {},
Self::Compact => EL3024RxPdo {},
EL3024PredefinedPdoAssignment::Standard => EL3024RxPdo {},
EL3024PredefinedPdoAssignment::Compact => EL3024RxPdo {},
}
}
}
+4 -4
View File
@@ -185,13 +185,13 @@ impl PredefinedPdoAssignment<EL3062_0030TxPdo, EL3062_0030RxPdo>
{
fn txpdo_assignment(&self) -> EL3062_0030TxPdo {
match self {
Self::Standard => EL3062_0030TxPdo {
EL3062_0030PredefinedPdoAssignment::Standard => EL3062_0030TxPdo {
ai_standard_channel1: Some(AiStandard::default()),
ai_compact_channel1: None,
ai_standard_channel2: Some(AiStandard::default()),
ai_compact_channel2: None,
},
Self::Compact => EL3062_0030TxPdo {
EL3062_0030PredefinedPdoAssignment::Compact => EL3062_0030TxPdo {
ai_standard_channel1: None,
ai_compact_channel1: Some(AiCompact::default()),
ai_standard_channel2: None,
@@ -202,8 +202,8 @@ impl PredefinedPdoAssignment<EL3062_0030TxPdo, EL3062_0030RxPdo>
fn rxpdo_assignment(&self) -> EL3062_0030RxPdo {
match self {
Self::Standard => EL3062_0030RxPdo {},
Self::Compact => EL3062_0030RxPdo {},
EL3062_0030PredefinedPdoAssignment::Standard => EL3062_0030RxPdo {},
EL3062_0030PredefinedPdoAssignment::Compact => EL3062_0030RxPdo {},
}
}
}
+9 -9
View File
@@ -38,10 +38,10 @@ impl TemperatureInputDevice<EL3204Port> for EL3204 {
fn get_input(&self, port: EL3204Port) -> TemperatureInputInput {
let expect_text = "All channels should be Some(_)";
let channel = match port {
EL3204Port::T1 => self.txpdo.channel1.as_ref().expect(expect_text),
EL3204Port::T2 => self.txpdo.channel2.as_ref().expect(expect_text),
EL3204Port::T3 => self.txpdo.channel3.as_ref().expect(expect_text),
EL3204Port::T4 => self.txpdo.channel4.as_ref().expect(expect_text),
EL3204Port::T1 => self.txpdo.channel1.as_ref().expect(&expect_text),
EL3204Port::T2 => self.txpdo.channel2.as_ref().expect(&expect_text),
EL3204Port::T3 => self.txpdo.channel3.as_ref().expect(&expect_text),
EL3204Port::T4 => self.txpdo.channel4.as_ref().expect(&expect_text),
};
TemperatureInputInput {
temperature: channel.temperature,
@@ -65,12 +65,12 @@ pub enum EL3204Port {
}
impl EL3204Port {
pub const fn to_byte_offset(&self) -> usize {
pub fn to_byte_offset(&self) -> usize {
match self {
Self::T1 => 0,
Self::T2 => 4,
Self::T3 => 8,
Self::T4 => 12,
EL3204Port::T1 => 0,
EL3204Port::T2 => 4,
EL3204Port::T3 => 8,
EL3204Port::T4 => 12,
}
}
}
+1 -1
View File
@@ -139,7 +139,7 @@ pub struct EL4002TxPdo {}
impl PredefinedPdoAssignment<EL4002TxPdo, EL4002RxPdo> for EL4002PredefinedPdoAssignment {
fn rxpdo_assignment(&self) -> EL4002RxPdo {
match self {
Self::Standard => EL4002RxPdo {
EL4002PredefinedPdoAssignment::Standard => EL4002RxPdo {
ao_channel1: Some(AnalogOutput::default()),
ao_channel2: Some(AnalogOutput::default()),
},
+34 -28
View File
@@ -37,7 +37,7 @@ pub enum EL6021Baudrate {
// Every Preset has 2 bytes at the beginning
// Standard98ByteMdp600 for example is 100bytes big but has 98 bytes of data
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq)]
pub enum EL6021PdoPreset {
/// Legacy 22 Byte MDP 600
Legacy22ByteMdp600,
@@ -59,13 +59,13 @@ impl TryFrom<u8> for EL6021Baudrate {
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
4 => Ok(Self::B2400),
5 => Ok(Self::B4800),
6 => Ok(Self::B9600),
7 => Ok(Self::B19200),
8 => Ok(Self::B38400),
9 => Ok(Self::B57600),
10 => Ok(Self::B115200),
4 => Ok(EL6021Baudrate::B2400),
5 => Ok(EL6021Baudrate::B4800),
6 => Ok(EL6021Baudrate::B9600),
7 => Ok(EL6021Baudrate::B19200),
8 => Ok(EL6021Baudrate::B38400),
9 => Ok(EL6021Baudrate::B57600),
10 => Ok(EL6021Baudrate::B115200),
_ => Err(anyhow::anyhow!(
"Error: specified Baudrate is not supported!"
)),
@@ -75,7 +75,7 @@ impl TryFrom<u8> for EL6021Baudrate {
impl From<EL6021Baudrate> for u8 {
fn from(baudrate: EL6021Baudrate) -> Self {
baudrate as Self
baudrate as u8
}
}
@@ -130,7 +130,7 @@ impl ConfigurableDevice<EL6021Configuration> for EL6021 {
}
/// Configuration structure for the EL6021 module.
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq)]
pub struct EL6021Configuration {
/// # 0x8000:01 - Enables Request to Send (RTS) flow control.
/// - `true` = RTS flow control enabled
@@ -198,7 +198,7 @@ pub struct EL6021Configuration {
pub pdo_assignment: EL6021PdoPreset,
}
const fn convert_serial_encoding(encoding: SerialEncoding) -> u8 {
fn convert_serial_encoding(encoding: SerialEncoding) -> u8 {
match encoding {
SerialEncoding::Coding7E1 => 1,
SerialEncoding::Coding7O1 => 2,
@@ -282,7 +282,7 @@ impl Configuration for EL6021Configuration {
}
}
#[derive(Default, Debug, Clone, PdoObject, PartialEq, Eq)]
#[derive(Default, Debug, Clone, PdoObject, PartialEq)]
#[pdo_object(bits = 192)]
pub struct Standard22ByteMdp600Output {
pub control: EL6021Control,
@@ -290,7 +290,7 @@ pub struct Standard22ByteMdp600Output {
pub data: [u8; 22],
}
#[derive(Default, Debug, PartialEq, Eq, Clone)]
#[derive(Default, Debug, PartialEq, Clone)]
pub struct EL6021Status {
pub transmit_accepted: bool,
pub receive_request: bool,
@@ -301,7 +301,7 @@ pub struct EL6021Status {
pub overrun_error: bool,
}
#[derive(Default, Debug, PartialEq, Eq, Clone)]
#[derive(Default, Debug, PartialEq, Clone)]
pub struct EL6021Control {
pub transmit_request: bool,
pub received_acepted: bool,
@@ -309,7 +309,7 @@ pub struct EL6021Control {
}
/// The value is accompanied by some metadata.
#[derive(Default, Debug, Clone, PdoObject, PartialEq, Eq)]
#[derive(Default, Debug, Clone, PdoObject, PartialEq)]
#[pdo_object(bits = 192)]
pub struct Standard22ByteMdp600Input {
pub status: EL6021Status,
@@ -328,7 +328,7 @@ impl TxPdoObject for Standard22ByteMdp600Input {
self.status.overrun_error = bits[6];
// Bit7 is reserved/unused
self.length = bits[8..8 + 8].load_le::<u8>();
let serial_bytes = bits[16..(16 + 22 * 8_usize)].chunks_exact(8);
let serial_bytes = bits[16..(16 + 22 * 8 as usize)].chunks_exact(8);
for (i, val) in serial_bytes.enumerate() {
self.data[i] = val.load_le();
}
@@ -381,7 +381,7 @@ impl EthercatDeviceProcessing for EL6021 {}
impl PredefinedPdoAssignment<EL6021TxPdo, EL6021RxPdo> for EL6021PdoPreset {
fn txpdo_assignment(&self) -> EL6021TxPdo {
match self {
Self::Standard22ByteMdp600 => EL6021TxPdo {
EL6021PdoPreset::Standard22ByteMdp600 => EL6021TxPdo {
com_tx_pdo_map_22_byte: Some(Standard22ByteMdp600Input::default()),
},
_ => todo!(),
@@ -390,7 +390,7 @@ impl PredefinedPdoAssignment<EL6021TxPdo, EL6021RxPdo> for EL6021PdoPreset {
fn rxpdo_assignment(&self) -> EL6021RxPdo {
match self {
Self::Standard22ByteMdp600 => EL6021RxPdo {
EL6021PdoPreset::Standard22ByteMdp600 => EL6021RxPdo {
com_rx_pdo_map_22_byte: Some(Standard22ByteMdp600Output::default()),
},
_ => todo!(),
@@ -425,7 +425,7 @@ impl SerialInterfaceDevice<EL6021Port> for EL6021 {
// Only check if the bit has changed, don't update our state yet
return tx_pdo.status.receive_request != self.has_messages_last_toggle;
}
false
return false;
}
fn serial_interface_read_message(&mut self, _port: EL6021Port) -> Option<Vec<u8>> {
@@ -456,7 +456,7 @@ impl SerialInterfaceDevice<EL6021Port> for EL6021 {
self.has_messages_last_toggle = tx_pdo.status.receive_request;
rx_pdo.control.received_acepted = !rx_pdo.control.received_acepted;
Some(received_data)
return Some(received_data);
}
fn serial_interface_write_message(
@@ -483,7 +483,7 @@ impl SerialInterfaceDevice<EL6021Port> for EL6021 {
));
}
// If we write a message of len zero, then this means we are waiting for our write_message to finish on the EL6021
if message.is_empty() {
if message.len() == 0 {
return Ok(rx_pdo.control.transmit_request == tx_pdo.status.transmit_accepted);
}
@@ -493,16 +493,16 @@ impl SerialInterfaceDevice<EL6021Port> for EL6021 {
rx_pdo.length = message.len() as u8;
rx_pdo.data = data_buffer;
rx_pdo.control.transmit_request = !rx_pdo.control.transmit_request;
Ok(true)
return Ok(true);
}
fn get_baudrate(&self, _port: EL6021Port) -> Option<u32> {
let baudrate: u32 = self.configuration.baud_rate.into();
Some(baudrate)
return Some(baudrate);
}
fn get_serial_encoding(&self, _port: EL6021Port) -> Option<SerialEncoding> {
Some(self.configuration.data_frame)
return Some(self.configuration.data_frame);
}
/// For el6021 this returns false for as long as the Initialization takes
@@ -543,7 +543,10 @@ impl SerialInterfaceDevice<EL6021Port> for EL6021 {
init_accepted 0: Initialization was completed by the terminal.
init_request 0: The terminal is ready again for serial data exchange.
*/
if !rxpdo.control.init_request && !txpdo.status.init_accepted && !self.initialized {
if rxpdo.control.init_request == false
&& txpdo.status.init_accepted == false
&& self.initialized == false
{
rxpdo.control.init_request = true;
self.initialized = true;
return false;
@@ -553,7 +556,7 @@ impl SerialInterfaceDevice<EL6021Port> for EL6021 {
init_accepted 1: Initialization was completed by the terminal.
init_request 0: The terminal is ready again for serial data exchange.
*/
if !rxpdo.control.init_request && txpdo.status.init_accepted {
if rxpdo.control.init_request == false && txpdo.status.init_accepted == true {
return false;
}
@@ -562,13 +565,16 @@ impl SerialInterfaceDevice<EL6021Port> for EL6021 {
init_accepted 0: The controller once again requests the terminal to prepare for serial data exchange.
init_request 0: The terminal is ready again for serial data exchange.
*/
if !rxpdo.control.init_request && !txpdo.status.init_accepted && self.initialized {
if rxpdo.control.init_request == false
&& txpdo.status.init_accepted == false
&& self.initialized == true
{
// set inital state of the toggle
self.has_messages_last_toggle = txpdo.status.receive_request;
return true;
}
false
return false;
}
}
}
+34 -28
View File
@@ -81,9 +81,8 @@ pub struct EL7031RxPdo {
pub pos_control_2: Option<PosControl2>,
}
#[derive(Debug, Clone, Default)]
#[derive(Debug, Clone)]
pub enum EL7031PredefinedPdoAssignment {
#[default]
VelocityControlCompact,
VelocityControlCompactWithInfoData,
VelocityControl,
@@ -98,7 +97,7 @@ pub enum EL7031PredefinedPdoAssignment {
impl PredefinedPdoAssignment<EL7031TxPdo, EL7031RxPdo> for EL7031PredefinedPdoAssignment {
fn txpdo_assignment(&self) -> EL7031TxPdo {
match self {
Self::VelocityControlCompact => EL7031TxPdo {
EL7031PredefinedPdoAssignment::VelocityControlCompact => EL7031TxPdo {
enc_status_compact: Some(EncStatusCompact::default()),
enc_status: None,
enc_timestamp_compact: None,
@@ -109,7 +108,7 @@ impl PredefinedPdoAssignment<EL7031TxPdo, EL7031RxPdo> for EL7031PredefinedPdoAs
stm_internal_position: None,
stm_external_position: None,
},
Self::VelocityControlCompactWithInfoData => EL7031TxPdo {
EL7031PredefinedPdoAssignment::VelocityControlCompactWithInfoData => EL7031TxPdo {
enc_status_compact: Some(EncStatusCompact::default()),
enc_status: None,
enc_timestamp_compact: None,
@@ -120,7 +119,7 @@ impl PredefinedPdoAssignment<EL7031TxPdo, EL7031RxPdo> for EL7031PredefinedPdoAs
stm_internal_position: None,
stm_external_position: None,
},
Self::VelocityControl => EL7031TxPdo {
EL7031PredefinedPdoAssignment::VelocityControl => EL7031TxPdo {
enc_status_compact: None,
enc_status: Some(EncStatus::default()),
enc_timestamp_compact: None,
@@ -131,7 +130,7 @@ impl PredefinedPdoAssignment<EL7031TxPdo, EL7031RxPdo> for EL7031PredefinedPdoAs
stm_internal_position: None,
stm_external_position: None,
},
Self::PositionControl => EL7031TxPdo {
EL7031PredefinedPdoAssignment::PositionControl => EL7031TxPdo {
enc_status_compact: None,
enc_status: Some(EncStatus::default()),
enc_timestamp_compact: None,
@@ -142,7 +141,7 @@ impl PredefinedPdoAssignment<EL7031TxPdo, EL7031RxPdo> for EL7031PredefinedPdoAs
stm_internal_position: None,
stm_external_position: None,
},
Self::PositionInterfaceCompact => EL7031TxPdo {
EL7031PredefinedPdoAssignment::PositionInterfaceCompact => EL7031TxPdo {
enc_status_compact: None,
enc_status: Some(EncStatus::default()),
enc_timestamp_compact: None,
@@ -153,7 +152,7 @@ impl PredefinedPdoAssignment<EL7031TxPdo, EL7031RxPdo> for EL7031PredefinedPdoAs
stm_internal_position: None,
stm_external_position: None,
},
Self::PositionInterface => EL7031TxPdo {
EL7031PredefinedPdoAssignment::PositionInterface => EL7031TxPdo {
enc_status_compact: None,
enc_status: Some(EncStatus::default()),
enc_timestamp_compact: None,
@@ -164,7 +163,7 @@ impl PredefinedPdoAssignment<EL7031TxPdo, EL7031RxPdo> for EL7031PredefinedPdoAs
stm_internal_position: None,
stm_external_position: None,
},
Self::PositionInterfaceWithInfoData => EL7031TxPdo {
EL7031PredefinedPdoAssignment::PositionInterfaceWithInfoData => EL7031TxPdo {
enc_status_compact: None,
enc_status: Some(EncStatus::default()),
enc_timestamp_compact: None,
@@ -175,7 +174,7 @@ impl PredefinedPdoAssignment<EL7031TxPdo, EL7031RxPdo> for EL7031PredefinedPdoAs
stm_internal_position: None,
stm_external_position: None,
},
Self::PositionInterfaceAutoStart => EL7031TxPdo {
EL7031PredefinedPdoAssignment::PositionInterfaceAutoStart => EL7031TxPdo {
enc_status_compact: None,
enc_status: Some(EncStatus::default()),
enc_timestamp_compact: None,
@@ -186,7 +185,7 @@ impl PredefinedPdoAssignment<EL7031TxPdo, EL7031RxPdo> for EL7031PredefinedPdoAs
stm_internal_position: None,
stm_external_position: None,
},
Self::PositionInterfaceAutoStartWithInfoData => EL7031TxPdo {
EL7031PredefinedPdoAssignment::PositionInterfaceAutoStartWithInfoData => EL7031TxPdo {
enc_status_compact: None,
enc_status: Some(EncStatus::default()),
enc_timestamp_compact: None,
@@ -202,19 +201,18 @@ impl PredefinedPdoAssignment<EL7031TxPdo, EL7031RxPdo> for EL7031PredefinedPdoAs
fn rxpdo_assignment(&self) -> EL7031RxPdo {
match self {
Self::VelocityControlCompact | Self::VelocityControlCompactWithInfoData => {
EL7031RxPdo {
enc_control_compact: Some(EncControlCompact::default()),
enc_control: None,
stm_control: Some(StmControl::default()),
stm_position: None,
stm_velocity: Some(StmVelocity::default()),
pos_control_compact: None,
pos_control: None,
pos_control_2: None,
}
}
Self::VelocityControl => EL7031RxPdo {
EL7031PredefinedPdoAssignment::VelocityControlCompact
| EL7031PredefinedPdoAssignment::VelocityControlCompactWithInfoData => EL7031RxPdo {
enc_control_compact: Some(EncControlCompact::default()),
enc_control: None,
stm_control: Some(StmControl::default()),
stm_position: None,
stm_velocity: Some(StmVelocity::default()),
pos_control_compact: None,
pos_control: None,
pos_control_2: None,
},
EL7031PredefinedPdoAssignment::VelocityControl => EL7031RxPdo {
enc_control_compact: None,
enc_control: Some(EncControl::default()),
stm_control: Some(StmControl::default()),
@@ -224,7 +222,7 @@ impl PredefinedPdoAssignment<EL7031TxPdo, EL7031RxPdo> for EL7031PredefinedPdoAs
pos_control: None,
pos_control_2: None,
},
Self::PositionControl => EL7031RxPdo {
EL7031PredefinedPdoAssignment::PositionControl => EL7031RxPdo {
enc_control_compact: None,
enc_control: Some(EncControl::default()),
stm_control: Some(StmControl::default()),
@@ -234,7 +232,7 @@ impl PredefinedPdoAssignment<EL7031TxPdo, EL7031RxPdo> for EL7031PredefinedPdoAs
pos_control: None,
pos_control_2: None,
},
Self::PositionInterfaceCompact => EL7031RxPdo {
EL7031PredefinedPdoAssignment::PositionInterfaceCompact => EL7031RxPdo {
enc_control_compact: None,
enc_control: Some(EncControl::default()),
stm_control: Some(StmControl::default()),
@@ -244,7 +242,8 @@ impl PredefinedPdoAssignment<EL7031TxPdo, EL7031RxPdo> for EL7031PredefinedPdoAs
pos_control: None,
pos_control_2: None,
},
Self::PositionInterface | Self::PositionInterfaceWithInfoData => EL7031RxPdo {
EL7031PredefinedPdoAssignment::PositionInterface
| EL7031PredefinedPdoAssignment::PositionInterfaceWithInfoData => EL7031RxPdo {
enc_control_compact: None,
enc_control: Some(EncControl::default()),
stm_control: Some(StmControl::default()),
@@ -254,7 +253,8 @@ impl PredefinedPdoAssignment<EL7031TxPdo, EL7031RxPdo> for EL7031PredefinedPdoAs
pos_control: Some(PosControl::default()),
pos_control_2: None,
},
Self::PositionInterfaceAutoStart | Self::PositionInterfaceAutoStartWithInfoData => {
EL7031PredefinedPdoAssignment::PositionInterfaceAutoStart
| EL7031PredefinedPdoAssignment::PositionInterfaceAutoStartWithInfoData => {
EL7031RxPdo {
enc_control_compact: None,
enc_control: Some(EncControl::default()),
@@ -270,6 +270,12 @@ impl PredefinedPdoAssignment<EL7031TxPdo, EL7031RxPdo> for EL7031PredefinedPdoAs
}
}
impl Default for EL7031PredefinedPdoAssignment {
fn default() -> Self {
EL7031PredefinedPdoAssignment::VelocityControlCompact
}
}
#[cfg(test)]
mod tests {
use super::*;
Loaded 100 of 178 files, more files were not shown because too many files have changed in this diff. Show more