Compare commits
No files matched your search
@@ -1,189 +0,0 @@
|
||||
use smol::Timer;
|
||||
use std::time::{Duration, Instant};
|
||||
use tracing::instrument;
|
||||
|
||||
pub struct LoopThrottle {
|
||||
target_cycle_time: Duration,
|
||||
update_interval_cycles: usize,
|
||||
window_start: Instant,
|
||||
total_cycles: usize,
|
||||
delay: Duration,
|
||||
last_avg_cycle_time: Duration,
|
||||
on_update: Option<Box<dyn Fn(LoopThrottleUpdate)>>,
|
||||
}
|
||||
|
||||
pub struct LoopThrottleUpdate {
|
||||
pub delay: Duration,
|
||||
pub avg_cycle_time: Duration,
|
||||
pub error: i128, // Error in nanoseconds
|
||||
}
|
||||
impl LoopThrottle {
|
||||
pub fn new(
|
||||
target_cycle_time: Duration,
|
||||
update_interval_cycles: usize,
|
||||
on_update: Option<Box<dyn Fn(LoopThrottleUpdate)>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
target_cycle_time,
|
||||
update_interval_cycles,
|
||||
window_start: Instant::now(),
|
||||
total_cycles: 0,
|
||||
delay: target_cycle_time,
|
||||
last_avg_cycle_time: target_cycle_time,
|
||||
on_update,
|
||||
}
|
||||
}
|
||||
#[instrument(skip_all)]
|
||||
pub async fn sleep(&mut self) {
|
||||
self.total_cycles += 1;
|
||||
|
||||
// Check if it's time to update (every N cycles)
|
||||
if self.total_cycles % self.update_interval_cycles == 0 {
|
||||
let actual_total_time = self.window_start.elapsed();
|
||||
|
||||
// Use integer arithmetic where possible
|
||||
let avg_actual_cycle_time_nanos =
|
||||
actual_total_time.as_nanos() / self.update_interval_cycles as u128;
|
||||
let target_cycle_time_nanos = self.target_cycle_time.as_nanos();
|
||||
|
||||
// Calculate error in nanoseconds (signed)
|
||||
let error_nanos = target_cycle_time_nanos as i128 - avg_actual_cycle_time_nanos as i128;
|
||||
|
||||
// Update current delay, allowing zero or negative values to become zero
|
||||
let new_delay_nanos = (self.delay.as_nanos() as i128 + error_nanos).max(0) as u64;
|
||||
|
||||
self.delay = Duration::from_nanos(new_delay_nanos);
|
||||
self.last_avg_cycle_time = Duration::from_nanos(avg_actual_cycle_time_nanos as u64);
|
||||
|
||||
// Reset for next measurement window
|
||||
self.window_start = Instant::now();
|
||||
self.total_cycles = 0; // Reset to prevent overflow
|
||||
|
||||
// if we have an update callback, call it
|
||||
if let Some(on_update) = &self.on_update {
|
||||
on_update(LoopThrottleUpdate {
|
||||
delay: self.delay,
|
||||
avg_cycle_time: self.last_avg_cycle_time,
|
||||
error: error_nanos,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Timer::after(self.delay).await;
|
||||
}
|
||||
|
||||
pub fn get_cycle_time(&self) -> Duration {
|
||||
self.last_avg_cycle_time
|
||||
}
|
||||
|
||||
pub fn get_delay(&self) -> Duration {
|
||||
self.delay
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use smol::block_on;
|
||||
|
||||
#[test]
|
||||
fn test_adaptive_throttle_timing() {
|
||||
block_on(async {
|
||||
let update_interval_cycles = 100;
|
||||
let target_cycle_time = Duration::from_micros(5);
|
||||
let mut throttle = LoopThrottle::new(target_cycle_time, update_interval_cycles, None);
|
||||
|
||||
let test_start = Instant::now();
|
||||
let mut cycle_times = Vec::new();
|
||||
|
||||
// Run for several cycles to trigger multiple updates
|
||||
for _ in 0..10000 {
|
||||
let cycle_start = Instant::now();
|
||||
|
||||
// Simulate some work
|
||||
Timer::after(Duration::from_micros(5)).await;
|
||||
|
||||
throttle.sleep().await;
|
||||
|
||||
let cycle_time = cycle_start.elapsed();
|
||||
cycle_times.push(cycle_time);
|
||||
}
|
||||
|
||||
let total_test_time = test_start.elapsed();
|
||||
let avg_cycle_time = cycle_times.iter().sum::<Duration>() / cycle_times.len() as u32;
|
||||
|
||||
println!("Test completed in: {:?}", total_test_time);
|
||||
println!("Average cycle time: {:?}", avg_cycle_time);
|
||||
println!("Target cycle time: {:?}", target_cycle_time);
|
||||
println!("Last measured avg: {:?}", throttle.get_cycle_time());
|
||||
|
||||
// Assert that we're reasonably close to target timing
|
||||
let tolerance = Duration::from_millis(10);
|
||||
assert!(
|
||||
avg_cycle_time >= target_cycle_time.saturating_sub(tolerance)
|
||||
&& avg_cycle_time <= target_cycle_time + tolerance,
|
||||
"Average cycle time {:?} not within tolerance of target {:?}",
|
||||
avg_cycle_time,
|
||||
target_cycle_time
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_throttle_adaptation() {
|
||||
block_on(async {
|
||||
let target_cycle_time = Duration::from_millis(100);
|
||||
let update_interval_cycles = 3;
|
||||
let mut throttle = LoopThrottle::new(target_cycle_time, update_interval_cycles, None);
|
||||
|
||||
let initial_delay = throttle.get_delay();
|
||||
|
||||
// Run enough cycles to trigger multiple updates
|
||||
for _ in 0..12 {
|
||||
// Add variable work to test adaptation
|
||||
Timer::after(Duration::from_millis(10)).await;
|
||||
throttle.sleep().await;
|
||||
}
|
||||
|
||||
println!("Initial delay: {:?}", initial_delay);
|
||||
println!("Final delay: {:?}", throttle.get_delay());
|
||||
println!("Last avg cycle time: {:?}", throttle.get_cycle_time());
|
||||
|
||||
assert!(throttle.get_cycle_time() > Duration::ZERO);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_throttle_initialization() {
|
||||
let update_interval_cycles = 10;
|
||||
let target_cycle_time = Duration::from_millis(75);
|
||||
let throttle = LoopThrottle::new(target_cycle_time, update_interval_cycles, None);
|
||||
|
||||
assert_eq!(throttle.get_delay(), target_cycle_time);
|
||||
assert_eq!(throttle.get_cycle_time(), target_cycle_time);
|
||||
assert_eq!(throttle.total_cycles, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_overflow_protection() {
|
||||
block_on(async {
|
||||
let target_cycle_time = Duration::from_micros(1);
|
||||
let update_interval_cycles = 5;
|
||||
let mut throttle = LoopThrottle::new(target_cycle_time, update_interval_cycles, None);
|
||||
|
||||
// Run many cycles to ensure total_cycles resets properly
|
||||
for i in 0..50 {
|
||||
throttle.sleep().await;
|
||||
|
||||
// Verify that total_cycles never exceeds update_interval_cycles
|
||||
assert!(
|
||||
throttle.total_cycles <= update_interval_cycles,
|
||||
"Cycle {}: total_cycles ({}) exceeded update_interval_cycles ({})",
|
||||
i,
|
||||
throttle.total_cycles,
|
||||
update_interval_cycles
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,5 @@ pub mod compare_lists;
|
||||
pub mod hasher_serializer;
|
||||
pub mod hashing;
|
||||
pub mod interpolation;
|
||||
pub mod loop_trottle;
|
||||
pub mod moving_time_window;
|
||||
pub mod retry;
|
||||
@@ -71,7 +71,7 @@ impl<'a> Iterator for MachineManagerIterator<'a> {
|
||||
|
||||
impl MachineManager {
|
||||
// Returns an iterator over all machines (both ethercat and serial)
|
||||
pub fn iter(&self) -> MachineManagerIterator {
|
||||
pub fn iter(&'_ self) -> MachineManagerIterator<'_> {
|
||||
MachineManagerIterator {
|
||||
iter: MachineManagerIter::EthercatMachines {
|
||||
iter: self.ethercat_machines.iter(),
|
||||
|
||||
@@ -9,7 +9,6 @@ use ethercrab::{SubDevice, SubDeviceRef};
|
||||
use smol::{channel::Sender, lock::RwLock};
|
||||
use socketioxide::extract::SocketRef;
|
||||
use std::{
|
||||
pin::Pin,
|
||||
sync::{Arc, Weak},
|
||||
time::Instant,
|
||||
};
|
||||
@@ -25,7 +24,7 @@ pub trait MachineNewTrait {
|
||||
}
|
||||
|
||||
pub trait MachineAct {
|
||||
fn act(&mut self, now: Instant) -> Pin<Box<dyn Future<Output = ()> + Send + '_>>;
|
||||
fn act(&mut self, now: Instant);
|
||||
}
|
||||
|
||||
pub struct MachineNewParams<
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"@hookform/resolvers": "^4.1.2",
|
||||
"@icons-pack/react-simple-icons": "^11.2.0",
|
||||
"@jub3i/tree-kill": "^0.0.15",
|
||||
"@radix-ui/react-collapsible": "^1.1.8",
|
||||
"@radix-ui/react-collapsible": "^1.1.12",
|
||||
"@radix-ui/react-dialog": "^1.1.6",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.15",
|
||||
"@radix-ui/react-label": "^2.1.2",
|
||||
@@ -54,7 +54,7 @@
|
||||
"xlsx": "^0.18.5",
|
||||
"zod": "^3.24.2",
|
||||
"zod-validation-error": "^3.4.0",
|
||||
"zustand": "^5.0.3"
|
||||
"zustand": "^5.0.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@electron-forge/cli": "^7.6.1",
|
||||
@@ -79,7 +79,7 @@
|
||||
"@types/node": "^24.2.1",
|
||||
"@types/react": "^19.0.10",
|
||||
"@types/react-dom": "^19.0.4",
|
||||
"electron": "^37.2.6",
|
||||
"electron": "^37.3.1",
|
||||
"electron-playwright-helpers": "^1.7.1",
|
||||
"eslint": "^9.20.1",
|
||||
"eslint-config-prettier": "^10.1.2",
|
||||
@@ -2323,15 +2323,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-collapsible": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.11.tgz",
|
||||
"integrity": "sha512-2qrRsVGSCYasSz1RFOorXwl0H7g7J1frQtgpQgYrt+MOidtPAINHn9CPovQXb83r8ahapdx3Tu0fa/pdFFSdPg==",
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.12.tgz",
|
||||
"integrity": "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/primitive": "1.1.2",
|
||||
"@radix-ui/primitive": "1.1.3",
|
||||
"@radix-ui/react-compose-refs": "1.1.2",
|
||||
"@radix-ui/react-context": "1.1.2",
|
||||
"@radix-ui/react-id": "1.1.1",
|
||||
"@radix-ui/react-presence": "1.1.4",
|
||||
"@radix-ui/react-presence": "1.1.5",
|
||||
"@radix-ui/react-primitive": "2.1.3",
|
||||
"@radix-ui/react-use-controllable-state": "1.2.2",
|
||||
"@radix-ui/react-use-layout-effect": "1.1.1"
|
||||
@@ -2351,6 +2352,36 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-collapsible/node_modules/@radix-ui/primitive": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz",
|
||||
"integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@radix-ui/react-collapsible/node_modules/@radix-ui/react-presence": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz",
|
||||
"integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-compose-refs": "1.1.2",
|
||||
"@radix-ui/react-use-layout-effect": "1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-collection": {
|
||||
"version": "1.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz",
|
||||
@@ -6285,9 +6316,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/electron": {
|
||||
"version": "37.2.6",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-37.2.6.tgz",
|
||||
"integrity": "sha512-Ns6xyxE+hIK5UlujtRlw7w4e2Ju/ImCWXf1Q/PoOhc0N3/6SN6YW7+ujCarsHbxWnolbW+1RlkHtdklUJpjbPA==",
|
||||
"version": "37.3.1",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-37.3.1.tgz",
|
||||
"integrity": "sha512-7DhktRLqhe6OJh/Bo75bTI0puUYEmIwSzMinocgO63mx3MVjtIn2tYMzLmAleNIlud2htkjpsMG2zT4PiTCloA==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
@@ -15204,9 +15235,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/zustand": {
|
||||
"version": "5.0.7",
|
||||
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.7.tgz",
|
||||
"integrity": "sha512-Ot6uqHDW/O2VdYsKLLU8GQu8sCOM1LcoE8RwvLv9uuRT9s6SOHCKs0ZEOhxg+I1Ld+A1Q5lwx+UlKXXUoCZITg==",
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.8.tgz",
|
||||
"integrity": "sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12.20.0"
|
||||
},
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
"@types/node": "^24.2.1",
|
||||
"@types/react": "^19.0.10",
|
||||
"@types/react-dom": "^19.0.4",
|
||||
"electron": "^37.2.6",
|
||||
"electron": "^37.3.1",
|
||||
"electron-playwright-helpers": "^1.7.1",
|
||||
"eslint": "^9.20.1",
|
||||
"eslint-config-prettier": "^10.1.2",
|
||||
@@ -67,7 +67,7 @@
|
||||
"@hookform/resolvers": "^4.1.2",
|
||||
"@icons-pack/react-simple-icons": "^11.2.0",
|
||||
"@jub3i/tree-kill": "^0.0.15",
|
||||
"@radix-ui/react-collapsible": "^1.1.8",
|
||||
"@radix-ui/react-collapsible": "^1.1.12",
|
||||
"@radix-ui/react-dialog": "^1.1.6",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.15",
|
||||
"@radix-ui/react-label": "^2.1.2",
|
||||
@@ -110,6 +110,6 @@
|
||||
"xlsx": "^0.18.5",
|
||||
"zod": "^3.24.2",
|
||||
"zod-validation-error": "^3.4.0",
|
||||
"zustand": "^5.0.3"
|
||||
"zustand": "^5.0.8"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 850 KiB |
|
After Width: | Height: | Size: 128 KiB |
|
After Width: | Height: | Size: 178 KiB |
|
After Width: | Height: | Size: 201 KiB |
|
After Width: | Height: | Size: 234 KiB |
|
After Width: | Height: | Size: 197 KiB |
|
After Width: | Height: | Size: 213 KiB |
|
After Width: | Height: | Size: 171 KiB |
|
After Width: | Height: | Size: 325 KiB |
|
After Width: | Height: | Size: 664 KiB |
|
After Width: | Height: | Size: 108 KiB |
|
After Width: | Height: | Size: 159 KiB |
|
After Width: | Height: | Size: 298 KiB |
|
After Width: | Height: | Size: 226 KiB |
@@ -697,3 +697,26 @@ The following persons must not work on the machine:
|
||||
| Cleaning, maintenance | |
|
||||
| Maintenance | Trained and instructed specialised personnel of the operator or service technicians of the manufacturer |
|
||||
| Decommissioning | Trained and instructed specialised personnel of the operator |
|
||||
|
||||
<br>
|
||||
<br>
|
||||
|
||||
## Electrical Diagrams
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
@@ -687,3 +687,18 @@ The following persons may not work on the machine:
|
||||
| K Cleaning, maintenance | |
|
||||
| L Maintenance | Trained and instructed specialized personnel of the operator or service technicians of the manufacturer |
|
||||
| M Decommissioning | Trained and instructed specialized personnel of the operator |
|
||||
|
||||
<br>
|
||||
<br>
|
||||
|
||||
## Electrical Diagrams
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
@@ -50,12 +50,11 @@ export function useBuffer1() {
|
||||
// Single optimistic state for all state management
|
||||
const stateOptimistic = useStateOptimistic<StateEvent>();
|
||||
|
||||
// Update optimistic state when real state changes
|
||||
useEffect(() => {
|
||||
if (state) {
|
||||
stateOptimistic.setReal(state);
|
||||
}
|
||||
}, [state, stateOptimistic]);
|
||||
}, [state]);
|
||||
|
||||
// Helper function for optimistic updates using produce
|
||||
const updateStateOptimistically = (
|
||||
@@ -63,7 +62,7 @@ export function useBuffer1() {
|
||||
serverRequest: () => void,
|
||||
) => {
|
||||
const currentState = stateOptimistic.value;
|
||||
if (currentState) {
|
||||
if (currentState && !stateOptimistic.isOptimistic) {
|
||||
stateOptimistic.setOptimistic(produce(currentState, producer));
|
||||
}
|
||||
serverRequest();
|
||||
|
||||
@@ -61,12 +61,11 @@ export function useExtruder2() {
|
||||
// Single optimistic state for all state management
|
||||
const stateOptimistic = useStateOptimistic<StateEvent>();
|
||||
|
||||
// Update optimistic state when real state changes
|
||||
useEffect(() => {
|
||||
if (state) {
|
||||
stateOptimistic.setReal(state);
|
||||
}
|
||||
}, [state, stateOptimistic]);
|
||||
}, [state]);
|
||||
|
||||
// Helper function for optimistic updates using produce
|
||||
const updateStateOptimistically = (
|
||||
@@ -74,7 +73,7 @@ export function useExtruder2() {
|
||||
serverRequest: () => void,
|
||||
) => {
|
||||
const currentState = stateOptimistic.value;
|
||||
if (currentState) {
|
||||
if (currentState && !stateOptimistic.isOptimistic) {
|
||||
stateOptimistic.setOptimistic(produce(currentState, producer));
|
||||
}
|
||||
serverRequest();
|
||||
|
||||
@@ -18,12 +18,11 @@ function useLaser(machine_identification_unique: MachineIdentificationUnique) {
|
||||
// Single optimistic state for all state management
|
||||
const stateOptimistic = useStateOptimistic<StateEvent>();
|
||||
|
||||
// Update optimistic state when real state changes
|
||||
useEffect(() => {
|
||||
if (state) {
|
||||
stateOptimistic.setReal(state);
|
||||
}
|
||||
}, [state, stateOptimistic]);
|
||||
}, [state]);
|
||||
|
||||
// Helper function for optimistic updates using produce
|
||||
const updateStateOptimistically = (
|
||||
@@ -31,7 +30,7 @@ function useLaser(machine_identification_unique: MachineIdentificationUnique) {
|
||||
serverRequest: () => void,
|
||||
) => {
|
||||
const currentState = stateOptimistic.value;
|
||||
if (currentState) {
|
||||
if (currentState && !stateOptimistic.isOptimistic) {
|
||||
stateOptimistic.setOptimistic(produce(currentState, producer));
|
||||
}
|
||||
serverRequest();
|
||||
|
||||
@@ -17,12 +17,11 @@ function useMock(machine_identification_unique: MachineIdentificationUnique) {
|
||||
// Single optimistic state for all state management
|
||||
const stateOptimistic = useStateOptimistic<StateEvent>();
|
||||
|
||||
// Update optimistic state when real state changes
|
||||
useEffect(() => {
|
||||
if (state) {
|
||||
stateOptimistic.setReal(state);
|
||||
}
|
||||
}, [state, stateOptimistic]);
|
||||
}, [state]);
|
||||
|
||||
// Helper function for optimistic updates using produce
|
||||
const updateStateOptimistically = (
|
||||
@@ -30,7 +29,7 @@ function useMock(machine_identification_unique: MachineIdentificationUnique) {
|
||||
serverRequest: () => Promise<any>,
|
||||
) => {
|
||||
const currentState = stateOptimistic.value;
|
||||
if (currentState) {
|
||||
if (currentState && !stateOptimistic.isOptimistic) {
|
||||
stateOptimistic.setOptimistic(produce(currentState, producer));
|
||||
}
|
||||
serverRequest()
|
||||
|
||||
@@ -69,12 +69,12 @@ 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);
|
||||
}
|
||||
}, [state, stateOptimistic]);
|
||||
}, [state]);
|
||||
|
||||
// Request functions for all operations
|
||||
const { request: requestTraverseGotoLimitInner } = useMachineMutation(
|
||||
@@ -178,7 +178,7 @@ export function useWinder2() {
|
||||
serverRequest: () => void,
|
||||
) => {
|
||||
const currentState = stateOptimistic.value;
|
||||
if (currentState) {
|
||||
if (currentState && !stateOptimistic.isOptimistic) {
|
||||
stateOptimistic.setOptimistic(produce(currentState, producer));
|
||||
}
|
||||
serverRequest();
|
||||
|
||||
@@ -103,7 +103,7 @@ in {
|
||||
|
||||
environment = {
|
||||
RUST_BACKTRACE = "full";
|
||||
RUST_LOG = "debug";
|
||||
RUST_LOG = "info";
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -78,13 +78,13 @@ pub async fn setup_loop(
|
||||
pdu,
|
||||
Timeouts {
|
||||
// Default 5000ms
|
||||
state_transition: Duration::from_millis(10 * 1000),
|
||||
state_transition: Duration::from_millis(5000),
|
||||
// Default 30_000us
|
||||
pdu: Duration::from_millis(500),
|
||||
pdu: Duration::from_micros(30_000),
|
||||
// Default 10ms
|
||||
eeprom: Duration::from_millis(10),
|
||||
// Default 0ms
|
||||
wait_loop_delay: Duration::from_millis(0),
|
||||
wait_loop_delay: Duration::from_millis(1),
|
||||
// Default 100ms
|
||||
mailbox_echo: Duration::from_millis(100),
|
||||
// Default 1000ms
|
||||
@@ -92,7 +92,7 @@ pub async fn setup_loop(
|
||||
},
|
||||
MainDeviceConfig {
|
||||
// Default RetryBehaviour::None
|
||||
retry_behaviour: RetryBehaviour::Count(10), // 100ms * 25 = 2.5s
|
||||
retry_behaviour: RetryBehaviour::Count(5),
|
||||
// Default 10_000
|
||||
dc_static_sync_iterations: 10_000,
|
||||
},
|
||||
|
||||
@@ -1,19 +1,15 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use control_core::helpers::loop_trottle::LoopThrottle;
|
||||
use std::{thread::sleep, time::Duration};
|
||||
|
||||
pub fn init_jemalloc_stats() {
|
||||
std::thread::Builder::new()
|
||||
.name("jemalloc-stats".to_string())
|
||||
.spawn(|| {
|
||||
let mut throttle = LoopThrottle::new(Duration::from_secs(10), 1, None);
|
||||
let mut last_allocated = 0usize;
|
||||
let mut last_time = std::time::Instant::now();
|
||||
|
||||
loop {
|
||||
smol::block_on(async {
|
||||
throttle.sleep().await;
|
||||
|
||||
sleep(Duration::from_secs(10));
|
||||
// Advance epoch and get allocated bytes
|
||||
let _ = tikv_jemalloc_ctl::epoch::advance();
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use crate::app_state::AppState;
|
||||
use crate::panic::{PanicDetails, send_panic};
|
||||
use bitvec::prelude::*;
|
||||
use control_core::helpers::loop_trottle::LoopThrottle;
|
||||
use control_core::realtime::{set_core_affinity, set_realtime_priority};
|
||||
use smol::Timer;
|
||||
use smol::channel::Sender;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
use tokio::time::MissedTickBehavior;
|
||||
use tracing::{instrument, trace_span};
|
||||
|
||||
pub fn init_loop(
|
||||
@@ -18,7 +19,6 @@ pub fn init_loop(
|
||||
.spawn(move || {
|
||||
send_panic(thread_panic_tx.clone());
|
||||
let rt = smol::LocalExecutor::new();
|
||||
let mut throttle = LoopThrottle::new(Duration::from_millis(1), 10, None);
|
||||
|
||||
// Set core affinity to third core
|
||||
let _ = set_core_affinity(2);
|
||||
@@ -37,17 +37,25 @@ pub fn init_loop(
|
||||
);
|
||||
}
|
||||
|
||||
loop {
|
||||
let res = smol::block_on(rt.run(async {
|
||||
throttle.sleep().await;
|
||||
loop_once(app_state.clone()).await
|
||||
}));
|
||||
smol::block_on(rt.run(async move {
|
||||
let tick_duration = Duration::from_millis(1);
|
||||
|
||||
if let Err(err) = res {
|
||||
tracing::error!("Loop failed\n{:?}", err);
|
||||
break;
|
||||
loop {
|
||||
let loop_start = Instant::now();
|
||||
|
||||
if let Err(err) = loop_once(app_state.clone()).await {
|
||||
tracing::error!("Loop failed: {:?}", err);
|
||||
break;
|
||||
}
|
||||
|
||||
// Sleep until next tick
|
||||
let elapsed = loop_start.elapsed();
|
||||
if elapsed < tick_duration {
|
||||
smol::Timer::after(tick_duration - elapsed).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
// Exit the entire program if the Loop fails (gets restarted by systemd if running on NixOS)
|
||||
std::process::exit(1);
|
||||
})
|
||||
@@ -151,7 +159,7 @@ pub async fn loop_once<'maindevice>(app_state: Arc<AppState>) -> Result<(), anyh
|
||||
let span = trace_span!("loop_once_act_machine",);
|
||||
let _enter = span.enter();
|
||||
// execute machine
|
||||
machine_guard.act(now).await;
|
||||
machine_guard.act(now);
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
|
||||
@@ -3,16 +3,13 @@ use std::time::{Duration, Instant};
|
||||
use super::BufferV1;
|
||||
use control_core::machines::new::MachineAct;
|
||||
impl MachineAct for BufferV1 {
|
||||
fn act(&mut self, now: Instant) -> std::pin::Pin<Box<dyn Future<Output = ()> + Send + '_>> {
|
||||
Box::pin(async move {
|
||||
// if last measurement is older than 1 second, emit a new measurement
|
||||
if now.duration_since(self.last_measurement_emit) > Duration::from_secs_f64(1.0 / 30.0)
|
||||
{
|
||||
// Emit live values at 30 FPS
|
||||
self.emit_live_values();
|
||||
fn act(&mut self, now: Instant) {
|
||||
// if last measurement is older than 1 second, emit a new measurement
|
||||
if now.duration_since(self.last_measurement_emit) > Duration::from_secs_f64(1.0 / 30.0) {
|
||||
// Emit live values at 30 FPS
|
||||
self.emit_live_values();
|
||||
|
||||
self.last_measurement_emit = now;
|
||||
}
|
||||
})
|
||||
self.last_measurement_emit = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,45 +1,39 @@
|
||||
use control_core::machines::new::MachineAct;
|
||||
|
||||
use super::{ExtruderV2, ExtruderV2Mode};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use crate::machines::extruder1::ExtruderV2;
|
||||
|
||||
impl MachineAct for ExtruderV2 {
|
||||
fn act(
|
||||
&mut self,
|
||||
now_ts: Instant,
|
||||
) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send + '_>> {
|
||||
Box::pin(async move {
|
||||
self.temperature_controller_back.update(now_ts);
|
||||
self.temperature_controller_nozzle.update(now_ts);
|
||||
self.temperature_controller_front.update(now_ts);
|
||||
self.temperature_controller_middle.update(now_ts);
|
||||
fn act(&mut self, now: Instant) {
|
||||
self.temperature_controller_back.update(now);
|
||||
self.temperature_controller_nozzle.update(now);
|
||||
self.temperature_controller_front.update(now);
|
||||
self.temperature_controller_middle.update(now);
|
||||
|
||||
if self.mode == ExtruderV2Mode::Extrude {
|
||||
self.screw_speed_controller.update(now_ts, true);
|
||||
} else {
|
||||
self.screw_speed_controller.update(now_ts, false);
|
||||
}
|
||||
if self.mode == super::ExtruderV2Mode::Extrude {
|
||||
self.screw_speed_controller.update(now, true);
|
||||
} else {
|
||||
self.screw_speed_controller.update(now, false);
|
||||
}
|
||||
|
||||
if self.mode == ExtruderV2Mode::Standby {
|
||||
self.turn_heating_off();
|
||||
}
|
||||
if self.mode == super::ExtruderV2Mode::Standby {
|
||||
self.turn_heating_off();
|
||||
}
|
||||
|
||||
if self.mode == ExtruderV2Mode::Extrude
|
||||
&& !self.screw_speed_controller.get_motor_enabled()
|
||||
{
|
||||
self.switch_to_heat();
|
||||
}
|
||||
if self.mode == super::ExtruderV2Mode::Extrude
|
||||
&& !self.screw_speed_controller.get_motor_enabled()
|
||||
{
|
||||
self.switch_to_heat();
|
||||
}
|
||||
|
||||
let now = Instant::now();
|
||||
let now = Instant::now();
|
||||
|
||||
// more than 33ms have passed since last emit (30 "fps" target)
|
||||
if now.duration_since(self.last_measurement_emit) > Duration::from_secs_f64(1.0 / 30.0)
|
||||
{
|
||||
self.maybe_emit_state_event();
|
||||
// Emit live values at 30 FPS
|
||||
self.emit_live_values();
|
||||
self.last_measurement_emit = now;
|
||||
}
|
||||
})
|
||||
// more than 33ms have passed since last emit (30 "fps" target)
|
||||
if now.duration_since(self.last_measurement_emit) > Duration::from_secs_f64(1.0 / 30.0) {
|
||||
self.maybe_emit_state_event();
|
||||
// Emit live values at 30 FPS
|
||||
self.emit_live_values();
|
||||
self.last_measurement_emit = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,6 @@
|
||||
use super::LaserMachine;
|
||||
use control_core::machines::new::MachineAct;
|
||||
use std::{
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
/// Implements the `MachineAct` trait for the `LaserMachine`.
|
||||
///
|
||||
@@ -22,15 +18,12 @@ use std::{
|
||||
/// The method ensures that the diameter value is updated approximately 60 times per second.
|
||||
///
|
||||
impl MachineAct for LaserMachine {
|
||||
fn act(&mut self, now: Instant) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
|
||||
Box::pin(async move {
|
||||
// more than 33ms have passed since last emit (30 "fps" target)
|
||||
if now.duration_since(self.last_measurement_emit) > Duration::from_secs_f64(1.0 / 30.0)
|
||||
{
|
||||
self.maybe_emit_state_event();
|
||||
self.emit_live_values();
|
||||
self.last_measurement_emit = now;
|
||||
}
|
||||
})
|
||||
fn act(&mut self, now: Instant) {
|
||||
// more than 33ms have passed since last emit (30 "fps" target)
|
||||
if now.duration_since(self.last_measurement_emit) > Duration::from_secs_f64(1.0 / 30.0) {
|
||||
self.maybe_emit_state_event();
|
||||
self.emit_live_values();
|
||||
self.last_measurement_emit = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,7 @@
|
||||
use control_core::machines::new::MachineAct;
|
||||
|
||||
use super::MockMachine;
|
||||
use std::{
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
/// Implements the `MachineAct` trait for the `MockMachine`.
|
||||
///
|
||||
@@ -24,18 +20,13 @@ use std::{
|
||||
/// The method ensures that the sine wave value is updated approximately 60 times per second (16ms intervals) when running.
|
||||
///
|
||||
impl MachineAct for MockMachine {
|
||||
fn act(&mut self, _now_ts: Instant) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
|
||||
Box::pin(async move {
|
||||
let now = Instant::now();
|
||||
|
||||
// Only emit live values if machine is in Running mode
|
||||
// The live values are updated approximately 30 times per second
|
||||
if now.duration_since(self.last_measurement_emit) > Duration::from_secs_f64(1.0 / 30.0)
|
||||
{
|
||||
self.maybe_emit_state_event();
|
||||
self.emit_live_values();
|
||||
self.last_measurement_emit = now;
|
||||
}
|
||||
})
|
||||
fn act(&mut self, now: Instant) {
|
||||
// Only emit live values if machine is in Running mode
|
||||
// The live values are updated approximately 30 times per second
|
||||
if now.duration_since(self.last_measurement_emit) > Duration::from_secs_f64(1.0 / 30.0) {
|
||||
self.maybe_emit_state_event();
|
||||
self.emit_live_values();
|
||||
self.last_measurement_emit = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,33 +1,26 @@
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use super::Winder2;
|
||||
use control_core::machines::new::MachineAct;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
impl MachineAct for Winder2 {
|
||||
fn act(
|
||||
&mut self,
|
||||
now: Instant,
|
||||
) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send + '_>> {
|
||||
Box::pin(async move {
|
||||
// sync the spool speed
|
||||
self.sync_spool_speed(now);
|
||||
fn act(&mut self, now: Instant) {
|
||||
// sync the spool speed
|
||||
self.sync_spool_speed(now);
|
||||
|
||||
// sync the puller speed
|
||||
self.sync_puller_speed(now);
|
||||
// sync the puller speed
|
||||
self.sync_puller_speed(now);
|
||||
|
||||
// sync the traverse speed
|
||||
self.sync_traverse_speed();
|
||||
// sync the traverse speed
|
||||
self.sync_traverse_speed();
|
||||
|
||||
// automatically stops or pulls after N Meters if enabled
|
||||
self.stop_or_pull_spool(now);
|
||||
// automatically stops or pulls after N Meters if enabled
|
||||
self.stop_or_pull_spool(now);
|
||||
|
||||
// more than 33ms have passed since last emit (30 "fps" target)
|
||||
if now.duration_since(self.last_measurement_emit) > Duration::from_secs_f64(1.0 / 30.0)
|
||||
{
|
||||
self.maybe_emit_state_event();
|
||||
self.emit_live_values();
|
||||
self.last_measurement_emit = now;
|
||||
}
|
||||
})
|
||||
// more than 33ms have passed since last emit (30 "fps" target)
|
||||
if now.duration_since(self.last_measurement_emit) > Duration::from_secs_f64(1.0 / 30.0) {
|
||||
self.maybe_emit_state_event();
|
||||
self.emit_live_values();
|
||||
self.last_measurement_emit = now;
|
||||
}
|
||||
}
|
||||
}
|
||||