Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e47a30a2c | ||
|
|
c675495c30 | ||
|
|
12749c1a1f | ||
|
|
57c94f891f | ||
|
|
d6911ed6f0 |
No files matched your search
Vendored
+2
-3
@@ -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,
|
||||
}
|
||||
+5
-43
@@ -1,94 +1,57 @@
|
||||
# `2.7.0`
|
||||
|
||||
_17.09.2025_
|
||||
|
||||
## Extruder V2
|
||||
|
||||
- [#695](https://github.com/qitechgmbh/control/pull/695) Pressure and Rpm Regulation properly switch again
|
||||
|
||||
## Winder V2
|
||||
|
||||
- [#727](https://github.com/qitechgmbh/control/pull/727) Removed estimated diameter
|
||||
|
||||
## Laser V1/V2
|
||||
|
||||
- [#731](https://github.com/qitechgmbh/control/pull/731) Added LaserV2 with two-axis measurements
|
||||
|
||||
## General
|
||||
|
||||
- [#679](https://github.com/qitechgmbh/control/pull/679),[#677](https://github.com/qitechgmbh/control/pull/677),[#698](https://github.com/qitechgmbh/control/pull/698), [#684](https://github.com/qitechgmbh/control/pull/684),[#706](https://github.com/qitechgmbh/control/pull/706) Greatly improved overall system latency and stability
|
||||
|
||||
- [#720](https://github.com/qitechgmbh/control/pull/720), [#686](https://github.com/qitechgmbh/control/pull/686) Improved User Interface
|
||||
|
||||
# `2.6.0`
|
||||
|
||||
_03.09.2025_
|
||||
|
||||
## Extruder V2
|
||||
|
||||
- [#658](https://github.com/qitechgmbh/control/pull/658) Added manual
|
||||
- [#639](https://github.com/qitechgmbh/control/pull/639) Swapped order of screw speed regulation buttons
|
||||
|
||||
## Laser V1
|
||||
|
||||
## Laser V1
|
||||
- [#642](https://github.com/qitechgmbh/control/pull/642) Improved stability of laser connection
|
||||
|
||||
## General
|
||||
|
||||
- [#657](https://github.com/qitechgmbh/control/pull/657) Time frame for graphs is now remembered
|
||||
- [#641](https://github.com/qitechgmbh/control/pull/641) Removed duplicate scrollbar from manuals
|
||||
|
||||
# `2.5.0`
|
||||
|
||||
_27.08.2025_
|
||||
|
||||
## Extruder V2
|
||||
|
||||
- [#595](https://github.com/qitechgmbh/control/pull/600) Simplified power graphs
|
||||
- [#593](https://github.com/qitechgmbh/control/pull/599) Added identifiers for temperature sensors in temperature graph
|
||||
|
||||
## Winder V2
|
||||
|
||||
- [#598](https://github.com/qitechgmbh/control/pull/601) Images now load properly in the manual
|
||||
|
||||
## General
|
||||
|
||||
- [#540](https://github.com/qitechgmbh/control/pull/592) Improved readability of the presets
|
||||
- [#591](https://github.com/qitechgmbh/control/pull/626) Significantly improved user interface performance and stability
|
||||
|
||||
# `2.4.0`
|
||||
|
||||
_14.08.2025_
|
||||
|
||||
## Winder V2
|
||||
|
||||
## Winder V2
|
||||
- [#550](https://github.com/qitechgmbh/control/pull/551) Can now reset wound distance while winding
|
||||
|
||||
|
||||
## General
|
||||
|
||||
- [#568](https://github.com/qitechgmbh/control/pull/569),[#572](https://github.com/qitechgmbh/control/pull/572), [#574](https://github.com/qitechgmbh/control/pull/587) Improved EtherCAT performance
|
||||
- [#568](https://github.com/qitechgmbh/control/pull/569),[#572](https://github.com/qitechgmbh/control/pull/572), [#574](https://github.com/qitechgmbh/control/pull/587) Improved EtherCAT performance
|
||||
- [#588](https://github.com/qitechgmbh/control/pull/589) Improved frontend performance
|
||||
|
||||
# `2.3.0`
|
||||
|
||||
_06.08.2025_
|
||||
|
||||
## Extruder V2
|
||||
|
||||
- [#520](https://github.com/qitechgmbh/control/pull/523) added warning message incase inverter is overloaded
|
||||
- [#501](https://github.com/qitechgmbh/control/pull/508) raised pressure limit of pressured PID mode for extruder
|
||||
- [#519](https://github.com/qitechgmbh/control/pull/549) added total power consumption monitor and graphs for voltage,current and wattage of the motor
|
||||
|
||||
## Winder V2
|
||||
|
||||
## Winder V2
|
||||
- [#64](https://github.com/qitechgmbh/control/pull/504) added winder handbook
|
||||
|
||||
## Laser V1
|
||||
|
||||
- [#497](https://github.com/qitechgmbh/control/pull/498) improved diameter visualisation
|
||||
|
||||
## General
|
||||
|
||||
- [#62](https://github.com/qitechgmbh/control/pull/514),[#524](https://github.com/qitechgmbh/control/pull/528) added machine presets to load configurations
|
||||
- [#526](https://github.com/qitechgmbh/control/pull/541) added import and export of presets
|
||||
|
||||
@@ -104,7 +67,6 @@ _15.07.2025_
|
||||
- [#169](https://github.com/qitechgmbh/control/pull/492), Added a Confirmation Dialog when extruder has not reached 90% of target temperature
|
||||
|
||||
## Winder V2
|
||||
|
||||
- [#471](https://github.com/qitechgmbh/control/pull/480), Added a live value to track the pulled distance and the option of switching to a different Mode after a given distance.
|
||||
|
||||
## General
|
||||
|
||||
Generated
-1
@@ -596,7 +596,6 @@ dependencies = [
|
||||
"smol",
|
||||
"socketioxide",
|
||||
"textplots",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"uom",
|
||||
]
|
||||
|
||||
+1
-18
@@ -4,24 +4,7 @@ members = [
|
||||
"ethercat-hal",
|
||||
"ethercat-hal-derive",
|
||||
"control-core",
|
||||
"ethercat-eeprom-dump",
|
||||
"control-core-derive",
|
||||
"ethercat-eeprom-dump", "control-core-derive",
|
||||
]
|
||||
exclude = []
|
||||
resolver = "2"
|
||||
|
||||
[workspace.lints.clippy]
|
||||
# Individual lints (priority 0)
|
||||
|
||||
# don't lint poissbilities for let chains because they were introduces after Rust 1.86
|
||||
collapsible_if = "allow"
|
||||
|
||||
# Lint groups (priority -1)
|
||||
correctness = { level = "warn", priority = -1 }
|
||||
suspicious = { level = "warn", priority = -1 }
|
||||
complexity = { level = "warn", priority = -1 }
|
||||
perf = { level = "warn", priority = -1 }
|
||||
style = { level = "warn", priority = -1 }
|
||||
pendantic = { level = "warn", priority = -1 }
|
||||
cargo = { level = "warn", priority = -1 }
|
||||
nursery = { level = "warn", priority = -1 }
|
||||
+1
-1
@@ -24,7 +24,7 @@ if [ "$1" != "mock-machine" ]; then
|
||||
sudo setcap 'cap_net_raw,cap_sys_nice=eip' ./target/debug/server
|
||||
|
||||
echo "Setting permissions for serial ports"
|
||||
# sudo chown "root:$(groups | cut -f1 -d' ')" /dev/ttyUSB*
|
||||
sudo chown "root:$USER" /dev/ttyUSB*
|
||||
fi
|
||||
|
||||
# run
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Nix store uses sqlite ... SO HAHAHAHAHH NO CONCURRENT ACCESS AHAHAHHAHAHAHHAHAHAHAH
|
||||
time nix build .#packages.x86_64-linux.server --impure --extra-experimental-features nix-command --extra-experimental-features flakes --out-link /tmp/server-result
|
||||
serverstore=$(readlink -f /tmp/server-result)
|
||||
|
||||
time nix build .#packages.x86_64-linux.electron --impure --option sandbox false --extra-experimental-features nix-command --extra-experimental-features flakes --out-link /tmp/electron-result
|
||||
electronstore=$(readlink -f /tmp/electron-result)
|
||||
|
||||
|
||||
nix copy --to "file:///opt/dev-cache" \
|
||||
$(readlink -f /tmp/server-result) \
|
||||
$(readlink -f /tmp/electron-result) --extra-experimental-features nix-command
|
||||
|
||||
nix store sign \
|
||||
--key-file /opt/dev-cache/cache-private-key.pem \
|
||||
--store "file:///opt/dev-cache" \
|
||||
--extra-experimental-features nix-command \
|
||||
"$serverstore" "$electronstore"
|
||||
|
||||
|
||||
nix path-info "$serverstore" --extra-experimental-features nix-command --extra-experimental-features flakes
|
||||
|
||||
|
||||
# nix verify --store "file:///opt/dev-cache" \
|
||||
# --trusted-public-keys "$(cat /opt/dev-cache/cache-public-key.pem)" --extra-experimental-features nix-command --extra-experimental-features flakes \
|
||||
# "$FIRST_PATH" || echo "Verification failed!"
|
||||
|
||||
|
||||
#time nix build .#packages.x86_64-linux.electron --extra-experimental-features nix-command --extra-experimental-features flakes --out-link /tmp/electron-result
|
||||
#electronstore=$(readlink -f /tmp/electron-result)
|
||||
|
||||
#nix-store --generate-binary-cache-key cache.key cache.pem --extra-experimental-features nix-command
|
||||
#nix store sign --key-file cache.key "$serverstore" --extra-experimental-features nix-command
|
||||
#nix path-info --json "$serverstore" --extra-experimental-features nix-command --extra-experimental-features flakes
|
||||
#export
|
||||
#nix-store --export /nix/store/<hash>-server > nix-cache/server.nar
|
||||
#nix-store --export /nix/store/<hash>-electron > nix-cache/electron.nar
|
||||
|
||||
# Any http server
|
||||
# cd ~/nix-cache
|
||||
# python3 -m http.server 8080
|
||||
#
|
||||
@@ -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)
|
||||
}
|
||||
+1
-11
@@ -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"
|
||||
@@ -25,7 +22,6 @@ tracing = "0.1.41"
|
||||
erased-serde = "0.4.6"
|
||||
core_affinity = "0.8.3"
|
||||
control_core_derive = { version = "0.1.0", path = "../control-core-derive" }
|
||||
tokio = { version = "1.45.1", features = ["sync"], optional = true }
|
||||
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
@@ -34,9 +30,3 @@ libc = "0.2.175"
|
||||
[dev-dependencies]
|
||||
approx = "0.5.1"
|
||||
textplots = "0.8"
|
||||
tokio = { version = "1.45.1", features = ["rt-multi-thread", "macros", "time"] }
|
||||
|
||||
|
||||
[features]
|
||||
default = []
|
||||
video-streaming = ["dep:tokio"]
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -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>) {
|
||||
|
||||
+3
-3
@@ -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;
|
||||
|
||||
+46
-58
@@ -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;
|
||||
}
|
||||
|
||||
+6
-2
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
+6
-2
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,8 +11,5 @@ pub mod socketio;
|
||||
pub mod transmission;
|
||||
pub mod uom_extensions;
|
||||
|
||||
#[cfg(feature = "video-streaming")]
|
||||
pub mod video_streaming;
|
||||
|
||||
#[macro_use]
|
||||
extern crate uom;
|
||||
@@ -5,21 +5,4 @@ use crate::socketio::namespace::Namespace;
|
||||
pub trait MachineApi {
|
||||
fn api_mutate(&mut self, value: Value) -> Result<(), anyhow::Error>;
|
||||
fn api_event_namespace(&mut self) -> &mut Namespace;
|
||||
|
||||
/// Returns a list of available video stream identifiers for this machine
|
||||
#[cfg(feature = "video-streaming")]
|
||||
fn api_video_streams(&self) -> Vec<String> {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
/// Get a video stream receiver for the specified stream ID
|
||||
/// Returns None if the stream doesn't exist
|
||||
#[cfg(feature = "video-streaming")]
|
||||
fn api_get_video_stream(
|
||||
&mut self,
|
||||
stream_id: &str,
|
||||
) -> Option<tokio::sync::broadcast::Receiver<Vec<u8>>> {
|
||||
let _ = stream_id;
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -16,7 +16,9 @@ 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;
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use tracing::debug;
|
||||
use tracing::error;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(unix)]
|
||||
/// Makes the current thread a real-time thread using the PREEMPT_RT capabilities of Linux.
|
||||
///
|
||||
/// This function configures the current thread with real-time scheduling properties by:
|
||||
@@ -47,7 +47,7 @@ use tracing::error;
|
||||
///
|
||||
/// - `Ok(())` if the real-time priority was successfully set
|
||||
/// - `Err` with the error message if setting the priority failed
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(unix)]
|
||||
pub fn set_realtime_priority() -> Result<(), anyhow::Error> {
|
||||
use anyhow::anyhow;
|
||||
use libc::{SCHED_FIFO, pthread_setschedparam, sched_param};
|
||||
@@ -84,9 +84,9 @@ pub fn set_realtime_priority() -> Result<(), anyhow::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
#[cfg(not(unix))]
|
||||
pub fn set_realtime_priority() -> Result<(), anyhow::Error> {
|
||||
tracing::error!("This platform does not support realtime threads");
|
||||
log::error!("This platform does not support realtime threads");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ pub fn set_realtime_priority() -> Result<(), anyhow::Error> {
|
||||
/// # How it works
|
||||
///
|
||||
/// [Rest of documentation as before...]
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(unix)]
|
||||
pub fn lock_memory() -> Result<(), Box<dyn std::error::Error>> {
|
||||
use libc::{MCL_FUTURE, mlockall};
|
||||
|
||||
@@ -154,9 +154,9 @@ pub fn lock_memory() -> Result<(), Box<dyn std::error::Error>> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
#[cfg(not(unix))]
|
||||
pub fn lock_memory() -> Result<(), Box<dyn std::error::Error>> {
|
||||
tracing::error!("This platform does not support memory locking");
|
||||
log::error!("This platform does not support memory locking");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
@@ -33,19 +33,3 @@ where
|
||||
pub machine_identification_unique: MachineIdentificationUnique,
|
||||
pub data: T,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
pub struct MachineVideoStreamListBody {
|
||||
pub machine_identification_unique: MachineIdentificationUnique,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
pub struct MachineVideoStreamBody {
|
||||
pub machine_identification_unique: MachineIdentificationUnique,
|
||||
pub stream_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct VideoStreamListResponse {
|
||||
pub streams: Vec<String>,
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
@@ -1,208 +0,0 @@
|
||||
use anyhow::Error;
|
||||
use std::collections::HashMap;
|
||||
use tokio::sync::broadcast;
|
||||
|
||||
/// Video stream manager that handles multiple video streams per machine with true broadcasting
|
||||
/// Uses tokio broadcast channels to ensure all subscribers receive all messages without stealing
|
||||
#[derive(Debug)]
|
||||
pub struct VideoStreamManager {
|
||||
/// Each stream has its own broadcast transmitter and template receiver
|
||||
streams: HashMap<String, (broadcast::Sender<Vec<u8>>, broadcast::Receiver<Vec<u8>>)>,
|
||||
}
|
||||
|
||||
impl VideoStreamManager {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
streams: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Send a frame to all subscribers of a stream
|
||||
/// Returns Ok if the frame was sent, Err if the stream doesn't exist
|
||||
pub fn send_frame(&self, stream_id: &str, frame: Vec<u8>) -> Result<(), Error> {
|
||||
if let Some((tx, _rx)) = self.streams.get(stream_id) {
|
||||
match tx.send(frame) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(_) => Ok(()), // No receivers, but that's not an error
|
||||
}
|
||||
} else {
|
||||
Err(anyhow::anyhow!("Stream '{}' not found", stream_id))
|
||||
}
|
||||
}
|
||||
|
||||
/// Register a new video stream with the given ID
|
||||
pub fn add_stream(&mut self, stream_id: String) {
|
||||
let (tx, rx) = broadcast::channel::<Vec<u8>>(256); // 256 frame buffer capacity
|
||||
self.streams.insert(stream_id, (tx, rx));
|
||||
}
|
||||
|
||||
/// Get list of available stream IDs
|
||||
pub fn get_streams(&self) -> Vec<String> {
|
||||
self.streams.keys().cloned().collect()
|
||||
}
|
||||
|
||||
/// Check if a stream exists
|
||||
pub fn has_stream(&self, stream_id: &str) -> bool {
|
||||
self.streams.contains_key(stream_id)
|
||||
}
|
||||
|
||||
/// Remove a stream and all its subscribers
|
||||
pub fn remove_stream(&mut self, stream_id: &str) -> bool {
|
||||
self.streams.remove(stream_id).is_some()
|
||||
}
|
||||
|
||||
/// Subscribe to a video stream and get a receiver
|
||||
/// Returns the receiver for the new subscription
|
||||
pub fn subscribe_to_stream(&self, stream_id: &str) -> Option<broadcast::Receiver<Vec<u8>>> {
|
||||
self.streams
|
||||
.get(stream_id)
|
||||
.map(|(_tx, rx)| rx.resubscribe()) // Resubscribe to create a new independent receiver
|
||||
}
|
||||
|
||||
/// Get subscriber count for a stream
|
||||
pub fn get_subscriber_count(&self, stream_id: &str) -> usize {
|
||||
self.streams
|
||||
.get(stream_id)
|
||||
.map_or(0, |(tx, _rx)| tx.receiver_count())
|
||||
}
|
||||
|
||||
/// Get a stream transmitter for sending frames (used by machines to send video)
|
||||
/// Returns None if the stream doesn't exist
|
||||
pub fn get_sender(&self, stream_id: &str) -> Option<broadcast::Sender<Vec<u8>>> {
|
||||
self.streams.get(stream_id).map(|(tx, _rx)| tx.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for VideoStreamManager {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_video_stream_manager() {
|
||||
let mut manager = VideoStreamManager::new();
|
||||
|
||||
// Register streams
|
||||
manager.register_stream("camera1".to_string());
|
||||
manager.register_stream("camera2".to_string());
|
||||
|
||||
assert_eq!(manager.get_stream_ids().len(), 2);
|
||||
assert!(manager.has_stream("camera1"));
|
||||
assert!(manager.has_stream("camera2"));
|
||||
assert!(!manager.has_stream("camera3"));
|
||||
|
||||
// Subscribe to streams
|
||||
let mut rx1 = manager.subscribe_to_stream("camera1").unwrap();
|
||||
let mut rx2 = manager.subscribe_to_stream("camera1").unwrap();
|
||||
let mut rx3 = manager.subscribe_to_stream("camera2").unwrap();
|
||||
|
||||
assert_eq!(manager.get_subscriber_count("camera1"), 3); // Original + 2 clones
|
||||
assert_eq!(manager.get_subscriber_count("camera2"), 2); // Original + 1 clone
|
||||
assert_eq!(manager.get_subscriber_count("nonexistent"), 0);
|
||||
|
||||
// Send frames
|
||||
let frame1 = vec![1, 2, 3, 4];
|
||||
let frame2 = vec![5, 6, 7, 8];
|
||||
|
||||
assert!(manager.send_single_frame("camera1", frame1.clone()));
|
||||
assert!(manager.send_single_frame("camera2", frame2.clone()));
|
||||
assert!(!manager.send_single_frame("nonexistent", vec![]));
|
||||
|
||||
// Both camera1 subscribers should receive the frame
|
||||
assert_eq!(rx1.recv().await.unwrap(), frame1);
|
||||
assert_eq!(rx2.recv().await.unwrap(), frame1);
|
||||
|
||||
// Camera2 subscriber should receive its frame
|
||||
assert_eq!(rx3.recv().await.unwrap(), frame2);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_video_frame_broadcasting() {
|
||||
let mut manager = VideoStreamManager::new();
|
||||
manager.register_stream("main_camera".to_string());
|
||||
|
||||
// Multiple clients subscribe to the same camera
|
||||
let mut rx1 = manager.subscribe_to_stream("main_camera").unwrap();
|
||||
let mut rx2 = manager.subscribe_to_stream("main_camera").unwrap();
|
||||
let mut rx3 = manager.subscribe_to_stream("main_camera").unwrap();
|
||||
|
||||
// Simulate video frames
|
||||
let frames = vec![
|
||||
vec![0xFF, 0xD8, 0xFF, 0xE0], // JPEG header
|
||||
vec![0x00, 0x01, 0x02, 0x03], // Frame 1
|
||||
vec![0x04, 0x05, 0x06, 0x07], // Frame 2
|
||||
];
|
||||
|
||||
for frame in &frames {
|
||||
assert!(manager.send_single_frame("main_camera", frame.clone()));
|
||||
}
|
||||
|
||||
// All clients should receive all frames in order
|
||||
for frame in &frames {
|
||||
assert_eq!(rx1.recv().await.unwrap(), *frame);
|
||||
assert_eq!(rx2.recv().await.unwrap(), *frame);
|
||||
assert_eq!(rx3.recv().await.unwrap(), *frame);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stream_management() {
|
||||
let mut manager = VideoStreamManager::new();
|
||||
|
||||
// Initially no streams
|
||||
assert_eq!(manager.get_stream_ids().len(), 0);
|
||||
|
||||
// Add streams
|
||||
manager.register_stream("front_camera".to_string());
|
||||
manager.register_stream("rear_camera".to_string());
|
||||
manager.register_stream("side_camera".to_string());
|
||||
|
||||
let mut stream_ids = manager.get_stream_ids();
|
||||
stream_ids.sort(); // Sort for consistent testing
|
||||
assert_eq!(
|
||||
stream_ids,
|
||||
vec!["front_camera", "rear_camera", "side_camera"]
|
||||
);
|
||||
|
||||
// Remove a stream
|
||||
assert!(manager.remove_stream("rear_camera"));
|
||||
assert!(!manager.remove_stream("rear_camera")); // Already removed
|
||||
assert!(!manager.has_stream("rear_camera"));
|
||||
|
||||
assert_eq!(manager.get_stream_ids().len(), 2);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_send_frame_results() {
|
||||
let mut manager = VideoStreamManager::new();
|
||||
manager.register_stream("test_camera".to_string());
|
||||
|
||||
// One initial receiver (the original one created in register_stream)
|
||||
let result = manager.send_frame("test_camera", vec![1, 2, 3]).unwrap();
|
||||
assert_eq!(result, 1); // Number of subscribers
|
||||
|
||||
// Add subscribers by cloning the receiver
|
||||
let _rx1 = manager.subscribe_to_stream("test_camera").unwrap();
|
||||
let _rx2 = manager.subscribe_to_stream("test_camera").unwrap();
|
||||
|
||||
// Send frame to multiple subscribers (original + 2 clones = 3 total)
|
||||
let result = manager.send_frame("test_camera", vec![4, 5, 6]).unwrap();
|
||||
assert_eq!(result, 3); // Number of subscribers
|
||||
|
||||
// Try sending to non-existent stream
|
||||
let result = manager.send_frame("nonexistent", vec![7, 8, 9]);
|
||||
assert!(result.is_err());
|
||||
assert_eq!(result.unwrap_err(), "Stream 'nonexistent' not found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_video_stream_manager() {
|
||||
let manager = VideoStreamManager::default();
|
||||
assert_eq!(manager.get_stream_ids().len(), 0);
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
# Ethercat Tab shows no Ethercat Terminals!
|
||||
|
||||
When the Ethercat page does not show any terminals, this can be due to multiple reasons
|
||||
|
||||
## 1: Incorrect Connection of EK1100
|
||||
|
||||
Check that the computers ethernet cable is properly connected to the EK1100 IN port
|
||||
|
||||
Check the cabling if more than one EK1100 is used.
|
||||
When you have multiple EK the connection should be like this:
|
||||
|
||||
Master (Your pc) -> Ethernet cable -> IN EK1100 (A)
|
||||
|
||||
OUT EK1100 (A) -> Ethernet cable -> IN EK1100 (B)
|
||||
|
||||
If you connected any part of this incorrectly the ethercat software will try to communicate but will fail, because the protocol works only if all devices are connect in series.
|
||||
|
||||
## 2: Defective EtherCAT Terminal
|
||||
|
||||
If a Terminal is somehow defective it can disrupt the communication flow expected by EtherCAT thus causing no devices to show up, as ethercat requires all terminals to work properly.
|
||||
|
||||
To identify the defective terminal start unplugging terminals starting from the End terminal. Working your way backwards. Every time you unplug a terminal restart the backend process.
|
||||
|
||||
You know that you have removed the defective terminal when the ethercat tab shows devices after your restart.
|
||||
|
||||
Replace that terminal with an appropriate one and repeat this if the error persists.
|
||||
|
||||
## 3: Corrupt Firmware on EtherCAT Terminal
|
||||
|
||||
Another reason, that an Ethercat Terminal doesnt work properly, is that the firmware is corrupt. This could happen due to overvoltage,undervoltage or shorts, in some cases this is recoverable by reflashing the firmware.
|
||||
|
||||
Our repository contains dumps for all of the EtherCAT Terminals, that we have used. To upload the firmware you can follow this guide: https://infosys.beckhoff.com/english.php?content=../content/1033/ethercatsystem/402909835.html&id=
|
||||
|
||||
To follow this guide, you need to have TwinCat installed and a License.
|
||||
|
||||
# Motor does not start!
|
||||
|
||||
If the Motor does not start after trying to talk to the inverter, the most common reason is that the inverter's serial connection settings (baudrate,encoding ...) are incorrect.
|
||||
|
||||
The correct settings can be found in mitsubishi_inverter.md under `All Settings needed for the Inverter Communication/Operation with Extruder`
|
||||
Generated
+4
-5
@@ -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"
|
||||
}
|
||||
},
|
||||
@@ -14552,10 +14552,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",
|
||||
|
||||
@@ -60,7 +60,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"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -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 = "";
|
||||
|
||||
@@ -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,7 +56,6 @@ export function useExtruder2() {
|
||||
middlePower,
|
||||
backPower,
|
||||
combinedPower,
|
||||
totalEnergyKWh,
|
||||
} = useExtruder2Namespace(machineIdentification);
|
||||
|
||||
// Single optimistic state for all state management
|
||||
@@ -383,7 +382,6 @@ export function useExtruder2() {
|
||||
middlePower,
|
||||
backPower,
|
||||
combinedPower,
|
||||
totalEnergyKWh,
|
||||
|
||||
// Loading states
|
||||
isLoading: stateOptimistic.isOptimistic,
|
||||
|
||||
@@ -2,13 +2,11 @@ import React from "react";
|
||||
import { TimeSeries } from "@/lib/timeseries";
|
||||
import { cva } from "class-variance-authority";
|
||||
|
||||
type DiameterVisualisationProps = {
|
||||
type AnimateCableProps = {
|
||||
targetDiameter: number;
|
||||
lowTolerance: number;
|
||||
highTolerance: number;
|
||||
diameter: TimeSeries;
|
||||
x_value?: TimeSeries;
|
||||
y_value?: TimeSeries;
|
||||
};
|
||||
|
||||
// Ring color depending on tolerance
|
||||
@@ -26,7 +24,7 @@ const dashedRingClass = cva(
|
||||
"absolute rounded-full border border-dashed border-black z-15",
|
||||
);
|
||||
|
||||
// Dynamic animated circle/ellipse
|
||||
// Dynamic animated circle
|
||||
const dynamicCircleClass = cva(
|
||||
"absolute rounded-full border-2 border-black bg-transparent z-30 transition-all duration-300 ease-in-out",
|
||||
);
|
||||
@@ -36,86 +34,103 @@ export function DiameterVisualisation({
|
||||
lowTolerance,
|
||||
highTolerance,
|
||||
diameter,
|
||||
x_value,
|
||||
y_value,
|
||||
}: DiameterVisualisationProps) {
|
||||
}: AnimateCableProps) {
|
||||
const actualDiameter = diameter.current?.value ?? 0.0;
|
||||
const actualX = x_value?.current?.value ?? actualDiameter;
|
||||
const actualY = y_value?.current?.value ?? actualDiameter;
|
||||
|
||||
const minDia = targetDiameter - lowTolerance;
|
||||
const maxDia = targetDiameter + highTolerance;
|
||||
const totalTolerance = lowTolerance + highTolerance;
|
||||
const midDia = minDia + (maxDia - minDia) * (lowTolerance / totalTolerance);
|
||||
|
||||
const pixelMin = 100;
|
||||
const pixelMax = 200;
|
||||
|
||||
const lerp = (
|
||||
value: number,
|
||||
inputMin: number,
|
||||
inputMax: number,
|
||||
outputMin: number,
|
||||
outputMax: number,
|
||||
) =>
|
||||
outputMin +
|
||||
((value - inputMin) * (outputMax - outputMin)) / (inputMax - inputMin);
|
||||
) => {
|
||||
return (
|
||||
outputMin +
|
||||
((value - inputMin) * (outputMax - outputMin)) / (inputMax - inputMin)
|
||||
);
|
||||
};
|
||||
const pixelMid = lerp(midDia, minDia, maxDia, pixelMin, pixelMax);
|
||||
let dynamicDiameterPx: number | null = null;
|
||||
|
||||
const clamp = (value: number, min: number, max: number) =>
|
||||
Math.min(Math.max(value, min), max);
|
||||
|
||||
const radiusX = clamp(
|
||||
lerp(actualX, minDia, maxDia, pixelMin / 2, pixelMax / 2),
|
||||
pixelMin / 2,
|
||||
pixelMax / 2,
|
||||
);
|
||||
|
||||
const radiusY = clamp(
|
||||
lerp(actualY, minDia, maxDia, pixelMin / 2, pixelMax / 2),
|
||||
pixelMin / 2,
|
||||
pixelMax / 2,
|
||||
);
|
||||
if (actualDiameter > 0) {
|
||||
if (actualDiameter < minDia) {
|
||||
dynamicDiameterPx = lerp(actualDiameter, 0, minDia, 0, pixelMin);
|
||||
} else if (actualDiameter <= maxDia) {
|
||||
dynamicDiameterPx = lerp(
|
||||
actualDiameter,
|
||||
minDia,
|
||||
maxDia,
|
||||
pixelMin,
|
||||
pixelMax,
|
||||
);
|
||||
} else {
|
||||
const dynamicDiameterPxLerp = lerp(
|
||||
actualDiameter,
|
||||
maxDia,
|
||||
maxDia * 1.5,
|
||||
pixelMax,
|
||||
pixelMax * 1.1,
|
||||
);
|
||||
dynamicDiameterPx = clamp(
|
||||
dynamicDiameterPxLerp,
|
||||
pixelMin,
|
||||
pixelMax * 1.1,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const bigRadius = pixelMax / 2;
|
||||
const smallRadius = pixelMin / 2;
|
||||
const midRadius = pixelMid / 2;
|
||||
const dynamicRadius = dynamicDiameterPx ? dynamicDiameterPx / 2 : 0;
|
||||
const inTolerance = actualDiameter >= minDia && actualDiameter <= maxDia;
|
||||
|
||||
// Center the shape
|
||||
const centerStyle = (rx: number, ry: number) => ({
|
||||
width: rx * 2,
|
||||
height: ry * 2,
|
||||
top: `${bigRadius - ry}px`,
|
||||
left: `${bigRadius - rx}px`,
|
||||
const centerStyle = (radius: number) => ({
|
||||
width: radius * 2,
|
||||
height: radius * 2,
|
||||
top: `${bigRadius - radius}px`,
|
||||
left: `${bigRadius - radius}px`,
|
||||
});
|
||||
|
||||
// Check if current measurement is in tolerance
|
||||
const inTolerance =
|
||||
actualX >= minDia &&
|
||||
actualX <= maxDia &&
|
||||
actualY >= minDia &&
|
||||
actualY <= maxDia;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="relative mx-auto"
|
||||
style={{ width: pixelMax, height: pixelMax }}
|
||||
>
|
||||
{/* Outer ring background */}
|
||||
<div
|
||||
className={`${ringClass({ state: inTolerance ? "inRange" : "outOfRange" })} top-0 left-0 z-10`}
|
||||
className={`${ringClass({
|
||||
state: inTolerance ? "inRange" : "outOfRange",
|
||||
})} top-0 left-0 z-10`}
|
||||
style={{ width: pixelMax, height: pixelMax }}
|
||||
/>
|
||||
|
||||
{/* Inner white circle to cut out the center */}
|
||||
<div
|
||||
className="absolute z-20 rounded-full bg-white"
|
||||
style={centerStyle(pixelMin / 2, pixelMin / 2)}
|
||||
/>
|
||||
<div
|
||||
className={dashedRingClass()}
|
||||
style={centerStyle(pixelMax / 2, pixelMax / 2)}
|
||||
/>
|
||||
<div
|
||||
className={dynamicCircleClass()}
|
||||
style={{
|
||||
...centerStyle(radiusX, radiusY),
|
||||
/* rotate by 45deg to mimic the rotated axis of the Laser */
|
||||
transform: "rotate(45deg)",
|
||||
}}
|
||||
style={centerStyle(smallRadius)}
|
||||
/>
|
||||
|
||||
{/* Dashed ring in the middle */}
|
||||
<div className={dashedRingClass()} style={centerStyle(midRadius)} />
|
||||
|
||||
{/* Dynamic circle */}
|
||||
{dynamicDiameterPx !== null && (
|
||||
<div
|
||||
className={dynamicCircleClass()}
|
||||
style={centerStyle(dynamicRadius)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -14,8 +14,6 @@ import { DiameterVisualisation } from "../DiameterVisualisation";
|
||||
export function Laser1ControlPage() {
|
||||
const {
|
||||
diameter,
|
||||
x_value,
|
||||
y_value,
|
||||
state,
|
||||
defaultState,
|
||||
setTargetDiameter,
|
||||
@@ -36,37 +34,15 @@ export function Laser1ControlPage() {
|
||||
lowTolerance={lowerTolerance}
|
||||
highTolerance={higherTolerance}
|
||||
diameter={diameter}
|
||||
x_value={x_value}
|
||||
y_value={y_value}
|
||||
/>
|
||||
<div className="flex flex-row items-center gap-6">
|
||||
<TimeSeriesValueNumeric
|
||||
label="Diameter"
|
||||
label="Current Diameter"
|
||||
unit="mm"
|
||||
timeseries={diameter}
|
||||
renderValue={(value) => value.toFixed(3)}
|
||||
/>
|
||||
</div>
|
||||
{x_value?.current && (
|
||||
<div className="flex flex-row items-center gap-6">
|
||||
<TimeSeriesValueNumeric
|
||||
label="X-Diameter"
|
||||
unit="mm"
|
||||
timeseries={x_value}
|
||||
renderValue={(value) => value.toFixed(3)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{y_value?.current && (
|
||||
<div className="flex flex-row items-center gap-6">
|
||||
<TimeSeriesValueNumeric
|
||||
label="Y-Diameter"
|
||||
unit="mm"
|
||||
timeseries={y_value}
|
||||
renderValue={(value) => value.toFixed(3)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</ControlCard>
|
||||
<ControlCard title="Settings">
|
||||
<Label label="Set Target Diameter">
|
||||
|
||||
@@ -10,124 +10,63 @@ import React from "react";
|
||||
import { useLaser1 } from "./useLaser1";
|
||||
|
||||
export function Laser1GraphsPage() {
|
||||
const { diameter, x_value, y_value, state } = useLaser1();
|
||||
const { diameter, state } = useLaser1();
|
||||
|
||||
const syncHook = useGraphSync("diameter-group");
|
||||
const targetDiameter = state?.laser_state?.target_diameter ?? 0;
|
||||
const lowerTolerance = state?.laser_state?.lower_tolerance ?? 0;
|
||||
const higherTolerance = state?.laser_state?.higher_tolerance ?? 0;
|
||||
|
||||
const isTwoAxis = !!x_value?.current || !!y_value?.current;
|
||||
|
||||
const diameterColor = "#3b82f6";
|
||||
const xDiameterColor = "#ef4444";
|
||||
const yDiameterColor = "#22c55e";
|
||||
|
||||
const config: GraphConfig = {
|
||||
title: "Diameter",
|
||||
defaultTimeWindow: 30 * 60 * 1000, // 30 minutes
|
||||
exportFilename: "diameter_data",
|
||||
colors: {
|
||||
primary: diameterColor,
|
||||
primary: "#3b82f6",
|
||||
grid: "#e2e8f0",
|
||||
axis: "#64748b",
|
||||
background: "#ffffff",
|
||||
},
|
||||
};
|
||||
if (!isTwoAxis) {
|
||||
return (
|
||||
<Page className="pb-27">
|
||||
<div className="flex flex-col gap-4">
|
||||
<AutoSyncedBigGraph
|
||||
syncHook={syncHook}
|
||||
newData={{
|
||||
newData: diameter,
|
||||
color: diameterColor,
|
||||
lines: [
|
||||
{
|
||||
type: "threshold",
|
||||
value: targetDiameter + higherTolerance,
|
||||
label: "Upper Threshold",
|
||||
color: diameterColor,
|
||||
dash: [5, 5],
|
||||
},
|
||||
{
|
||||
type: "threshold",
|
||||
value: targetDiameter - lowerTolerance,
|
||||
label: "Lower Threshold",
|
||||
color: diameterColor,
|
||||
dash: [5, 5],
|
||||
},
|
||||
{
|
||||
type: "target",
|
||||
value: targetDiameter,
|
||||
label: "Target",
|
||||
color: diameterColor,
|
||||
},
|
||||
],
|
||||
}}
|
||||
unit="mm"
|
||||
renderValue={(value) => value.toFixed(3)}
|
||||
config={config}
|
||||
graphId="diameter-graph"
|
||||
/>
|
||||
</div>
|
||||
<SyncedFloatingControlPanel controlProps={syncHook.controlProps} />
|
||||
</Page>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Page className="pb-27">
|
||||
<div className="flex flex-col gap-4">
|
||||
<AutoSyncedBigGraph
|
||||
syncHook={syncHook}
|
||||
newData={[
|
||||
|
||||
return (
|
||||
<Page className="pb-27">
|
||||
<div className="flex flex-col gap-4">
|
||||
<AutoSyncedBigGraph
|
||||
syncHook={syncHook}
|
||||
newData={{
|
||||
newData: diameter,
|
||||
color: "#3b82f6",
|
||||
lines: [
|
||||
{
|
||||
newData: diameter,
|
||||
color: diameterColor,
|
||||
title: "Diameter",
|
||||
lines: [
|
||||
{
|
||||
type: "threshold",
|
||||
value: targetDiameter + higherTolerance,
|
||||
label: "Upper Threshold",
|
||||
color: diameterColor,
|
||||
dash: [5, 5],
|
||||
},
|
||||
{
|
||||
type: "threshold",
|
||||
value: targetDiameter - lowerTolerance,
|
||||
label: "Lower Threshold",
|
||||
color: diameterColor,
|
||||
dash: [5, 5],
|
||||
},
|
||||
{
|
||||
type: "target",
|
||||
value: targetDiameter,
|
||||
label: "Target",
|
||||
color: diameterColor,
|
||||
},
|
||||
],
|
||||
type: "threshold",
|
||||
value: targetDiameter + higherTolerance,
|
||||
label: "Upper Threshold",
|
||||
color: "#3b82f6",
|
||||
dash: [5, 5],
|
||||
},
|
||||
{
|
||||
newData: x_value,
|
||||
color: xDiameterColor,
|
||||
title: "X-Diameter",
|
||||
type: "threshold",
|
||||
value: targetDiameter - lowerTolerance,
|
||||
label: "Lower Threshold",
|
||||
color: "#3b82f6",
|
||||
dash: [5, 5],
|
||||
},
|
||||
{
|
||||
newData: y_value,
|
||||
color: yDiameterColor,
|
||||
title: "Y-Diameter",
|
||||
type: "target",
|
||||
value: targetDiameter,
|
||||
label: "Target",
|
||||
color: "#3b82f6",
|
||||
},
|
||||
]}
|
||||
unit="mm"
|
||||
renderValue={(value) => value.toFixed(3)}
|
||||
config={config}
|
||||
graphId="diameter-graph"
|
||||
/>
|
||||
</div>
|
||||
<SyncedFloatingControlPanel controlProps={syncHook.controlProps} />
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
],
|
||||
}}
|
||||
unit="mm"
|
||||
renderValue={(value) => value.toFixed(3)}
|
||||
config={config}
|
||||
graphId="diameter-graph"
|
||||
/>
|
||||
</div>
|
||||
<SyncedFloatingControlPanel controlProps={syncHook.controlProps} />
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
@@ -28,8 +28,6 @@ import {
|
||||
*/
|
||||
export const liveValuesEventDataSchema = z.object({
|
||||
diameter: z.number(),
|
||||
x_value: z.number().nullable(),
|
||||
y_value: z.number().nullable(),
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -59,8 +57,6 @@ export type Laser1NamespaceStore = {
|
||||
|
||||
// Time series data for live values
|
||||
diameter: TimeSeries;
|
||||
x_value: TimeSeries;
|
||||
y_value: TimeSeries;
|
||||
};
|
||||
|
||||
// Constants for time durations
|
||||
@@ -74,20 +70,6 @@ const { initialTimeSeries: diameter, insert: addDiameter } = createTimeSeries(
|
||||
FIVE_SECOND,
|
||||
ONE_HOUR,
|
||||
);
|
||||
|
||||
const { initialTimeSeries: x_value, insert: addXValue } = createTimeSeries(
|
||||
TWENTY_MILLISECOND,
|
||||
ONE_SECOND,
|
||||
FIVE_SECOND,
|
||||
ONE_HOUR,
|
||||
);
|
||||
|
||||
const { initialTimeSeries: y_value, insert: addYValue } = createTimeSeries(
|
||||
TWENTY_MILLISECOND,
|
||||
ONE_SECOND,
|
||||
FIVE_SECOND,
|
||||
ONE_HOUR,
|
||||
);
|
||||
/**
|
||||
* Factory function to create a new Laser1 namespace store
|
||||
* @returns A new Zustand store instance for Laser1 namespace
|
||||
@@ -98,8 +80,6 @@ export const createLaser1NamespaceStore = (): StoreApi<Laser1NamespaceStore> =>
|
||||
state: null,
|
||||
defaultState: null,
|
||||
diameter: diameter,
|
||||
x_value: x_value,
|
||||
y_value: y_value,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -139,34 +119,13 @@ export function laser1MessageHandler(
|
||||
// Live values events (keep for 1 hour)
|
||||
else if (eventName === "LiveValuesEvent") {
|
||||
const liveValuesEvent = liveValuesEventSchema.parse(event);
|
||||
const diameterValue: TimeSeriesValue = {
|
||||
const timeseriesValue: TimeSeriesValue = {
|
||||
value: liveValuesEvent.data.diameter,
|
||||
timestamp: event.ts,
|
||||
};
|
||||
if (liveValuesEvent.data.x_value !== null) {
|
||||
const xValue: TimeSeriesValue = {
|
||||
value: liveValuesEvent.data.x_value,
|
||||
timestamp: event.ts,
|
||||
};
|
||||
updateStore((state) => ({
|
||||
...state,
|
||||
x_value: addXValue(state.x_value, xValue),
|
||||
}));
|
||||
}
|
||||
|
||||
if (liveValuesEvent.data.y_value !== null) {
|
||||
const yValue: TimeSeriesValue = {
|
||||
value: liveValuesEvent.data.y_value,
|
||||
timestamp: event.ts,
|
||||
};
|
||||
updateStore((state) => ({
|
||||
...state,
|
||||
y_value: addYValue(state.y_value, yValue),
|
||||
}));
|
||||
}
|
||||
updateStore((state) => ({
|
||||
...state,
|
||||
diameter: addDiameter(state.diameter, diameterValue),
|
||||
diameter: addDiameter(state.diameter, timeseriesValue),
|
||||
}));
|
||||
} else {
|
||||
handleUnhandledEventError(eventName);
|
||||
|
||||
@@ -11,8 +11,9 @@ import { produce } from "immer";
|
||||
|
||||
function useLaser(machine_identification_unique: MachineIdentificationUnique) {
|
||||
// Get consolidated state and live values from namespace
|
||||
const { state, defaultState, diameter, x_value, y_value } =
|
||||
useLaser1Namespace(machine_identification_unique);
|
||||
const { state, defaultState, diameter } = useLaser1Namespace(
|
||||
machine_identification_unique,
|
||||
);
|
||||
|
||||
// Single optimistic state for all state management
|
||||
const stateOptimistic = useStateOptimistic<StateEvent>();
|
||||
@@ -110,8 +111,6 @@ function useLaser(machine_identification_unique: MachineIdentificationUnique) {
|
||||
|
||||
// Live values (TimeSeries)
|
||||
diameter,
|
||||
x_value,
|
||||
y_value,
|
||||
|
||||
// Loading states
|
||||
isLoading: stateOptimistic.isOptimistic,
|
||||
|
||||
@@ -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">
|
||||
@@ -112,14 +119,15 @@ export function Winder2ControlPage() {
|
||||
maxLabel="OUT"
|
||||
max={180}
|
||||
renderValue={(value) => roundToDecimals(value, 0)}
|
||||
inverted
|
||||
onChange={setTraverseLimitOuter}
|
||||
/>
|
||||
<TouchButton
|
||||
variant="outline"
|
||||
icon="lu:ArrowLeftToLine"
|
||||
onClick={gotoTraverseLimitOuter}
|
||||
disabled={isDisabled}
|
||||
isLoading={isLoading}
|
||||
disabled={isDisabled || !state?.traverse_state?.can_go_out}
|
||||
isLoading={isLoading || state?.traverse_state?.is_going_out}
|
||||
>
|
||||
Go to Outer Limit
|
||||
</TouchButton>
|
||||
@@ -148,8 +156,8 @@ export function Winder2ControlPage() {
|
||||
variant="outline"
|
||||
icon="lu:ArrowRightToLine"
|
||||
onClick={gotoTraverseLimitInner}
|
||||
disabled={isDisabled}
|
||||
isLoading={isLoading}
|
||||
disabled={isDisabled || !state?.traverse_state?.can_go_in}
|
||||
isLoading={isLoading || state?.traverse_state?.is_going_in}
|
||||
>
|
||||
Go to Inner Limit
|
||||
</TouchButton>
|
||||
@@ -170,8 +178,8 @@ export function Winder2ControlPage() {
|
||||
variant="outline"
|
||||
icon="lu:House"
|
||||
onClick={() => gotoTraverseHome()}
|
||||
disabled={isDisabled}
|
||||
isLoading={isLoading}
|
||||
disabled={isDisabled || !state?.traverse_state?.can_go_home}
|
||||
isLoading={isLoading || state?.traverse_state?.is_going_home}
|
||||
>
|
||||
Go to Home
|
||||
</TouchButton>
|
||||
|
||||
@@ -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);
|
||||
@@ -68,7 +69,7 @@ export function useWinder2() {
|
||||
// Single optimistic state for all state management
|
||||
const stateOptimistic = useStateOptimistic<StateEvent>();
|
||||
|
||||
// Update optimistic state when real state changes
|
||||
// Update optimistic state to real when real state changes
|
||||
useEffect(() => {
|
||||
if (state) {
|
||||
stateOptimistic.setReal(state);
|
||||
@@ -596,6 +597,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,
|
||||
};
|
||||
@@ -259,7 +265,6 @@ export function winder2MessageHandler(
|
||||
|
||||
try {
|
||||
if (eventName === "StateEvent") {
|
||||
console.log(event);
|
||||
// Parse and validate the state event
|
||||
const stateEvent = stateEventSchema.parse(event);
|
||||
|
||||
@@ -280,6 +285,7 @@ export function winder2MessageHandler(
|
||||
traverse_position,
|
||||
puller_speed,
|
||||
spool_rpm,
|
||||
spool_diameter,
|
||||
tension_arm_angle,
|
||||
spool_progress,
|
||||
} = liveValuesEvent.data;
|
||||
@@ -327,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::*;
|
||||
|
||||
@@ -278,9 +278,8 @@ impl StmFeatures {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum EL7031_0030DigitalInputEmulation {
|
||||
#[default]
|
||||
Off,
|
||||
/// Uin > Limit 1 (threshold)
|
||||
Threshold1,
|
||||
@@ -300,18 +299,24 @@ pub enum EL7031_0030DigitalInputEmulation {
|
||||
Hysteresis2,
|
||||
}
|
||||
|
||||
impl Default for EL7031_0030DigitalInputEmulation {
|
||||
fn default() -> Self {
|
||||
EL7031_0030DigitalInputEmulation::Off
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for EL7031_0030DigitalInputEmulation {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
0 => Ok(Self::Off),
|
||||
1 => Ok(Self::Threshold1),
|
||||
2 => Ok(Self::Threshold2),
|
||||
3 => Ok(Self::Band1),
|
||||
4 => Ok(Self::Band2),
|
||||
5 => Ok(Self::Hysteresis1),
|
||||
6 => Ok(Self::Hysteresis2),
|
||||
0 => Ok(EL7031_0030DigitalInputEmulation::Off),
|
||||
1 => Ok(EL7031_0030DigitalInputEmulation::Threshold1),
|
||||
2 => Ok(EL7031_0030DigitalInputEmulation::Threshold2),
|
||||
3 => Ok(EL7031_0030DigitalInputEmulation::Band1),
|
||||
4 => Ok(EL7031_0030DigitalInputEmulation::Band2),
|
||||
5 => Ok(EL7031_0030DigitalInputEmulation::Hysteresis1),
|
||||
6 => Ok(EL7031_0030DigitalInputEmulation::Hysteresis2),
|
||||
_ => Err(anyhow::anyhow!(
|
||||
"Invalid value for EL7031_0030DigitalInputEmulation: {}",
|
||||
value
|
||||
|
||||
@@ -81,12 +81,11 @@ pub struct EL7031_0030RxPdo {
|
||||
pub pos_control_2: Option<PosControl2>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum EL7031_0030PredefinedPdoAssignment {
|
||||
VelocityControlCompact,
|
||||
VelocityControlCompactWithInfoData,
|
||||
VelocityControl,
|
||||
#[default]
|
||||
PositionControl,
|
||||
PositionInterfaceCompact,
|
||||
PositionInterface,
|
||||
@@ -100,7 +99,7 @@ impl PredefinedPdoAssignment<EL7031_0030TxPdo, EL7031_0030RxPdo>
|
||||
{
|
||||
fn txpdo_assignment(&self) -> EL7031_0030TxPdo {
|
||||
match self {
|
||||
Self::VelocityControlCompact => EL7031_0030TxPdo {
|
||||
EL7031_0030PredefinedPdoAssignment::VelocityControlCompact => EL7031_0030TxPdo {
|
||||
enc_status_compact: Some(EncStatusCompact::default()),
|
||||
enc_status: None,
|
||||
enc_timestamp_compact: None,
|
||||
@@ -116,23 +115,25 @@ impl PredefinedPdoAssignment<EL7031_0030TxPdo, EL7031_0030RxPdo>
|
||||
ai_standard_channel_2: Some(AiStandard::default()),
|
||||
ai_compact_channel_2: None,
|
||||
},
|
||||
Self::VelocityControlCompactWithInfoData => EL7031_0030TxPdo {
|
||||
enc_status_compact: Some(EncStatusCompact::default()),
|
||||
enc_status: None,
|
||||
enc_timestamp_compact: None,
|
||||
stm_status: Some(StmStatus::default()),
|
||||
stm_synchron_info_data: Some(StmSynchronInfoData::default()),
|
||||
pos_status_compact: None,
|
||||
pos_status: None,
|
||||
stm_internal_position: None,
|
||||
stm_external_position: None,
|
||||
pos_actual_position_lag: None,
|
||||
ai_standard_channel_1: Some(AiStandard::default()),
|
||||
ai_compact_channel_1: None,
|
||||
ai_standard_channel_2: Some(AiStandard::default()),
|
||||
ai_compact_channel_2: None,
|
||||
},
|
||||
Self::VelocityControl => EL7031_0030TxPdo {
|
||||
EL7031_0030PredefinedPdoAssignment::VelocityControlCompactWithInfoData => {
|
||||
EL7031_0030TxPdo {
|
||||
enc_status_compact: Some(EncStatusCompact::default()),
|
||||
enc_status: None,
|
||||
enc_timestamp_compact: None,
|
||||
stm_status: Some(StmStatus::default()),
|
||||
stm_synchron_info_data: Some(StmSynchronInfoData::default()),
|
||||
pos_status_compact: None,
|
||||
pos_status: None,
|
||||
stm_internal_position: None,
|
||||
stm_external_position: None,
|
||||
pos_actual_position_lag: None,
|
||||
ai_standard_channel_1: Some(AiStandard::default()),
|
||||
ai_compact_channel_1: None,
|
||||
ai_standard_channel_2: Some(AiStandard::default()),
|
||||
ai_compact_channel_2: None,
|
||||
}
|
||||
}
|
||||
EL7031_0030PredefinedPdoAssignment::VelocityControl => EL7031_0030TxPdo {
|
||||
enc_status_compact: None,
|
||||
enc_status: Some(EncStatus::default()),
|
||||
enc_timestamp_compact: None,
|
||||
@@ -148,7 +149,7 @@ impl PredefinedPdoAssignment<EL7031_0030TxPdo, EL7031_0030RxPdo>
|
||||
ai_standard_channel_2: Some(AiStandard::default()),
|
||||
ai_compact_channel_2: None,
|
||||
},
|
||||
Self::PositionControl => EL7031_0030TxPdo {
|
||||
EL7031_0030PredefinedPdoAssignment::PositionControl => EL7031_0030TxPdo {
|
||||
enc_status_compact: None,
|
||||
enc_status: Some(EncStatus::default()),
|
||||
enc_timestamp_compact: None,
|
||||
@@ -164,7 +165,7 @@ impl PredefinedPdoAssignment<EL7031_0030TxPdo, EL7031_0030RxPdo>
|
||||
ai_standard_channel_2: Some(AiStandard::default()),
|
||||
ai_compact_channel_2: None,
|
||||
},
|
||||
Self::PositionInterfaceCompact => EL7031_0030TxPdo {
|
||||
EL7031_0030PredefinedPdoAssignment::PositionInterfaceCompact => EL7031_0030TxPdo {
|
||||
enc_status_compact: None,
|
||||
enc_status: Some(EncStatus::default()),
|
||||
enc_timestamp_compact: None,
|
||||
@@ -180,7 +181,7 @@ impl PredefinedPdoAssignment<EL7031_0030TxPdo, EL7031_0030RxPdo>
|
||||
ai_standard_channel_2: Some(AiStandard::default()),
|
||||
ai_compact_channel_2: None,
|
||||
},
|
||||
Self::PositionInterface => EL7031_0030TxPdo {
|
||||
EL7031_0030PredefinedPdoAssignment::PositionInterface => EL7031_0030TxPdo {
|
||||
enc_status_compact: None,
|
||||
enc_status: Some(EncStatus::default()),
|
||||
enc_timestamp_compact: None,
|
||||
@@ -196,7 +197,7 @@ impl PredefinedPdoAssignment<EL7031_0030TxPdo, EL7031_0030RxPdo>
|
||||
ai_standard_channel_2: Some(AiStandard::default()),
|
||||
ai_compact_channel_2: None,
|
||||
},
|
||||
Self::PositionInterfaceWithInfoData => EL7031_0030TxPdo {
|
||||
EL7031_0030PredefinedPdoAssignment::PositionInterfaceWithInfoData => EL7031_0030TxPdo {
|
||||
enc_status_compact: None,
|
||||
enc_status: Some(EncStatus::default()),
|
||||
enc_timestamp_compact: None,
|
||||
@@ -212,7 +213,7 @@ impl PredefinedPdoAssignment<EL7031_0030TxPdo, EL7031_0030RxPdo>
|
||||
ai_standard_channel_2: Some(AiStandard::default()),
|
||||
ai_compact_channel_2: None,
|
||||
},
|
||||
Self::PositionInterfaceAutoStart => EL7031_0030TxPdo {
|
||||
EL7031_0030PredefinedPdoAssignment::PositionInterfaceAutoStart => EL7031_0030TxPdo {
|
||||
enc_status_compact: None,
|
||||
enc_status: Some(EncStatus::default()),
|
||||
enc_timestamp_compact: None,
|
||||
@@ -228,28 +229,31 @@ impl PredefinedPdoAssignment<EL7031_0030TxPdo, EL7031_0030RxPdo>
|
||||
ai_standard_channel_2: Some(AiStandard::default()),
|
||||
ai_compact_channel_2: None,
|
||||
},
|
||||
Self::PositionInterfaceAutoStartWithInfoData => EL7031_0030TxPdo {
|
||||
enc_status_compact: None,
|
||||
enc_status: Some(EncStatus::default()),
|
||||
enc_timestamp_compact: None,
|
||||
stm_status: Some(StmStatus::default()),
|
||||
stm_synchron_info_data: Some(StmSynchronInfoData::default()),
|
||||
pos_status_compact: None,
|
||||
pos_status: Some(PosStatus::default()),
|
||||
stm_internal_position: None,
|
||||
stm_external_position: None,
|
||||
pos_actual_position_lag: None,
|
||||
ai_standard_channel_1: Some(AiStandard::default()),
|
||||
ai_compact_channel_1: None,
|
||||
ai_standard_channel_2: Some(AiStandard::default()),
|
||||
ai_compact_channel_2: None,
|
||||
},
|
||||
EL7031_0030PredefinedPdoAssignment::PositionInterfaceAutoStartWithInfoData => {
|
||||
EL7031_0030TxPdo {
|
||||
enc_status_compact: None,
|
||||
enc_status: Some(EncStatus::default()),
|
||||
enc_timestamp_compact: None,
|
||||
stm_status: Some(StmStatus::default()),
|
||||
stm_synchron_info_data: Some(StmSynchronInfoData::default()),
|
||||
pos_status_compact: None,
|
||||
pos_status: Some(PosStatus::default()),
|
||||
stm_internal_position: None,
|
||||
stm_external_position: None,
|
||||
pos_actual_position_lag: None,
|
||||
ai_standard_channel_1: Some(AiStandard::default()),
|
||||
ai_compact_channel_1: None,
|
||||
ai_standard_channel_2: Some(AiStandard::default()),
|
||||
ai_compact_channel_2: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn rxpdo_assignment(&self) -> EL7031_0030RxPdo {
|
||||
match self {
|
||||
Self::VelocityControlCompact | Self::VelocityControlCompactWithInfoData => {
|
||||
EL7031_0030PredefinedPdoAssignment::VelocityControlCompact
|
||||
| EL7031_0030PredefinedPdoAssignment::VelocityControlCompactWithInfoData => {
|
||||
EL7031_0030RxPdo {
|
||||
enc_control_compact: Some(EncControlCompact::default()),
|
||||
enc_control: None,
|
||||
@@ -261,7 +265,7 @@ impl PredefinedPdoAssignment<EL7031_0030TxPdo, EL7031_0030RxPdo>
|
||||
pos_control_2: None,
|
||||
}
|
||||
}
|
||||
Self::VelocityControl => EL7031_0030RxPdo {
|
||||
EL7031_0030PredefinedPdoAssignment::VelocityControl => EL7031_0030RxPdo {
|
||||
enc_control_compact: None,
|
||||
enc_control: Some(EncControl::default()),
|
||||
stm_control: Some(StmControl::default()),
|
||||
@@ -271,7 +275,7 @@ impl PredefinedPdoAssignment<EL7031_0030TxPdo, EL7031_0030RxPdo>
|
||||
pos_control: None,
|
||||
pos_control_2: None,
|
||||
},
|
||||
Self::PositionControl => EL7031_0030RxPdo {
|
||||
EL7031_0030PredefinedPdoAssignment::PositionControl => EL7031_0030RxPdo {
|
||||
enc_control_compact: None,
|
||||
enc_control: Some(EncControl::default()),
|
||||
stm_control: Some(StmControl::default()),
|
||||
@@ -281,7 +285,7 @@ impl PredefinedPdoAssignment<EL7031_0030TxPdo, EL7031_0030RxPdo>
|
||||
pos_control: None,
|
||||
pos_control_2: None,
|
||||
},
|
||||
Self::PositionInterfaceCompact => EL7031_0030RxPdo {
|
||||
EL7031_0030PredefinedPdoAssignment::PositionInterfaceCompact => EL7031_0030RxPdo {
|
||||
enc_control_compact: None,
|
||||
enc_control: Some(EncControl::default()),
|
||||
stm_control: Some(StmControl::default()),
|
||||
@@ -291,17 +295,21 @@ impl PredefinedPdoAssignment<EL7031_0030TxPdo, EL7031_0030RxPdo>
|
||||
pos_control: None,
|
||||
pos_control_2: None,
|
||||
},
|
||||
Self::PositionInterface | Self::PositionInterfaceWithInfoData => EL7031_0030RxPdo {
|
||||
enc_control_compact: None,
|
||||
enc_control: Some(EncControl::default()),
|
||||
stm_control: Some(StmControl::default()),
|
||||
stm_position: None,
|
||||
stm_velocity: None,
|
||||
pos_control_compact: None,
|
||||
pos_control: Some(PosControl::default()),
|
||||
pos_control_2: None,
|
||||
},
|
||||
Self::PositionInterfaceAutoStart | Self::PositionInterfaceAutoStartWithInfoData => {
|
||||
EL7031_0030PredefinedPdoAssignment::PositionInterface
|
||||
| EL7031_0030PredefinedPdoAssignment::PositionInterfaceWithInfoData => {
|
||||
EL7031_0030RxPdo {
|
||||
enc_control_compact: None,
|
||||
enc_control: Some(EncControl::default()),
|
||||
stm_control: Some(StmControl::default()),
|
||||
stm_position: None,
|
||||
stm_velocity: None,
|
||||
pos_control_compact: None,
|
||||
pos_control: Some(PosControl::default()),
|
||||
pos_control_2: None,
|
||||
}
|
||||
}
|
||||
EL7031_0030PredefinedPdoAssignment::PositionInterfaceAutoStart
|
||||
| EL7031_0030PredefinedPdoAssignment::PositionInterfaceAutoStartWithInfoData => {
|
||||
EL7031_0030RxPdo {
|
||||
enc_control_compact: None,
|
||||
enc_control: Some(EncControl::default()),
|
||||
@@ -317,6 +325,12 @@ impl PredefinedPdoAssignment<EL7031_0030TxPdo, EL7031_0030RxPdo>
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for EL7031_0030PredefinedPdoAssignment {
|
||||
fn default() -> Self {
|
||||
EL7031_0030PredefinedPdoAssignment::PositionControl
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -32,7 +32,7 @@ pub struct EL7041_0052 {
|
||||
impl NewEthercatDevice for EL7041_0052 {
|
||||
fn new() -> Self {
|
||||
let configuration = EL7041_0052Configuration::default();
|
||||
Self {
|
||||
EL7041_0052 {
|
||||
txpdo: configuration.pdo_assignment.txpdo_assignment(),
|
||||
rxpdo: configuration.pdo_assignment.rxpdo_assignment(),
|
||||
is_used: false,
|
||||
@@ -148,10 +148,12 @@ impl StepperVelocityEL70x1Device<EL7041_0052Port> for EL7041_0052 {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
_ => Err(anyhow!(
|
||||
"Port {:?} is not supported for stepper velocity",
|
||||
port
|
||||
)),
|
||||
_ => {
|
||||
return Err(anyhow!(
|
||||
"Port {:?} is not supported for stepper velocity",
|
||||
port
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,10 +188,12 @@ impl StepperVelocityEL70x1Device<EL7041_0052Port> for EL7041_0052 {
|
||||
torque_reduced: stm_status.torque_reduced,
|
||||
})
|
||||
}
|
||||
_ => Err(anyhow!(
|
||||
"Port {:?} is not supported for stepper velocity",
|
||||
port
|
||||
)),
|
||||
_ => {
|
||||
return Err(anyhow!(
|
||||
"Port {:?} is not supported for stepper velocity",
|
||||
port
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,10 +229,12 @@ impl StepperVelocityEL70x1Device<EL7041_0052Port> for EL7041_0052 {
|
||||
set_counter: self.counter_wrapper.get_override(),
|
||||
})
|
||||
}
|
||||
_ => Err(anyhow!(
|
||||
"Port {:?} is not supported for stepper velocity",
|
||||
port
|
||||
)),
|
||||
_ => {
|
||||
return Err(anyhow!(
|
||||
"Port {:?} is not supported for stepper velocity",
|
||||
port
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -68,9 +68,8 @@ pub struct EL7041_0052RxPdo {
|
||||
pub pos_control_2: Option<PosControl2>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum EL7041_0052PredefinedPdoAssignment {
|
||||
#[default]
|
||||
VelocityControlCompact,
|
||||
VelocityControlCompactWithInfoData,
|
||||
VelocityControl,
|
||||
@@ -87,7 +86,7 @@ impl PredefinedPdoAssignment<EL7041_0052TxPdo, EL7041_0052RxPdo>
|
||||
{
|
||||
fn txpdo_assignment(&self) -> EL7041_0052TxPdo {
|
||||
match self {
|
||||
Self::VelocityControlCompact => EL7041_0052TxPdo {
|
||||
EL7041_0052PredefinedPdoAssignment::VelocityControlCompact => EL7041_0052TxPdo {
|
||||
enc_status_compact: Some(EncStatusCompact::default()),
|
||||
enc_status: None,
|
||||
enc_timestamp_compact: None,
|
||||
@@ -99,19 +98,21 @@ impl PredefinedPdoAssignment<EL7041_0052TxPdo, EL7041_0052RxPdo>
|
||||
stm_external_position: None,
|
||||
pos_actual_position_lag: None,
|
||||
},
|
||||
Self::VelocityControlCompactWithInfoData => EL7041_0052TxPdo {
|
||||
enc_status_compact: Some(EncStatusCompact::default()),
|
||||
enc_status: None,
|
||||
enc_timestamp_compact: None,
|
||||
stm_status: Some(StmStatus::default()),
|
||||
stm_synchron_info_data: Some(StmSynchronInfoData::default()),
|
||||
pos_status_compact: None,
|
||||
pos_status: None,
|
||||
stm_internal_position: None,
|
||||
stm_external_position: None,
|
||||
pos_actual_position_lag: None,
|
||||
},
|
||||
Self::VelocityControl => EL7041_0052TxPdo {
|
||||
EL7041_0052PredefinedPdoAssignment::VelocityControlCompactWithInfoData => {
|
||||
EL7041_0052TxPdo {
|
||||
enc_status_compact: Some(EncStatusCompact::default()),
|
||||
enc_status: None,
|
||||
enc_timestamp_compact: None,
|
||||
stm_status: Some(StmStatus::default()),
|
||||
stm_synchron_info_data: Some(StmSynchronInfoData::default()),
|
||||
pos_status_compact: None,
|
||||
pos_status: None,
|
||||
stm_internal_position: None,
|
||||
stm_external_position: None,
|
||||
pos_actual_position_lag: None,
|
||||
}
|
||||
}
|
||||
EL7041_0052PredefinedPdoAssignment::VelocityControl => EL7041_0052TxPdo {
|
||||
enc_status_compact: None,
|
||||
enc_status: Some(EncStatus::default()),
|
||||
enc_timestamp_compact: None,
|
||||
@@ -123,7 +124,7 @@ impl PredefinedPdoAssignment<EL7041_0052TxPdo, EL7041_0052RxPdo>
|
||||
stm_external_position: None,
|
||||
pos_actual_position_lag: None,
|
||||
},
|
||||
Self::PositionControl => EL7041_0052TxPdo {
|
||||
EL7041_0052PredefinedPdoAssignment::PositionControl => EL7041_0052TxPdo {
|
||||
enc_status_compact: None,
|
||||
enc_status: Some(EncStatus::default()),
|
||||
enc_timestamp_compact: None,
|
||||
@@ -135,7 +136,7 @@ impl PredefinedPdoAssignment<EL7041_0052TxPdo, EL7041_0052RxPdo>
|
||||
stm_external_position: None,
|
||||
pos_actual_position_lag: None,
|
||||
},
|
||||
Self::PositionInterfaceCompact => EL7041_0052TxPdo {
|
||||
EL7041_0052PredefinedPdoAssignment::PositionInterfaceCompact => EL7041_0052TxPdo {
|
||||
enc_status_compact: None,
|
||||
enc_status: Some(EncStatus::default()),
|
||||
enc_timestamp_compact: None,
|
||||
@@ -147,7 +148,7 @@ impl PredefinedPdoAssignment<EL7041_0052TxPdo, EL7041_0052RxPdo>
|
||||
stm_external_position: None,
|
||||
pos_actual_position_lag: None,
|
||||
},
|
||||
Self::PositionInterface => EL7041_0052TxPdo {
|
||||
EL7041_0052PredefinedPdoAssignment::PositionInterface => EL7041_0052TxPdo {
|
||||
enc_status_compact: None,
|
||||
enc_status: Some(EncStatus::default()),
|
||||
enc_timestamp_compact: None,
|
||||
@@ -159,7 +160,7 @@ impl PredefinedPdoAssignment<EL7041_0052TxPdo, EL7041_0052RxPdo>
|
||||
stm_external_position: None,
|
||||
pos_actual_position_lag: None,
|
||||
},
|
||||
Self::PositionInterfaceWithInfoData => EL7041_0052TxPdo {
|
||||
EL7041_0052PredefinedPdoAssignment::PositionInterfaceWithInfoData => EL7041_0052TxPdo {
|
||||
enc_status_compact: None,
|
||||
enc_status: Some(EncStatus::default()),
|
||||
enc_timestamp_compact: None,
|
||||
@@ -171,7 +172,7 @@ impl PredefinedPdoAssignment<EL7041_0052TxPdo, EL7041_0052RxPdo>
|
||||
stm_external_position: None,
|
||||
pos_actual_position_lag: None,
|
||||
},
|
||||
Self::PositionInterfaceAutoStart => EL7041_0052TxPdo {
|
||||
EL7041_0052PredefinedPdoAssignment::PositionInterfaceAutoStart => EL7041_0052TxPdo {
|
||||
enc_status_compact: None,
|
||||
enc_status: Some(EncStatus::default()),
|
||||
enc_timestamp_compact: None,
|
||||
@@ -183,24 +184,27 @@ impl PredefinedPdoAssignment<EL7041_0052TxPdo, EL7041_0052RxPdo>
|
||||
stm_external_position: None,
|
||||
pos_actual_position_lag: None,
|
||||
},
|
||||
Self::PositionInterfaceAutoStartWithInfoData => EL7041_0052TxPdo {
|
||||
enc_status_compact: None,
|
||||
enc_status: Some(EncStatus::default()),
|
||||
enc_timestamp_compact: None,
|
||||
stm_status: Some(StmStatus::default()),
|
||||
stm_synchron_info_data: Some(StmSynchronInfoData::default()),
|
||||
pos_status_compact: None,
|
||||
pos_status: Some(PosStatus::default()),
|
||||
stm_internal_position: None,
|
||||
stm_external_position: None,
|
||||
pos_actual_position_lag: None,
|
||||
},
|
||||
EL7041_0052PredefinedPdoAssignment::PositionInterfaceAutoStartWithInfoData => {
|
||||
EL7041_0052TxPdo {
|
||||
enc_status_compact: None,
|
||||
enc_status: Some(EncStatus::default()),
|
||||
enc_timestamp_compact: None,
|
||||
stm_status: Some(StmStatus::default()),
|
||||
stm_synchron_info_data: Some(StmSynchronInfoData::default()),
|
||||
pos_status_compact: None,
|
||||
pos_status: Some(PosStatus::default()),
|
||||
stm_internal_position: None,
|
||||
stm_external_position: None,
|
||||
pos_actual_position_lag: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn rxpdo_assignment(&self) -> EL7041_0052RxPdo {
|
||||
match self {
|
||||
Self::VelocityControlCompact | Self::VelocityControlCompactWithInfoData => {
|
||||
EL7041_0052PredefinedPdoAssignment::VelocityControlCompact
|
||||
| EL7041_0052PredefinedPdoAssignment::VelocityControlCompactWithInfoData => {
|
||||
EL7041_0052RxPdo {
|
||||
enc_control_compact: Some(EncControlCompact::default()),
|
||||
enc_control: None,
|
||||
@@ -212,7 +216,7 @@ impl PredefinedPdoAssignment<EL7041_0052TxPdo, EL7041_0052RxPdo>
|
||||
pos_control_2: None,
|
||||
}
|
||||
}
|
||||
Self::VelocityControl => EL7041_0052RxPdo {
|
||||
EL7041_0052PredefinedPdoAssignment::VelocityControl => EL7041_0052RxPdo {
|
||||
enc_control_compact: None,
|
||||
enc_control: Some(EncControl::default()),
|
||||
stm_control: Some(StmControl::default()),
|
||||
@@ -222,7 +226,7 @@ impl PredefinedPdoAssignment<EL7041_0052TxPdo, EL7041_0052RxPdo>
|
||||
pos_control: None,
|
||||
pos_control_2: Some(PosControl2::default()),
|
||||
},
|
||||
Self::PositionControl => EL7041_0052RxPdo {
|
||||
EL7041_0052PredefinedPdoAssignment::PositionControl => EL7041_0052RxPdo {
|
||||
enc_control_compact: None,
|
||||
enc_control: Some(EncControl::default()),
|
||||
stm_control: Some(StmControl::default()),
|
||||
@@ -232,7 +236,7 @@ impl PredefinedPdoAssignment<EL7041_0052TxPdo, EL7041_0052RxPdo>
|
||||
pos_control: None,
|
||||
pos_control_2: Some(PosControl2::default()),
|
||||
},
|
||||
Self::PositionInterfaceCompact => EL7041_0052RxPdo {
|
||||
EL7041_0052PredefinedPdoAssignment::PositionInterfaceCompact => EL7041_0052RxPdo {
|
||||
enc_control_compact: None,
|
||||
enc_control: Some(EncControl::default()),
|
||||
stm_control: Some(StmControl::default()),
|
||||
@@ -242,17 +246,21 @@ impl PredefinedPdoAssignment<EL7041_0052TxPdo, EL7041_0052RxPdo>
|
||||
pos_control: None,
|
||||
pos_control_2: Some(PosControl2::default()),
|
||||
},
|
||||
Self::PositionInterface | Self::PositionInterfaceWithInfoData => EL7041_0052RxPdo {
|
||||
enc_control_compact: None,
|
||||
enc_control: Some(EncControl::default()),
|
||||
stm_control: Some(StmControl::default()),
|
||||
stm_position: None,
|
||||
stm_velocity: None,
|
||||
pos_control_compact: None,
|
||||
pos_control: Some(PosControl::default()),
|
||||
pos_control_2: None,
|
||||
},
|
||||
Self::PositionInterfaceAutoStart | Self::PositionInterfaceAutoStartWithInfoData => {
|
||||
EL7041_0052PredefinedPdoAssignment::PositionInterface
|
||||
| EL7041_0052PredefinedPdoAssignment::PositionInterfaceWithInfoData => {
|
||||
EL7041_0052RxPdo {
|
||||
enc_control_compact: None,
|
||||
enc_control: Some(EncControl::default()),
|
||||
stm_control: Some(StmControl::default()),
|
||||
stm_position: None,
|
||||
stm_velocity: None,
|
||||
pos_control_compact: None,
|
||||
pos_control: Some(PosControl::default()),
|
||||
pos_control_2: None,
|
||||
}
|
||||
}
|
||||
EL7041_0052PredefinedPdoAssignment::PositionInterfaceAutoStart
|
||||
| EL7041_0052PredefinedPdoAssignment::PositionInterfaceAutoStartWithInfoData => {
|
||||
EL7041_0052RxPdo {
|
||||
enc_control_compact: None,
|
||||
enc_control: Some(EncControl::default()),
|
||||
@@ -267,3 +275,9 @@ impl PredefinedPdoAssignment<EL7041_0052TxPdo, EL7041_0052RxPdo>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for EL7041_0052PredefinedPdoAssignment {
|
||||
fn default() -> Self {
|
||||
EL7041_0052PredefinedPdoAssignment::VelocityControlCompact
|
||||
}
|
||||
}
|
||||
@@ -262,6 +262,6 @@ pub async fn specific_device_from_devices<DEVICE: EthercatDevice>(
|
||||
|
||||
pub type SubDeviceIdentityTuple = (u32, u32, u32);
|
||||
/// function that converts SubDeviceIdentity to tuple
|
||||
pub const fn subdevice_identity_to_tuple(identity: &SubDeviceIdentity) -> SubDeviceIdentityTuple {
|
||||
pub fn subdevice_identity_to_tuple(identity: &SubDeviceIdentity) -> SubDeviceIdentityTuple {
|
||||
(identity.vendor_id, identity.product_id, identity.revision)
|
||||
}
|
||||
@@ -14,14 +14,8 @@ pub struct CounterWrapperU16U128 {
|
||||
set_counter: Option<i128>,
|
||||
}
|
||||
|
||||
impl Default for CounterWrapperU16U128 {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl CounterWrapperU16U128 {
|
||||
pub const fn new() -> Self {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
counter: 0,
|
||||
last_counter: 0,
|
||||
@@ -31,7 +25,7 @@ impl CounterWrapperU16U128 {
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn update(&mut self, counter: u16, counter_underflow: bool, counter_overflow: bool) {
|
||||
pub fn update(&mut self, counter: u16, counter_underflow: bool, counter_overflow: bool) {
|
||||
// Only process rising edges of the underflow and overflow flags
|
||||
let counter_underflow_rising = counter_underflow && !self.last_counter_underflow;
|
||||
let counter_overflow_rising = counter_overflow && !self.last_counter_overflow;
|
||||
@@ -48,19 +42,19 @@ impl CounterWrapperU16U128 {
|
||||
self.last_counter_overflow = counter_overflow;
|
||||
}
|
||||
|
||||
pub const fn current(&self) -> i128 {
|
||||
pub fn current(&self) -> i128 {
|
||||
self.counter
|
||||
}
|
||||
|
||||
/// Schedules a counter override
|
||||
///
|
||||
/// The value is only set when `pop_set` is called.
|
||||
pub const fn push_override(&mut self, new_counter: i128) {
|
||||
pub fn push_override(&mut self, new_counter: i128) {
|
||||
self.set_counter = Some(new_counter);
|
||||
}
|
||||
|
||||
/// Return the override value as an u16 and overrides the current counter.
|
||||
pub const fn pop_override(&mut self) -> Option<u16> {
|
||||
pub fn pop_override(&mut self) -> Option<u16> {
|
||||
match self.set_counter {
|
||||
Some(counter) => {
|
||||
// set our coutner to the new value
|
||||
@@ -79,12 +73,12 @@ impl CounterWrapperU16U128 {
|
||||
}
|
||||
|
||||
/// Returns the override value as an i128
|
||||
pub const fn get_override(&self) -> Option<i128> {
|
||||
pub fn get_override(&self) -> Option<i128> {
|
||||
self.set_counter
|
||||
}
|
||||
}
|
||||
|
||||
const fn counter_change(
|
||||
fn counter_change(
|
||||
last_counter: u16,
|
||||
counter: u16,
|
||||
counter_underflow: bool,
|
||||
@@ -115,15 +109,15 @@ const fn counter_change(
|
||||
}
|
||||
}
|
||||
|
||||
const fn set_counter_u16_to_i128(new_counter: i128) -> u16 {
|
||||
fn set_counter_u16_to_i128(new_counter: i128) -> u16 {
|
||||
// Use modulo arithmetic to handle both positive and negative values
|
||||
let modulo = (new_counter % (U16_MAX + 1)) as i32;
|
||||
|
||||
if modulo < 0 {
|
||||
// If negative, wrap around from the end
|
||||
(modulo + (U16_MAX + 1) as i32) as u16
|
||||
return (modulo + (U16_MAX + 1) as i32) as u16;
|
||||
} else {
|
||||
modulo as u16
|
||||
return modulo as u16;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ pub struct EL70x1VelocityConverter {
|
||||
}
|
||||
|
||||
impl EL70x1VelocityConverter {
|
||||
pub const fn new(speed_range: &EL70x1SpeedRange) -> Self {
|
||||
pub fn new(speed_range: &EL70x1SpeedRange) -> Self {
|
||||
let speed_range_value = match speed_range {
|
||||
EL70x1SpeedRange::Steps1000 => 1000,
|
||||
EL70x1SpeedRange::Steps2000 => 2000,
|
||||
|
||||
@@ -17,19 +17,19 @@ impl U16SigningConverter {
|
||||
/// assert_eq!(value.into_unsigned(), 42);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub const fn load_raw(value: u16) -> Self {
|
||||
pub fn load_raw(value: u16) -> Self {
|
||||
Self { raw: value }
|
||||
}
|
||||
|
||||
/// Converts the value to an unsigned 16-bit integer.
|
||||
#[inline]
|
||||
pub const fn as_unsigned(self) -> u16 {
|
||||
pub fn as_unsigned(self) -> u16 {
|
||||
self.raw
|
||||
}
|
||||
|
||||
/// Converts the value to a signed 16-bit integer using two's complement.
|
||||
#[inline]
|
||||
pub const fn as_signed(self) -> i16 {
|
||||
pub fn as_signed(self) -> i16 {
|
||||
self.raw as i16
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ impl U16SigningConverter {
|
||||
/// In sign-magnitude representation, the most significant bit represents the sign
|
||||
/// (0 for positive, 1 for negative) and the remaining bits represent the magnitude.
|
||||
#[inline]
|
||||
pub const fn as_signed_magnitude(self) -> i16 {
|
||||
pub fn as_signed_magnitude(self) -> i16 {
|
||||
if self.raw & 0x8000 != 0 {
|
||||
-((self.raw & 0x7FFF) as i16)
|
||||
} else {
|
||||
@@ -47,7 +47,7 @@ impl U16SigningConverter {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn as_absolute(self) -> i16 {
|
||||
pub fn as_absolute(self) -> i16 {
|
||||
self.as_signed().abs()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ impl fmt::Debug for AnalogInput {
|
||||
|
||||
/// Implement on device that have analog inputs
|
||||
impl AnalogInput {
|
||||
pub fn new<PORT>(device: Arc<RwLock<dyn AnalogInputDevice<PORT>>>, port: PORT) -> Self
|
||||
pub fn new<PORT>(device: Arc<RwLock<dyn AnalogInputDevice<PORT>>>, port: PORT) -> AnalogInput
|
||||
where
|
||||
PORT: Clone + Send + Sync + 'static,
|
||||
{
|
||||
@@ -46,7 +46,7 @@ impl AnalogInput {
|
||||
})
|
||||
});
|
||||
|
||||
Self { get_input, range }
|
||||
AnalogInput { get_input, range }
|
||||
}
|
||||
|
||||
/// Value from -1.0 to 1.0
|
||||
|
||||
@@ -23,34 +23,34 @@ pub enum AnalogInputRange {
|
||||
}
|
||||
|
||||
impl AnalogInputRange {
|
||||
pub const fn get_min_raw(&self) -> i16 {
|
||||
match self {
|
||||
Self::Potential { min_raw, .. } => *min_raw,
|
||||
Self::Current { min_raw, .. } => *min_raw,
|
||||
}
|
||||
pub fn get_min_raw(&self) -> i16 {
|
||||
return match self {
|
||||
AnalogInputRange::Potential { min_raw, .. } => *min_raw,
|
||||
AnalogInputRange::Current { min_raw, .. } => *min_raw,
|
||||
};
|
||||
}
|
||||
|
||||
pub const fn get_max_raw(&self) -> i16 {
|
||||
match self {
|
||||
Self::Potential { max_raw, .. } => *max_raw,
|
||||
Self::Current { max_raw, .. } => *max_raw,
|
||||
}
|
||||
pub fn get_max_raw(&self) -> i16 {
|
||||
return match self {
|
||||
AnalogInputRange::Potential { max_raw, .. } => *max_raw,
|
||||
AnalogInputRange::Current { max_raw, .. } => *max_raw,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn raw_to_normalized(&self, raw_value: i16) -> f64 {
|
||||
let range = (self.get_max_raw() as i32 - self.get_min_raw() as i32) as f64;
|
||||
(raw_value as i32 - self.get_min_raw() as i32) as f64 / range
|
||||
return (raw_value as i32 - self.get_min_raw() as i32) as f64 / range;
|
||||
}
|
||||
|
||||
pub fn raw_to_physical(&self, raw_value: i16) -> AnalogInputValue {
|
||||
let normalized = self.raw_to_normalized(raw_value);
|
||||
match self {
|
||||
Self::Potential { min, max, .. } => {
|
||||
let value = *min + (*max - *min).abs() * normalized;
|
||||
AnalogInputRange::Potential { min, max, .. } => {
|
||||
let value = *min + (*max - *min).abs() * normalized as f64;
|
||||
AnalogInputValue::Potential(value)
|
||||
}
|
||||
Self::Current { min, max, .. } => {
|
||||
let value = *min + (*max - *min).abs() * normalized;
|
||||
AnalogInputRange::Current { min, max, .. } => {
|
||||
let value = *min + (*max - *min).abs() * normalized as f64;
|
||||
AnalogInputValue::Current(value)
|
||||
}
|
||||
}
|
||||
@@ -59,11 +59,11 @@ impl AnalogInputRange {
|
||||
/// Convert a normalized value (0 to 1.0) to a physical value
|
||||
pub fn normalized_to_physical(&self, normalized: f32) -> AnalogInputValue {
|
||||
match self {
|
||||
Self::Potential { min, max, .. } => {
|
||||
AnalogInputRange::Potential { min, max, .. } => {
|
||||
let value = *min + (*max - *min).abs() * normalized as f64;
|
||||
AnalogInputValue::Potential(value)
|
||||
}
|
||||
Self::Current { min, max, .. } => {
|
||||
AnalogInputRange::Current { min, max, .. } => {
|
||||
let value = *min + (*max - *min).abs() * normalized as f64;
|
||||
AnalogInputValue::Current(value)
|
||||
}
|
||||
|
||||
Loaded 100 of 168 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user