Skip to main content

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//!
81//! ### Example: Edge Detection
82//!
83//! ```
84//! use autocore_std::RTrig;
85//!
86//! let mut trigger = RTrig::new();
87//!
88//! // First call with false - no edge
89//! assert_eq!(trigger.call(false), false);
90//!
91//! // Rising edge detected!
92//! assert_eq!(trigger.call(true), true);
93//!
94//! // Still true, but no edge (already high)
95//! assert_eq!(trigger.call(true), false);
96//!
97//! // Back to false
98//! assert_eq!(trigger.call(false), false);
99//!
100//! // Another rising edge
101//! assert_eq!(trigger.call(true), true);
102//! ```
103//!
104//! ### Example: Timer
105//!
106//! ```
107//! use autocore_std::Ton;
108//! use std::time::Duration;
109//!
110//! let mut timer = Ton::new();
111//! let delay = Duration::from_millis(100);
112//!
113//! // Timer not enabled - output is false
114//! assert_eq!(timer.call(false, delay), false);
115//!
116//! // Enable timer - starts counting
117//! assert_eq!(timer.call(true, delay), false);
118//!
119//! // Still counting...
120//! std::thread::sleep(Duration::from_millis(50));
121//! assert_eq!(timer.call(true, delay), false);
122//! assert!(timer.et < delay); // Elapsed time < preset
123//!
124//! // After delay elapsed
125//! std::thread::sleep(Duration::from_millis(60));
126//! assert_eq!(timer.call(true, delay), true); // Output is now true!
127//! ```
128//!
129//! ## Logging
130//!
131//! Control programs can send log messages to the autocore-server for display in the
132//! web console. Logging is handled automatically when using [`ControlRunner`].
133//!
134//! ```ignore
135//! use autocore_std::log;
136//!
137//! log::trace!("Detailed trace message");
138//! log::debug!("Debug information");
139//! log::info!("Normal operation message");
140//! log::warn!("Warning condition detected");
141//! log::error!("Error occurred!");
142//! ```
143//!
144//! See the [`logger`] module for advanced configuration.
145//!
146//! ## Memory Synchronization
147//!
148//! The [`ControlRunner`] handles all shared memory synchronization automatically:
149//!
150//! 1. **Wait for tick** - Blocks until the server signals a new cycle
151//! 2. **Read inputs** - Copies shared memory to local buffer (atomic snapshot)
152//! 3. **Execute logic** - Your `process_tick` runs on the local buffer
153//! 4. **Write outputs** - Copies local buffer back to shared memory
154//!
155//! This ensures your control logic always sees a consistent view of the data,
156//! even when other processes are modifying shared memory.
157
158#![warn(missing_docs)]
159#![warn(rustdoc::missing_crate_level_docs)]
160#![doc(html_root_url = "https://docs.rs/autocore-std/3.3.0")]
161
162use anyhow::{anyhow, Result};
163use async_trait::async_trait;
164use log::LevelFilter;
165use mechutil::ipc::{CommandMessage, IpcClient, ModuleHandler};
166use raw_sync::events::{Event, EventInit, EventState};
167use raw_sync::Timeout;
168use shared_memory::ShmemConf;
169use std::collections::HashMap;
170use std::sync::atomic::{fence, Ordering};
171use std::time::{Duration, Instant};
172
173/// UDP logger for sending log messages to autocore-server.
174///
175/// This module provides a non-blocking logger implementation that sends log messages
176/// via UDP to the autocore-server. Messages are batched and sent asynchronously to
177/// avoid impacting control loop timing.
178///
179/// # Example
180///
181/// ```ignore
182/// use autocore_std::logger;
183/// use log::LevelFilter;
184///
185/// // Initialize the logger (done automatically by ControlRunner)
186/// logger::init_udp_logger("127.0.0.1", 39101, LevelFilter::Info, "control")?;
187///
188/// // Now you can use the log macros
189/// log::info!("System initialized");
190/// ```
191pub mod logger;
192
193// Re-export log crate for convenience - control programs can use autocore_std::log::info!() etc.
194pub use log;
195
196// ============================================================================
197// Standard Library Function Blocks (IEC 61131-3 Inspired)
198// ============================================================================
199
200/// Rising Edge Trigger (R_TRIG)
201///
202/// Detects a rising edge (false → true transition) on the input signal.
203/// The output `q` is `true` for exactly one cycle when the input `clk`
204/// transitions from `false` to `true`.
205///
206/// This is equivalent to the IEC 61131-3 R_TRIG function block.
207///
208/// # Example
209///
210/// ```
211/// use autocore_std::RTrig;
212///
213/// let mut trigger = RTrig::new();
214///
215/// // No edge yet
216/// assert_eq!(trigger.call(false), false);
217///
218/// // Rising edge detected!
219/// assert_eq!(trigger.call(true), true);
220///
221/// // Signal still high, but edge already passed
222/// assert_eq!(trigger.call(true), false);
223/// assert_eq!(trigger.call(true), false);
224///
225/// // Signal goes low
226/// assert_eq!(trigger.call(false), false);
227///
228/// // Another rising edge
229/// assert_eq!(trigger.call(true), true);
230/// ```
231///
232/// # Timing Diagram
233///
234/// ```text
235/// clk: _____|‾‾‾‾‾‾‾‾‾|_____|‾‾‾‾‾
236///   q: _____|‾|_____________|‾|____
237/// ```
238///
239/// # Use Cases
240///
241/// - Detecting button presses (trigger on press, not hold)
242/// - Counting events (increment counter on each rising edge)
243/// - State machine transitions
244#[derive(Debug, Clone)]
245pub struct RTrig {
246    /// Current input value
247    pub clk: bool,
248    /// Output: true for one cycle on rising edge
249    pub q: bool,
250    /// Internal memory of previous input state
251    m: bool,
252}
253
254impl RTrig {
255    /// Creates a new rising edge trigger with all values initialized to `false`.
256    ///
257    /// # Example
258    ///
259    /// ```
260    /// use autocore_std::RTrig;
261    ///
262    /// let trigger = RTrig::new();
263    /// assert_eq!(trigger.q, false);
264    /// ```
265    pub fn new() -> Self {
266        Self {
267            clk: false,
268            q: false,
269            m: false,
270        }
271    }
272
273    /// Executes the rising edge detection logic.
274    ///
275    /// Call this method once per control cycle with the current input value.
276    /// Returns `true` for exactly one cycle when a rising edge is detected.
277    ///
278    /// # Arguments
279    ///
280    /// * `clk` - The current state of the input signal
281    ///
282    /// # Returns
283    ///
284    /// `true` if a rising edge (false → true transition) was detected, `false` otherwise.
285    ///
286    /// # Example
287    ///
288    /// ```
289    /// use autocore_std::RTrig;
290    ///
291    /// let mut trigger = RTrig::new();
292    ///
293    /// let button_pressed = true;
294    /// if trigger.call(button_pressed) {
295    ///     println!("Button was just pressed!");
296    /// }
297    /// ```
298    pub fn call(&mut self, clk: bool) -> bool {
299        self.clk = clk;
300        self.q = self.clk && !self.m;
301        self.m = self.clk;
302        self.q
303    }
304}
305
306impl Default for RTrig {
307    fn default() -> Self {
308        Self::new()
309    }
310}
311
312
313/// Falling Edge Trigger (F_TRIG)
314///
315/// Detects a falling edge (true → false transition) on the input signal.
316/// The output `q` is `true` for exactly one cycle when the input `clk`
317/// transitions from `true` to `false`.
318///
319/// This is equivalent to the IEC 61131-3 F_TRIG function block.
320///
321/// # Example
322///
323/// ```
324/// use autocore_std::FTrig;
325///
326/// let mut trigger = FTrig::new();
327///
328/// // Signal starts low
329/// assert_eq!(trigger.call(false), false);
330///
331/// // Signal goes high
332/// assert_eq!(trigger.call(true), false);
333///
334/// // Falling edge detected!
335/// assert_eq!(trigger.call(false), true);
336///
337/// // Signal still low, edge already passed
338/// assert_eq!(trigger.call(false), false);
339/// ```
340///
341/// # Timing Diagram
342///
343/// ```text
344/// clk: _____|‾‾‾‾‾‾‾‾‾|_____|‾‾‾‾‾
345///   q: _______________|‾|________
346/// ```
347///
348/// # Use Cases
349///
350/// - Detecting button releases
351/// - Detecting signal loss
352/// - Triggering actions when a condition ends
353#[derive(Debug, Clone)]
354pub struct FTrig {
355    /// Current input value
356    pub clk: bool,
357    /// Output: true for one cycle on falling edge
358    pub q: bool,
359    /// Internal memory of previous input state
360    m: bool,
361}
362
363impl FTrig {
364    /// Creates a new falling edge trigger with all values initialized to `false`.
365    ///
366    /// # Example
367    ///
368    /// ```
369    /// use autocore_std::FTrig;
370    ///
371    /// let trigger = FTrig::new();
372    /// assert_eq!(trigger.q, false);
373    /// ```
374    pub fn new() -> Self {
375        Self {
376            clk: false,
377            q: false,
378            m: false,
379        }
380    }
381
382    /// Executes the falling edge detection logic.
383    ///
384    /// Call this method once per control cycle with the current input value.
385    /// Returns `true` for exactly one cycle when a falling edge is detected.
386    ///
387    /// # Arguments
388    ///
389    /// * `clk` - The current state of the input signal
390    ///
391    /// # Returns
392    ///
393    /// `true` if a falling edge (true → false transition) was detected, `false` otherwise.
394    ///
395    /// # Example
396    ///
397    /// ```
398    /// use autocore_std::FTrig;
399    ///
400    /// let mut trigger = FTrig::new();
401    ///
402    /// // Simulate button release
403    /// trigger.call(true);  // Button held
404    /// if trigger.call(false) {  // Button released
405    ///     println!("Button was just released!");
406    /// }
407    /// ```
408    pub fn call(&mut self, clk: bool) -> bool {
409        self.clk = clk;
410        self.q = !self.clk && self.m;
411        self.m = self.clk;
412        self.q
413    }
414}
415
416impl Default for FTrig {
417    fn default() -> Self {
418        Self::new()
419    }
420}
421
422
423/// Timer On Delay (TON)
424///
425/// A timer that delays turning on the output. The output `q` becomes `true`
426/// after the enable input `en` has been continuously `true` for the preset
427/// time `pt`. The elapsed time is available in `et`.
428///
429/// This is equivalent to the IEC 61131-3 TON function block.
430///
431/// # Behavior
432///
433/// - When `en` becomes `true`, the timer starts counting from zero
434/// - While counting, `et` shows the elapsed time and `q` is `false`
435/// - When `et` reaches `pt`, `q` becomes `true` and `et` is clamped to `pt`
436/// - When `en` becomes `false`, the timer resets: `q` = `false`, `et` = 0
437///
438/// # Example
439///
440/// ```
441/// use autocore_std::Ton;
442/// use std::time::Duration;
443///
444/// let mut timer = Ton::new();
445/// let delay = Duration::from_secs(5);
446///
447/// // Timer disabled - output is false
448/// assert_eq!(timer.call(false, delay), false);
449/// assert_eq!(timer.et, Duration::ZERO);
450///
451/// // Enable timer - starts counting
452/// timer.call(true, delay);
453/// assert_eq!(timer.q, false);  // Not done yet
454/// // timer.et is now counting up...
455///
456/// // Disable resets the timer
457/// timer.call(false, delay);
458/// assert_eq!(timer.et, Duration::ZERO);
459/// ```
460///
461/// # Timing Diagram
462///
463/// ```text
464///  en: _____|‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾|_____
465///   q: _____________|‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾|_____
466///  et: 0---|++++++++|PT----------------|0----
467///          ^        ^                  ^
468///          |        |                  |
469///      en rises   et=pt             en falls
470/// ```
471///
472/// # Use Cases
473///
474/// - Motor start delay (allow contactors to engage)
475/// - Debouncing switches (ignore brief transitions)
476/// - Timeout detection (alarm if condition persists too long)
477#[derive(Debug, Clone)]
478pub struct Ton {
479    /// Input: Enable the timer (true = counting, false = reset)
480    pub en: bool,
481    /// Input: Preset time (duration before output activates)
482    pub pt: Duration,
483    /// Output: Timer done (true when elapsed time >= preset time)
484    pub q: bool,
485    /// Output: Elapsed time since timer was enabled
486    pub et: Duration,
487
488    start_time: Option<Instant>,
489    active: bool,
490}
491
492impl Ton {
493    /// Creates a new timer with default values.
494    ///
495    /// The timer starts in the disabled state with zero elapsed time.
496    ///
497    /// # Example
498    ///
499    /// ```
500    /// use autocore_std::Ton;
501    ///
502    /// let timer = Ton::new();
503    /// assert_eq!(timer.q, false);
504    /// assert_eq!(timer.et, std::time::Duration::ZERO);
505    /// ```
506    pub fn new() -> Self {
507        Self {
508            en: false,
509            pt: Duration::default(),
510            q: false,
511            et: Duration::default(),
512            start_time: None,
513            active: false,
514        }
515    }
516
517    /// Executes the timer logic.
518    ///
519    /// Call this method once per control cycle. The timer counts real elapsed
520    /// time (not cycles), so the output timing is independent of scan rate.
521    ///
522    /// # Arguments
523    ///
524    /// * `en` - Enable input: `true` to run timer, `false` to reset
525    /// * `pt` - Preset time: duration before output activates
526    ///
527    /// # Returns
528    ///
529    /// The current state of the output `q` (true if timer has elapsed).
530    ///
531    /// # Example
532    ///
533    /// ```
534    /// use autocore_std::Ton;
535    /// use std::time::Duration;
536    ///
537    /// let mut timer = Ton::new();
538    ///
539    /// // Use in a control loop
540    /// let motor_request = true;
541    /// let start_delay = Duration::from_millis(500);
542    ///
543    /// let motor_enabled = timer.call(motor_request, start_delay);
544    /// // motor_enabled will be true after 500ms of motor_request being true
545    /// ```
546    pub fn call(&mut self, en: bool, pt: Duration) -> bool {
547        self.en = en;
548        self.pt = pt;
549
550        if !self.en {
551            // Reset
552            self.q = false;
553            self.et = Duration::ZERO;
554            self.start_time = None;
555            self.active = false;
556        } else {
557            if !self.active {
558                // Rising edge of EN - start timing
559                self.start_time = Some(Instant::now());
560                self.active = true;
561                self.et = Duration::ZERO;
562                self.q = false;
563            } else {
564                // Timer running
565                if let Some(start) = self.start_time {
566                    self.et = start.elapsed();
567                    if self.et >= self.pt {
568                        self.et = self.pt; // Clamp ET to PT
569                        self.q = true;
570                    }
571                }
572            }
573        }
574        self.q
575    }
576
577    /// Resets the timer to its initial state.
578    ///
579    /// This is equivalent to calling `call(false, ...)`.
580    ///
581    /// # Example
582    ///
583    /// ```
584    /// use autocore_std::Ton;
585    /// use std::time::Duration;
586    ///
587    /// let mut timer = Ton::new();
588    /// timer.call(true, Duration::from_secs(1));
589    /// // ... timer is running ...
590    ///
591    /// timer.reset();
592    /// assert_eq!(timer.q, false);
593    /// assert_eq!(timer.et, Duration::ZERO);
594    /// ```
595    pub fn reset(&mut self) {
596        self.q = false;
597        self.et = Duration::ZERO;
598        self.start_time = None;
599        self.active = false;
600    }
601}
602
603impl Default for Ton {
604    fn default() -> Self {
605        Self::new()
606    }
607}
608
609// ============================================================================
610// Core Framework
611// ============================================================================
612
613/// Marker trait for generated GlobalMemory structs.
614///
615/// This trait is implemented by the auto-generated `GlobalMemory` struct
616/// that represents the shared memory layout. It serves as a marker for
617/// type safety in the control framework.
618///
619/// You don't need to implement this trait yourself - it's automatically
620/// implemented by the code generator.
621pub trait AutoCoreMemory {}
622
623/// The trait that defines a control program's logic.
624///
625/// Implement this trait to create your control program. The associated `Memory`
626/// type should be the generated `GlobalMemory` struct from your project.
627///
628/// # Memory Type Requirements
629///
630/// The `Memory` type must implement `Copy` to allow efficient synchronization
631/// between shared memory and local buffers. This is automatically satisfied
632/// by the generated `GlobalMemory` struct.
633///
634/// # Lifecycle
635///
636/// 1. `initialize` is called once at startup
637/// 2. `process_tick` is called repeatedly in the control loop
638///
639/// # Example
640///
641/// ```ignore
642/// use autocore_std::ControlProgram;
643///
644/// mod gm;
645/// use gm::GlobalMemory;
646///
647/// pub struct MyController {
648///     cycle_counter: u64,
649/// }
650///
651/// impl MyController {
652///     pub fn new() -> Self {
653///         Self { cycle_counter: 0 }
654///     }
655/// }
656///
657/// impl ControlProgram for MyController {
658///     type Memory = GlobalMemory;
659///
660///     fn initialize(&mut self, mem: &mut GlobalMemory) {
661///         // Set initial output states
662///         mem.outputs.ready = true;
663///         log::info!("Controller initialized");
664///     }
665///
666///     fn process_tick(&mut self, mem: &mut GlobalMemory, cycle: u64) {
667///         self.cycle_counter = cycle;
668///
669///         // Your control logic here
670///         if mem.inputs.start && !mem.inputs.estop {
671///             mem.outputs.running = true;
672///         }
673///     }
674/// }
675/// ```
676pub trait ControlProgram {
677    /// The shared memory structure type (usually the generated `GlobalMemory`).
678    ///
679    /// Must implement `Copy` to allow efficient memory synchronization.
680    type Memory: Copy;
681
682    /// Called once when the control program starts.
683    ///
684    /// Use this to initialize output states, reset counters, or perform
685    /// any one-time setup. The default implementation does nothing.
686    ///
687    /// # Arguments
688    ///
689    /// * `mem` - Mutable reference to the shared memory. Changes are written
690    ///           back to shared memory after this method returns.
691    fn initialize(&mut self, _mem: &mut Self::Memory) {}
692
693    /// The main control loop - called once per scan cycle.
694    ///
695    /// This is where your control logic lives. Read inputs from `mem`,
696    /// perform calculations, and write outputs back to `mem`.
697    ///
698    /// # Arguments
699    ///
700    /// * `mem` - Mutable reference to a local copy of the shared memory.
701    ///           Changes made here are written back to shared memory after
702    ///           this method returns.
703    /// * `cycle` - The current cycle number (increments each tick, starting at 1).
704    ///
705    /// # Timing
706    ///
707    /// This method should complete within the scan cycle time. Long-running
708    /// operations will cause cycle overruns.
709    fn process_tick(&mut self, mem: &mut Self::Memory, cycle: u64);
710}
711
712/// Configuration for the [`ControlRunner`].
713///
714/// Specifies connection parameters, shared memory names, and logging settings.
715/// Use [`Default::default()`] for typical configurations.
716///
717/// # Example
718///
719/// ```
720/// use autocore_std::RunnerConfig;
721/// use log::LevelFilter;
722///
723/// let config = RunnerConfig {
724///     ipc_address: "192.168.1.100:9100".to_string(),
725///     module_name: "my_controller".to_string(),
726///     shm_name: "my_project_shm".to_string(),
727///     tick_signal_name: "tick".to_string(),
728///     busy_signal_name: Some("busy".to_string()),
729///     log_level: LevelFilter::Debug,
730///     ..Default::default()
731/// };
732/// ```
733#[derive(Debug, Clone)]
734pub struct RunnerConfig {
735    /// IPC server address in "host:port" format (default: "127.0.0.1:9100")
736    pub ipc_address: String,
737    /// Module name for registration with the server (default: "control")
738    pub module_name: String,
739    /// Shared memory segment name (must match server configuration)
740    pub shm_name: String,
741    /// Name of the tick signal in shared memory (triggers each scan cycle)
742    pub tick_signal_name: String,
743    /// Optional name of the busy signal (set when cycle completes)
744    pub busy_signal_name: Option<String>,
745    /// Minimum log level to send to the server (default: Info)
746    pub log_level: LevelFilter,
747    /// UDP port for sending logs to the server (default: 39101)
748    pub log_udp_port: u16,
749}
750
751impl Default for RunnerConfig {
752    fn default() -> Self {
753        Self {
754            ipc_address: "127.0.0.1:9100".to_string(),
755            module_name: "control".to_string(),
756            shm_name: "autocore_cyclic".to_string(),
757            tick_signal_name: "tick".to_string(),
758            busy_signal_name: None,
759            log_level: LevelFilter::Info,
760            log_udp_port: logger::DEFAULT_LOG_UDP_PORT,
761        }
762    }
763}
764
765/// Internal handler for IPC setup phase.
766/// The control program only uses IPC during initialization to get the memory layout.
767struct ControlSetupHandler {
768    domain: String,
769}
770
771impl ControlSetupHandler {
772    fn new(domain: &str) -> Self {
773        Self {
774            domain: domain.to_string(),
775        }
776    }
777}
778
779#[async_trait]
780impl ModuleHandler for ControlSetupHandler {
781    async fn handle_message(&mut self, msg: CommandMessage) -> CommandMessage {
782        // We don't expect to handle any messages during setup
783        msg.into_error_response("Control module does not handle external messages")
784    }
785
786    async fn on_initialize(&mut self) -> Result<(), anyhow::Error> {
787        Ok(())
788    }
789
790    async fn on_finalize(&mut self) -> Result<(), anyhow::Error> {
791        Ok(())
792    }
793
794    fn domain(&self) -> &str {
795        &self.domain
796    }
797
798    fn version(&self) -> &str {
799        "1.0.0"
800    }
801}
802
803/// The main execution engine for control programs.
804///
805/// `ControlRunner` handles all the infrastructure required to run a control program:
806///
807/// - Connecting to the autocore-server via IPC
808/// - Opening and mapping shared memory
809/// - Setting up synchronization signals
810/// - Running the real-time control loop
811/// - Sending log messages to the server
812///
813/// # Usage
814///
815/// ```ignore
816/// use autocore_std::{ControlRunner, RunnerConfig};
817///
818/// let config = RunnerConfig {
819///     shm_name: "my_project_shm".to_string(),
820///     tick_signal_name: "tick".to_string(),
821///     ..Default::default()
822/// };
823///
824/// ControlRunner::new(MyProgram::new())
825///     .config(config)
826///     .run()?;  // Blocks forever
827/// ```
828///
829/// # Control Loop
830///
831/// The runner executes a synchronous control loop:
832///
833/// 1. **Wait** - Blocks until the tick signal is set by the server
834/// 2. **Read** - Copies shared memory to a local buffer (acquire barrier)
835/// 3. **Execute** - Calls your `process_tick` method
836/// 4. **Write** - Copies local buffer back to shared memory (release barrier)
837/// 5. **Signal** - Sets the busy signal (if configured) to indicate completion
838///
839/// This ensures your code always sees a consistent snapshot of the data
840/// and that your writes are atomically visible to other processes.
841pub struct ControlRunner<P: ControlProgram> {
842    config: RunnerConfig,
843    program: P,
844}
845
846impl<P: ControlProgram> ControlRunner<P> {
847    /// Creates a new runner for the given control program.
848    ///
849    /// Uses default configuration. Call [`.config()`](Self::config) to customize.
850    ///
851    /// # Arguments
852    ///
853    /// * `program` - Your control program instance
854    ///
855    /// # Example
856    ///
857    /// ```ignore
858    /// let runner = ControlRunner::new(MyProgram::new());
859    /// ```
860    pub fn new(program: P) -> Self {
861        Self {
862            config: RunnerConfig::default(),
863            program,
864        }
865    }
866
867    /// Sets the configuration for this runner.
868    ///
869    /// # Arguments
870    ///
871    /// * `config` - The configuration to use
872    ///
873    /// # Example
874    ///
875    /// ```ignore
876    /// ControlRunner::new(MyProgram::new())
877    ///     .config(RunnerConfig {
878    ///         shm_name: "custom_shm".to_string(),
879    ///         ..Default::default()
880    ///     })
881    ///     .run()?;
882    /// ```
883    pub fn config(mut self, config: RunnerConfig) -> Self {
884        self.config = config;
885        self
886    }
887
888    /// Starts the control loop.
889    ///
890    /// This method blocks indefinitely, running the control loop until
891    /// an error occurs or the process is terminated.
892    ///
893    /// # Returns
894    ///
895    /// Returns `Ok(())` only if the loop exits cleanly (which typically
896    /// doesn't happen). Returns an error if:
897    ///
898    /// - IPC connection fails
899    /// - Shared memory cannot be opened
900    /// - Signal offsets cannot be found
901    /// - A critical error occurs during execution
902    ///
903    /// # Example
904    ///
905    /// ```ignore
906    /// fn main() -> anyhow::Result<()> {
907    ///     ControlRunner::new(MyProgram::new())
908    ///         .config(config)
909    ///         .run()
910    /// }
911    /// ```
912    pub fn run(mut self) -> Result<()> {
913        // Initialize UDP logger FIRST (before any log statements)
914        // Extract host from ipc_address (format: "host:port")
915        let log_host = self.config.ipc_address
916            .split(':')
917            .next()
918            .unwrap_or("127.0.0.1");
919
920        if let Err(e) = logger::init_udp_logger(
921            log_host,
922            self.config.log_udp_port,
923            self.config.log_level,
924            "control",
925        ) {
926            eprintln!("Warning: Failed to initialize UDP logger: {}", e);
927            // Continue anyway - logging will just go nowhere
928        }
929
930        // We use a dedicated runtime for the setup phase
931        let rt = tokio::runtime::Builder::new_current_thread()
932            .enable_all()
933            .build()?;
934
935        rt.block_on(async {
936            log::info!("AutoCore Control Runner Starting...");
937
938            // 1. Connect IPC using the new ModuleHandler API
939            let handler = ControlSetupHandler::new(&self.config.module_name);
940            let client = IpcClient::connect(&self.config.ipc_address, handler).await
941                .map_err(|e| anyhow!("Failed to connect to server: {}", e))?;
942            log::info!("IPC Connected.");
943
944            // 2. Get Layout to find signal offsets
945            // Use the request method with a timeout
946            let response = client.request("gm.get_layout", serde_json::Value::Null, Duration::from_secs(5)).await
947                .map_err(|e| anyhow!("Failed to get layout: {}", e))?;
948
949            if !response.success {
950                return Err(anyhow!("Failed to get layout: {}", response.error_message));
951            }
952
953            let layout: HashMap<String, serde_json::Value> = serde_json::from_value(response.data)?;
954
955            // 3. Find Signal Offsets
956            let tick_offset = self.find_offset(&layout, &self.config.tick_signal_name)?;
957            let busy_offset = if let Some(name) = &self.config.busy_signal_name {
958                Some(self.find_offset(&layout, name)?)
959            } else {
960                None
961            };
962
963            // Shutdown the IPC client - we don't need it anymore
964            client.shutdown();
965
966            // 4. Open Shared Memory
967            let shmem = ShmemConf::new().os_id(&self.config.shm_name).open()?;
968            let base_ptr = shmem.as_ptr();
969            log::info!("Shared Memory '{}' mapped.", self.config.shm_name);
970
971            // 5. Setup Pointers
972            // SAFETY: We trust the server's layout matches the generated GlobalMemory struct.
973            let gm = unsafe { &mut *(base_ptr as *mut P::Memory) };
974
975            // EventInit::from_existing returns (Box<dyn EventImpl>, usize)
976            let (tick_event, _) = unsafe {
977                Event::from_existing(base_ptr.add(tick_offset))
978                    .map_err(|e| anyhow!("Failed to open tick event: {}", e))?
979            };
980
981            let busy_event = if let Some(offset) = busy_offset {
982                let (event, _) = unsafe {
983                    Event::from_existing(base_ptr.add(offset))
984                        .map_err(|e| anyhow!("Failed to open busy event: {}", e))?
985                };
986                Some(event)
987            } else {
988                None
989            };
990
991            // 6. Initialize local memory buffer and user program
992            // We use a local copy for the control loop to ensure:
993            // - Consistent snapshot of inputs at start of cycle
994            // - Atomic commit of outputs at end of cycle
995            // - Proper memory barriers for cross-process visibility
996            let mut local_mem: P::Memory = unsafe { std::ptr::read_volatile(gm) };
997            fence(Ordering::Acquire); // Ensure we see all prior writes from other processes
998
999            self.program.initialize(&mut local_mem);
1000
1001            // Write back any changes from initialize
1002            fence(Ordering::Release);
1003            unsafe { std::ptr::write_volatile(gm, local_mem) };
1004
1005            log::info!("Entering Control Loop...");
1006            let mut cycle_count: u64 = 0;
1007
1008            loop {
1009                // Wait for Tick
1010                if let Err(e) = tick_event.wait(Timeout::Infinite) {
1011                    eprintln!("Tick wait error: {}", e);
1012                    break;
1013                }
1014
1015                cycle_count += 1;
1016
1017                // === INPUT PHASE ===
1018                // Read all variables from shared memory into local buffer.
1019                // This gives us a consistent snapshot of inputs for this cycle.
1020                // Acquire fence ensures we see all writes from other processes (server, modules).
1021                local_mem = unsafe { std::ptr::read_volatile(gm) };
1022                fence(Ordering::Acquire);
1023
1024                // === EXECUTE PHASE ===
1025                // Execute user logic on the local copy.
1026                // All reads/writes during process_tick operate on local_mem.
1027                self.program.process_tick(&mut local_mem, cycle_count);
1028
1029                // === OUTPUT PHASE ===
1030                // Write all variables from local buffer back to shared memory.
1031                // Release fence ensures our writes are visible to other processes.
1032                fence(Ordering::Release);
1033                unsafe { std::ptr::write_volatile(gm, local_mem) };
1034
1035                // Signal Busy/Done
1036                if let Some(ref busy) = busy_event {
1037                    let _ = busy.set(EventState::Signaled);
1038                }
1039            }
1040
1041            Ok(())
1042        })
1043    }
1044
1045    fn find_offset(&self, layout: &HashMap<String, serde_json::Value>, name: &str) -> Result<usize> {
1046        let info = layout.get(name).ok_or_else(|| anyhow!("Signal '{}' not found in layout", name))?;
1047        info.get("offset")
1048            .and_then(|v| v.as_u64())
1049            .map(|v| v as usize)
1050            .ok_or_else(|| anyhow!("Invalid offset for '{}'", name))
1051    }
1052}
1053
1054/// Generates the standard `main` function for a control program.
1055///
1056/// This macro reduces boilerplate by creating a properly configured `main`
1057/// function that initializes and runs your control program.
1058///
1059/// # Arguments
1060///
1061/// * `$prog_type` - The type of your control program (must implement [`ControlProgram`])
1062/// * `$shm_name` - The shared memory segment name (string literal)
1063/// * `$tick_signal` - The tick signal name in shared memory (string literal)
1064///
1065/// # Example
1066///
1067/// ```ignore
1068/// mod gm;
1069/// use gm::GlobalMemory;
1070///
1071/// pub struct MyProgram;
1072///
1073/// impl MyProgram {
1074///     pub fn new() -> Self { Self }
1075/// }
1076///
1077/// impl autocore_std::ControlProgram for MyProgram {
1078///     type Memory = GlobalMemory;
1079///
1080///     fn process_tick(&mut self, mem: &mut GlobalMemory, _cycle: u64) {
1081///         // Your logic here
1082///     }
1083/// }
1084///
1085/// // This generates the main function
1086/// autocore_std::autocore_main!(MyProgram, "my_project_shm", "tick");
1087/// ```
1088///
1089/// # Generated Code
1090///
1091/// The macro expands to:
1092///
1093/// ```ignore
1094/// fn main() -> anyhow::Result<()> {
1095///     let config = autocore_std::RunnerConfig {
1096///         ipc_address: "127.0.0.1:9100".to_string(),
1097///         module_name: "control".to_string(),
1098///         shm_name: "my_project_shm".to_string(),
1099///         tick_signal_name: "tick".to_string(),
1100///         busy_signal_name: None,
1101///         log_level: log::LevelFilter::Info,
1102///         log_udp_port: autocore_std::logger::DEFAULT_LOG_UDP_PORT,
1103///     };
1104///
1105///     autocore_std::ControlRunner::new(MyProgram::new())
1106///         .config(config)
1107///         .run()
1108/// }
1109/// ```
1110#[macro_export]
1111macro_rules! autocore_main {
1112    ($prog_type:ty, $shm_name:expr, $tick_signal:expr) => {
1113        fn main() -> anyhow::Result<()> {
1114            let config = autocore_std::RunnerConfig {
1115                ipc_address: "127.0.0.1:9100".to_string(),
1116                module_name: "control".to_string(),
1117                shm_name: $shm_name.to_string(),
1118                tick_signal_name: $tick_signal.to_string(),
1119                busy_signal_name: None,
1120                log_level: log::LevelFilter::Info,
1121                log_udp_port: autocore_std::logger::DEFAULT_LOG_UDP_PORT,
1122            };
1123
1124            autocore_std::ControlRunner::new(<$prog_type>::new())
1125                .config(config)
1126                .run()
1127        }
1128    };
1129}
1130
1131#[cfg(test)]
1132mod tests {
1133    use super::*;
1134
1135    #[test]
1136    fn test_rtrig_rising_edge() {
1137        let mut trigger = RTrig::new();
1138
1139        // No edge initially
1140        assert_eq!(trigger.call(false), false);
1141
1142        // Rising edge
1143        assert_eq!(trigger.call(true), true);
1144
1145        // No edge while high
1146        assert_eq!(trigger.call(true), false);
1147        assert_eq!(trigger.call(true), false);
1148
1149        // No edge on falling
1150        assert_eq!(trigger.call(false), false);
1151
1152        // Rising edge again
1153        assert_eq!(trigger.call(true), true);
1154    }
1155
1156    #[test]
1157    fn test_ftrig_falling_edge() {
1158        let mut trigger = FTrig::new();
1159
1160        // No edge initially
1161        assert_eq!(trigger.call(false), false);
1162
1163        // No edge on rising
1164        assert_eq!(trigger.call(true), false);
1165
1166        // Falling edge
1167        assert_eq!(trigger.call(false), true);
1168
1169        // No edge while low
1170        assert_eq!(trigger.call(false), false);
1171
1172        // Rising, then falling edge
1173        assert_eq!(trigger.call(true), false);
1174        assert_eq!(trigger.call(false), true);
1175    }
1176
1177    #[test]
1178    fn test_ton_basic() {
1179        let mut timer = Ton::new();
1180        let pt = Duration::from_millis(50);
1181
1182        // Disabled
1183        assert_eq!(timer.call(false, pt), false);
1184        assert_eq!(timer.et, Duration::ZERO);
1185
1186        // Enable
1187        assert_eq!(timer.call(true, pt), false);
1188        assert!(timer.et < pt);
1189
1190        // Wait for timer
1191        std::thread::sleep(Duration::from_millis(60));
1192        assert_eq!(timer.call(true, pt), true);
1193        assert_eq!(timer.et, pt);
1194
1195        // Reset
1196        timer.reset();
1197        assert_eq!(timer.q, false);
1198        assert_eq!(timer.et, Duration::ZERO);
1199    }
1200}