Add better configuration support
	
		
			
	
		
	
	
		
	
		
			Some checks failed
		
		
	
	
		
			
				
	
				continuous-integration/drone/push Build is failing
				
			
		
		
	
	
				
					
				
			
		
			Some checks failed
		
		
	
	continuous-integration/drone/push Build is failing
				
			And allow switching between different section interface implementations
This commit is contained in:
		
							parent
							
								
									11eba1a9e5
								
							
						
					
					
						commit
						8408f21a72
					
				
							
								
								
									
										5
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -8,4 +8,7 @@ Cargo.lock | ||||
| # Sqlite databases | ||||
| /*.db | ||||
| *.db-shm | ||||
| *.db-wal | ||||
| *.db-wal | ||||
| 
 | ||||
| # Config file | ||||
| /sprinklers_rs.json | ||||
| @ -5,5 +5,6 @@ members = [ | ||||
|     "sprinklers_database", | ||||
|     "sprinklers_actors", | ||||
|     "sprinklers_mqtt", | ||||
|     "sprinklers_linux", | ||||
|     "sprinklers_rs" | ||||
| ] | ||||
| @ -110,7 +110,7 @@ impl Default for SecRunnerState { | ||||
| pub type SecRunnerStateRecv = watch::Receiver<SecRunnerState>; | ||||
| 
 | ||||
| struct SectionRunnerInner { | ||||
|     interface: Arc<dyn SectionInterface + Sync>, | ||||
|     interface: Arc<dyn SectionInterface>, | ||||
|     event_send: Option<SectionEventSend>, | ||||
|     state_send: watch::Sender<SecRunnerState>, | ||||
|     delay_future: Option<SpawnHandle>, | ||||
| @ -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<Process> for SectionRunnerActor { | ||||
| 
 | ||||
| impl SectionRunnerActor { | ||||
|     fn new( | ||||
|         interface: Arc<dyn SectionInterface + Sync>, | ||||
|         interface: Arc<dyn SectionInterface>, | ||||
|         state_send: watch::Sender<SecRunnerState>, | ||||
|     ) -> Self { | ||||
|         Self { | ||||
| @ -536,7 +542,7 @@ pub struct SectionRunner { | ||||
| 
 | ||||
| #[allow(dead_code)] | ||||
| 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 addr = SectionRunnerActor::new(interface, state_send).start(); | ||||
|         Self { | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
| @ -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" | ||||
|  | ||||
							
								
								
									
										12
									
								
								sprinklers_rs/sprinklers_rs.default.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								sprinklers_rs/sprinklers_rs.default.json
									
									
									
									
									
										Normal file
									
								
							| @ -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 | ||||
|    } | ||||
| } | ||||
| @ -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(), | ||||
|  | ||||
							
								
								
									
										37
									
								
								sprinklers_rs/src/section_interface.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								sprinklers_rs/src/section_interface.rs
									
									
									
									
									
										Normal file
									
								
							| @ -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
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								sprinklers_rs/src/settings.rs
									
									
									
									
									
										Normal file
									
								
							| @ -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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user