autocore_std/lib.rs
1//! # AutoCore Standard Library
2//!
3//! The standard library for writing AutoCore control programs. This crate provides
4//! everything you need to build real-time control applications that integrate with
5//! the AutoCore server ecosystem.
6//!
7//! ## Overview
8//!
9//! AutoCore control programs run as separate processes that communicate with the
10//! autocore-server via shared memory and IPC. This library handles all the low-level
11//! details, allowing you to focus on your control logic.
12//!
13//! ```text
14//! ┌─────────────────────────┐ ┌─────────────────────────┐
15//! │ autocore-server │ │ Your Control Program │
16//! │ │ │ │
17//! │ ┌─────────────────┐ │ │ ┌─────────────────┐ │
18//! │ │ Shared Memory │◄───┼─────┼──│ ControlRunner │ │
19//! │ │ (GlobalMemory) │ │ │ │ │ │
20//! │ └─────────────────┘ │ │ │ ┌─────────────┐ │ │
21//! │ │ │ │ │ Your Logic │ │ │
22//! │ ┌─────────────────┐ │ │ │ └─────────────┘ │ │
23//! │ │ Tick Signal │────┼─────┼──│ │ │
24//! │ └─────────────────┘ │ │ └─────────────────┘ │
25//! └─────────────────────────┘ └─────────────────────────┘
26//! ```
27//!
28//! ## Quick Start
29//!
30//! 1. Create a new control project using `acctl`:
31//! ```bash
32//! acctl clone <server-ip> <project-name>
33//! ```
34//!
35//! 2. Implement the [`ControlProgram`] trait:
36//! ```ignore
37//! use autocore_std::{ControlProgram, RTrig};
38//!
39//! // GlobalMemory is generated from your project.json
40//! mod gm;
41//! use gm::GlobalMemory;
42//!
43//! pub struct MyProgram {
44//! start_button: RTrig,
45//! }
46//!
47//! impl MyProgram {
48//! pub fn new() -> Self {
49//! Self {
50//! start_button: RTrig::new(),
51//! }
52//! }
53//! }
54//!
55//! impl ControlProgram for MyProgram {
56//! type Memory = GlobalMemory;
57//!
58//! fn process_tick(&mut self, mem: &mut GlobalMemory, _cycle: u64) {
59//! // Detect rising edge on start button
60//! if self.start_button.call(mem.inputs.start_button) {
61//! mem.outputs.motor_running = true;
62//! autocore_std::log::info!("Motor started!");
63//! }
64//! }
65//! }
66//! ```
67//!
68//! 3. Use the [`autocore_main!`] macro for the entry point:
69//! ```ignore
70//! autocore_std::autocore_main!(MyProgram, "my_project_shm", "tick");
71//! ```
72//!
73//! ## Function Blocks (IEC 61131-3 Inspired)
74//!
75//! This library includes standard function blocks commonly used in PLC programming:
76//!
77//! - [`RTrig`] - Rising edge detector (false→true transition)
78//! - [`FTrig`] - Falling edge detector (true→false transition)
79//! - [`Ton`] - Timer On Delay (output after delay)
80//! - [`SimpleTimer`] - Simple one-shot timer (NOT IEC 61131-3, for imperative use)
81//! - [`StateMachine`] - State machine helper with automatic timer management
82//!
83//! ### Example: Edge Detection
84//!
85//! ```
86//! use autocore_std::RTrig;
87//!
88//! let mut trigger = RTrig::new();
89//!
90//! // First call with false - no edge
91//! assert_eq!(trigger.call(false), false);
92//!
93//! // Rising edge detected!
94//! assert_eq!(trigger.call(true), true);
95//!
96//! // Still true, but no edge (already high)
97//! assert_eq!(trigger.call(true), false);
98//!
99//! // Back to false
100//! assert_eq!(trigger.call(false), false);
101//!
102//! // Another rising edge
103//! assert_eq!(trigger.call(true), true);
104//! ```
105//!
106//! ### Example: Timer
107//!
108//! ```
109//! use autocore_std::Ton;
110//! use std::time::Duration;
111//!
112//! let mut timer = Ton::new();
113//! let delay = Duration::from_millis(100);
114//!
115//! // Timer not enabled - output is false
116//! assert_eq!(timer.call(false, delay), false);
117//!
118//! // Enable timer - starts counting
119//! assert_eq!(timer.call(true, delay), false);
120//!
121//! // Still counting...
122//! std::thread::sleep(Duration::from_millis(50));
123//! assert_eq!(timer.call(true, delay), false);
124//! assert!(timer.et < delay); // Elapsed time < preset
125//!
126//! // After delay elapsed
127//! std::thread::sleep(Duration::from_millis(60));
128//! assert_eq!(timer.call(true, delay), true); // Output is now true!
129//! ```
130//!
131//! ## Logging
132//!
133//! Control programs can send log messages to the autocore-server for display in the
134//! web console. Logging is handled automatically when using [`ControlRunner`].
135//!
136//! ```ignore
137//! use autocore_std::log;
138//!
139//! log::trace!("Detailed trace message");
140//! log::debug!("Debug information");
141//! log::info!("Normal operation message");
142//! log::warn!("Warning condition detected");
143//! log::error!("Error occurred!");
144//! ```
145//!
146//! See the [`logger`] module for advanced configuration.
147//!
148//! ## Memory Synchronization
149//!
150//! The [`ControlRunner`] handles all shared memory synchronization automatically:
151//!
152//! 1. **Wait for tick** - Blocks until the server signals a new cycle
153//! 2. **Read inputs** - Copies shared memory to local buffer (atomic snapshot)
154//! 3. **Execute logic** - Your `process_tick` runs on the local buffer
155//! 4. **Write outputs** - Copies local buffer back to shared memory
156//!
157//! This ensures your control logic always sees a consistent view of the data,
158//! even when other processes are modifying shared memory.
159
160#![warn(missing_docs)]
161#![warn(rustdoc::missing_crate_level_docs)]
162#![doc(html_root_url = "https://docs.rs/autocore-std/3.3.0")]
163
164use anyhow::{anyhow, Result};
165use futures_util::{SinkExt, StreamExt};
166use log::LevelFilter;
167use mechutil::ipc::{CommandMessage, MessageType};
168use raw_sync::events::{Event, EventInit, EventState};
169use raw_sync::Timeout;
170use shared_memory::ShmemConf;
171use std::collections::HashMap;
172use std::sync::atomic::{fence, Ordering};
173use std::time::{Duration, Instant};
174use tokio_tungstenite::{connect_async, tungstenite::Message};
175
176/// UDP logger for sending log messages to autocore-server.
177///
178/// This module provides a non-blocking logger implementation that sends log messages
179/// via UDP to the autocore-server. Messages are batched and sent asynchronously to
180/// avoid impacting control loop timing.
181///
182/// # Example
183///
184/// ```ignore
185/// use autocore_std::logger;
186/// use log::LevelFilter;
187///
188/// // Initialize the logger (done automatically by ControlRunner)
189/// logger::init_udp_logger("127.0.0.1", 39101, LevelFilter::Info, "control")?;
190///
191/// // Now you can use the log macros
192/// log::info!("System initialized");
193/// ```
194pub mod logger;
195
196// Re-export log crate for convenience - control programs can use autocore_std::log::info!() etc.
197pub use log;
198
199// ============================================================================
200// Standard Library Function Blocks (IEC 61131-3 Inspired)
201// ============================================================================
202
203/// Rising Edge Trigger (R_TRIG)
204///
205/// Detects a rising edge (false → true transition) on the input signal.
206/// The output `q` is `true` for exactly one cycle when the input `clk`
207/// transitions from `false` to `true`.
208///
209/// This is equivalent to the IEC 61131-3 R_TRIG function block.
210///
211/// # Example
212///
213/// ```
214/// use autocore_std::RTrig;
215///
216/// let mut trigger = RTrig::new();
217///
218/// // No edge yet
219/// assert_eq!(trigger.call(false), false);
220///
221/// // Rising edge detected!
222/// assert_eq!(trigger.call(true), true);
223///
224/// // Signal still high, but edge already passed
225/// assert_eq!(trigger.call(true), false);
226/// assert_eq!(trigger.call(true), false);
227///
228/// // Signal goes low
229/// assert_eq!(trigger.call(false), false);
230///
231/// // Another rising edge
232/// assert_eq!(trigger.call(true), true);
233/// ```
234///
235/// # Timing Diagram
236///
237/// ```text
238/// clk: _____|‾‾‾‾‾‾‾‾‾|_____|‾‾‾‾‾
239/// q: _____|‾|_____________|‾|____
240/// ```
241///
242/// # Use Cases
243///
244/// - Detecting button presses (trigger on press, not hold)
245/// - Counting events (increment counter on each rising edge)
246/// - State machine transitions
247#[derive(Debug, Clone)]
248pub struct RTrig {
249 /// Current input value
250 pub clk: bool,
251 /// Output: true for one cycle on rising edge
252 pub q: bool,
253 /// Internal memory of previous input state
254 m: bool,
255}
256
257impl RTrig {
258 /// Creates a new rising edge trigger with all values initialized to `false`.
259 ///
260 /// # Example
261 ///
262 /// ```
263 /// use autocore_std::RTrig;
264 ///
265 /// let trigger = RTrig::new();
266 /// assert_eq!(trigger.q, false);
267 /// ```
268 pub fn new() -> Self {
269 Self {
270 clk: false,
271 q: false,
272 m: false,
273 }
274 }
275
276 /// Executes the rising edge detection logic.
277 ///
278 /// Call this method once per control cycle with the current input value.
279 /// Returns `true` for exactly one cycle when a rising edge is detected.
280 ///
281 /// # Arguments
282 ///
283 /// * `clk` - The current state of the input signal
284 ///
285 /// # Returns
286 ///
287 /// `true` if a rising edge (false → true transition) was detected, `false` otherwise.
288 ///
289 /// # Example
290 ///
291 /// ```
292 /// use autocore_std::RTrig;
293 ///
294 /// let mut trigger = RTrig::new();
295 ///
296 /// let button_pressed = true;
297 /// if trigger.call(button_pressed) {
298 /// println!("Button was just pressed!");
299 /// }
300 /// ```
301 pub fn call(&mut self, clk: bool) -> bool {
302 self.clk = clk;
303 self.q = self.clk && !self.m;
304 self.m = self.clk;
305 self.q
306 }
307}
308
309impl Default for RTrig {
310 fn default() -> Self {
311 Self::new()
312 }
313}
314
315
316/// Falling Edge Trigger (F_TRIG)
317///
318/// Detects a falling edge (true → false transition) on the input signal.
319/// The output `q` is `true` for exactly one cycle when the input `clk`
320/// transitions from `true` to `false`.
321///
322/// This is equivalent to the IEC 61131-3 F_TRIG function block.
323///
324/// # Example
325///
326/// ```
327/// use autocore_std::FTrig;
328///
329/// let mut trigger = FTrig::new();
330///
331/// // Signal starts low
332/// assert_eq!(trigger.call(false), false);
333///
334/// // Signal goes high
335/// assert_eq!(trigger.call(true), false);
336///
337/// // Falling edge detected!
338/// assert_eq!(trigger.call(false), true);
339///
340/// // Signal still low, edge already passed
341/// assert_eq!(trigger.call(false), false);
342/// ```
343///
344/// # Timing Diagram
345///
346/// ```text
347/// clk: _____|‾‾‾‾‾‾‾‾‾|_____|‾‾‾‾‾
348/// q: _______________|‾|________
349/// ```
350///
351/// # Use Cases
352///
353/// - Detecting button releases
354/// - Detecting signal loss
355/// - Triggering actions when a condition ends
356#[derive(Debug, Clone)]
357pub struct FTrig {
358 /// Current input value
359 pub clk: bool,
360 /// Output: true for one cycle on falling edge
361 pub q: bool,
362 /// Internal memory of previous input state
363 m: bool,
364}
365
366impl FTrig {
367 /// Creates a new falling edge trigger with all values initialized to `false`.
368 ///
369 /// # Example
370 ///
371 /// ```
372 /// use autocore_std::FTrig;
373 ///
374 /// let trigger = FTrig::new();
375 /// assert_eq!(trigger.q, false);
376 /// ```
377 pub fn new() -> Self {
378 Self {
379 clk: false,
380 q: false,
381 m: false,
382 }
383 }
384
385 /// Executes the falling edge detection logic.
386 ///
387 /// Call this method once per control cycle with the current input value.
388 /// Returns `true` for exactly one cycle when a falling edge is detected.
389 ///
390 /// # Arguments
391 ///
392 /// * `clk` - The current state of the input signal
393 ///
394 /// # Returns
395 ///
396 /// `true` if a falling edge (true → false transition) was detected, `false` otherwise.
397 ///
398 /// # Example
399 ///
400 /// ```
401 /// use autocore_std::FTrig;
402 ///
403 /// let mut trigger = FTrig::new();
404 ///
405 /// // Simulate button release
406 /// trigger.call(true); // Button held
407 /// if trigger.call(false) { // Button released
408 /// println!("Button was just released!");
409 /// }
410 /// ```
411 pub fn call(&mut self, clk: bool) -> bool {
412 self.clk = clk;
413 self.q = !self.clk && self.m;
414 self.m = self.clk;
415 self.q
416 }
417}
418
419impl Default for FTrig {
420 fn default() -> Self {
421 Self::new()
422 }
423}
424
425
426/// Timer On Delay (TON)
427///
428/// A timer that delays turning on the output. The output `q` becomes `true`
429/// after the enable input `en` has been continuously `true` for the preset
430/// time `pt`. The elapsed time is available in `et`.
431///
432/// This is equivalent to the IEC 61131-3 TON function block.
433///
434/// # Behavior
435///
436/// - When `en` becomes `true`, the timer starts counting from zero
437/// - While counting, `et` shows the elapsed time and `q` is `false`
438/// - When `et` reaches `pt`, `q` becomes `true` and `et` is clamped to `pt`
439/// - When `en` becomes `false`, the timer resets: `q` = `false`, `et` = 0
440///
441/// # Example
442///
443/// ```
444/// use autocore_std::Ton;
445/// use std::time::Duration;
446///
447/// let mut timer = Ton::new();
448/// let delay = Duration::from_secs(5);
449///
450/// // Timer disabled - output is false
451/// assert_eq!(timer.call(false, delay), false);
452/// assert_eq!(timer.et, Duration::ZERO);
453///
454/// // Enable timer - starts counting
455/// timer.call(true, delay);
456/// assert_eq!(timer.q, false); // Not done yet
457/// // timer.et is now counting up...
458///
459/// // Disable resets the timer
460/// timer.call(false, delay);
461/// assert_eq!(timer.et, Duration::ZERO);
462/// ```
463///
464/// # Timing Diagram
465///
466/// ```text
467/// en: _____|‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾|_____
468/// q: _____________|‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾|_____
469/// et: 0---|++++++++|PT----------------|0----
470/// ^ ^ ^
471/// | | |
472/// en rises et=pt en falls
473/// ```
474///
475/// # Use Cases
476///
477/// - Motor start delay (allow contactors to engage)
478/// - Debouncing switches (ignore brief transitions)
479/// - Timeout detection (alarm if condition persists too long)
480#[derive(Debug, Clone)]
481pub struct Ton {
482 /// Input: Enable the timer (true = counting, false = reset)
483 pub en: bool,
484 /// Input: Preset time (duration before output activates)
485 pub pt: Duration,
486 /// Output: Timer done (true when elapsed time >= preset time)
487 pub q: bool,
488 /// Output: Elapsed time since timer was enabled
489 pub et: Duration,
490
491 start_time: Option<Instant>,
492 active: bool,
493}
494
495impl Ton {
496 /// Creates a new timer with default values.
497 ///
498 /// The timer starts in the disabled state with zero elapsed time.
499 ///
500 /// # Example
501 ///
502 /// ```
503 /// use autocore_std::Ton;
504 ///
505 /// let timer = Ton::new();
506 /// assert_eq!(timer.q, false);
507 /// assert_eq!(timer.et, std::time::Duration::ZERO);
508 /// ```
509 pub fn new() -> Self {
510 Self {
511 en: false,
512 pt: Duration::default(),
513 q: false,
514 et: Duration::default(),
515 start_time: None,
516 active: false,
517 }
518 }
519
520 /// Executes the timer logic.
521 ///
522 /// Call this method once per control cycle. The timer counts real elapsed
523 /// time (not cycles), so the output timing is independent of scan rate.
524 ///
525 /// # Arguments
526 ///
527 /// * `en` - Enable input: `true` to run timer, `false` to reset
528 /// * `pt` - Preset time: duration before output activates
529 ///
530 /// # Returns
531 ///
532 /// The current state of the output `q` (true if timer has elapsed).
533 ///
534 /// # Example
535 ///
536 /// ```
537 /// use autocore_std::Ton;
538 /// use std::time::Duration;
539 ///
540 /// let mut timer = Ton::new();
541 ///
542 /// // Use in a control loop
543 /// let motor_request = true;
544 /// let start_delay = Duration::from_millis(500);
545 ///
546 /// let motor_enabled = timer.call(motor_request, start_delay);
547 /// // motor_enabled will be true after 500ms of motor_request being true
548 /// ```
549 pub fn call(&mut self, en: bool, pt: Duration) -> bool {
550 self.en = en;
551 self.pt = pt;
552
553 if !self.en {
554 // Reset
555 self.q = false;
556 self.et = Duration::ZERO;
557 self.start_time = None;
558 self.active = false;
559 } else {
560 if !self.active {
561 // Rising edge of EN - start timing
562 self.start_time = Some(Instant::now());
563 self.active = true;
564 self.et = Duration::ZERO;
565 self.q = false;
566 } else {
567 // Timer running
568 if let Some(start) = self.start_time {
569 self.et = start.elapsed();
570 if self.et >= self.pt {
571 self.et = self.pt; // Clamp ET to PT
572 self.q = true;
573 }
574 }
575 }
576 }
577 self.q
578 }
579
580 /// Resets the timer to its initial state.
581 ///
582 /// This is equivalent to calling `call(false, ...)`.
583 ///
584 /// # Example
585 ///
586 /// ```
587 /// use autocore_std::Ton;
588 /// use std::time::Duration;
589 ///
590 /// let mut timer = Ton::new();
591 /// timer.call(true, Duration::from_secs(1));
592 /// // ... timer is running ...
593 ///
594 /// timer.reset();
595 /// assert_eq!(timer.q, false);
596 /// assert_eq!(timer.et, Duration::ZERO);
597 /// ```
598 pub fn reset(&mut self) {
599 self.q = false;
600 self.et = Duration::ZERO;
601 self.start_time = None;
602 self.active = false;
603 }
604}
605
606impl Default for Ton {
607 fn default() -> Self {
608 Self::new()
609 }
610}
611
612
613/// Simple One-Shot Timer
614///
615/// A simple timer for one-shot timing operations. Unlike [`Ton`], this timer
616/// does NOT follow the IEC 61131-3 standard and is designed for imperative
617/// "start then check" patterns rather than cyclic function block calls.
618///
619/// **Note:** For standard PLC timer patterns (delay while condition is true),
620/// use [`Ton`] instead. `SimpleTimer` is intended for one-shot use cases
621/// like "do X, wait, then do Y".
622///
623/// # Safety
624///
625/// This timer uses [`Instant`] (monotonic clock) internally, making it immune
626/// to system clock adjustments (NTP sync, manual changes, DST). This is
627/// critical for reliable timing in industrial control applications.
628///
629/// # Example
630///
631/// ```
632/// use autocore_std::SimpleTimer;
633/// use std::time::Duration;
634///
635/// let mut timer = SimpleTimer::new(Duration::from_millis(100));
636///
637/// // Timer hasn't started yet
638/// assert_eq!(timer.is_done(), false);
639/// assert_eq!(timer.elapsed(), Duration::ZERO);
640///
641/// // Start the timer
642/// timer.start();
643///
644/// // Check if done (will be false initially)
645/// assert_eq!(timer.is_done(), false);
646///
647/// // After waiting...
648/// std::thread::sleep(Duration::from_millis(110));
649/// assert_eq!(timer.is_done(), true);
650///
651/// // Reset for reuse
652/// timer.reset();
653/// assert_eq!(timer.is_done(), false);
654/// ```
655///
656/// # Use Cases
657///
658/// - One-shot delays ("wait 500ms then continue")
659/// - Tracking time since an event occurred
660/// - Non-cyclic async contexts
661/// - Simple timeout checks
662///
663/// # Comparison with Ton
664///
665/// | Aspect | `Ton` | `SimpleTimer` |
666/// |--------|-------|---------------|
667/// | IEC 61131-3 | Yes | No |
668/// | Pattern | Cyclic `call()` | Imperative `start()`/`is_done()` |
669/// | Auto-reset on disable | Yes | No (explicit `reset()`) |
670/// | Best for | PLC-style control | One-shot operations |
671#[derive(Debug, Clone)]
672pub struct SimpleTimer {
673 start_time: Option<Instant>,
674 preset: Duration,
675}
676
677impl SimpleTimer {
678 /// Creates a new simple timer with the specified preset duration.
679 ///
680 /// The timer starts in the stopped state and must be explicitly started
681 /// with [`start()`](Self::start).
682 ///
683 /// # Arguments
684 ///
685 /// * `preset` - The duration after which [`is_done()`](Self::is_done) returns `true`
686 ///
687 /// # Example
688 ///
689 /// ```
690 /// use autocore_std::SimpleTimer;
691 /// use std::time::Duration;
692 ///
693 /// let timer = SimpleTimer::new(Duration::from_secs(5));
694 /// assert_eq!(timer.is_done(), false);
695 /// ```
696 pub fn new(preset: Duration) -> Self {
697 Self {
698 start_time: None,
699 preset,
700 }
701 }
702
703 /// Starts (or restarts) the timer.
704 ///
705 /// Records the current time as the start time. If the timer was already
706 /// running, this restarts it from zero.
707 ///
708 /// # Example
709 ///
710 /// ```
711 /// use autocore_std::SimpleTimer;
712 /// use std::time::Duration;
713 ///
714 /// let mut timer = SimpleTimer::new(Duration::from_millis(100));
715 /// timer.start();
716 /// // Timer is now counting...
717 /// ```
718 pub fn start(&mut self) {
719 self.start_time = Some(Instant::now());
720 }
721
722 /// Checks if the preset time has elapsed since the timer was started.
723 ///
724 /// Returns `false` if the timer hasn't been started yet.
725 ///
726 /// # Returns
727 ///
728 /// `true` if the timer was started and the preset duration has elapsed,
729 /// `false` otherwise.
730 ///
731 /// # Example
732 ///
733 /// ```
734 /// use autocore_std::SimpleTimer;
735 /// use std::time::Duration;
736 ///
737 /// let mut timer = SimpleTimer::new(Duration::from_millis(50));
738 ///
739 /// // Not started yet
740 /// assert_eq!(timer.is_done(), false);
741 ///
742 /// timer.start();
743 /// std::thread::sleep(Duration::from_millis(60));
744 /// assert_eq!(timer.is_done(), true);
745 /// ```
746 pub fn is_done(&self) -> bool {
747 self.start_time
748 .map(|t| t.elapsed() >= self.preset)
749 .unwrap_or(false)
750 }
751
752 /// Returns the elapsed time since the timer was started.
753 ///
754 /// Returns [`Duration::ZERO`] if the timer hasn't been started yet.
755 ///
756 /// # Returns
757 ///
758 /// The elapsed time since [`start()`](Self::start) was called, or
759 /// `Duration::ZERO` if not started.
760 ///
761 /// # Example
762 ///
763 /// ```
764 /// use autocore_std::SimpleTimer;
765 /// use std::time::Duration;
766 ///
767 /// let mut timer = SimpleTimer::new(Duration::from_secs(10));
768 ///
769 /// // Not started
770 /// assert_eq!(timer.elapsed(), Duration::ZERO);
771 ///
772 /// timer.start();
773 /// std::thread::sleep(Duration::from_millis(50));
774 /// assert!(timer.elapsed() >= Duration::from_millis(50));
775 /// ```
776 pub fn elapsed(&self) -> Duration {
777 self.start_time
778 .map(|t| t.elapsed())
779 .unwrap_or(Duration::ZERO)
780 }
781
782 /// Resets the timer to its initial (stopped) state.
783 ///
784 /// After calling this, [`is_done()`](Self::is_done) will return `false`
785 /// and [`elapsed()`](Self::elapsed) will return zero until
786 /// [`start()`](Self::start) is called again.
787 ///
788 /// # Example
789 ///
790 /// ```
791 /// use autocore_std::SimpleTimer;
792 /// use std::time::Duration;
793 ///
794 /// let mut timer = SimpleTimer::new(Duration::from_millis(50));
795 /// timer.start();
796 /// std::thread::sleep(Duration::from_millis(60));
797 /// assert_eq!(timer.is_done(), true);
798 ///
799 /// timer.reset();
800 /// assert_eq!(timer.is_done(), false);
801 /// assert_eq!(timer.elapsed(), Duration::ZERO);
802 /// ```
803 pub fn reset(&mut self) {
804 self.start_time = None;
805 }
806
807 /// Returns the preset duration for this timer.
808 ///
809 /// # Example
810 ///
811 /// ```
812 /// use autocore_std::SimpleTimer;
813 /// use std::time::Duration;
814 ///
815 /// let timer = SimpleTimer::new(Duration::from_secs(5));
816 /// assert_eq!(timer.preset(), Duration::from_secs(5));
817 /// ```
818 pub fn preset(&self) -> Duration {
819 self.preset
820 }
821
822 /// Sets a new preset duration.
823 ///
824 /// This does not reset or restart the timer. If the timer is running,
825 /// the new preset takes effect immediately for [`is_done()`](Self::is_done)
826 /// checks.
827 ///
828 /// # Arguments
829 ///
830 /// * `preset` - The new duration after which `is_done()` returns `true`
831 ///
832 /// # Example
833 ///
834 /// ```
835 /// use autocore_std::SimpleTimer;
836 /// use std::time::Duration;
837 ///
838 /// let mut timer = SimpleTimer::new(Duration::from_secs(5));
839 /// timer.set_preset(Duration::from_secs(10));
840 /// assert_eq!(timer.preset(), Duration::from_secs(10));
841 /// ```
842 pub fn set_preset(&mut self, preset: Duration) {
843 self.preset = preset;
844 }
845
846 /// Returns whether the timer is currently running (has been started but not reset).
847 ///
848 /// # Example
849 ///
850 /// ```
851 /// use autocore_std::SimpleTimer;
852 /// use std::time::Duration;
853 ///
854 /// let mut timer = SimpleTimer::new(Duration::from_secs(5));
855 /// assert_eq!(timer.is_running(), false);
856 ///
857 /// timer.start();
858 /// assert_eq!(timer.is_running(), true);
859 ///
860 /// timer.reset();
861 /// assert_eq!(timer.is_running(), false);
862 /// ```
863 pub fn is_running(&self) -> bool {
864 self.start_time.is_some()
865 }
866}
867
868impl Default for SimpleTimer {
869 /// Creates a timer with a preset of zero (will be immediately done when started).
870 fn default() -> Self {
871 Self::new(Duration::ZERO)
872 }
873}
874
875
876/// State Machine Helper (FB_StateMachine)
877///
878/// A state machine helper with automatic timer management and error tracking.
879/// Provides two timers that automatically reset when the state index changes:
880///
881/// - **Timer** (`timer_done()`) - General purpose timer for delays and debouncing
882/// - **Timeout** (`timed_out()`) - For detecting stuck states
883///
884/// This is equivalent to the IEC 61131-3 FB_StateMachine function block.
885///
886/// # Automatic Timer Reset
887///
888/// Both timers automatically reset when `index` changes. This eliminates a
889/// common source of bugs in state machines where timers are not properly reset.
890///
891/// The pattern is:
892/// 1. Set `timer_preset` (and optionally `timeout_preset`) in state N
893/// 2. Change `index` to state N+1 (timers reset and start counting)
894/// 3. In state N+1, check `timer_done()` or `timed_out()`
895///
896/// # Example
897///
898/// ```
899/// use autocore_std::StateMachine;
900/// use std::time::Duration;
901///
902/// let mut state = StateMachine::new();
903///
904/// // Simulate a control loop
905/// loop {
906/// match state.index {
907/// 0 => { // Reset
908/// state.clear_error();
909/// state.index = 10;
910/// }
911/// 10 => { // Idle - wait for start signal
912/// // For demo, just proceed
913/// state.timer_preset = Duration::from_millis(100);
914/// state.index = 20;
915/// }
916/// 20 => { // Debounce
917/// if state.timer_done() {
918/// state.timeout_preset = Duration::from_secs(10);
919/// state.index = 30;
920/// }
921/// }
922/// 30 => { // Wait for operation (simulated)
923/// // In real code: check operation_complete
924/// // For demo, check timeout
925/// if state.timed_out() {
926/// state.set_error(30, "Operation timeout");
927/// state.index = 0;
928/// }
929/// // Exit demo loop
930/// break;
931/// }
932/// _ => { state.index = 0; }
933/// }
934///
935/// state.call(); // Call at end of each scan cycle
936/// # break; // Exit for doctest
937/// }
938/// ```
939///
940/// # Timer Presets Persist
941///
942/// Timer presets persist until you change them. This allows setting a preset
943/// once and using it across multiple states:
944///
945/// ```ignore
946/// 100 => {
947/// state.timer_preset = Duration::from_millis(300);
948/// state.index = 110;
949/// }
950/// 110 => {
951/// // Uses 300ms preset set in state 100
952/// if some_condition && state.timer_done() {
953/// state.index = 120;
954/// }
955/// }
956/// 120 => {
957/// // Still uses 300ms preset (timer reset on state change)
958/// if state.timer_done() {
959/// state.index = 10;
960/// }
961/// }
962/// ```
963///
964/// # Error Handling Pattern
965///
966/// ```ignore
967/// 200 => {
968/// state.timeout_preset = Duration::from_secs(7);
969/// start_operation();
970/// state.index = 210;
971/// }
972/// 210 => {
973/// if operation_complete {
974/// state.index = 1000; // Success
975/// } else if state.timed_out() {
976/// state.set_error(210, "Operation timed out");
977/// state.index = 5000; // Error handler
978/// }
979/// }
980/// 5000 => {
981/// // Error recovery
982/// state.index = 0;
983/// }
984/// ```
985#[derive(Debug, Clone)]
986pub struct StateMachine {
987 /// Current state index.
988 pub index: i32,
989
990 /// Timer preset. `timer_done()` returns true when time in current state >= this value.
991 /// Defaults to `Duration::MAX` (timer never triggers unless you set a preset).
992 pub timer_preset: Duration,
993
994 /// Timeout preset. `timed_out()` returns true when time in current state >= this value.
995 /// Defaults to `Duration::MAX` (timeout never triggers unless you set a preset).
996 pub timeout_preset: Duration,
997
998 /// Error code. A value of 0 indicates no error.
999 /// When non-zero, `is_error()` returns true.
1000 pub error_code: i32,
1001
1002 /// Status message for UI display. Content does not indicate an error.
1003 pub message: String,
1004
1005 /// Error message for UI display. Should only have content when `error_code != 0`.
1006 pub error_message: String,
1007
1008 // Internal state
1009 last_index: Option<i32>,
1010 state_entered_at: Option<Instant>,
1011}
1012
1013impl StateMachine {
1014 /// Creates a new state machine starting at state 0.
1015 ///
1016 /// Timer presets default to `Duration::MAX`, meaning timers won't trigger
1017 /// until you explicitly set a preset.
1018 ///
1019 /// # Example
1020 ///
1021 /// ```
1022 /// use autocore_std::StateMachine;
1023 ///
1024 /// let state = StateMachine::new();
1025 /// assert_eq!(state.index, 0);
1026 /// assert_eq!(state.error_code, 0);
1027 /// assert!(!state.is_error());
1028 /// ```
1029 pub fn new() -> Self {
1030 Self {
1031 index: 0,
1032 timer_preset: Duration::MAX,
1033 timeout_preset: Duration::MAX,
1034 error_code: 0,
1035 message: String::new(),
1036 error_message: String::new(),
1037 last_index: None,
1038 state_entered_at: None,
1039 }
1040 }
1041
1042 /// Call once per scan cycle at the END of your state machine logic.
1043 ///
1044 /// This method:
1045 /// - Detects state changes (when `index` differs from the previous call)
1046 /// - Resets internal timers on state change
1047 /// - Updates internal tracking for `elapsed()`, `timer_done()`, and `timed_out()`
1048 ///
1049 /// # Example
1050 ///
1051 /// ```
1052 /// use autocore_std::StateMachine;
1053 ///
1054 /// let mut state = StateMachine::new();
1055 ///
1056 /// // Your state machine logic here...
1057 /// match state.index {
1058 /// 0 => { state.index = 10; }
1059 /// _ => {}
1060 /// }
1061 ///
1062 /// state.call(); // Always call at the end
1063 /// ```
1064 pub fn call(&mut self) {
1065 if self.last_index != Some(self.index) {
1066 self.state_entered_at = Some(Instant::now());
1067 }
1068 self.last_index = Some(self.index);
1069 }
1070
1071 /// Returns true when time in current state >= `timer_preset`.
1072 ///
1073 /// The timer automatically resets when the state index changes.
1074 ///
1075 /// # Example
1076 ///
1077 /// ```
1078 /// use autocore_std::StateMachine;
1079 /// use std::time::Duration;
1080 ///
1081 /// let mut state = StateMachine::new();
1082 /// state.timer_preset = Duration::from_millis(50);
1083 /// state.call(); // Start tracking
1084 ///
1085 /// assert!(!state.timer_done()); // Not enough time elapsed
1086 ///
1087 /// std::thread::sleep(Duration::from_millis(60));
1088 /// assert!(state.timer_done()); // Now it's done
1089 /// ```
1090 pub fn timer_done(&self) -> bool {
1091 self.elapsed() >= self.timer_preset
1092 }
1093
1094 /// Returns true when time in current state >= `timeout_preset`.
1095 ///
1096 /// Use this for detecting stuck states. The timeout automatically
1097 /// resets when the state index changes.
1098 ///
1099 /// # Example
1100 ///
1101 /// ```
1102 /// use autocore_std::StateMachine;
1103 /// use std::time::Duration;
1104 ///
1105 /// let mut state = StateMachine::new();
1106 /// state.timeout_preset = Duration::from_millis(50);
1107 /// state.call();
1108 ///
1109 /// assert!(!state.timed_out());
1110 ///
1111 /// std::thread::sleep(Duration::from_millis(60));
1112 /// assert!(state.timed_out());
1113 /// ```
1114 pub fn timed_out(&self) -> bool {
1115 self.elapsed() >= self.timeout_preset
1116 }
1117
1118 /// Returns elapsed time since entering the current state.
1119 ///
1120 /// Returns `Duration::ZERO` if `call()` has never been called.
1121 ///
1122 /// # Example
1123 ///
1124 /// ```
1125 /// use autocore_std::StateMachine;
1126 /// use std::time::Duration;
1127 ///
1128 /// let mut state = StateMachine::new();
1129 /// state.call();
1130 ///
1131 /// std::thread::sleep(Duration::from_millis(10));
1132 /// assert!(state.elapsed() >= Duration::from_millis(10));
1133 /// ```
1134 pub fn elapsed(&self) -> Duration {
1135 self.state_entered_at
1136 .map(|t| t.elapsed())
1137 .unwrap_or(Duration::ZERO)
1138 }
1139
1140 /// Returns true if `error_code != 0`.
1141 ///
1142 /// # Example
1143 ///
1144 /// ```
1145 /// use autocore_std::StateMachine;
1146 ///
1147 /// let mut state = StateMachine::new();
1148 /// assert!(!state.is_error());
1149 ///
1150 /// state.error_code = 100;
1151 /// assert!(state.is_error());
1152 /// ```
1153 pub fn is_error(&self) -> bool {
1154 self.error_code != 0
1155 }
1156
1157 /// Set error state with code and message.
1158 ///
1159 /// This is a convenience method equivalent to setting `error_code`
1160 /// and `error_message` directly.
1161 ///
1162 /// # Example
1163 ///
1164 /// ```
1165 /// use autocore_std::StateMachine;
1166 ///
1167 /// let mut state = StateMachine::new();
1168 /// state.set_error(110, "Failed to home X axis");
1169 ///
1170 /// assert_eq!(state.error_code, 110);
1171 /// assert_eq!(state.error_message, "Failed to home X axis");
1172 /// assert!(state.is_error());
1173 /// ```
1174 pub fn set_error(&mut self, code: i32, message: impl Into<String>) {
1175 self.error_code = code;
1176 self.error_message = message.into();
1177 }
1178
1179 /// Clear error state.
1180 ///
1181 /// Sets `error_code` to 0 and clears `error_message`.
1182 ///
1183 /// # Example
1184 ///
1185 /// ```
1186 /// use autocore_std::StateMachine;
1187 ///
1188 /// let mut state = StateMachine::new();
1189 /// state.set_error(100, "Some error");
1190 /// assert!(state.is_error());
1191 ///
1192 /// state.clear_error();
1193 /// assert!(!state.is_error());
1194 /// assert_eq!(state.error_code, 0);
1195 /// assert!(state.error_message.is_empty());
1196 /// ```
1197 pub fn clear_error(&mut self) {
1198 self.error_code = 0;
1199 self.error_message.clear();
1200 }
1201
1202 /// Returns the current state index.
1203 ///
1204 /// This is equivalent to reading `state.index` directly but provided
1205 /// for API consistency.
1206 pub fn state(&self) -> i32 {
1207 self.index
1208 }
1209}
1210
1211impl Default for StateMachine {
1212 fn default() -> Self {
1213 Self::new()
1214 }
1215}
1216
1217
1218// ============================================================================
1219// Core Framework
1220// ============================================================================
1221
1222/// Marker trait for generated GlobalMemory structs.
1223///
1224/// This trait is implemented by the auto-generated `GlobalMemory` struct
1225/// that represents the shared memory layout. It serves as a marker for
1226/// type safety in the control framework.
1227///
1228/// You don't need to implement this trait yourself - it's automatically
1229/// implemented by the code generator.
1230pub trait AutoCoreMemory {}
1231
1232/// The trait that defines a control program's logic.
1233///
1234/// Implement this trait to create your control program. The associated `Memory`
1235/// type should be the generated `GlobalMemory` struct from your project.
1236///
1237/// # Memory Type Requirements
1238///
1239/// The `Memory` type must implement `Copy` to allow efficient synchronization
1240/// between shared memory and local buffers. This is automatically satisfied
1241/// by the generated `GlobalMemory` struct.
1242///
1243/// # Lifecycle
1244///
1245/// 1. `initialize` is called once at startup
1246/// 2. `process_tick` is called repeatedly in the control loop
1247///
1248/// # Example
1249///
1250/// ```ignore
1251/// use autocore_std::ControlProgram;
1252///
1253/// mod gm;
1254/// use gm::GlobalMemory;
1255///
1256/// pub struct MyController {
1257/// cycle_counter: u64,
1258/// }
1259///
1260/// impl MyController {
1261/// pub fn new() -> Self {
1262/// Self { cycle_counter: 0 }
1263/// }
1264/// }
1265///
1266/// impl ControlProgram for MyController {
1267/// type Memory = GlobalMemory;
1268///
1269/// fn initialize(&mut self, mem: &mut GlobalMemory) {
1270/// // Set initial output states
1271/// mem.outputs.ready = true;
1272/// log::info!("Controller initialized");
1273/// }
1274///
1275/// fn process_tick(&mut self, mem: &mut GlobalMemory, cycle: u64) {
1276/// self.cycle_counter = cycle;
1277///
1278/// // Your control logic here
1279/// if mem.inputs.start && !mem.inputs.estop {
1280/// mem.outputs.running = true;
1281/// }
1282/// }
1283/// }
1284/// ```
1285pub trait ControlProgram {
1286 /// The shared memory structure type (usually the generated `GlobalMemory`).
1287 ///
1288 /// Must implement `Copy` to allow efficient memory synchronization.
1289 type Memory: Copy;
1290
1291 /// Called once when the control program starts.
1292 ///
1293 /// Use this to initialize output states, reset counters, or perform
1294 /// any one-time setup. The default implementation does nothing.
1295 ///
1296 /// # Arguments
1297 ///
1298 /// * `mem` - Mutable reference to the shared memory. Changes are written
1299 /// back to shared memory after this method returns.
1300 fn initialize(&mut self, _mem: &mut Self::Memory) {}
1301
1302 /// The main control loop - called once per scan cycle.
1303 ///
1304 /// This is where your control logic lives. Read inputs from `mem`,
1305 /// perform calculations, and write outputs back to `mem`.
1306 ///
1307 /// # Arguments
1308 ///
1309 /// * `mem` - Mutable reference to a local copy of the shared memory.
1310 /// Changes made here are written back to shared memory after
1311 /// this method returns.
1312 /// * `cycle` - The current cycle number (increments each tick, starting at 1).
1313 ///
1314 /// # Timing
1315 ///
1316 /// This method should complete within the scan cycle time. Long-running
1317 /// operations will cause cycle overruns.
1318 fn process_tick(&mut self, mem: &mut Self::Memory, cycle: u64);
1319}
1320
1321/// Configuration for the [`ControlRunner`].
1322///
1323/// Specifies connection parameters, shared memory names, and logging settings.
1324/// Use [`Default::default()`] for typical configurations.
1325///
1326/// # Example
1327///
1328/// ```
1329/// use autocore_std::RunnerConfig;
1330/// use log::LevelFilter;
1331///
1332/// let config = RunnerConfig {
1333/// server_host: "192.168.1.100".to_string(),
1334/// module_name: "my_controller".to_string(),
1335/// shm_name: "my_project_shm".to_string(),
1336/// tick_signal_name: "tick".to_string(),
1337/// busy_signal_name: Some("busy".to_string()),
1338/// log_level: LevelFilter::Debug,
1339/// ..Default::default()
1340/// };
1341/// ```
1342#[derive(Debug, Clone)]
1343pub struct RunnerConfig {
1344 /// Server host address (default: "127.0.0.1")
1345 pub server_host: String,
1346 /// WebSocket port for commands (default: 11969)
1347 pub ws_port: u16,
1348 /// Module name for identification (default: "control")
1349 pub module_name: String,
1350 /// Shared memory segment name (must match server configuration)
1351 pub shm_name: String,
1352 /// Name of the tick signal in shared memory (triggers each scan cycle)
1353 pub tick_signal_name: String,
1354 /// Optional name of the busy signal (set when cycle completes)
1355 pub busy_signal_name: Option<String>,
1356 /// Minimum log level to send to the server (default: Info)
1357 pub log_level: LevelFilter,
1358 /// UDP port for sending logs to the server (default: 39101)
1359 pub log_udp_port: u16,
1360}
1361
1362/// Default WebSocket port for autocore-server
1363pub const DEFAULT_WS_PORT: u16 = 11969;
1364
1365impl Default for RunnerConfig {
1366 fn default() -> Self {
1367 Self {
1368 server_host: "127.0.0.1".to_string(),
1369 ws_port: DEFAULT_WS_PORT,
1370 module_name: "control".to_string(),
1371 shm_name: "autocore_cyclic".to_string(),
1372 tick_signal_name: "tick".to_string(),
1373 busy_signal_name: None,
1374 log_level: LevelFilter::Info,
1375 log_udp_port: logger::DEFAULT_LOG_UDP_PORT,
1376 }
1377 }
1378}
1379
1380
1381/// The main execution engine for control programs.
1382///
1383/// `ControlRunner` handles all the infrastructure required to run a control program:
1384///
1385/// - Reading memory layout from the server's layout file
1386/// - Opening and mapping shared memory
1387/// - Setting up synchronization signals
1388/// - Running the real-time control loop
1389/// - Sending log messages to the server
1390///
1391/// # Usage
1392///
1393/// ```ignore
1394/// use autocore_std::{ControlRunner, RunnerConfig};
1395///
1396/// let config = RunnerConfig {
1397/// shm_name: "my_project_shm".to_string(),
1398/// tick_signal_name: "tick".to_string(),
1399/// ..Default::default()
1400/// };
1401///
1402/// ControlRunner::new(MyProgram::new())
1403/// .config(config)
1404/// .run()?; // Blocks forever
1405/// ```
1406///
1407/// # Control Loop
1408///
1409/// The runner executes a synchronous control loop:
1410///
1411/// 1. **Wait** - Blocks until the tick signal is set by the server
1412/// 2. **Read** - Copies shared memory to a local buffer (acquire barrier)
1413/// 3. **Execute** - Calls your `process_tick` method
1414/// 4. **Write** - Copies local buffer back to shared memory (release barrier)
1415/// 5. **Signal** - Sets the busy signal (if configured) to indicate completion
1416///
1417/// This ensures your code always sees a consistent snapshot of the data
1418/// and that your writes are atomically visible to other processes.
1419pub struct ControlRunner<P: ControlProgram> {
1420 config: RunnerConfig,
1421 program: P,
1422}
1423
1424impl<P: ControlProgram> ControlRunner<P> {
1425 /// Creates a new runner for the given control program.
1426 ///
1427 /// Uses default configuration. Call [`.config()`](Self::config) to customize.
1428 ///
1429 /// # Arguments
1430 ///
1431 /// * `program` - Your control program instance
1432 ///
1433 /// # Example
1434 ///
1435 /// ```ignore
1436 /// let runner = ControlRunner::new(MyProgram::new());
1437 /// ```
1438 pub fn new(program: P) -> Self {
1439 Self {
1440 config: RunnerConfig::default(),
1441 program,
1442 }
1443 }
1444
1445 /// Sets the configuration for this runner.
1446 ///
1447 /// # Arguments
1448 ///
1449 /// * `config` - The configuration to use
1450 ///
1451 /// # Example
1452 ///
1453 /// ```ignore
1454 /// ControlRunner::new(MyProgram::new())
1455 /// .config(RunnerConfig {
1456 /// shm_name: "custom_shm".to_string(),
1457 /// ..Default::default()
1458 /// })
1459 /// .run()?;
1460 /// ```
1461 pub fn config(mut self, config: RunnerConfig) -> Self {
1462 self.config = config;
1463 self
1464 }
1465
1466 /// Starts the control loop.
1467 ///
1468 /// This method blocks indefinitely, running the control loop until
1469 /// an error occurs or the process is terminated.
1470 ///
1471 /// # Returns
1472 ///
1473 /// Returns `Ok(())` only if the loop exits cleanly (which typically
1474 /// doesn't happen). Returns an error if:
1475 ///
1476 /// - IPC connection fails
1477 /// - Shared memory cannot be opened
1478 /// - Signal offsets cannot be found
1479 /// - A critical error occurs during execution
1480 ///
1481 /// # Example
1482 ///
1483 /// ```ignore
1484 /// fn main() -> anyhow::Result<()> {
1485 /// ControlRunner::new(MyProgram::new())
1486 /// .config(config)
1487 /// .run()
1488 /// }
1489 /// ```
1490 pub fn run(mut self) -> Result<()> {
1491 // Initialize UDP logger FIRST (before any log statements)
1492 if let Err(e) = logger::init_udp_logger(
1493 &self.config.server_host,
1494 self.config.log_udp_port,
1495 self.config.log_level,
1496 "control",
1497 ) {
1498 eprintln!("Warning: Failed to initialize UDP logger: {}", e);
1499 // Continue anyway - logging will just go nowhere
1500 }
1501
1502 // We use a dedicated runtime for the setup phase
1503 let rt = tokio::runtime::Builder::new_current_thread()
1504 .enable_all()
1505 .build()?;
1506
1507 rt.block_on(async {
1508 log::info!("AutoCore Control Runner Starting...");
1509
1510 // 1. Connect to server via WebSocket and get layout
1511 let ws_url = format!("ws://{}:{}/ws/", self.config.server_host, self.config.ws_port);
1512 log::info!("Connecting to server at {}", ws_url);
1513
1514 let (ws_stream, _) = connect_async(&ws_url).await
1515 .map_err(|e| anyhow!("Failed to connect to server at {}: {}", ws_url, e))?;
1516
1517 let (mut write, mut read) = ws_stream.split();
1518
1519 // Send gm.get_layout request
1520 let request = CommandMessage::request("gm.get_layout", serde_json::Value::Null);
1521 let transaction_id = request.transaction_id;
1522 let request_json = serde_json::to_string(&request)?;
1523
1524 write.send(Message::Text(request_json)).await
1525 .map_err(|e| anyhow!("Failed to send layout request: {}", e))?;
1526
1527 // Wait for response with matching transaction_id
1528 let timeout = Duration::from_secs(10);
1529 let start = std::time::Instant::now();
1530 let mut layout: Option<HashMap<String, serde_json::Value>> = None;
1531
1532 while start.elapsed() < timeout {
1533 match tokio::time::timeout(Duration::from_secs(1), read.next()).await {
1534 Ok(Some(Ok(Message::Text(text)))) => {
1535 if let Ok(response) = serde_json::from_str::<CommandMessage>(&text) {
1536 if response.transaction_id == transaction_id {
1537 if !response.success {
1538 return Err(anyhow!("Server error: {}", response.error_message));
1539 }
1540 layout = Some(serde_json::from_value(response.data)?);
1541 break;
1542 }
1543 // Skip broadcasts and other messages
1544 if response.message_type == MessageType::Broadcast {
1545 continue;
1546 }
1547 }
1548 }
1549 Ok(Some(Ok(_))) => continue,
1550 Ok(Some(Err(e))) => return Err(anyhow!("WebSocket error: {}", e)),
1551 Ok(None) => return Err(anyhow!("Server closed connection")),
1552 Err(_) => continue, // Timeout on single read, keep trying
1553 }
1554 }
1555
1556 let layout = layout.ok_or_else(|| anyhow!("Timeout waiting for layout response"))?;
1557 log::info!("Layout received with {} entries.", layout.len());
1558
1559 // Close WebSocket - we don't need it anymore
1560 let _ = write.close().await;
1561
1562 // 2. Find Signal Offsets
1563 let tick_offset = self.find_offset(&layout, &self.config.tick_signal_name)?;
1564 let busy_offset = if let Some(name) = &self.config.busy_signal_name {
1565 Some(self.find_offset(&layout, name)?)
1566 } else {
1567 None
1568 };
1569
1570 // 4. Open Shared Memory
1571 let shmem = ShmemConf::new().os_id(&self.config.shm_name).open()?;
1572 let base_ptr = shmem.as_ptr();
1573 log::info!("Shared Memory '{}' mapped.", self.config.shm_name);
1574
1575 // 5. Setup Pointers
1576 // SAFETY: We trust the server's layout matches the generated GlobalMemory struct.
1577 let gm = unsafe { &mut *(base_ptr as *mut P::Memory) };
1578
1579 // Get tick event from shared memory
1580 log::info!("Setting up tick event at offset {} (base_ptr: {:p})", tick_offset, base_ptr);
1581 let (tick_event, _) = unsafe {
1582 Event::from_existing(base_ptr.add(tick_offset))
1583 }.map_err(|e| anyhow!("Failed to open tick event: {:?}", e))?;
1584 log::info!("Tick event ready");
1585
1586 // Busy signal event (optional)
1587 let busy_event = busy_offset.map(|offset| {
1588 unsafe { Event::from_existing(base_ptr.add(offset)) }
1589 .map(|(event, _)| event)
1590 .ok()
1591 }).flatten();
1592
1593 // 6. Initialize local memory buffer and user program
1594 // We use a local copy for the control loop to ensure:
1595 // - Consistent snapshot of inputs at start of cycle
1596 // - Atomic commit of outputs at end of cycle
1597 // - Proper memory barriers for cross-process visibility
1598 let mut local_mem: P::Memory = unsafe { std::ptr::read_volatile(gm) };
1599 fence(Ordering::Acquire); // Ensure we see all prior writes from other processes
1600
1601 self.program.initialize(&mut local_mem);
1602
1603 // Write back any changes from initialize
1604 fence(Ordering::Release);
1605 unsafe { std::ptr::write_volatile(gm, local_mem) };
1606
1607 log::info!("Entering Control Loop - waiting for first tick...");
1608 let mut cycle_count: u64 = 0;
1609
1610 loop {
1611 // Wait for Tick - Event-based synchronization
1612 // This blocks efficiently using OS primitives (futex/pthread_cond)
1613 tick_event.wait(Timeout::Infinite)
1614 .map_err(|e| anyhow!("Tick wait failed: {:?}", e))?;
1615
1616 cycle_count += 1;
1617 if cycle_count == 1 {
1618 log::info!("First tick received!");
1619 }
1620
1621 // === INPUT PHASE ===
1622 // Read all variables from shared memory into local buffer.
1623 // This gives us a consistent snapshot of inputs for this cycle.
1624 // Acquire fence ensures we see all writes from other processes (server, modules).
1625 local_mem = unsafe { std::ptr::read_volatile(gm) };
1626 fence(Ordering::Acquire);
1627
1628 // === EXECUTE PHASE ===
1629 // Execute user logic on the local copy.
1630 // All reads/writes during process_tick operate on local_mem.
1631 self.program.process_tick(&mut local_mem, cycle_count);
1632
1633 // === OUTPUT PHASE ===
1634 // Write all variables from local buffer back to shared memory.
1635 // Release fence ensures our writes are visible to other processes.
1636 fence(Ordering::Release);
1637 unsafe { std::ptr::write_volatile(gm, local_mem) };
1638
1639 // Signal Busy/Done event
1640 if let Some(ref busy_ev) = busy_event {
1641 let _ = busy_ev.set(EventState::Signaled);
1642 }
1643 }
1644
1645 Ok(())
1646 })
1647 }
1648
1649 fn find_offset(&self, layout: &HashMap<String, serde_json::Value>, name: &str) -> Result<usize> {
1650 let info = layout.get(name).ok_or_else(|| anyhow!("Signal '{}' not found in layout", name))?;
1651 info.get("offset")
1652 .and_then(|v| v.as_u64())
1653 .map(|v| v as usize)
1654 .ok_or_else(|| anyhow!("Invalid offset for '{}'", name))
1655 }
1656}
1657
1658/// Generates the standard `main` function for a control program.
1659///
1660/// This macro reduces boilerplate by creating a properly configured `main`
1661/// function that initializes and runs your control program.
1662///
1663/// # Arguments
1664///
1665/// * `$prog_type` - The type of your control program (must implement [`ControlProgram`])
1666/// * `$shm_name` - The shared memory segment name (string literal)
1667/// * `$tick_signal` - The tick signal name in shared memory (string literal)
1668///
1669/// # Example
1670///
1671/// ```ignore
1672/// mod gm;
1673/// use gm::GlobalMemory;
1674///
1675/// pub struct MyProgram;
1676///
1677/// impl MyProgram {
1678/// pub fn new() -> Self { Self }
1679/// }
1680///
1681/// impl autocore_std::ControlProgram for MyProgram {
1682/// type Memory = GlobalMemory;
1683///
1684/// fn process_tick(&mut self, mem: &mut GlobalMemory, _cycle: u64) {
1685/// // Your logic here
1686/// }
1687/// }
1688///
1689/// // This generates the main function
1690/// autocore_std::autocore_main!(MyProgram, "my_project_shm", "tick");
1691/// ```
1692///
1693/// # Generated Code
1694///
1695/// The macro expands to:
1696///
1697/// ```ignore
1698/// fn main() -> anyhow::Result<()> {
1699/// let config = autocore_std::RunnerConfig {
1700/// server_host: "127.0.0.1".to_string(),
1701/// ws_port: autocore_std::DEFAULT_WS_PORT,
1702/// module_name: "control".to_string(),
1703/// shm_name: "my_project_shm".to_string(),
1704/// tick_signal_name: "tick".to_string(),
1705/// busy_signal_name: None,
1706/// log_level: log::LevelFilter::Info,
1707/// log_udp_port: autocore_std::logger::DEFAULT_LOG_UDP_PORT,
1708/// };
1709///
1710/// autocore_std::ControlRunner::new(MyProgram::new())
1711/// .config(config)
1712/// .run()
1713/// }
1714/// ```
1715#[macro_export]
1716macro_rules! autocore_main {
1717 ($prog_type:ty, $shm_name:expr, $tick_signal:expr) => {
1718 fn main() -> anyhow::Result<()> {
1719 let config = autocore_std::RunnerConfig {
1720 server_host: "127.0.0.1".to_string(),
1721 ws_port: autocore_std::DEFAULT_WS_PORT,
1722 module_name: "control".to_string(),
1723 shm_name: $shm_name.to_string(),
1724 tick_signal_name: $tick_signal.to_string(),
1725 busy_signal_name: None,
1726 log_level: log::LevelFilter::Info,
1727 log_udp_port: autocore_std::logger::DEFAULT_LOG_UDP_PORT,
1728 };
1729
1730 autocore_std::ControlRunner::new(<$prog_type>::new())
1731 .config(config)
1732 .run()
1733 }
1734 };
1735}
1736
1737#[cfg(test)]
1738mod tests {
1739 use super::*;
1740
1741 #[test]
1742 fn test_rtrig_rising_edge() {
1743 let mut trigger = RTrig::new();
1744
1745 // No edge initially
1746 assert_eq!(trigger.call(false), false);
1747
1748 // Rising edge
1749 assert_eq!(trigger.call(true), true);
1750
1751 // No edge while high
1752 assert_eq!(trigger.call(true), false);
1753 assert_eq!(trigger.call(true), false);
1754
1755 // No edge on falling
1756 assert_eq!(trigger.call(false), false);
1757
1758 // Rising edge again
1759 assert_eq!(trigger.call(true), true);
1760 }
1761
1762 #[test]
1763 fn test_ftrig_falling_edge() {
1764 let mut trigger = FTrig::new();
1765
1766 // No edge initially
1767 assert_eq!(trigger.call(false), false);
1768
1769 // No edge on rising
1770 assert_eq!(trigger.call(true), false);
1771
1772 // Falling edge
1773 assert_eq!(trigger.call(false), true);
1774
1775 // No edge while low
1776 assert_eq!(trigger.call(false), false);
1777
1778 // Rising, then falling edge
1779 assert_eq!(trigger.call(true), false);
1780 assert_eq!(trigger.call(false), true);
1781 }
1782
1783 #[test]
1784 fn test_ton_basic() {
1785 let mut timer = Ton::new();
1786 let pt = Duration::from_millis(50);
1787
1788 // Disabled
1789 assert_eq!(timer.call(false, pt), false);
1790 assert_eq!(timer.et, Duration::ZERO);
1791
1792 // Enable
1793 assert_eq!(timer.call(true, pt), false);
1794 assert!(timer.et < pt);
1795
1796 // Wait for timer
1797 std::thread::sleep(Duration::from_millis(60));
1798 assert_eq!(timer.call(true, pt), true);
1799 assert_eq!(timer.et, pt);
1800
1801 // Reset
1802 timer.reset();
1803 assert_eq!(timer.q, false);
1804 assert_eq!(timer.et, Duration::ZERO);
1805 }
1806
1807 #[test]
1808 fn test_simple_timer_basic() {
1809 let mut timer = SimpleTimer::new(Duration::from_millis(50));
1810
1811 // Not started yet
1812 assert_eq!(timer.is_done(), false);
1813 assert_eq!(timer.is_running(), false);
1814 assert_eq!(timer.elapsed(), Duration::ZERO);
1815
1816 // Start
1817 timer.start();
1818 assert_eq!(timer.is_running(), true);
1819 assert_eq!(timer.is_done(), false);
1820
1821 // Wait for timer
1822 std::thread::sleep(Duration::from_millis(60));
1823 assert_eq!(timer.is_done(), true);
1824 assert!(timer.elapsed() >= Duration::from_millis(50));
1825
1826 // Reset
1827 timer.reset();
1828 assert_eq!(timer.is_done(), false);
1829 assert_eq!(timer.is_running(), false);
1830 assert_eq!(timer.elapsed(), Duration::ZERO);
1831 }
1832
1833 #[test]
1834 fn test_simple_timer_restart() {
1835 let mut timer = SimpleTimer::new(Duration::from_millis(100));
1836
1837 timer.start();
1838 std::thread::sleep(Duration::from_millis(30));
1839
1840 // Restart resets the timer
1841 timer.start();
1842 assert!(timer.elapsed() < Duration::from_millis(20));
1843 }
1844
1845 #[test]
1846 fn test_simple_timer_preset() {
1847 let mut timer = SimpleTimer::new(Duration::from_secs(5));
1848
1849 assert_eq!(timer.preset(), Duration::from_secs(5));
1850
1851 timer.set_preset(Duration::from_secs(10));
1852 assert_eq!(timer.preset(), Duration::from_secs(10));
1853 }
1854
1855 #[test]
1856 fn test_simple_timer_default() {
1857 let mut timer = SimpleTimer::default();
1858
1859 // Default preset is zero, so immediately done when started
1860 assert_eq!(timer.preset(), Duration::ZERO);
1861
1862 timer.start();
1863 assert_eq!(timer.is_done(), true);
1864 }
1865
1866 #[test]
1867 fn test_state_machine_basic() {
1868 let state = StateMachine::new();
1869
1870 assert_eq!(state.index, 0);
1871 assert_eq!(state.error_code, 0);
1872 assert!(!state.is_error());
1873 assert_eq!(state.timer_preset, Duration::MAX);
1874 assert_eq!(state.timeout_preset, Duration::MAX);
1875 }
1876
1877 #[test]
1878 fn test_state_machine_timer() {
1879 let mut state = StateMachine::new();
1880 state.timer_preset = Duration::from_millis(50);
1881 state.call();
1882
1883 // Timer shouldn't be done yet
1884 assert!(!state.timer_done());
1885
1886 // Wait for timer
1887 std::thread::sleep(Duration::from_millis(60));
1888 assert!(state.timer_done());
1889 assert!(state.elapsed() >= Duration::from_millis(50));
1890 }
1891
1892 #[test]
1893 fn test_state_machine_timeout() {
1894 let mut state = StateMachine::new();
1895 state.timeout_preset = Duration::from_millis(50);
1896 state.call();
1897
1898 assert!(!state.timed_out());
1899
1900 std::thread::sleep(Duration::from_millis(60));
1901 assert!(state.timed_out());
1902 }
1903
1904 #[test]
1905 fn test_state_machine_timer_reset_on_state_change() {
1906 let mut state = StateMachine::new();
1907 state.timer_preset = Duration::from_millis(50);
1908 state.call();
1909
1910 // Wait a bit
1911 std::thread::sleep(Duration::from_millis(30));
1912 let elapsed_before = state.elapsed();
1913 assert!(elapsed_before >= Duration::from_millis(30));
1914
1915 // Change state
1916 state.index = 10;
1917 state.call();
1918
1919 // Timer should have reset
1920 assert!(state.elapsed() < Duration::from_millis(20));
1921 assert!(!state.timer_done());
1922 }
1923
1924 #[test]
1925 fn test_state_machine_error_handling() {
1926 let mut state = StateMachine::new();
1927
1928 assert!(!state.is_error());
1929
1930 state.set_error(110, "Failed to home axis");
1931 assert!(state.is_error());
1932 assert_eq!(state.error_code, 110);
1933 assert_eq!(state.error_message, "Failed to home axis");
1934
1935 state.clear_error();
1936 assert!(!state.is_error());
1937 assert_eq!(state.error_code, 0);
1938 assert!(state.error_message.is_empty());
1939 }
1940
1941 #[test]
1942 fn test_state_machine_preset_persists() {
1943 let mut state = StateMachine::new();
1944
1945 // Set preset in state 0
1946 state.timer_preset = Duration::from_millis(50);
1947 state.index = 10;
1948 state.call();
1949
1950 // Preset should still be 50ms
1951 assert_eq!(state.timer_preset, Duration::from_millis(50));
1952
1953 // Change to state 20 without changing preset
1954 state.index = 20;
1955 state.call();
1956
1957 // Preset still 50ms
1958 assert_eq!(state.timer_preset, Duration::from_millis(50));
1959 }
1960
1961 #[test]
1962 fn test_state_machine_default_presets_never_trigger() {
1963 let mut state = StateMachine::new();
1964 state.call();
1965
1966 // Default presets are Duration::MAX, so timers should never trigger
1967 std::thread::sleep(Duration::from_millis(10));
1968 assert!(!state.timer_done());
1969 assert!(!state.timed_out());
1970 }
1971}