Browse Source

Add better configuration support

And allow switching between different section interface implementations
master
Alex Mikhalev 4 years ago
parent
commit
8408f21a72
  1. 3
      .gitignore
  2. 1
      Cargo.toml
  3. 12
      sprinklers_actors/src/section_runner.rs
  4. 2
      sprinklers_core/src/section_interface.rs
  5. 6
      sprinklers_rs/Cargo.toml
  6. 12
      sprinklers_rs/sprinklers_rs.default.json
  7. 22
      sprinklers_rs/src/main.rs
  8. 37
      sprinklers_rs/src/section_interface.rs
  9. 44
      sprinklers_rs/src/settings.rs

3
.gitignore vendored

@ -9,3 +9,6 @@ Cargo.lock
/*.db /*.db
*.db-shm *.db-shm
*.db-wal *.db-wal
# Config file
/sprinklers_rs.json

1
Cargo.toml

@ -5,5 +5,6 @@ members = [
"sprinklers_database", "sprinklers_database",
"sprinklers_actors", "sprinklers_actors",
"sprinklers_mqtt", "sprinklers_mqtt",
"sprinklers_linux",
"sprinklers_rs" "sprinklers_rs"
] ]

12
sprinklers_actors/src/section_runner.rs

@ -110,7 +110,7 @@ impl Default for SecRunnerState {
pub type SecRunnerStateRecv = watch::Receiver<SecRunnerState>; pub type SecRunnerStateRecv = watch::Receiver<SecRunnerState>;
struct SectionRunnerInner { struct SectionRunnerInner {
interface: Arc<dyn SectionInterface + Sync>, interface: Arc<dyn SectionInterface>,
event_send: Option<SectionEventSend>, event_send: Option<SectionEventSend>,
state_send: watch::Sender<SecRunnerState>, state_send: watch::Sender<SecRunnerState>,
delay_future: Option<SpawnHandle>, delay_future: Option<SpawnHandle>,
@ -289,10 +289,16 @@ impl Actor for SectionRunnerActor {
fn started(&mut self, _ctx: &mut Self::Context) { fn started(&mut self, _ctx: &mut Self::Context) {
trace!("section_runner starting"); 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) { fn stopped(&mut self, _ctx: &mut Self::Context) {
trace!("section_runner stopped"); 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<Process> for SectionRunnerActor {
impl SectionRunnerActor { impl SectionRunnerActor {
fn new( fn new(
interface: Arc<dyn SectionInterface + Sync>, interface: Arc<dyn SectionInterface>,
state_send: watch::Sender<SecRunnerState>, state_send: watch::Sender<SecRunnerState>,
) -> Self { ) -> Self {
Self { Self {
@ -536,7 +542,7 @@ pub struct SectionRunner {
#[allow(dead_code)] #[allow(dead_code)]
impl SectionRunner { impl SectionRunner {
pub fn new(interface: Arc<dyn SectionInterface + Sync>) -> Self { pub fn new(interface: Arc<dyn SectionInterface>) -> Self {
let (state_send, state_recv) = watch::channel(SecRunnerState::default()); let (state_send, state_recv) = watch::channel(SecRunnerState::default());
let addr = SectionRunnerActor::new(interface, state_send).start(); let addr = SectionRunnerActor::new(interface, state_send).start();
Self { Self {

2
sprinklers_core/src/section_interface.rs

@ -4,7 +4,7 @@ use tracing::debug;
pub type SecId = u32; pub type SecId = u32;
pub trait SectionInterface: Send { pub trait SectionInterface: Send + Sync {
fn num_sections(&self) -> SecId; fn num_sections(&self) -> SecId;
fn set_section_state(&self, id: SecId, running: bool); fn set_section_state(&self, id: SecId, running: bool);
fn get_section_state(&self, id: SecId) -> bool; fn get_section_state(&self, id: SecId) -> bool;

6
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 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features] [features]
default = ["sprinklers_linux"]
bundled_sqlite = ["sprinklers_database/bundled"] bundled_sqlite = ["sprinklers_database/bundled"]
[dependencies] [dependencies]
@ -14,14 +15,17 @@ sprinklers_core = { path = "../sprinklers_core" }
sprinklers_database = { path = "../sprinklers_database" } sprinklers_database = { path = "../sprinklers_database" }
sprinklers_actors = { path = "../sprinklers_actors" } sprinklers_actors = { path = "../sprinklers_actors" }
sprinklers_mqtt = { path = "../sprinklers_mqtt" } sprinklers_mqtt = { path = "../sprinklers_mqtt" }
sprinklers_linux = { path = "../sprinklers_linux", optional = true }
color-eyre = "0.5.1" color-eyre = "0.5.1"
eyre = "0.6.0" eyre = "0.6.0"
tokio = "0.2.22" 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 = { version = "0.10.0", default-features = false }
actix-rt = "1.1.1" actix-rt = "1.1.1"
chrono = "0.4.19" chrono = "0.4.19"
serde = { version = "1.0.116", features = ["derive"] }
config = { version = "0.10.1", default-features = false, features = ["json"] }
[dependencies.tracing-subscriber] [dependencies.tracing-subscriber]
version = "0.2.11" version = "0.2.11"

12
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
}
}

22
sprinklers_rs/src/main.rs

@ -2,28 +2,32 @@
#![warn(clippy::print_stdout)] #![warn(clippy::print_stdout)]
// mod option_future; // mod option_future;
mod section_interface;
mod settings;
mod state_manager; mod state_manager;
use sprinklers_actors as actors; use sprinklers_actors as actors;
use sprinklers_core::section_interface::MockSectionInterface;
use sprinklers_database as database; use sprinklers_database as database;
use sprinklers_mqtt as mqtt; use sprinklers_mqtt as mqtt;
use eyre::Result; use eyre::{Result, WrapErr};
use std::sync::Arc; use settings::Settings;
use tracing::{debug, info}; use tracing::{debug, info};
use tracing_subscriber::EnvFilter; use tracing_subscriber::EnvFilter;
#[actix_rt::main] #[actix_rt::main]
async fn main() -> Result<()> { async fn main() -> Result<()> {
color_eyre::install()?;
tracing_subscriber::fmt() tracing_subscriber::fmt()
.with_ansi(true) .with_ansi(true)
.with_env_filter( .with_env_filter(
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")), EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")),
) )
.init(); .init();
let settings: Settings = Settings::new().wrap_err("could not load settings")?;
info!("Starting sprinklers_rs..."); info!("Starting sprinklers_rs...");
color_eyre::install()?;
let db_conn = database::setup_db()?; let db_conn = database::setup_db()?;
@ -32,19 +36,13 @@ async fn main() -> Result<()> {
debug!(section = debug(&sec), "read section"); debug!(section = debug(&sec), "read section");
} }
// TODO: Section interface which actual does something. Preferrably selectable somehow let section_interface = settings.section_interface.build()?;
let section_interface: Arc<_> = MockSectionInterface::new(6).into();
let mut section_runner = actors::SectionRunner::new(section_interface); let mut section_runner = actors::SectionRunner::new(section_interface);
let mut program_runner = actors::ProgramRunner::new(section_runner.clone()); let mut program_runner = actors::ProgramRunner::new(section_runner.clone());
let state_manager = crate::state_manager::StateManagerThread::start(db_conn); let state_manager = crate::state_manager::StateManagerThread::start(db_conn);
let mqtt_options = mqtt::Options { let mqtt_options = settings.mqtt;
broker_host: "localhost".into(),
broker_port: 1883,
device_id: "sprinklers_rs-0001".into(),
client_id: "sprinklers_rs-0001".into(),
};
// TODO: have ability to update sections / other data // TODO: have ability to update sections / other data
let request_context = mqtt::RequestContext { let request_context = mqtt::RequestContext {
sections: sections.clone(), sections: sections.clone(),

37
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<Arc<dyn SectionInterface>> {
Ok(match self {
SectionInterfaceConfig::Mock { num_sections } => {
Arc::new(MockSectionInterface::new(num_sections))
}
#[cfg(feature = "sprinklers_linux")]
SectionInterfaceConfig::LinuxGpio(config) => {
Arc::new(config.build()?)
}
})
}
}

44
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<Self> {
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)
}
}
Loading…
Cancel
Save