ready-active-safe 0.1.3

Lifecycle engine for externally driven systems
Documentation
//! Channel-based event source.
//!
//! Shows how to wire `Runner` to real threads sending events over
//! `std::sync::mpsc`. A timer thread, a sensor thread, and a fault
//! injector each push events into a shared channel. The main thread
//! receives and dispatches them.

#![allow(clippy::print_stdout, clippy::enum_glob_use)]

use std::sync::mpsc;
use std::thread;
use std::time::Duration;

use ready_active_safe::prelude::*;

#[derive(Debug, Clone, PartialEq, Eq)]
enum Mode {
    Initializing,
    Sampling,
    Safe,
}

impl core::fmt::Display for Mode {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        write!(f, "{self:?}")
    }
}

#[derive(Debug)]
enum Event {
    Tick,
    Measurement(u32),
    Fault,
}

#[derive(Debug, PartialEq)]
enum Command {
    RecordSample(u32),
    TriggerAlarm,
    LogTick,
}

struct SensorSystem;

impl Machine for SensorSystem {
    type Mode = Mode;
    type Event = Event;
    type Command = Command;

    fn initial_mode(&self) -> Mode {
        Mode::Initializing
    }

    fn on_event(&self, mode: &Mode, event: &Event) -> Decision<Mode, Command> {
        use Command::*;
        use Event::*;
        use Mode::*;

        match (mode, event) {
            (Initializing, Tick) => transition(Sampling),
            (Sampling, Measurement(val)) => stay().emit(RecordSample(*val)),
            (Sampling, Tick) => stay().emit(LogTick),
            (Sampling, Fault) => transition(Safe).emit(TriggerAlarm),
            _ => ignore(),
        }
    }
}

fn spawn_timer(tx: mpsc::Sender<Event>) {
    thread::spawn(move || {
        for _ in 0..6 {
            thread::sleep(Duration::from_millis(50));
            if tx.send(Event::Tick).is_err() {
                return;
            }
        }
    });
}

fn spawn_sensor(tx: mpsc::Sender<Event>) {
    thread::spawn(move || {
        for i in 0..4 {
            thread::sleep(Duration::from_millis(80));
            if tx.send(Event::Measurement(100 + i)).is_err() {
                return;
            }
        }
    });
}

fn spawn_fault_injector(tx: mpsc::Sender<Event>) {
    thread::spawn(move || {
        thread::sleep(Duration::from_millis(350));
        let _ = tx.send(Event::Fault);
    });
}

fn main() {
    let system = SensorSystem;
    let mut runner = Runner::new(&system);

    let (tx, rx) = mpsc::channel();

    spawn_timer(tx.clone());
    spawn_sensor(tx.clone());
    spawn_fault_injector(tx);

    println!("Starting in {:?}", runner.mode());

    loop {
        match rx.recv_timeout(Duration::from_millis(500)) {
            Ok(event) => {
                let before = runner.mode().clone();
                runner.feed_and_dispatch(&event, |cmd| {
                    println!("  dispatch: {cmd:?}");
                });
                let after = runner.mode();
                println!("{before:?} + {event:?} => {after:?}");

                if *after == Mode::Safe {
                    println!("Reached Safe mode, shutting down.");
                    break;
                }
            }
            Err(mpsc::RecvTimeoutError::Timeout) => {
                println!("No events for 500ms, shutting down.");
                break;
            }
            Err(mpsc::RecvTimeoutError::Disconnected) => {
                println!("All senders dropped, shutting down.");
                break;
            }
        }
    }

    println!("Final mode: {:?}", runner.mode());
}