Compare commits
3 commits
c201f40d65
...
4acf07f389
| Author | SHA1 | Date | |
|---|---|---|---|
| 4acf07f389 | |||
| 92c2ffa9cd | |||
| 76d1311440 |
7 changed files with 140 additions and 87 deletions
|
|
@ -8,3 +8,6 @@ build = "build.rs"
|
||||||
ctrlc = "3.5.2"
|
ctrlc = "3.5.2"
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3.22", features = ["env-filter", "fmt"] }
|
tracing-subscriber = { version = "0.3.22", features = ["env-filter", "fmt"] }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
increment = []
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,17 @@
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
use std::env;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
println!("cargo:rerun-if-changed=src/");
|
||||||
|
|
||||||
let pkg_version = env!("CARGO_PKG_VERSION");
|
let pkg_version = env!("CARGO_PKG_VERSION");
|
||||||
let mut content = fs::read_to_string("build_number.txt").unwrap_or("0".to_string());
|
let content = fs::read_to_string("build_number.txt").unwrap_or_else(|_| "0".to_string());
|
||||||
let mut build_num: u32 = content.trim().parse().unwrap_or(0);
|
let mut build_num: u32 = content.trim().parse().unwrap_or(0);
|
||||||
|
|
||||||
build_num += 1;
|
if env::var("CARGO_FEATURE_INCREMENT").is_ok() {
|
||||||
let full_version = format!("{}.{}", pkg_version, build_num);
|
build_num += 1;
|
||||||
|
let _ = fs::write("build_number.txt", build_num.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
fs::write("build_number.txt", build_num.to_string()).unwrap();
|
println!("cargo:rustc-env=FULL_VERSION={}.{}", pkg_version, build_num);
|
||||||
|
|
||||||
println!("cargo:rustc-env=FULL_VERSION={}", full_version);
|
|
||||||
}
|
}
|
||||||
|
|
@ -1 +1 @@
|
||||||
98
|
133
|
||||||
|
|
@ -1,49 +1,75 @@
|
||||||
pub mod state;
|
|
||||||
pub mod led_pump;
|
|
||||||
|
|
||||||
pub use self::state::State;
|
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
use std::thread::JoinHandle;
|
use std::sync::mpsc::{Receiver, Sender};
|
||||||
use self::led_pump::LedPump;
|
use crate::lifecycle::{LifeState, LifecycleCommand};
|
||||||
|
use crate::lifecycle::LifeState::{Buried, Genisys};
|
||||||
|
|
||||||
pub struct Brain {
|
pub struct Brain {
|
||||||
pub state: State,
|
state:LifeState,
|
||||||
device_threads: Vec<JoinHandle<()>>,
|
divine_rx : Receiver<LifecycleCommand>,
|
||||||
led_pump: LedPump
|
divine_tx: Sender<String>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Brain {
|
impl Brain {
|
||||||
pub fn new() -> Self {
|
pub fn new(divine_rx: Receiver<LifecycleCommand>, divine_tx: Sender<String>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
state: State::Starting,
|
state: LifeState::Dead,
|
||||||
device_threads: Vec::new(),
|
divine_rx,
|
||||||
led_pump: LedPump::new()
|
divine_tx,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn request_shutdown(&mut self) {
|
pub fn run(&mut self) {
|
||||||
if self.state == State::Running {
|
loop {
|
||||||
self.state = State::Stopping;
|
while let Ok(command) = self.divine_rx.try_recv() {
|
||||||
|
self.handle_divine_command(command)
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.state == LifeState::Buried {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Drain the Organ Feedback Mailbox
|
||||||
|
// (We will add this once we define the Organ channels)
|
||||||
|
|
||||||
|
Self::rest()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&mut self) {
|
fn rest() {
|
||||||
match self.state {
|
std::thread::sleep(std::time::Duration::from_millis(1))
|
||||||
State::Starting => {
|
|
||||||
// For now, we just instantly transition
|
|
||||||
self.state = State::Running;
|
|
||||||
}
|
|
||||||
State::Running => {
|
|
||||||
self.led_pump.beat();
|
|
||||||
}
|
|
||||||
State::Stopping => {
|
|
||||||
info!("Brain: Waiting for all threads to join...");
|
|
||||||
while let Some(handle) = self.device_threads.pop() {
|
|
||||||
let _ = handle.join();
|
|
||||||
}
|
|
||||||
self.state = State::Stopped;
|
|
||||||
}
|
|
||||||
State::Stopped => {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
fn handle_divine_command(&mut self, command: LifecycleCommand) {
|
||||||
|
info!("God has commanded {:?}", command);
|
||||||
|
|
||||||
|
if self.can_transition_lifecycle(&command) {
|
||||||
|
self.set_lifecycle_state(&command);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.report_to_god("REFUSED")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn can_transition_lifecycle(&self, command: &LifecycleCommand) -> bool
|
||||||
|
{
|
||||||
|
|
||||||
|
if (command.required_state == Buried) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.state == LifeState::Dead && command.required_state == LifeState::Genisys {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
fn set_lifecycle_state(&mut self, state: &LifecycleCommand) {
|
||||||
|
self.state = state.required_state;
|
||||||
|
self.report_to_god("OK");
|
||||||
|
}
|
||||||
|
fn report_to_god(&self, msg: &str) {
|
||||||
|
info!("Reporting to God {}", msg);
|
||||||
|
let _ = self.divine_tx.send(msg.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
use std::time::{Duration, Instant};
|
|
||||||
use tracing::debug;
|
|
||||||
pub struct LedPump {
|
|
||||||
last_beat: Option<Instant>,
|
|
||||||
interval: Duration,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LedPump {
|
|
||||||
pub fn new() -> LedPump {
|
|
||||||
Self{
|
|
||||||
last_beat: None,
|
|
||||||
interval: Duration::from_millis(500),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn beat(&mut self) {
|
|
||||||
let now = Instant::now();
|
|
||||||
|
|
||||||
|
|
||||||
let should_beat = self.last_beat.map_or(
|
|
||||||
true, |last_beat| now.duration_since(last_beat) > self.interval);
|
|
||||||
|
|
||||||
if should_beat {
|
|
||||||
self.last_beat = Some(now);
|
|
||||||
debug!("LED Pump beat.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
22
src/rustylee/src/lifecycle.rs
Normal file
22
src/rustylee/src/lifecycle.rs
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum LifeState {
|
||||||
|
Dead,
|
||||||
|
Genisys,
|
||||||
|
Sleeping,
|
||||||
|
Wakening,
|
||||||
|
Awake,
|
||||||
|
DeepThought,
|
||||||
|
ActionFocused,
|
||||||
|
Flight,
|
||||||
|
Panic,
|
||||||
|
Dying,
|
||||||
|
Buried
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct LifecycleCommand {
|
||||||
|
pub required_state: LifeState,
|
||||||
|
pub command_time: Instant
|
||||||
|
}
|
||||||
|
|
@ -1,13 +1,18 @@
|
||||||
mod brain;
|
pub mod lifecycle;
|
||||||
use brain::{Brain, State};
|
pub mod brain;
|
||||||
|
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::mpsc::{self, Receiver, Sender};
|
use std::sync::mpsc;
|
||||||
use std::{thread, time::Duration};
|
use std::thread;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
use crate::brain::Brain;
|
||||||
|
use crate::lifecycle::{LifeState, LifecycleCommand};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let semantic_version = env!("CARGO_PKG_VERSION").to_string();
|
let _semantic_version = env!("CARGO_PKG_VERSION").to_string();
|
||||||
let shutdown_requested = Arc::new(AtomicBool::new(false));
|
let shutdown_requested = Arc::new(AtomicBool::new(false));
|
||||||
let s = shutdown_requested.clone();
|
let s = shutdown_requested.clone();
|
||||||
|
|
||||||
|
|
@ -19,23 +24,45 @@ fn main() {
|
||||||
s.store(true, Ordering::SeqCst);
|
s.store(true, Ordering::SeqCst);
|
||||||
}).expect("Error setting signal handler");
|
}).expect("Error setting signal handler");
|
||||||
|
|
||||||
let mut robot_brain = Brain::new();
|
// 1. Setup the Divine Nerves
|
||||||
|
let (to_brain_tx, to_brain_rx) = mpsc::channel::<LifecycleCommand>();
|
||||||
|
let (from_brain_tx, from_brain_rx) = mpsc::channel::<String>();
|
||||||
|
|
||||||
while robot_brain.state != State::Stopped {
|
// 2. Spawn the Brain thread
|
||||||
|
thread::spawn(move || {
|
||||||
|
let mut brain = Brain::new(to_brain_rx, from_brain_tx);
|
||||||
|
brain.run();
|
||||||
|
});
|
||||||
|
|
||||||
// If the signal handler tripped the flag, tell the brain to stop
|
// 3. Command: Genisys
|
||||||
if shutdown_requested.load(Ordering::SeqCst) && robot_brain.state == State::Running {
|
info!("God: Commanding Genisys...");
|
||||||
robot_brain.request_shutdown();
|
let _ = to_brain_tx.send(LifecycleCommand {
|
||||||
}
|
required_state: LifeState::Genisys,
|
||||||
|
command_time: Instant::now(),
|
||||||
|
});
|
||||||
|
|
||||||
// Run one "tick" of brain logic
|
// Wait for Brain's "OK"
|
||||||
robot_brain.update();
|
if let Ok(reply) = from_brain_rx.recv() {
|
||||||
|
info!("Brain: {}", reply);
|
||||||
info!("Main Loop: I'm alive! State: {:?}", robot_brain.state);
|
|
||||||
thread::sleep(Duration::from_millis(500));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("Main Loop: Brain has Stopped. Exiting cleanly.");
|
// 4. Wait two seconds
|
||||||
|
info!("God: Resting for 2 seconds...");
|
||||||
|
thread::sleep(Duration::from_secs(2));
|
||||||
|
|
||||||
|
// 5. Command: Buried
|
||||||
|
info!("God: Commanding Burial...");
|
||||||
|
let _ = to_brain_tx.send(LifecycleCommand {
|
||||||
|
required_state: LifeState::Buried,
|
||||||
|
command_time: Instant::now(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Wait for Brain's final "OK"
|
||||||
|
if let Ok(reply) = from_brain_rx.recv() {
|
||||||
|
info!("Brain: {}", reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("God: Brain has been buried. Shutting down.");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_logging() {
|
fn setup_logging() {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue