diff --git a/.gitignore b/.gitignore index 407522a..b5cfca4 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,7 @@ Cargo.lock # Sqlite databases /*.db *.db-shm -*.db-wal \ No newline at end of file +*.db-wal + +# Config file +/sprinklers_rs.json \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index b0b3dca..60bd55c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,5 +5,6 @@ members = [ "sprinklers_database", "sprinklers_actors", "sprinklers_mqtt", + "sprinklers_linux", "sprinklers_rs" ] \ No newline at end of file diff --git a/sprinklers_actors/src/section_runner.rs b/sprinklers_actors/src/section_runner.rs index 2b5a4fa..db219e7 100644 --- a/sprinklers_actors/src/section_runner.rs +++ b/sprinklers_actors/src/section_runner.rs @@ -110,7 +110,7 @@ impl Default for SecRunnerState { pub type SecRunnerStateRecv = watch::Receiver; struct SectionRunnerInner { - interface: Arc, + interface: Arc, event_send: Option, state_send: watch::Sender, delay_future: Option, @@ -289,10 +289,16 @@ impl Actor for SectionRunnerActor { fn started(&mut self, _ctx: &mut Self::Context) { trace!("section_runner starting"); + for i in 0..self.inner.interface.num_sections() { + self.inner.interface.set_section_state(i, false); + } } fn stopped(&mut self, _ctx: &mut Self::Context) { trace!("section_runner stopped"); + for i in 0..self.inner.interface.num_sections() { + self.inner.interface.set_section_state(i, false); + } } } @@ -452,7 +458,7 @@ impl Handler for SectionRunnerActor { impl SectionRunnerActor { fn new( - interface: Arc, + interface: Arc, state_send: watch::Sender, ) -> Self { Self { @@ -536,7 +542,7 @@ pub struct SectionRunner { #[allow(dead_code)] impl SectionRunner { - pub fn new(interface: Arc) -> Self { + pub fn new(interface: Arc) -> Self { let (state_send, state_recv) = watch::channel(SecRunnerState::default()); let addr = SectionRunnerActor::new(interface, state_send).start(); Self { diff --git a/sprinklers_core/src/section_interface.rs b/sprinklers_core/src/section_interface.rs index f9aaf91..59e84f1 100644 --- a/sprinklers_core/src/section_interface.rs +++ b/sprinklers_core/src/section_interface.rs @@ -4,7 +4,7 @@ use tracing::debug; pub type SecId = u32; -pub trait SectionInterface: Send { +pub trait SectionInterface: Send + Sync { fn num_sections(&self) -> SecId; fn set_section_state(&self, id: SecId, running: bool); fn get_section_state(&self, id: SecId) -> bool; diff --git a/sprinklers_rs/Cargo.toml b/sprinklers_rs/Cargo.toml index e89022d..6b7aed3 100644 --- a/sprinklers_rs/Cargo.toml +++ b/sprinklers_rs/Cargo.toml @@ -7,6 +7,7 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] +default = ["sprinklers_linux"] bundled_sqlite = ["sprinklers_database/bundled"] [dependencies] @@ -14,14 +15,17 @@ sprinklers_core = { path = "../sprinklers_core" } sprinklers_database = { path = "../sprinklers_database" } sprinklers_actors = { path = "../sprinklers_actors" } sprinklers_mqtt = { path = "../sprinklers_mqtt" } +sprinklers_linux = { path = "../sprinklers_linux", optional = true } color-eyre = "0.5.1" eyre = "0.6.0" tokio = "0.2.22" -tracing = { version = "0.1.19", features = ["log"] } +tracing = { version = "0.1.19" } actix = { version = "0.10.0", default-features = false } actix-rt = "1.1.1" chrono = "0.4.19" +serde = { version = "1.0.116", features = ["derive"] } +config = { version = "0.10.1", default-features = false, features = ["json"] } [dependencies.tracing-subscriber] version = "0.2.11" diff --git a/sprinklers_rs/sprinklers_rs.default.json b/sprinklers_rs/sprinklers_rs.default.json new file mode 100644 index 0000000..046fc9a --- /dev/null +++ b/sprinklers_rs/sprinklers_rs.default.json @@ -0,0 +1,12 @@ +{ + "mqtt": { + "broker_host": "localhost", + "broker_port": 1883, + "client_id": "sprinklers_rs-0001", + "device_id": "sprinklers_rs-0001" + }, + "section_interface": { + "provider": "Mock", + "num_sections": 6 + } +} \ No newline at end of file diff --git a/sprinklers_rs/src/main.rs b/sprinklers_rs/src/main.rs index 986ce22..bc58d66 100644 --- a/sprinklers_rs/src/main.rs +++ b/sprinklers_rs/src/main.rs @@ -2,28 +2,32 @@ #![warn(clippy::print_stdout)] // mod option_future; +mod section_interface; +mod settings; mod state_manager; use sprinklers_actors as actors; -use sprinklers_core::section_interface::MockSectionInterface; use sprinklers_database as database; use sprinklers_mqtt as mqtt; -use eyre::Result; -use std::sync::Arc; +use eyre::{Result, WrapErr}; +use settings::Settings; use tracing::{debug, info}; use tracing_subscriber::EnvFilter; #[actix_rt::main] async fn main() -> Result<()> { + color_eyre::install()?; tracing_subscriber::fmt() .with_ansi(true) .with_env_filter( EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")), ) .init(); + + let settings: Settings = Settings::new().wrap_err("could not load settings")?; + info!("Starting sprinklers_rs..."); - color_eyre::install()?; let db_conn = database::setup_db()?; @@ -32,19 +36,13 @@ async fn main() -> Result<()> { debug!(section = debug(&sec), "read section"); } - // TODO: Section interface which actual does something. Preferrably selectable somehow - let section_interface: Arc<_> = MockSectionInterface::new(6).into(); + let section_interface = settings.section_interface.build()?; let mut section_runner = actors::SectionRunner::new(section_interface); let mut program_runner = actors::ProgramRunner::new(section_runner.clone()); let state_manager = crate::state_manager::StateManagerThread::start(db_conn); - let mqtt_options = mqtt::Options { - broker_host: "localhost".into(), - broker_port: 1883, - device_id: "sprinklers_rs-0001".into(), - client_id: "sprinklers_rs-0001".into(), - }; + let mqtt_options = settings.mqtt; // TODO: have ability to update sections / other data let request_context = mqtt::RequestContext { sections: sections.clone(), diff --git a/sprinklers_rs/src/section_interface.rs b/sprinklers_rs/src/section_interface.rs new file mode 100644 index 0000000..b965b6f --- /dev/null +++ b/sprinklers_rs/src/section_interface.rs @@ -0,0 +1,37 @@ +use sprinklers_core::section_interface::{MockSectionInterface, SecId, SectionInterface}; + +#[cfg(feature = "sprinklers_linux")] +use sprinklers_linux::LinuxGpioConfig; + +use serde::{Deserialize, Serialize}; +use std::sync::Arc; + +#[derive(Debug, Serialize, Deserialize)] +#[serde(tag = "provider")] +pub enum SectionInterfaceConfig { + Mock { + num_sections: SecId, + }, + #[cfg(feature = "sprinklers_linux")] + LinuxGpio(LinuxGpioConfig), +} + +impl Default for SectionInterfaceConfig { + fn default() -> Self { + SectionInterfaceConfig::Mock { num_sections: 6 } + } +} + +impl SectionInterfaceConfig { + pub fn build(self) -> eyre::Result> { + Ok(match self { + SectionInterfaceConfig::Mock { num_sections } => { + Arc::new(MockSectionInterface::new(num_sections)) + } + #[cfg(feature = "sprinklers_linux")] + SectionInterfaceConfig::LinuxGpio(config) => { + Arc::new(config.build()?) + } + }) + } +} diff --git a/sprinklers_rs/src/settings.rs b/sprinklers_rs/src/settings.rs new file mode 100644 index 0000000..2b3c00c --- /dev/null +++ b/sprinklers_rs/src/settings.rs @@ -0,0 +1,44 @@ +use crate::section_interface::SectionInterfaceConfig; + +use serde::{Deserialize, Serialize}; +use tracing::trace; + +#[derive(Debug, Serialize, Deserialize)] +#[serde(remote = "sprinklers_mqtt::Options")] +struct MqttOptions { + pub broker_host: String, + pub broker_port: u16, + pub device_id: String, + pub client_id: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Settings { + #[serde(with = "MqttOptions")] + pub mqtt: sprinklers_mqtt::Options, + #[serde(default)] + pub section_interface: SectionInterfaceConfig, +} + +impl Settings { + pub fn new() -> eyre::Result { + let mut s = config::Config::new(); + + let default_config = config::File::from_str( + include_str!("../sprinklers_rs.default.json"), + config::FileFormat::Json, + ); + s.merge(default_config)?; + + // TODO: specify configuration path from arguments or env + s.merge(config::File::with_name("sprinklers_rs").required(false))?; + + s.merge(config::Environment::with_prefix("SPRINKLERS").separator("__"))?; + + let settings: Settings = s.try_into()?; + + trace!("settings: {:#?}", settings); + + Ok(settings) + } +}