Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10972aabde |
No files matched your search
@@ -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,
|
||||
}
|
||||
@@ -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 }
|
||||
@@ -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
|
||||
|
||||
@@ -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,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)
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
);
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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(());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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/{}/{}/{}",
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 850 KiB |
|
Before Width: | Height: | Size: 128 KiB |
|
Before Width: | Height: | Size: 178 KiB |
|
Before Width: | Height: | Size: 201 KiB |
|
Before Width: | Height: | Size: 234 KiB |
|
Before Width: | Height: | Size: 197 KiB |
|
Before Width: | Height: | Size: 213 KiB |
|
Before Width: | Height: | Size: 171 KiB |
|
Before Width: | Height: | Size: 325 KiB |
|
Before Width: | Height: | Size: 664 KiB |
|
Before Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 159 KiB |
|
Before Width: | Height: | Size: 298 KiB |
|
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
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
@@ -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
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
@@ -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();
|
||||
|
||||
@@ -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,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"
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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,9 +3,6 @@ name = "ethercat_hal_derive"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
|
||||
@@ -3,9 +3,6 @@ name = "ethercat_hal"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.99"
|
||||
ethercrab = "0.6"
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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!()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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!()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()),
|
||||
|
||||
@@ -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()),
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()),
|
||||
},
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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::*;
|
||||
|
||||