Compare commits
15
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
71de39e871 | ||
|
|
54ffa193c3 | ||
|
|
03164f1ceb | ||
|
|
f67a46ab4c | ||
|
|
610790a778 | ||
|
|
6eaf451ad4 | ||
|
|
022e0adae3 | ||
|
|
6e6d4b31e7 | ||
|
|
0dafad3fb5 | ||
|
|
534de55239 | ||
|
|
d9f92bcdf2 | ||
|
|
1570299e38 | ||
|
|
c95f6f3bd9 | ||
|
|
9fcde2a234 | ||
|
|
a2ffc41e05 |
No files matched your search
Generated
-33
@@ -2689,8 +2689,6 @@ dependencies = [
|
||||
"smol",
|
||||
"socketioxide",
|
||||
"textplots",
|
||||
"tikv-jemalloc-ctl",
|
||||
"tikv-jemallocator",
|
||||
"tokio",
|
||||
"tonic",
|
||||
"tower-http",
|
||||
@@ -3013,37 +3011,6 @@ dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tikv-jemalloc-ctl"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f21f216790c8df74ce3ab25b534e0718da5a1916719771d3fec23315c99e468b"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"paste",
|
||||
"tikv-jemalloc-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tikv-jemalloc-sys"
|
||||
version = "0.6.0+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd3c60906412afa9c2b5b5a48ca6a5abe5736aec9eb48ad05037a677e52e4e2d"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tikv-jemallocator"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4cec5ff18518d81584f477e9bfdf957f5bb0979b0bac3af4ca30b5b3ae2d2865"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"tikv-jemalloc-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "timerfd"
|
||||
version = "1.6.0"
|
||||
|
||||
@@ -32,7 +32,7 @@ Other
|
||||
|
||||
## Backend
|
||||
|
||||
To interface with Beckhoff and other EtherCAT devices we need an EtherCAY master stoftware. Possibilities are [PySOEM](https://github.com/bnjmnp/pysoem) (Python), [SOEM](https://github.com/OpenEtherCATsociety/SOEM) (C) and [Ethercrab](https://github.com/ethercrab-rs/ethercrab) (Rust). For realtime operation only C and Rust are suitable. We chose Rust because of safety and confidence in the written code.
|
||||
To interface with Beckhoff and other EtherCAT devices we need an EtherCAT master stoftware. Possibilities are [PySOEM](https://github.com/bnjmnp/pysoem) (Python), [SOEM](https://github.com/OpenEtherCATsociety/SOEM) (C) and [Ethercrab](https://github.com/ethercrab-rs/ethercrab) (Rust). For realtime operation only C and Rust are suitable. We chose Rust because of safety and confidence in the written code.
|
||||
|
||||
[SocketIO](https://socket.io/) was chosen for performant event driven communication from the backend to the server. But we still use REST with [axum](https://docs.rs/axum/latest/axum/) for the communication thet benefits from the request/response model.
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ serde_json = "1.0.143"
|
||||
serialport = "4.7.3"
|
||||
smol = "2.0.2"
|
||||
socketioxide = "0.17.2"
|
||||
uom = "0.36.0"
|
||||
uom = { version = "0.36.0", default-features = false, features = ["f64"] }
|
||||
bitvec = { version = "1.0.1", features = ["alloc"] }
|
||||
crc = "3.3.0"
|
||||
serial = "0.4.0"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use uom::si::angle::radian;
|
||||
use uom::si::{angle::radian, f64::Angle};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct AngleConverter {
|
||||
@@ -63,7 +63,7 @@ impl AngleConverter {
|
||||
}
|
||||
|
||||
/// Convert angle from mathematical system to this system (f32)
|
||||
pub fn degrees_encode(&self, math_angle_degrees: f32) -> f32 {
|
||||
pub fn degrees_encode(&self, math_angle_degrees: f64) -> f64 {
|
||||
let normalized_input = self.normalize_angle(math_angle_degrees);
|
||||
|
||||
let mut result = normalized_input;
|
||||
@@ -85,7 +85,7 @@ impl AngleConverter {
|
||||
}
|
||||
|
||||
/// Convert angle from this system to mathematical system (f32)
|
||||
pub fn degrees_decode(&self, system_angle_degrees: f32) -> f32 {
|
||||
pub fn degrees_decode(&self, system_angle_degrees: f64) -> f64 {
|
||||
let normalized_input = self.normalize_angle(system_angle_degrees);
|
||||
|
||||
let mut result = normalized_input;
|
||||
@@ -107,14 +107,14 @@ impl AngleConverter {
|
||||
}
|
||||
|
||||
/// Convert angle in radians from mathematical system to this system (f32)
|
||||
pub fn radians_encode(&self, math_angle_radians: f32) -> f32 {
|
||||
pub fn radians_encode(&self, math_angle_radians: f64) -> f64 {
|
||||
let degrees = math_angle_radians.to_degrees();
|
||||
let converted_degrees = self.degrees_encode(degrees);
|
||||
converted_degrees.to_radians()
|
||||
}
|
||||
|
||||
/// Convert angle in radians from this system to mathematical system (f32)
|
||||
pub fn radians_decode(&self, system_angle_radians: f32) -> f32 {
|
||||
pub fn radians_decode(&self, system_angle_radians: f64) -> f64 {
|
||||
let degrees = system_angle_radians.to_degrees();
|
||||
let converted_degrees = self.degrees_decode(degrees);
|
||||
converted_degrees.to_radians()
|
||||
@@ -179,7 +179,7 @@ impl AngleConverter {
|
||||
}
|
||||
|
||||
/// Normalize angle to [0, 360) range (f32)
|
||||
fn normalize_angle(&self, angle: f32) -> f32 {
|
||||
fn normalize_angle(&self, angle: f64) -> f64 {
|
||||
let mut normalized = angle % 360.0;
|
||||
if normalized < 0.0 {
|
||||
normalized += 360.0;
|
||||
@@ -207,30 +207,12 @@ impl AngleConverterUom {
|
||||
Self { angle_converter }
|
||||
}
|
||||
|
||||
pub fn encode(&self, angle: uom::si::f32::Angle) -> uom::si::f32::Angle {
|
||||
uom::si::f32::Angle::new::<radian>(
|
||||
self.angle_converter.radians_encode(angle.get::<radian>()),
|
||||
)
|
||||
pub fn encode(&self, angle: Angle) -> Angle {
|
||||
Angle::new::<radian>(self.angle_converter.radians_encode(angle.get::<radian>()))
|
||||
}
|
||||
|
||||
pub fn decode(&self, angle: uom::si::f32::Angle) -> uom::si::f32::Angle {
|
||||
uom::si::f32::Angle::new::<radian>(
|
||||
self.angle_converter.radians_decode(angle.get::<radian>()),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn encode_f64(&self, angle: uom::si::f64::Angle) -> uom::si::f64::Angle {
|
||||
uom::si::f64::Angle::new::<radian>(
|
||||
self.angle_converter
|
||||
.radians_encode_f64(angle.get::<radian>()),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn decode_f64(&self, angle: uom::si::f64::Angle) -> uom::si::f64::Angle {
|
||||
uom::si::f64::Angle::new::<radian>(
|
||||
self.angle_converter
|
||||
.radians_decode_f64(angle.get::<radian>()),
|
||||
)
|
||||
pub fn decode(&self, angle: Angle) -> Angle {
|
||||
Angle::new::<radian>(self.angle_converter.radians_decode(angle.get::<radian>()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -297,8 +279,8 @@ mod tests {
|
||||
let converter = AngleConverter::screen();
|
||||
|
||||
// Test radians_encode
|
||||
let result = converter.radians_encode(PI / 2.0); // 90° in radians
|
||||
let expected = 270.0 * PI / 180.0; // 270° in radians
|
||||
let result = converter.radians_encode(PI as f64 / 2.0) as f64; // 90° in radians
|
||||
let expected: f64 = 270.0f64 * PI as f64 / 180.0f64; // 270° in radians
|
||||
assert!((result - expected).abs() < 1e-6);
|
||||
}
|
||||
|
||||
@@ -306,10 +288,10 @@ mod tests {
|
||||
fn test_roundtrip_conversion() {
|
||||
let converter = AngleConverter::new(true, false, true); // X-flip + CW
|
||||
|
||||
let original = 75.0_f32;
|
||||
let original = 75.0_f64;
|
||||
let converted = converter.degrees_encode(original);
|
||||
let roundtrip = converter.degrees_decode(converted);
|
||||
|
||||
assert!((original - roundtrip).abs() < 1e-6);
|
||||
assert!((original - roundtrip as f64).abs() < 1e-6);
|
||||
}
|
||||
}
|
||||
@@ -58,15 +58,10 @@ export function MarkdownWithToc({
|
||||
className={`sticky ${stickyTop}`}
|
||||
style={{ maxHeight: "calc(100vh - 6rem)" }}
|
||||
>
|
||||
{/* Hide scrollbar for the TOC container across browsers */}
|
||||
<style>{`
|
||||
[data-toc-scroll-container] { -ms-overflow-style: none; scrollbar-width: none; }
|
||||
[data-toc-scroll-container]::-webkit-scrollbar { display: none; }
|
||||
`}</style>
|
||||
{/* Table of Contents with hidden scrollbar */}
|
||||
<div
|
||||
className="overflow-y-auto"
|
||||
className="scrollbar-hidden overflow-y-auto"
|
||||
style={{ maxHeight: "calc(100vh - 8rem)" }}
|
||||
data-toc-scroll-container
|
||||
>
|
||||
<MarkdownToc
|
||||
markdownContent={markdownContent}
|
||||
|
||||
@@ -199,7 +199,7 @@ export function Terminal({
|
||||
</div>
|
||||
|
||||
{/* Virtualized List */}
|
||||
<div className="flex-grow bg-black text-white">
|
||||
<div className="flex-grow overflow-y-auto bg-neutral-900 p-4 text-neutral-300">
|
||||
<AutoSizer>
|
||||
{({ width, height }) => (
|
||||
<List
|
||||
|
||||
@@ -177,3 +177,107 @@
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
|
||||
/* macOS-style scrollbars */
|
||||
@layer base {
|
||||
/* WebKit browsers (Safari, Chrome, newer Edge) */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border-radius: 10px;
|
||||
border: 2px solid transparent;
|
||||
background-clip: content-box;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
background-clip: content-box;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:active {
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
background-clip: content-box;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-corner {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* Dark mode adjustments */
|
||||
.dark ::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
background-clip: content-box;
|
||||
}
|
||||
|
||||
.dark ::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(255, 255, 255, 0.4);
|
||||
background-clip: content-box;
|
||||
}
|
||||
|
||||
.dark ::-webkit-scrollbar-thumb:active {
|
||||
background: rgba(255, 255, 255, 0.6);
|
||||
background-clip: content-box;
|
||||
}
|
||||
|
||||
/* Firefox */
|
||||
html,
|
||||
body {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(0, 0, 0, 0.2) transparent;
|
||||
}
|
||||
|
||||
.dark html,
|
||||
.dark body {
|
||||
scrollbar-color: rgba(255, 255, 255, 0.2) transparent;
|
||||
}
|
||||
|
||||
/* Auto-hide behavior for overlay scrollbars */
|
||||
@media (pointer: fine) {
|
||||
::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar:hover {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
/* Container hover shows scrollbars */
|
||||
.scrollbar-visible:hover::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Utility classes for scrollbar customization */
|
||||
.scrollbar-hidden {
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.scrollbar-hidden::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.scrollbar-always-visible::-webkit-scrollbar {
|
||||
width: 8px !important;
|
||||
height: 8px !important;
|
||||
}
|
||||
|
||||
@media (pointer: fine) {
|
||||
.scrollbar-always-visible::-webkit-scrollbar {
|
||||
width: 8px !important;
|
||||
height: 8px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ ethercrab = "0.6"
|
||||
ethercat_hal_derive = { version = "0.1.0", path = "../ethercat-hal-derive" }
|
||||
bitvec = { version = "1.0.1", features = ["alloc"] }
|
||||
smol = "2.0.2"
|
||||
uom = "0.36.0"
|
||||
uom = { version = "0.36.0", default-features = false, features = ["f64","si"] }
|
||||
rand = "0.9.2"
|
||||
futures = "0.3.31"
|
||||
tracing = "0.1.41"
|
||||
|
||||
@@ -78,18 +78,20 @@ serviceConfig = {
|
||||
Restart = "always";
|
||||
RestartSec = "10s";
|
||||
|
||||
# Capabilities (unchanged, as you wanted)
|
||||
CapabilityBoundingSet = "CAP_NET_RAW CAP_NET_ADMIN CAP_SYS_NICE CAP_DAC_OVERRIDE";
|
||||
AmbientCapabilities = "CAP_NET_RAW CAP_NET_ADMIN CAP_SYS_NICE CAP_DAC_OVERRIDE";
|
||||
# Capabilities
|
||||
CapabilityBoundingSet = "CAP_NET_RAW CAP_IPC_LOCK CAP_NET_ADMIN CAP_SYS_NICE CAP_DAC_OVERRIDE";
|
||||
AmbientCapabilities = "CAP_NET_RAW CAP_IPC_LOCK CAP_NET_ADMIN CAP_SYS_NICE CAP_DAC_OVERRIDE";
|
||||
|
||||
# Hardening options
|
||||
NoNewPrivileges = true;
|
||||
ProtectSystem = "strict";
|
||||
|
||||
# Open only /proc/irq explicitly
|
||||
ReadWritePaths = [ "/proc/irq" ];
|
||||
ProtectHome = true;
|
||||
PrivateTmp = true;
|
||||
PrivateDevices = false;
|
||||
|
||||
# Must disable this to allow /proc/irq writes
|
||||
ProtectKernelTunables = false;
|
||||
ProtectControlGroups = true;
|
||||
|
||||
+3
-3
@@ -55,9 +55,9 @@ opentelemetry-otlp = { version = "0.30.0", features = [
|
||||
], optional = true }
|
||||
tonic = { version = "0.13.1", optional = true }
|
||||
|
||||
[target.'cfg(not(target_env = "msvc"))'.dependencies]
|
||||
tikv-jemallocator = { version = "0.6", features = ["profiling", "stats"] }
|
||||
tikv-jemalloc-ctl = "0.6"
|
||||
#[target.'cfg(not(target_env = "msvc"))'.dependencies]
|
||||
#tikv-jemallocator = { version = "0.6", features = ["profiling", "stats"] }
|
||||
#tikv-jemalloc-ctl = "0.6"
|
||||
|
||||
[dev-dependencies]
|
||||
approx = "0.5.1"
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
use std::{thread::sleep, time::Duration};
|
||||
|
||||
pub fn init_jemalloc_stats() {
|
||||
std::thread::Builder::new()
|
||||
.name("jemalloc-stats".to_string())
|
||||
.spawn(|| {
|
||||
let mut last_allocated = 0usize;
|
||||
let mut last_time = std::time::Instant::now();
|
||||
|
||||
loop {
|
||||
smol::block_on(async {
|
||||
sleep(Duration::from_secs(10));
|
||||
// Advance epoch and get allocated bytes
|
||||
let _ = tikv_jemalloc_ctl::epoch::advance();
|
||||
|
||||
if let Ok(allocated_bytes) =
|
||||
unsafe { tikv_jemalloc_ctl::raw::read::<usize>(b"stats.allocated\0") }
|
||||
{
|
||||
let current_time = std::time::Instant::now();
|
||||
let time_diff = current_time.duration_since(last_time).as_secs_f64();
|
||||
|
||||
let change = allocated_bytes as i64 - last_allocated as i64;
|
||||
let change_per_sec = if time_diff > 0.0 {
|
||||
(change as f64 / time_diff) as i64
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
let change_str = if change_per_sec >= 0 {
|
||||
format!("+{} KB/s", format_bytes(change_per_sec))
|
||||
} else {
|
||||
format!("-{} KB/s", format_bytes(-change_per_sec))
|
||||
};
|
||||
|
||||
let formatted = format_bytes(allocated_bytes as i64);
|
||||
|
||||
tracing::debug!("Memory: {} KB ({})", formatted, change_str);
|
||||
last_allocated = allocated_bytes;
|
||||
last_time = current_time;
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
.expect("Failed to spawn jemalloc-stats thread");
|
||||
}
|
||||
|
||||
fn format_bytes(bytes: i64) -> String {
|
||||
let kb = bytes / 1024;
|
||||
format!("{}", kb)
|
||||
.chars()
|
||||
.rev()
|
||||
.enumerate()
|
||||
.map(|(i, c)| {
|
||||
if i > 0 && i % 3 == 0 {
|
||||
format!("_{}", c)
|
||||
} else {
|
||||
c.to_string()
|
||||
}
|
||||
})
|
||||
.collect::<String>()
|
||||
.chars()
|
||||
.rev()
|
||||
.collect::<String>()
|
||||
}
|
||||
@@ -1,20 +1,16 @@
|
||||
use super::{AquaPathV1, AquaPathV1Mode};
|
||||
use crate::machines::aquapath1::{
|
||||
Flow, Temperature, api::AquaPathV1Namespace, controller::Controller,
|
||||
use crate::machines::{
|
||||
aquapath1::{Flow, Temperature, api::AquaPathV1Namespace, controller::Controller},
|
||||
get_ethercat_device,
|
||||
};
|
||||
use anyhow::Error;
|
||||
use control_core::machines::{
|
||||
identification::DeviceHardwareIdentification,
|
||||
new::{
|
||||
MachineNewHardware, MachineNewParams, MachineNewTrait, get_device_identification_by_role,
|
||||
get_ethercat_device_by_index, get_subdevice_by_index, validate_no_role_dublicates,
|
||||
validate_same_machine_identification_unique,
|
||||
},
|
||||
use control_core::machines::new::{
|
||||
MachineNewHardware, MachineNewParams, MachineNewTrait, get_subdevice_by_index,
|
||||
validate_no_role_dublicates, validate_same_machine_identification_unique,
|
||||
};
|
||||
use ethercat_hal::{
|
||||
coe::ConfigurableDevice,
|
||||
devices::{
|
||||
EthercatDeviceUsed, downcast_device,
|
||||
ek1100::{EK1100, EK1100_IDENTITY_A},
|
||||
el2008::{EL2008, EL2008_IDENTITY_A, EL2008Port},
|
||||
el3204::{EL3204, EL3204_IDENTITY_A, EL3204_IDENTITY_B, EL3204Port},
|
||||
@@ -23,7 +19,6 @@ use ethercat_hal::{
|
||||
EL5152, EL5152_IDENTITY_A, EL5152Configuration, EL5152Port,
|
||||
EL5152PredefinedPdoAssignment,
|
||||
},
|
||||
subdevice_identity_to_tuple,
|
||||
},
|
||||
io::{
|
||||
analog_output::AnalogOutput, digital_output::DigitalOutput, encoder_input::EncoderInput,
|
||||
@@ -56,217 +51,46 @@ impl MachineNewTrait for AquaPathV1 {
|
||||
|
||||
smol::block_on(async {
|
||||
// Role 0 - Buscoupler EK1100
|
||||
{
|
||||
let device_identification =
|
||||
get_device_identification_by_role(params.device_group, 0)?;
|
||||
let device_hardware_identification_ethercat = match &device_identification
|
||||
.device_hardware_identification
|
||||
{
|
||||
DeviceHardwareIdentification::Ethercat(
|
||||
device_hardware_identification_ethercat,
|
||||
) => device_hardware_identification_ethercat,
|
||||
_ => Err(anyhow::anyhow!(
|
||||
"[{}::MachineNewTrait/AquaPath1::new] Device with role 0 is not Ethercat",
|
||||
module_path!()
|
||||
))?,
|
||||
};
|
||||
let subdevice_index = device_hardware_identification_ethercat.subdevice_index;
|
||||
let subdevice = get_subdevice_by_index(hardware.subdevices, subdevice_index)?;
|
||||
let subdevice_identity = subdevice.identity();
|
||||
let device = match subdevice_identity_to_tuple(&subdevice_identity) {
|
||||
EK1100_IDENTITY_A => {
|
||||
let ethercat_device = get_ethercat_device_by_index(
|
||||
&hardware.ethercat_devices,
|
||||
subdevice_index,
|
||||
)?;
|
||||
downcast_device::<EK1100>(ethercat_device).await?
|
||||
}
|
||||
_ => {
|
||||
return Err(anyhow::anyhow!(
|
||||
"[{}::MachineNewTrait/AquaPath1::new] Device with role 0 is not an EK1100",
|
||||
module_path!()
|
||||
));
|
||||
}
|
||||
};
|
||||
{
|
||||
let mut device_guard = device.write().await;
|
||||
device_guard.set_used(true);
|
||||
}
|
||||
}
|
||||
let _ek1100 =
|
||||
get_ethercat_device::<EK1100>(hardware, params, 0, [EK1100_IDENTITY_A].to_vec());
|
||||
|
||||
// Role 1 - EL2008 Digital Output Module
|
||||
let el2008 = {
|
||||
let device_identification =
|
||||
get_device_identification_by_role(params.device_group, 1)?;
|
||||
let device_hardware_identification_ethercat = match &device_identification
|
||||
.device_hardware_identification
|
||||
{
|
||||
DeviceHardwareIdentification::Ethercat(
|
||||
device_hardware_identification_ethercat,
|
||||
) => device_hardware_identification_ethercat,
|
||||
_ => Err(anyhow::anyhow!(
|
||||
"[{}::MachineNewTrait/AquaPath1::new] Device with role 1 is not Ethercat",
|
||||
module_path!()
|
||||
))?,
|
||||
};
|
||||
let subdevice_index = device_hardware_identification_ethercat.subdevice_index;
|
||||
let subdevice = get_subdevice_by_index(hardware.subdevices, subdevice_index)?;
|
||||
let subdevice_identity = subdevice.identity();
|
||||
let device = match subdevice_identity_to_tuple(&subdevice_identity) {
|
||||
EL2008_IDENTITY_A => {
|
||||
let ethercat_device = get_ethercat_device_by_index(
|
||||
&hardware.ethercat_devices,
|
||||
subdevice_index,
|
||||
)?;
|
||||
downcast_device::<EL2008>(ethercat_device).await?
|
||||
}
|
||||
_ => {
|
||||
return Err(anyhow::anyhow!(
|
||||
"[{}::MachineNewTrait/AquaPath1::new] Device with role 1 is not an EL2008",
|
||||
module_path!()
|
||||
));
|
||||
}
|
||||
};
|
||||
{
|
||||
let mut device_guard = device.write().await;
|
||||
device_guard.set_used(true);
|
||||
}
|
||||
device
|
||||
};
|
||||
let el2008 =
|
||||
get_ethercat_device::<EL2008>(hardware, params, 1, [EL2008_IDENTITY_A].to_vec())
|
||||
.await?
|
||||
.0;
|
||||
|
||||
// Role 2 - EL4002 Analog Output Module
|
||||
let el4002 = {
|
||||
let device_identification =
|
||||
get_device_identification_by_role(params.device_group, 2)?;
|
||||
let device_hardware_identification_ethercat = match &device_identification
|
||||
.device_hardware_identification
|
||||
{
|
||||
DeviceHardwareIdentification::Ethercat(
|
||||
device_hardware_identification_ethercat,
|
||||
) => device_hardware_identification_ethercat,
|
||||
_ => Err(anyhow::anyhow!(
|
||||
"[{}::MachineNewTrait/AquaPath1::new] Device with role 2 is not Ethercat",
|
||||
module_path!()
|
||||
))?,
|
||||
};
|
||||
let el4002 =
|
||||
get_ethercat_device::<EL4002>(hardware, params, 2, [EL4002_IDENTITY_A].to_vec())
|
||||
.await?
|
||||
.0;
|
||||
|
||||
let subdevice = get_subdevice_by_index(
|
||||
hardware.subdevices,
|
||||
device_hardware_identification_ethercat.subdevice_index,
|
||||
)?;
|
||||
let subdevice_index = device_hardware_identification_ethercat.subdevice_index;
|
||||
let subdevice_identity = subdevice.identity();
|
||||
let el3204 = get_ethercat_device::<EL3204>(
|
||||
hardware,
|
||||
params,
|
||||
3,
|
||||
[EL3204_IDENTITY_A, EL3204_IDENTITY_B].to_vec(),
|
||||
)
|
||||
.await?
|
||||
.0;
|
||||
|
||||
let device = match subdevice_identity_to_tuple(&subdevice_identity) {
|
||||
EL4002_IDENTITY_A => {
|
||||
let ethercat_device = get_ethercat_device_by_index(
|
||||
&hardware.ethercat_devices,
|
||||
subdevice_index,
|
||||
)?;
|
||||
downcast_device::<EL4002>(ethercat_device).await?
|
||||
}
|
||||
_ => Err(anyhow::anyhow!(
|
||||
"[{}::MachineNewTrait/AquaPath1::new] Device with role 2 is not an EL4002",
|
||||
module_path!()
|
||||
))?,
|
||||
};
|
||||
let el5152 =
|
||||
get_ethercat_device::<EL5152>(hardware, params, 4, [EL5152_IDENTITY_A].to_vec())
|
||||
.await?
|
||||
.0;
|
||||
|
||||
{
|
||||
let mut device_guard = device.write().await;
|
||||
device_guard.set_used(true);
|
||||
}
|
||||
device
|
||||
};
|
||||
let el3204 = {
|
||||
let device_identification =
|
||||
get_device_identification_by_role(params.device_group, 3)?;
|
||||
let device_hardware_identification_ethercat = match &device_identification
|
||||
.device_hardware_identification
|
||||
{
|
||||
DeviceHardwareIdentification::Ethercat(
|
||||
device_hardware_identification_ethercat,
|
||||
) => device_hardware_identification_ethercat,
|
||||
_ => Err(anyhow::anyhow!(
|
||||
"[{}::MachineNewTrait/AquaPath1::new] Device with role 3 is not Ethercat",
|
||||
module_path!()
|
||||
))?, //uncommented
|
||||
};
|
||||
let subdevice_index = device_hardware_identification_ethercat.subdevice_index;
|
||||
let subdevice = get_subdevice_by_index(hardware.subdevices, subdevice_index)?;
|
||||
let subdevice_identity = subdevice.identity();
|
||||
let device = match subdevice_identity_to_tuple(&subdevice_identity) {
|
||||
EL3204_IDENTITY_A | EL3204_IDENTITY_B => {
|
||||
let ethercat_device = get_ethercat_device_by_index(
|
||||
&hardware.ethercat_devices,
|
||||
subdevice_index,
|
||||
)?;
|
||||
downcast_device::<EL3204>(ethercat_device).await?
|
||||
}
|
||||
_ => {
|
||||
return Err(anyhow::anyhow!(
|
||||
"[{}::MachineNewTrait/AquaPath1::new] Device with role 3 is not an EL3204",
|
||||
module_path!()
|
||||
));
|
||||
}
|
||||
};
|
||||
{
|
||||
let mut device_guard = device.write().await;
|
||||
device_guard.set_used(true);
|
||||
}
|
||||
device
|
||||
let config = EL5152Configuration {
|
||||
pdo_assignment: EL5152PredefinedPdoAssignment::Frequency,
|
||||
..Default::default()
|
||||
};
|
||||
let subdevice = get_subdevice_by_index(hardware.subdevices, 4)?;
|
||||
el5152
|
||||
.write()
|
||||
.await
|
||||
.write_config(&subdevice, &config)
|
||||
.await?;
|
||||
|
||||
let el5152 = {
|
||||
let device_identification =
|
||||
get_device_identification_by_role(params.device_group, 4)?;
|
||||
let device_hardware_identification_ethercat = match &device_identification
|
||||
.device_hardware_identification
|
||||
{
|
||||
DeviceHardwareIdentification::Ethercat(
|
||||
device_hardware_identification_ethercat,
|
||||
) => device_hardware_identification_ethercat,
|
||||
_ => Err(anyhow::anyhow!(
|
||||
"[{}::MachineNewTrait/AquaPath1::new] Device with role 4 is not Ethercat",
|
||||
module_path!()
|
||||
))?,
|
||||
};
|
||||
|
||||
let subdevice_index = device_hardware_identification_ethercat.subdevice_index;
|
||||
let subdevice = get_subdevice_by_index(hardware.subdevices, subdevice_index)?;
|
||||
let subdevice_identity = subdevice.identity();
|
||||
|
||||
let device = match subdevice_identity_to_tuple(&subdevice_identity) {
|
||||
EL5152_IDENTITY_A => {
|
||||
let ethercat_device = get_ethercat_device_by_index(
|
||||
&hardware.ethercat_devices,
|
||||
subdevice_index,
|
||||
)?;
|
||||
downcast_device::<EL5152>(ethercat_device).await?
|
||||
}
|
||||
_ => {
|
||||
return Err(anyhow::anyhow!(
|
||||
"[{}::MachineNewTrait/AquaPath1::new] Device with role 4 is not an EL5152",
|
||||
module_path!()
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let config = EL5152Configuration {
|
||||
pdo_assignment: EL5152PredefinedPdoAssignment::Frequency,
|
||||
..Default::default()
|
||||
};
|
||||
device
|
||||
.write()
|
||||
.await
|
||||
.write_config(&subdevice, &config)
|
||||
.await?;
|
||||
|
||||
{
|
||||
let mut device_guard = device.write().await;
|
||||
device_guard.set_used(true);
|
||||
}
|
||||
device
|
||||
};
|
||||
let enc1 = EncoderInput::new(el5152.clone(), EL5152Port::ENC1);
|
||||
|
||||
let enc2 = EncoderInput::new(el5152.clone(), EL5152Port::ENC2);
|
||||
|
||||
@@ -86,9 +86,7 @@ impl MachineNewTrait for ExtruderV2 {
|
||||
// if its async the compiler thinks &subdevices is persisted in the future which might never execute
|
||||
// so we can't drop subdevices unless this machine is dropped, which is bad
|
||||
smol::block_on(async {
|
||||
// Role 0
|
||||
// Buscoupler
|
||||
// EK1100
|
||||
// Role 0 - Buscoupler EK1100
|
||||
let _ek1100 =
|
||||
get_ethercat_device::<EK1100>(hardware, params, 0, [EK1100_IDENTITY_A].to_vec());
|
||||
|
||||
|
||||
@@ -0,0 +1,502 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use crate::machines::buffer1::BufferV1;
|
||||
|
||||
use super::puller_speed_controller::PullerRegulationMode;
|
||||
use super::{TraverseMode, Winder2, Winder2Mode, api, spool_speed_controller};
|
||||
use api::{
|
||||
LiveValuesEvent, ModeState, PullerState, SpoolAutomaticActionMode, SpoolAutomaticActionState,
|
||||
SpoolSpeedControllerState, StateEvent, TensionArmState, TraverseState, Winder2Events,
|
||||
};
|
||||
use control_core::socketio::event::BuildEvent;
|
||||
use control_core::{
|
||||
machines::identification::MachineIdentificationUnique,
|
||||
socketio::namespace::NamespaceCacheingLogic, uom_extensions::velocity::meter_per_minute,
|
||||
};
|
||||
|
||||
use uom::si::{
|
||||
angle::degree,
|
||||
angular_velocity::revolution_per_minute,
|
||||
f64::{Length, Velocity},
|
||||
length::{meter, millimeter},
|
||||
};
|
||||
|
||||
impl Winder2 {
|
||||
/// Implement Spool
|
||||
/// called by `act`
|
||||
pub fn sync_spool_speed(&mut self, t: Instant) {
|
||||
let angular_velocity = self.spool_speed_controller.update_speed(
|
||||
t,
|
||||
&self.tension_arm,
|
||||
&self.puller_speed_controller,
|
||||
);
|
||||
let steps_per_second = self
|
||||
.spool_step_converter
|
||||
.angular_velocity_to_steps(angular_velocity);
|
||||
let _ = self.spool.set_speed(steps_per_second);
|
||||
}
|
||||
|
||||
pub fn stop_or_pull_spool(&mut self, now: Instant) {
|
||||
if matches!(
|
||||
self.spool_automatic_action.mode,
|
||||
SpoolAutomaticActionMode::NoAction
|
||||
) {
|
||||
self.calculate_spool_auto_progress_(now);
|
||||
return;
|
||||
}
|
||||
|
||||
match self.mode {
|
||||
Winder2Mode::Pull => self.calculate_spool_auto_progress_(now),
|
||||
Winder2Mode::Wind => self.calculate_spool_auto_progress_(now),
|
||||
_ => {
|
||||
self.spool_automatic_action.progress_last_check = now;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if self.spool_automatic_action.progress >= self.spool_automatic_action.target_length {
|
||||
match self.spool_automatic_action.mode {
|
||||
SpoolAutomaticActionMode::NoAction => (),
|
||||
SpoolAutomaticActionMode::Pull => {
|
||||
self.stop_or_pull_spool_reset(now);
|
||||
self.set_mode(&Winder2Mode::Pull);
|
||||
}
|
||||
SpoolAutomaticActionMode::Hold => {
|
||||
self.stop_or_pull_spool_reset(now);
|
||||
self.set_mode(&Winder2Mode::Hold);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Implement Mode
|
||||
pub fn set_mode(&mut self, mode: &Winder2Mode) {
|
||||
let should_update = *mode != Winder2Mode::Wind || self.can_wind();
|
||||
|
||||
if should_update {
|
||||
// all transitions are allowed
|
||||
self.mode = mode.clone();
|
||||
|
||||
// Apply the mode changes to the spool and puller
|
||||
self.set_spool_mode(mode);
|
||||
self.set_puller_mode(mode);
|
||||
self.set_traverse_mode(mode);
|
||||
}
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Implement Traverse
|
||||
pub fn set_laser(&mut self, value: bool) {
|
||||
self.laser.set(value);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn traverse_set_limit_inner(&mut self, limit: f64) {
|
||||
let new_inner = Length::new::<millimeter>(limit);
|
||||
let current_outer = self.traverse_controller.get_limit_outer();
|
||||
|
||||
// Validate the new inner limit against current outer limit
|
||||
if !Self::validate_traverse_limits(new_inner, current_outer) {
|
||||
// Don't update if validation fails - keep the current value
|
||||
return;
|
||||
}
|
||||
self.traverse_controller.set_limit_inner(new_inner);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn traverse_set_limit_outer(&mut self, limit: f64) {
|
||||
let new_outer = Length::new::<millimeter>(limit);
|
||||
let current_inner = self.traverse_controller.get_limit_inner();
|
||||
|
||||
// Validate the new outer limit against current inner limit
|
||||
if !Self::validate_traverse_limits(current_inner, new_outer) {
|
||||
// Don't update if validation fails - keep the current value
|
||||
return;
|
||||
}
|
||||
|
||||
self.traverse_controller.set_limit_outer(new_outer);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn traverse_set_step_size(&mut self, step_size: f64) {
|
||||
let step_size = Length::new::<millimeter>(step_size);
|
||||
self.traverse_controller.set_step_size(step_size);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn traverse_set_padding(&mut self, padding: f64) {
|
||||
let padding = Length::new::<millimeter>(padding);
|
||||
self.traverse_controller.set_padding(padding);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn traverse_goto_limit_inner(&mut self) {
|
||||
if self.can_go_in() {
|
||||
self.traverse_controller.goto_limit_inner();
|
||||
}
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn traverse_goto_limit_outer(&mut self) {
|
||||
if self.can_go_out() {
|
||||
self.traverse_controller.goto_limit_outer();
|
||||
}
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn traverse_goto_home(&mut self) {
|
||||
if self.can_go_home() {
|
||||
self.traverse_controller.goto_home();
|
||||
}
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn emit_live_values(&mut self) {
|
||||
let angle_deg = self.tension_arm.get_angle().get::<degree>();
|
||||
|
||||
// Wrap [270;<360] to [-90; 0]
|
||||
// This is done to reduce flicker in the graphs around the zero point
|
||||
let angle_deg = if angle_deg >= 270.0 {
|
||||
angle_deg - 360.0
|
||||
} else {
|
||||
angle_deg
|
||||
};
|
||||
|
||||
// Calculate puller speed from current motor steps
|
||||
let steps_per_second = self.puller.get_speed();
|
||||
let angular_velocity = self
|
||||
.puller_speed_controller
|
||||
.converter
|
||||
.steps_to_angular_velocity(steps_per_second as f64);
|
||||
let puller_speed = self
|
||||
.puller_speed_controller
|
||||
.angular_velocity_to_speed(angular_velocity);
|
||||
|
||||
// Calculate spool RPM from current motor steps
|
||||
let spool_rpm = self
|
||||
.spool_step_converter
|
||||
.steps_to_angular_velocity(self.spool.get_speed() as f64)
|
||||
.get::<revolution_per_minute>();
|
||||
|
||||
let live_values = LiveValuesEvent {
|
||||
traverse_position: self
|
||||
.traverse_controller
|
||||
.get_current_position()
|
||||
.map(|x| x.get::<millimeter>()),
|
||||
puller_speed: puller_speed.get::<meter_per_minute>(),
|
||||
spool_rpm,
|
||||
tension_arm_angle: angle_deg,
|
||||
spool_progress: self.spool_automatic_action.progress.get::<meter>(),
|
||||
};
|
||||
|
||||
let event = live_values.build();
|
||||
self.namespace.emit(Winder2Events::LiveValues(event));
|
||||
}
|
||||
|
||||
pub fn build_state_event(&mut self) -> StateEvent {
|
||||
StateEvent {
|
||||
is_default_state: !std::mem::replace(&mut self.emitted_default_state, true),
|
||||
traverse_state: TraverseState {
|
||||
limit_inner: self
|
||||
.traverse_controller
|
||||
.get_limit_inner()
|
||||
.get::<millimeter>(),
|
||||
limit_outer: self
|
||||
.traverse_controller
|
||||
.get_limit_outer()
|
||||
.get::<millimeter>(),
|
||||
position_in: self
|
||||
.traverse_controller
|
||||
.get_limit_inner()
|
||||
.get::<millimeter>(),
|
||||
position_out: self
|
||||
.traverse_controller
|
||||
.get_limit_outer()
|
||||
.get::<millimeter>(),
|
||||
is_going_in: self.traverse_controller.is_going_in(),
|
||||
is_going_out: self.traverse_controller.is_going_out(),
|
||||
is_homed: self.traverse_controller.is_homed(),
|
||||
is_going_home: self.traverse_controller.is_going_home(),
|
||||
is_traversing: self.traverse_controller.is_traversing(),
|
||||
laserpointer: self.laser.get(),
|
||||
step_size: self.traverse_controller.get_step_size().get::<millimeter>(),
|
||||
padding: self.traverse_controller.get_padding().get::<millimeter>(),
|
||||
can_go_in: self.can_go_in(),
|
||||
can_go_out: self.can_go_out(),
|
||||
can_go_home: self.can_go_home(),
|
||||
},
|
||||
puller_state: PullerState {
|
||||
regulation: self.puller_speed_controller.regulation_mode.clone(),
|
||||
target_speed: self
|
||||
.puller_speed_controller
|
||||
.target_speed
|
||||
.get::<meter_per_minute>(),
|
||||
target_diameter: self
|
||||
.puller_speed_controller
|
||||
.target_diameter
|
||||
.get::<millimeter>(),
|
||||
forward: self.puller_speed_controller.forward,
|
||||
},
|
||||
mode_state: ModeState {
|
||||
mode: self.mode.clone().into(),
|
||||
can_wind: self.can_wind(),
|
||||
},
|
||||
tension_arm_state: TensionArmState {
|
||||
zeroed: self.tension_arm.zeroed,
|
||||
},
|
||||
spool_speed_controller_state: SpoolSpeedControllerState {
|
||||
regulation_mode: self.spool_speed_controller.get_type().clone(),
|
||||
minmax_min_speed: self
|
||||
.spool_speed_controller
|
||||
.get_minmax_min_speed()
|
||||
.get::<revolution_per_minute>(),
|
||||
minmax_max_speed: self
|
||||
.spool_speed_controller
|
||||
.get_minmax_max_speed()
|
||||
.get::<revolution_per_minute>(),
|
||||
adaptive_tension_target: self.spool_speed_controller.get_adaptive_tension_target(),
|
||||
adaptive_radius_learning_rate: self
|
||||
.spool_speed_controller
|
||||
.get_adaptive_radius_learning_rate(),
|
||||
adaptive_max_speed_multiplier: self
|
||||
.spool_speed_controller
|
||||
.get_adaptive_max_speed_multiplier(),
|
||||
adaptive_acceleration_factor: self
|
||||
.spool_speed_controller
|
||||
.get_adaptive_acceleration_factor(),
|
||||
adaptive_deacceleration_urgency_multiplier: self
|
||||
.spool_speed_controller
|
||||
.get_adaptive_deacceleration_urgency_multiplier(),
|
||||
},
|
||||
spool_automatic_action_state: SpoolAutomaticActionState {
|
||||
spool_required_meters: self.spool_automatic_action.target_length.get::<meter>(),
|
||||
spool_automatic_action_mode: self.spool_automatic_action.mode.clone(),
|
||||
},
|
||||
connected_machine_state: self.connected_buffer.to_state(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn emit_state(&mut self) {
|
||||
let state_event = self.build_state_event();
|
||||
let event = state_event.build();
|
||||
self.namespace.emit(Winder2Events::State(event));
|
||||
}
|
||||
|
||||
/// Apply the mode changes to the spool
|
||||
///
|
||||
/// It contains a transition matrix for atomic changes.
|
||||
/// It will set [`Self::spool_mode`]
|
||||
fn set_traverse_mode(&mut self, mode: &Winder2Mode) {
|
||||
// Convert to `Winder2Mode` to `TraverseMode`
|
||||
let mode: TraverseMode = mode.clone().into();
|
||||
|
||||
// If coming out of standby
|
||||
if self.traverse_mode == TraverseMode::Standby && mode != TraverseMode::Standby {
|
||||
self.traverse.set_enabled(true);
|
||||
self.traverse_controller.set_enabled(true);
|
||||
}
|
||||
|
||||
// If going into standby
|
||||
if mode == TraverseMode::Standby && self.traverse_mode != TraverseMode::Standby {
|
||||
// If we are going into standby, we need to stop the traverse
|
||||
self.traverse.set_enabled(false);
|
||||
self.traverse_controller.set_enabled(false);
|
||||
}
|
||||
|
||||
// Transition matrix
|
||||
match self.traverse_mode {
|
||||
TraverseMode::Standby => match mode {
|
||||
TraverseMode::Standby => {}
|
||||
TraverseMode::Hold => {
|
||||
// From [`TraverseMode::Standby`] to [`TraverseMode::Hold`]
|
||||
self.traverse.set_enabled(true);
|
||||
self.traverse_controller.set_enabled(true);
|
||||
self.traverse_controller.goto_home();
|
||||
}
|
||||
TraverseMode::Traverse => {
|
||||
// From [`TraverseMode::Standby`] to [`TraverseMode::Wind`]
|
||||
self.traverse.set_enabled(true);
|
||||
self.traverse_controller.set_enabled(true);
|
||||
self.traverse_controller.start_traversing();
|
||||
}
|
||||
},
|
||||
TraverseMode::Hold => match mode {
|
||||
TraverseMode::Standby => {
|
||||
// From [`TraverseMode::Hold`] to [`TraverseMode::Standby`]
|
||||
self.traverse.set_enabled(false);
|
||||
self.traverse_controller.set_enabled(false);
|
||||
}
|
||||
TraverseMode::Hold => {}
|
||||
TraverseMode::Traverse => {
|
||||
// From [`TraverseMode::Hold`] to [`TraverseMode::Wind`]
|
||||
self.traverse_controller.start_traversing();
|
||||
}
|
||||
},
|
||||
TraverseMode::Traverse => match mode {
|
||||
TraverseMode::Standby => {
|
||||
// From [`TraverseMode::Wind`] to [`TraverseMode::Standby`]
|
||||
self.traverse.set_enabled(false);
|
||||
self.traverse_controller.set_enabled(false);
|
||||
}
|
||||
TraverseMode::Hold => {
|
||||
// From [`TraverseMode::Wind`] to [`TraverseMode::Hold`]
|
||||
self.traverse_controller.goto_home();
|
||||
}
|
||||
TraverseMode::Traverse => {}
|
||||
},
|
||||
}
|
||||
|
||||
// Update the internal state
|
||||
self.traverse_mode = mode;
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Implement Tension Arm
|
||||
pub fn tension_arm_zero(&mut self) {
|
||||
self.tension_arm.zero();
|
||||
self.emit_live_values(); // For angle update
|
||||
// For state update
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn set_spool_automatic_required_meters(&mut self, meters: f64) {
|
||||
self.spool_automatic_action.target_length = Length::new::<meter>(meters);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn set_spool_automatic_mode(&mut self, mode: SpoolAutomaticActionMode) {
|
||||
self.spool_automatic_action.mode = mode;
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn puller_set_regulation(&mut self, puller_regulation_mode: PullerRegulationMode) {
|
||||
self.puller_speed_controller
|
||||
.set_regulation_mode(puller_regulation_mode);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set target speed in m/min
|
||||
pub fn puller_set_target_speed(&mut self, target_speed: f64) {
|
||||
// Convert m/min to velocity
|
||||
let target_speed = Velocity::new::<meter_per_minute>(target_speed);
|
||||
self.puller_speed_controller.set_target_speed(target_speed);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set target diameter in mm
|
||||
pub fn puller_set_target_diameter(&mut self, target_diameter: f64) {
|
||||
// Convert m/min to velocity
|
||||
let target_diameter = Length::new::<millimeter>(target_diameter);
|
||||
self.puller_speed_controller
|
||||
.set_target_diameter(target_diameter);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set forward direction
|
||||
pub fn puller_set_forward(&mut self, forward: bool) {
|
||||
self.puller_speed_controller.set_forward(forward);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
// Spool Speed Controller API methods
|
||||
pub fn spool_set_regulation_mode(
|
||||
&mut self,
|
||||
regulation_mode: spool_speed_controller::SpoolSpeedControllerType,
|
||||
) {
|
||||
self.spool_speed_controller.set_type(regulation_mode);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set minimum speed for minmax mode in RPM
|
||||
pub fn spool_set_minmax_min_speed(&mut self, min_speed_rpm: f64) {
|
||||
let min_speed = uom::si::f64::AngularVelocity::new::<revolution_per_minute>(min_speed_rpm);
|
||||
if let Err(e) = self.spool_speed_controller.set_minmax_min_speed(min_speed) {
|
||||
tracing::error!("Failed to set spool min speed: {:?}", e);
|
||||
}
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set maximum speed for minmax mode in RPM
|
||||
pub fn spool_set_minmax_max_speed(&mut self, max_speed_rpm: f64) {
|
||||
let max_speed = uom::si::f64::AngularVelocity::new::<revolution_per_minute>(max_speed_rpm);
|
||||
if let Err(e) = self.spool_speed_controller.set_minmax_max_speed(max_speed) {
|
||||
tracing::error!("Failed to set spool max speed: {:?}", e);
|
||||
}
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set tension target for adaptive mode (0.0-1.0)
|
||||
pub fn spool_set_adaptive_tension_target(&mut self, tension_target: f64) {
|
||||
self.spool_speed_controller
|
||||
.set_adaptive_tension_target(tension_target);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set radius learning rate for adaptive mode
|
||||
pub fn spool_set_adaptive_radius_learning_rate(&mut self, radius_learning_rate: f64) {
|
||||
self.spool_speed_controller
|
||||
.set_adaptive_radius_learning_rate(radius_learning_rate);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set max speed multiplier for adaptive mode
|
||||
pub fn spool_set_adaptive_max_speed_multiplier(&mut self, max_speed_multiplier: f64) {
|
||||
self.spool_speed_controller
|
||||
.set_adaptive_max_speed_multiplier(max_speed_multiplier);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set acceleration factor for adaptive mode
|
||||
pub fn spool_set_adaptive_acceleration_factor(&mut self, acceleration_factor: f64) {
|
||||
self.spool_speed_controller
|
||||
.set_adaptive_acceleration_factor(acceleration_factor);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set deacceleration urgency multiplier for adaptive mode
|
||||
pub fn spool_set_adaptive_deacceleration_urgency_multiplier(
|
||||
&mut self,
|
||||
deacceleration_urgency_multiplier: f64,
|
||||
) {
|
||||
self.spool_speed_controller
|
||||
.set_adaptive_deacceleration_urgency_multiplier(deacceleration_urgency_multiplier);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// implement machine connection
|
||||
/// set connected buffer
|
||||
pub fn set_connected_buffer(
|
||||
&mut self,
|
||||
machine_identification_unique: MachineIdentificationUnique,
|
||||
) {
|
||||
if !matches!(
|
||||
machine_identification_unique.machine_identification,
|
||||
BufferV1::MACHINE_IDENTIFICATION
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.connected_buffer
|
||||
.set_connected_machine(&machine_identification_unique);
|
||||
self.connected_buffer.reverse_connect();
|
||||
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// disconnect buffer
|
||||
pub fn disconnect_buffer(
|
||||
&mut self,
|
||||
machine_identification_unique: MachineIdentificationUnique,
|
||||
) {
|
||||
if !matches!(
|
||||
machine_identification_unique.machine_identification,
|
||||
BufferV1::MACHINE_IDENTIFICATION
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.connected_buffer.reverse_disconnect();
|
||||
self.connected_buffer.disconnect();
|
||||
|
||||
self.emit_state();
|
||||
}
|
||||
}
|
||||
@@ -77,7 +77,7 @@ impl FilamentTensionCalculator {
|
||||
pub fn calc_filament_length(&self, tension_arm_angle: Angle) -> Length {
|
||||
let tension_arm_angle_rad = self
|
||||
.angle_converter
|
||||
.decode_f64(tension_arm_angle)
|
||||
.decode(tension_arm_angle)
|
||||
.get::<radian>();
|
||||
|
||||
// Calculate tension arm tip position (flipped Y-axis: 0° = down, 90° = left)
|
||||
|
||||
@@ -2,6 +2,7 @@ pub mod act;
|
||||
pub mod adaptive_spool_speed_controller;
|
||||
pub mod api;
|
||||
pub mod clamp_revolution;
|
||||
pub mod emit;
|
||||
pub mod filament_tension;
|
||||
pub mod minmax_spool_speed_controller;
|
||||
pub mod new;
|
||||
@@ -12,12 +13,7 @@ pub mod traverse_controller;
|
||||
|
||||
use std::{fmt::Debug, sync::Weak, time::Instant};
|
||||
|
||||
use api::{
|
||||
LiveValuesEvent, ModeState, PullerState, SpoolAutomaticActionMode, SpoolAutomaticActionState,
|
||||
SpoolSpeedControllerState, StateEvent, TensionArmState, TraverseState, Winder2Events,
|
||||
Winder2Namespace,
|
||||
};
|
||||
use control_core::socketio::event::BuildEvent;
|
||||
use api::{SpoolAutomaticActionMode, Winder2Namespace};
|
||||
use control_core::{
|
||||
converters::angular_step_converter::AngularStepConverter,
|
||||
machines::{
|
||||
@@ -25,15 +21,13 @@ use control_core::{
|
||||
identification::{MachineIdentification, MachineIdentificationUnique},
|
||||
manager::MachineManager,
|
||||
},
|
||||
socketio::namespace::NamespaceCacheingLogic,
|
||||
uom_extensions::velocity::meter_per_minute,
|
||||
};
|
||||
use control_core_derive::Machine;
|
||||
use ethercat_hal::io::{
|
||||
digital_input::DigitalInput, digital_output::DigitalOutput,
|
||||
stepper_velocity_el70x1::StepperVelocityEL70x1,
|
||||
};
|
||||
use puller_speed_controller::{PullerRegulationMode, PullerSpeedController};
|
||||
use puller_speed_controller::PullerSpeedController;
|
||||
use smol::lock::RwLock;
|
||||
use spool_speed_controller::SpoolSpeedController;
|
||||
use tension_arm::TensionArm;
|
||||
@@ -41,9 +35,7 @@ use traverse_controller::TraverseController;
|
||||
use uom::{
|
||||
ConstZero,
|
||||
si::{
|
||||
angle::degree,
|
||||
angular_velocity::revolution_per_minute,
|
||||
f64::{Length, Velocity},
|
||||
f64::Length,
|
||||
length::{meter, millimeter},
|
||||
velocity::meter_per_second,
|
||||
},
|
||||
@@ -116,12 +108,6 @@ impl Winder2 {
|
||||
machine: MACHINE_WINDER_V1,
|
||||
};
|
||||
|
||||
/// Implement Traverse
|
||||
fn set_laser(&mut self, value: bool) {
|
||||
self.laser.set(value);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Validates that traverse limits maintain proper constraints:
|
||||
/// - Inner limit must be smaller than outer limit
|
||||
/// - At least 0.9mm difference between inner and outer limits
|
||||
@@ -129,197 +115,6 @@ impl Winder2 {
|
||||
outer > inner + Length::new::<millimeter>(0.9)
|
||||
}
|
||||
|
||||
pub fn traverse_set_limit_inner(&mut self, limit: f64) {
|
||||
let new_inner = Length::new::<millimeter>(limit);
|
||||
let current_outer = self.traverse_controller.get_limit_outer();
|
||||
|
||||
// Validate the new inner limit against current outer limit
|
||||
if !Self::validate_traverse_limits(new_inner, current_outer) {
|
||||
// Don't update if validation fails - keep the current value
|
||||
return;
|
||||
}
|
||||
self.traverse_controller.set_limit_inner(new_inner);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn traverse_set_limit_outer(&mut self, limit: f64) {
|
||||
let new_outer = Length::new::<millimeter>(limit);
|
||||
let current_inner = self.traverse_controller.get_limit_inner();
|
||||
|
||||
// Validate the new outer limit against current inner limit
|
||||
if !Self::validate_traverse_limits(current_inner, new_outer) {
|
||||
// Don't update if validation fails - keep the current value
|
||||
return;
|
||||
}
|
||||
|
||||
self.traverse_controller.set_limit_outer(new_outer);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn traverse_set_step_size(&mut self, step_size: f64) {
|
||||
let step_size = Length::new::<millimeter>(step_size);
|
||||
self.traverse_controller.set_step_size(step_size);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn traverse_set_padding(&mut self, padding: f64) {
|
||||
let padding = Length::new::<millimeter>(padding);
|
||||
self.traverse_controller.set_padding(padding);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn traverse_goto_limit_inner(&mut self) {
|
||||
if self.can_go_in() {
|
||||
self.traverse_controller.goto_limit_inner();
|
||||
}
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn traverse_goto_limit_outer(&mut self) {
|
||||
if self.can_go_out() {
|
||||
self.traverse_controller.goto_limit_outer();
|
||||
}
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn traverse_goto_home(&mut self) {
|
||||
if self.can_go_home() {
|
||||
self.traverse_controller.goto_home();
|
||||
}
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn emit_live_values(&mut self) {
|
||||
let angle_deg = self.tension_arm.get_angle().get::<degree>();
|
||||
|
||||
// Wrap [270;<360] to [-90; 0]
|
||||
// This is done to reduce flicker in the graphs around the zero point
|
||||
let angle_deg = if angle_deg >= 270.0 {
|
||||
angle_deg - 360.0
|
||||
} else {
|
||||
angle_deg
|
||||
};
|
||||
|
||||
// Calculate puller speed from current motor steps
|
||||
let steps_per_second = self.puller.get_speed();
|
||||
let angular_velocity = self
|
||||
.puller_speed_controller
|
||||
.converter
|
||||
.steps_to_angular_velocity(steps_per_second as f64);
|
||||
let puller_speed = self
|
||||
.puller_speed_controller
|
||||
.angular_velocity_to_speed(angular_velocity);
|
||||
|
||||
// Calculate spool RPM from current motor steps
|
||||
let spool_rpm = self
|
||||
.spool_step_converter
|
||||
.steps_to_angular_velocity(self.spool.get_speed() as f64)
|
||||
.get::<revolution_per_minute>();
|
||||
|
||||
let live_values = LiveValuesEvent {
|
||||
traverse_position: self
|
||||
.traverse_controller
|
||||
.get_current_position()
|
||||
.map(|x| x.get::<millimeter>()),
|
||||
puller_speed: puller_speed.get::<meter_per_minute>(),
|
||||
spool_rpm,
|
||||
tension_arm_angle: angle_deg,
|
||||
spool_progress: self.spool_automatic_action.progress.get::<meter>(),
|
||||
};
|
||||
|
||||
let event = live_values.build();
|
||||
self.namespace.emit(Winder2Events::LiveValues(event));
|
||||
}
|
||||
|
||||
pub fn build_state_event(&mut self) -> StateEvent {
|
||||
StateEvent {
|
||||
is_default_state: !std::mem::replace(&mut self.emitted_default_state, true),
|
||||
traverse_state: TraverseState {
|
||||
limit_inner: self
|
||||
.traverse_controller
|
||||
.get_limit_inner()
|
||||
.get::<millimeter>(),
|
||||
limit_outer: self
|
||||
.traverse_controller
|
||||
.get_limit_outer()
|
||||
.get::<millimeter>(),
|
||||
position_in: self
|
||||
.traverse_controller
|
||||
.get_limit_inner()
|
||||
.get::<millimeter>(),
|
||||
position_out: self
|
||||
.traverse_controller
|
||||
.get_limit_outer()
|
||||
.get::<millimeter>(),
|
||||
is_going_in: self.traverse_controller.is_going_in(),
|
||||
is_going_out: self.traverse_controller.is_going_out(),
|
||||
is_homed: self.traverse_controller.is_homed(),
|
||||
is_going_home: self.traverse_controller.is_going_home(),
|
||||
is_traversing: self.traverse_controller.is_traversing(),
|
||||
laserpointer: self.laser.get(),
|
||||
step_size: self.traverse_controller.get_step_size().get::<millimeter>(),
|
||||
padding: self.traverse_controller.get_padding().get::<millimeter>(),
|
||||
can_go_in: self.can_go_in(),
|
||||
can_go_out: self.can_go_out(),
|
||||
can_go_home: self.can_go_home(),
|
||||
},
|
||||
puller_state: PullerState {
|
||||
regulation: self.puller_speed_controller.regulation_mode.clone(),
|
||||
target_speed: self
|
||||
.puller_speed_controller
|
||||
.target_speed
|
||||
.get::<meter_per_minute>(),
|
||||
target_diameter: self
|
||||
.puller_speed_controller
|
||||
.target_diameter
|
||||
.get::<millimeter>(),
|
||||
forward: self.puller_speed_controller.forward,
|
||||
},
|
||||
mode_state: ModeState {
|
||||
mode: self.mode.clone().into(),
|
||||
can_wind: self.can_wind(),
|
||||
},
|
||||
tension_arm_state: TensionArmState {
|
||||
zeroed: self.tension_arm.zeroed,
|
||||
},
|
||||
spool_speed_controller_state: SpoolSpeedControllerState {
|
||||
regulation_mode: self.spool_speed_controller.get_type().clone(),
|
||||
minmax_min_speed: self
|
||||
.spool_speed_controller
|
||||
.get_minmax_min_speed()
|
||||
.get::<revolution_per_minute>(),
|
||||
minmax_max_speed: self
|
||||
.spool_speed_controller
|
||||
.get_minmax_max_speed()
|
||||
.get::<revolution_per_minute>(),
|
||||
adaptive_tension_target: self.spool_speed_controller.get_adaptive_tension_target(),
|
||||
adaptive_radius_learning_rate: self
|
||||
.spool_speed_controller
|
||||
.get_adaptive_radius_learning_rate(),
|
||||
adaptive_max_speed_multiplier: self
|
||||
.spool_speed_controller
|
||||
.get_adaptive_max_speed_multiplier(),
|
||||
adaptive_acceleration_factor: self
|
||||
.spool_speed_controller
|
||||
.get_adaptive_acceleration_factor(),
|
||||
adaptive_deacceleration_urgency_multiplier: self
|
||||
.spool_speed_controller
|
||||
.get_adaptive_deacceleration_urgency_multiplier(),
|
||||
},
|
||||
spool_automatic_action_state: SpoolAutomaticActionState {
|
||||
spool_required_meters: self.spool_automatic_action.target_length.get::<meter>(),
|
||||
spool_automatic_action_mode: self.spool_automatic_action.mode.clone(),
|
||||
},
|
||||
connected_machine_state: self.connected_buffer.to_state(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn emit_state(&mut self) {
|
||||
let state_event = self.build_state_event();
|
||||
let event = state_event.build();
|
||||
self.namespace.emit(Winder2Events::State(event));
|
||||
}
|
||||
|
||||
pub fn sync_traverse_speed(&mut self) {
|
||||
self.traverse_controller.update_speed(
|
||||
&mut self.traverse,
|
||||
@@ -372,22 +167,6 @@ impl Winder2 {
|
||||
&& self.mode != Winder2Mode::Wind
|
||||
}
|
||||
|
||||
/// Implement Mode
|
||||
fn set_mode(&mut self, mode: &Winder2Mode) {
|
||||
let should_update = *mode != Winder2Mode::Wind || self.can_wind();
|
||||
|
||||
if should_update {
|
||||
// all transitions are allowed
|
||||
self.mode = mode.clone();
|
||||
|
||||
// Apply the mode changes to the spool and puller
|
||||
self.set_spool_mode(mode);
|
||||
self.set_puller_mode(mode);
|
||||
self.set_traverse_mode(mode);
|
||||
}
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Apply the mode changes to the spool
|
||||
///
|
||||
/// It contains a transition matrix for atomic changes.
|
||||
@@ -440,75 +219,6 @@ impl Winder2 {
|
||||
self.spool_mode = mode;
|
||||
}
|
||||
|
||||
/// Apply the mode changes to the spool
|
||||
///
|
||||
/// It contains a transition matrix for atomic changes.
|
||||
/// It will set [`Self::spool_mode`]
|
||||
fn set_traverse_mode(&mut self, mode: &Winder2Mode) {
|
||||
// Convert to `Winder2Mode` to `TraverseMode`
|
||||
let mode: TraverseMode = mode.clone().into();
|
||||
|
||||
// If coming out of standby
|
||||
if self.traverse_mode == TraverseMode::Standby && mode != TraverseMode::Standby {
|
||||
self.traverse.set_enabled(true);
|
||||
self.traverse_controller.set_enabled(true);
|
||||
}
|
||||
|
||||
// If going into standby
|
||||
if mode == TraverseMode::Standby && self.traverse_mode != TraverseMode::Standby {
|
||||
// If we are going into standby, we need to stop the traverse
|
||||
self.traverse.set_enabled(false);
|
||||
self.traverse_controller.set_enabled(false);
|
||||
}
|
||||
|
||||
// Transition matrix
|
||||
match self.traverse_mode {
|
||||
TraverseMode::Standby => match mode {
|
||||
TraverseMode::Standby => {}
|
||||
TraverseMode::Hold => {
|
||||
// From [`TraverseMode::Standby`] to [`TraverseMode::Hold`]
|
||||
self.traverse.set_enabled(true);
|
||||
self.traverse_controller.set_enabled(true);
|
||||
self.traverse_controller.goto_home();
|
||||
}
|
||||
TraverseMode::Traverse => {
|
||||
// From [`TraverseMode::Standby`] to [`TraverseMode::Wind`]
|
||||
self.traverse.set_enabled(true);
|
||||
self.traverse_controller.set_enabled(true);
|
||||
self.traverse_controller.start_traversing();
|
||||
}
|
||||
},
|
||||
TraverseMode::Hold => match mode {
|
||||
TraverseMode::Standby => {
|
||||
// From [`TraverseMode::Hold`] to [`TraverseMode::Standby`]
|
||||
self.traverse.set_enabled(false);
|
||||
self.traverse_controller.set_enabled(false);
|
||||
}
|
||||
TraverseMode::Hold => {}
|
||||
TraverseMode::Traverse => {
|
||||
// From [`TraverseMode::Hold`] to [`TraverseMode::Wind`]
|
||||
self.traverse_controller.start_traversing();
|
||||
}
|
||||
},
|
||||
TraverseMode::Traverse => match mode {
|
||||
TraverseMode::Standby => {
|
||||
// From [`TraverseMode::Wind`] to [`TraverseMode::Standby`]
|
||||
self.traverse.set_enabled(false);
|
||||
self.traverse_controller.set_enabled(false);
|
||||
}
|
||||
TraverseMode::Hold => {
|
||||
// From [`TraverseMode::Wind`] to [`TraverseMode::Hold`]
|
||||
self.traverse_controller.goto_home();
|
||||
}
|
||||
TraverseMode::Traverse => {}
|
||||
},
|
||||
}
|
||||
|
||||
// Update the internal state
|
||||
self.traverse_mode = mode;
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Apply the mode changes to the puller
|
||||
///
|
||||
/// It contains a transition matrix for atomic changes.
|
||||
@@ -560,71 +270,6 @@ impl Winder2 {
|
||||
self.puller_mode = mode;
|
||||
}
|
||||
|
||||
/// Implement Tension Arm
|
||||
fn tension_arm_zero(&mut self) {
|
||||
self.tension_arm.zero();
|
||||
self.emit_live_values(); // For angle update
|
||||
// For state update
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Implement Spool
|
||||
/// called by `act`
|
||||
pub fn sync_spool_speed(&mut self, t: Instant) {
|
||||
let angular_velocity = self.spool_speed_controller.update_speed(
|
||||
t,
|
||||
&self.tension_arm,
|
||||
&self.puller_speed_controller,
|
||||
);
|
||||
let steps_per_second = self
|
||||
.spool_step_converter
|
||||
.angular_velocity_to_steps(angular_velocity);
|
||||
let _ = self.spool.set_speed(steps_per_second);
|
||||
}
|
||||
|
||||
pub fn set_spool_automatic_required_meters(&mut self, meters: f64) {
|
||||
self.spool_automatic_action.target_length = Length::new::<meter>(meters);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn set_spool_automatic_mode(&mut self, mode: SpoolAutomaticActionMode) {
|
||||
self.spool_automatic_action.mode = mode;
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn stop_or_pull_spool(&mut self, now: Instant) {
|
||||
if matches!(
|
||||
self.spool_automatic_action.mode,
|
||||
SpoolAutomaticActionMode::NoAction
|
||||
) {
|
||||
self.calculate_spool_auto_progress_(now);
|
||||
return;
|
||||
}
|
||||
|
||||
match self.mode {
|
||||
Winder2Mode::Pull => self.calculate_spool_auto_progress_(now),
|
||||
Winder2Mode::Wind => self.calculate_spool_auto_progress_(now),
|
||||
_ => {
|
||||
self.spool_automatic_action.progress_last_check = now;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if self.spool_automatic_action.progress >= self.spool_automatic_action.target_length {
|
||||
match self.spool_automatic_action.mode {
|
||||
SpoolAutomaticActionMode::NoAction => (),
|
||||
SpoolAutomaticActionMode::Pull => {
|
||||
self.stop_or_pull_spool_reset(now);
|
||||
self.set_mode(&Winder2Mode::Pull);
|
||||
}
|
||||
SpoolAutomaticActionMode::Hold => {
|
||||
self.stop_or_pull_spool_reset(now);
|
||||
self.set_mode(&Winder2Mode::Hold);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn stop_or_pull_spool_reset(&mut self, now: Instant) {
|
||||
self.spool_automatic_action.progress = Length::ZERO;
|
||||
self.spool_automatic_action.progress_last_check = now;
|
||||
@@ -659,138 +304,6 @@ impl Winder2 {
|
||||
.angular_velocity_to_steps(angular_velocity);
|
||||
let _ = self.puller.set_speed(steps_per_second);
|
||||
}
|
||||
|
||||
pub fn puller_set_regulation(&mut self, puller_regulation_mode: PullerRegulationMode) {
|
||||
self.puller_speed_controller
|
||||
.set_regulation_mode(puller_regulation_mode);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set target speed in m/min
|
||||
pub fn puller_set_target_speed(&mut self, target_speed: f64) {
|
||||
// Convert m/min to velocity
|
||||
let target_speed = Velocity::new::<meter_per_minute>(target_speed);
|
||||
self.puller_speed_controller.set_target_speed(target_speed);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set target diameter in mm
|
||||
pub fn puller_set_target_diameter(&mut self, target_diameter: f64) {
|
||||
// Convert m/min to velocity
|
||||
let target_diameter = Length::new::<millimeter>(target_diameter);
|
||||
self.puller_speed_controller
|
||||
.set_target_diameter(target_diameter);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set forward direction
|
||||
pub fn puller_set_forward(&mut self, forward: bool) {
|
||||
self.puller_speed_controller.set_forward(forward);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
// Spool Speed Controller API methods
|
||||
pub fn spool_set_regulation_mode(
|
||||
&mut self,
|
||||
regulation_mode: spool_speed_controller::SpoolSpeedControllerType,
|
||||
) {
|
||||
self.spool_speed_controller.set_type(regulation_mode);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set minimum speed for minmax mode in RPM
|
||||
pub fn spool_set_minmax_min_speed(&mut self, min_speed_rpm: f64) {
|
||||
let min_speed = uom::si::f64::AngularVelocity::new::<revolution_per_minute>(min_speed_rpm);
|
||||
if let Err(e) = self.spool_speed_controller.set_minmax_min_speed(min_speed) {
|
||||
tracing::error!("Failed to set spool min speed: {:?}", e);
|
||||
}
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set maximum speed for minmax mode in RPM
|
||||
pub fn spool_set_minmax_max_speed(&mut self, max_speed_rpm: f64) {
|
||||
let max_speed = uom::si::f64::AngularVelocity::new::<revolution_per_minute>(max_speed_rpm);
|
||||
if let Err(e) = self.spool_speed_controller.set_minmax_max_speed(max_speed) {
|
||||
tracing::error!("Failed to set spool max speed: {:?}", e);
|
||||
}
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set tension target for adaptive mode (0.0-1.0)
|
||||
pub fn spool_set_adaptive_tension_target(&mut self, tension_target: f64) {
|
||||
self.spool_speed_controller
|
||||
.set_adaptive_tension_target(tension_target);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set radius learning rate for adaptive mode
|
||||
pub fn spool_set_adaptive_radius_learning_rate(&mut self, radius_learning_rate: f64) {
|
||||
self.spool_speed_controller
|
||||
.set_adaptive_radius_learning_rate(radius_learning_rate);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set max speed multiplier for adaptive mode
|
||||
pub fn spool_set_adaptive_max_speed_multiplier(&mut self, max_speed_multiplier: f64) {
|
||||
self.spool_speed_controller
|
||||
.set_adaptive_max_speed_multiplier(max_speed_multiplier);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set acceleration factor for adaptive mode
|
||||
pub fn spool_set_adaptive_acceleration_factor(&mut self, acceleration_factor: f64) {
|
||||
self.spool_speed_controller
|
||||
.set_adaptive_acceleration_factor(acceleration_factor);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// Set deacceleration urgency multiplier for adaptive mode
|
||||
pub fn spool_set_adaptive_deacceleration_urgency_multiplier(
|
||||
&mut self,
|
||||
deacceleration_urgency_multiplier: f64,
|
||||
) {
|
||||
self.spool_speed_controller
|
||||
.set_adaptive_deacceleration_urgency_multiplier(deacceleration_urgency_multiplier);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// implement machine connection
|
||||
/// set connected buffer
|
||||
pub fn set_connected_buffer(
|
||||
&mut self,
|
||||
machine_identification_unique: MachineIdentificationUnique,
|
||||
) {
|
||||
if !matches!(
|
||||
machine_identification_unique.machine_identification,
|
||||
BufferV1::MACHINE_IDENTIFICATION
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.connected_buffer
|
||||
.set_connected_machine(&machine_identification_unique);
|
||||
self.connected_buffer.reverse_connect();
|
||||
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
/// disconnect buffer
|
||||
pub fn disconnect_buffer(
|
||||
&mut self,
|
||||
machine_identification_unique: MachineIdentificationUnique,
|
||||
) {
|
||||
if !matches!(
|
||||
machine_identification_unique.machine_identification,
|
||||
BufferV1::MACHINE_IDENTIFICATION
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.connected_buffer.reverse_disconnect();
|
||||
self.connected_buffer.disconnect();
|
||||
|
||||
self.emit_state();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
|
||||
+10
-29
@@ -1,29 +1,16 @@
|
||||
#[cfg(all(not(target_env = "msvc"), not(feature = "dhat-heap")))]
|
||||
use tikv_jemallocator::Jemalloc;
|
||||
|
||||
#[cfg(all(not(target_env = "msvc"), not(feature = "dhat-heap")))]
|
||||
#[global_allocator]
|
||||
static GLOBAL: Jemalloc = Jemalloc;
|
||||
|
||||
#[cfg(feature = "dhat-heap")]
|
||||
#[global_allocator]
|
||||
static ALLOC: dhat::Alloc = dhat::Alloc;
|
||||
|
||||
use app_state::AppState;
|
||||
use r#loop::init_loop;
|
||||
#[cfg(feature = "mock-machine")]
|
||||
use mock::init::init_mock;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
|
||||
use r#loop::init_loop;
|
||||
use rest::init::init_api;
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use serial::init::init_serial;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[cfg(all(not(target_env = "msvc"), not(feature = "dhat-heap")))]
|
||||
use jemalloc_stats::init_jemalloc_stats;
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use crate::ethercat::init::init_ethercat;
|
||||
use crate::panic::init_panic;
|
||||
use crate::socketio::queue::init_socketio_queue;
|
||||
|
||||
@@ -40,25 +27,18 @@ pub mod rest;
|
||||
pub mod serial;
|
||||
pub mod socketio;
|
||||
|
||||
#[cfg(all(not(target_env = "msvc"), not(feature = "dhat-heap")))]
|
||||
pub mod jemalloc_stats;
|
||||
use crate::ethercat::init::init_ethercat;
|
||||
|
||||
fn main() {
|
||||
// Initialize panic handling
|
||||
let thread_panic_tx = init_panic();
|
||||
logging::init_tracing();
|
||||
tracing::info!("Tracing initialized successfully");
|
||||
|
||||
// lock memory (not working thus commented out)
|
||||
// if let Err(e) = lock_memory() {
|
||||
// tracing::error!("[{}::main] Failed to lock memory: {:?}", module_path!(), e);
|
||||
// } else {
|
||||
// tracing::info!("[{}::main] Memory locked successfully", module_path!());
|
||||
// }
|
||||
|
||||
#[cfg(all(not(target_env = "msvc"), not(feature = "dhat-heap")))]
|
||||
init_jemalloc_stats();
|
||||
|
||||
if let Err(e) = control_core::realtime::lock_memory() {
|
||||
tracing::error!("[{}::main] Failed to lock memory: {:?}", module_path!(), e);
|
||||
} else {
|
||||
tracing::info!("[{}::main] Memory locked successfully", module_path!());
|
||||
}
|
||||
let app_state = Arc::new(AppState::new());
|
||||
|
||||
// Spawn init thread
|
||||
@@ -97,7 +77,8 @@ fn main() {
|
||||
// Keep the main thread alive indefinitely
|
||||
// The program should only exit via panic handling or external signals
|
||||
loop {
|
||||
std::thread::sleep(Duration::from_secs(u64::MAX));
|
||||
// better way to "sleep", uses basically
|
||||
std::thread::park();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user