diff --git a/Cargo.lock b/Cargo.lock index e36d8f1..eb6b16d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -71,6 +71,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "errno" version = "0.3.8" @@ -98,6 +104,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + [[package]] name = "home" version = "0.5.9" @@ -107,6 +119,16 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "libc" version = "0.2.153" @@ -167,6 +189,24 @@ dependencies = [ "libc", ] +[[package]] +name = "proc-macro2" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + [[package]] name = "radix_trie" version = "0.2.1" @@ -186,6 +226,8 @@ dependencies = [ "nb 1.1.0", "rppal", "rustyline", + "serde", + "toml", ] [[package]] @@ -238,6 +280,35 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "serde" +version = "1.0.198" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.198" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_spanned" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" +dependencies = [ + "serde", +] + [[package]] name = "smallvec" version = "1.13.2" @@ -253,6 +324,57 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "syn" +version = "2.0.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "toml" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + [[package]] name = "unicode-segmentation" version = "1.11.0" @@ -349,3 +471,12 @@ name = "windows_x86_64_msvc" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" + +[[package]] +name = "winnow" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c976aaaa0e1f90dbb21e9587cdaf1d9679a1cde8875c0d6bd83ab96a208352" +dependencies = [ + "memchr", +] diff --git a/Cargo.toml b/Cargo.toml index d12101d..e31c65b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,4 +11,6 @@ rppal = { version = "0.17", features = ["hal"] } libc = "0.2" ads1x1x = "0.2" nb = "1.1.0" -rustyline = "14.0.0" \ No newline at end of file +rustyline = "14.0.0" +serde = { "version" = "1.0", features = ["derive"] } +toml = "0.8" diff --git a/src/actuators.rs b/src/actuators.rs index 7361183..abec3cf 100644 --- a/src/actuators.rs +++ b/src/actuators.rs @@ -4,14 +4,26 @@ use crate::{ io::{Relay, RelaySwitchState}, }; -pub fn switch_lights(relay: &mut Relay, state: RelaySwitchState) -> GenericResult<()> { - relay.switch(LIGHT_RELAY_PIN, state) +pub fn switch_lights( + relay: &mut Relay, + state: RelaySwitchState, + config: &Configuration, +) -> GenericResult<()> { + relay.switch(config.relay_settings.light_pin, state, config) } -pub fn switch_fan(relay: &mut Relay, state: RelaySwitchState) -> GenericResult<()> { - relay.switch(FAN_RELAY_PIN, state) +pub fn switch_fan( + relay: &mut Relay, + state: RelaySwitchState, + config: &Configuration, +) -> GenericResult<()> { + relay.switch(config.relay_settings.fan_pin, state, config) } -pub fn switch_water_pump(relay: &mut Relay, state: RelaySwitchState) -> GenericResult<()> { - relay.switch(WATER_PUMP_RELAY_PIN, state) +pub fn switch_water_pump( + relay: &mut Relay, + state: RelaySwitchState, + config: &Configuration, +) -> GenericResult<()> { + relay.switch(config.relay_settings.water_pump_pin, state, config) } diff --git a/src/cli_mode.rs b/src/cli_mode.rs index 35067c5..3bb9d73 100644 --- a/src/cli_mode.rs +++ b/src/cli_mode.rs @@ -3,6 +3,7 @@ use std::{thread, time::Duration}; use rustyline::{config::Configurer, error::ReadlineError, history::FileHistory}; use crate::{ + config::Configuration, error::GenericResult, io::{self, get_input_voltage}, sensors, @@ -12,14 +13,18 @@ struct LoopFlags { exit: bool, } -fn process_input(input: String, program_state: &mut ProgramState) -> GenericResult { +fn process_input( + input: String, + program_state: &mut ProgramState, + config: &Configuration, +) -> GenericResult { let args = input.split(' ').collect::>(); let main_command = *args.first().ok_or("No main command found.")?; match main_command { "ana" => command_ana(&args)?, - "rel" => command_rel(&args, program_state)?, - "soil" => command_soil(&args)?, - "temp" => command_temp(&args)?, + "rel" => command_rel(&args, program_state, config)?, + "soil" => command_soil(&args, config)?, + "temp" => command_temp(&args, config)?, "exit" => return Ok(LoopFlags { exit: true }), _ => return Err("Unknown main command".into()), }; @@ -27,13 +32,13 @@ fn process_input(input: String, program_state: &mut ProgramState) -> GenericResu Ok(LoopFlags { exit: false }) } -fn command_temp(args: &[&str]) -> GenericResult<()> { +fn command_temp(args: &[&str], config: &Configuration) -> GenericResult<()> { let show_loop = args .get(1) .map(|arg| matches!(*arg, "loop")) .unwrap_or(false); loop { - let temperature = sensors::get_temperature()?; + let temperature = sensors::get_temperature(config)?; println!("Temperature: {}C", temperature); if show_loop { break; @@ -43,14 +48,14 @@ fn command_temp(args: &[&str]) -> GenericResult<()> { Ok(()) } -fn command_soil(args: &[&str]) -> GenericResult<()> { +fn command_soil(args: &[&str], config: &Configuration) -> GenericResult<()> { let show_loop = args .get(1) .map(|arg| matches!(*arg, "loop")) .unwrap_or(false); loop { - let humidity = sensors::get_soil_moisture()?; + let humidity = sensors::get_soil_moisture(config)?; println!("Soil humidity: {}", humidity); if show_loop { break; @@ -61,7 +66,11 @@ fn command_soil(args: &[&str]) -> GenericResult<()> { Ok(()) } -fn command_rel(args: &[&str], program_state: &mut ProgramState) -> GenericResult<()> { +fn command_rel( + args: &[&str], + program_state: &mut ProgramState, + config: &Configuration, +) -> GenericResult<()> { let pin = args .get(1) .ok_or("Must specify pin number.")? @@ -81,11 +90,11 @@ fn command_rel(args: &[&str], program_state: &mut ProgramState) -> GenericResult match switch_state { Some(state) => { println!("Switching relay"); - program_state.relay.switch(pin, state?)? + program_state.relay.switch(pin, state?, config)? } None => { println!("Toggling relay"); - program_state.relay.toggle(pin)? + program_state.relay.toggle(pin, config)? } }; @@ -116,13 +125,17 @@ fn command_ana(args: &[&str]) -> GenericResult<()> { Ok(()) } -fn cli_loop(rl: &mut CLIEditor, program_state: &mut ProgramState) -> GenericResult { +fn cli_loop( + rl: &mut CLIEditor, + program_state: &mut ProgramState, + config: &Configuration, +) -> GenericResult { let readline = rl.readline("growpi>> "); match readline { Ok(line) => { rl.add_history_entry(line.as_str())?; - process_input(line, program_state) + process_input(line, program_state, config) } Err(ReadlineError::Eof) => Ok(LoopFlags { exit: true }), Err(_) => Err("No input".into()), @@ -139,18 +152,19 @@ struct ProgramState { relay: io::Relay, } -fn init_state() -> GenericResult { +fn init_state(config: &Configuration) -> GenericResult { Ok(ProgramState { - relay: io::Relay::new()?, + relay: io::Relay::new(config)?, }) } pub fn run_cli() { let mut rl = init_readline().unwrap(); - let mut program_state = init_state().unwrap(); + let config = Configuration::default(); + let mut program_state = init_state(&config).unwrap(); 'cli_loop: loop { - match cli_loop(&mut rl, &mut program_state) { + match cli_loop(&mut rl, &mut program_state, &config) { Ok(loop_flags) => { if loop_flags.exit { println!("Leaving CLI"); diff --git a/src/config.rs b/src/config.rs index e27a8af..9821b54 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,13 +1,85 @@ -pub const LOGIC_LEVEL: f32 = 3.3; -pub const THERMISTOR_ANALOG_PIN: u8 = 0; -pub const THERMISTOR_VOLTAGE_DIVIDER_RESISTANCE: f32 = 9_700.; - -pub const LIGHT_RELAY_PIN: u8 = 0; -pub const FAN_RELAY_PIN: u8 = 1; -pub const WATER_PUMP_RELAY_PIN: u8 = 2; -pub const RELAY_GPIO_PINS: [Option; 4] = [Some(17), Some(27), Some(22), None]; - -pub const SOIL_MOISTURE_PIN: u8 = 1; -pub const SOIL_MOISTURE_100_VOLTAGE: f32 = 1.417; -pub const SOIL_NOMINAL_MOISTURE: f32 = 0.41; -pub const SOIL_NOMINAL_MOISTURE_VOLTAGE: f32 = 2.823; +use serde::{Deserialize, Serialize}; + +use crate::error::GenericResult; + +#[derive(Serialize, Deserialize)] +pub struct RelaySettings { + pub light_pin: u8, + pub fan_pin: u8, + pub water_pump_pin: u8, + pub relay_gpio_pins: Vec>, +} + +#[derive(Serialize, Deserialize)] +pub struct ThermistorSettings { + pub pin: u8, + pub voltage_divider_resistance: f32, + pub nominal_resistance: f32, + pub nominal_temperature: f32, + pub thermal_constant: f32, +} + +#[derive(Serialize, Deserialize)] +pub struct SoilMoistureSettings { + pub pin: u8, + pub voltage_100: f32, + pub voltage_nominal: f32, + pub moisture_nominal: f32, +} + +#[derive(Serialize, Deserialize)] +pub struct BoardSettings { + pub logic_level: f32, +} + +#[derive(Serialize, Deserialize)] +pub struct Configuration { + pub board_settings: BoardSettings, + pub relay_settings: RelaySettings, + pub soil_moisture_settings: SoilMoistureSettings, + pub thermistor_settings: ThermistorSettings, +} + +impl Configuration { + fn from_file(path: &std::path::Path) -> GenericResult { + let text = std::fs::read_to_string(path)?; + let config: Configuration = toml::from_str(text.as_str())?; + Ok(config) + } + fn save_to_file(path: &std::path::Path, config: &Configuration) -> GenericResult<()> { + let text = toml::to_string_pretty(config)?; + std::fs::write(path, text)?; + Ok(()) + } +} + +const THERMISTOR_NOMINAL_RESISTANCE: f32 = 10_000.; +const THERMISTOR_NOMINAL_TEMPERATURE: f32 = 298.15; +const THERMISTOR_CONSTANT: f32 = 3950.; + +impl Default for Configuration { + fn default() -> Self { + Self { + board_settings: BoardSettings { logic_level: 3.3 }, + relay_settings: RelaySettings { + light_pin: 0, + fan_pin: 1, + water_pump_pin: 2, + relay_gpio_pins: [Some(17), Some(27), Some(22), None].to_vec(), + }, + soil_moisture_settings: SoilMoistureSettings { + pin: 1, + voltage_100: 1.417, + voltage_nominal: 2.823, + moisture_nominal: 0.41, + }, + thermistor_settings: ThermistorSettings { + pin: 0, + voltage_divider_resistance: 9_700., + nominal_resistance: 10_000., + nominal_temperature: 298.15, + thermal_constant: 3950., + }, + } + } +} diff --git a/src/io.rs b/src/io.rs index 3832139..73858af 100644 --- a/src/io.rs +++ b/src/io.rs @@ -25,21 +25,28 @@ pub fn get_input_voltage(pin: u8) -> GenericResult { } pub struct Relay { - relay_pins: [Option; RELAY_GPIO_PINS.len()], + relay_pins: Vec>, } pub enum RelaySwitchState { On, Off, } impl Relay { - pub fn new() -> GenericResult { - let mut output_pins = RELAY_GPIO_PINS.map(|pin| { - pin.and_then(|pin| { - let result = - (|| -> GenericResult { Ok(Gpio::new()?.get(pin)?.into_output()) })(); - result.ok() + pub fn new(config: &Configuration) -> GenericResult { + let mut output_pins = config + .relay_settings + .relay_gpio_pins + .clone() + .into_iter() + .map(|pin| { + pin.and_then(|pin| { + let result = (|| -> GenericResult { + Ok(Gpio::new()?.get(pin)?.into_output()) + })(); + result.ok() + }) }) - }); + .collect::>(); for pin in output_pins.iter_mut().flatten() { // The relay turns ON on LOW pin.set_high(); @@ -48,14 +55,19 @@ impl Relay { relay_pins: output_pins, }) } - pub fn toggle(&mut self, pin: u8) -> GenericResult<()> { - let pin = self.get_output_pin(pin)?; + pub fn toggle(&mut self, pin: u8, config: &Configuration) -> GenericResult<()> { + let pin = self.get_output_pin(pin, config)?; pin.toggle(); Ok(()) } - pub fn switch(&mut self, pin: u8, state: RelaySwitchState) -> GenericResult<()> { - let pin = self.get_output_pin(pin)?; + pub fn switch( + &mut self, + pin: u8, + state: RelaySwitchState, + config: &Configuration, + ) -> GenericResult<()> { + let pin = self.get_output_pin(pin, config)?; match state { RelaySwitchState::On => pin.set_low(), RelaySwitchState::Off => pin.set_high(), @@ -63,14 +75,14 @@ impl Relay { Ok(()) } - fn get_output_pin(&mut self, pin: u8) -> GenericResult<&mut OutputPin> { + fn get_output_pin(&mut self, pin: u8, config: &Configuration) -> GenericResult<&mut OutputPin> { Ok(self .relay_pins .get_mut(pin as usize) .ok_or(format!( "Pin {} not within pin array with length {}", pin, - RELAY_GPIO_PINS.len() + config.relay_settings.relay_gpio_pins.len() ))? .as_mut() .ok_or("Pin not configured.")?) diff --git a/src/sensors.rs b/src/sensors.rs index 229747e..f8df28d 100644 --- a/src/sensors.rs +++ b/src/sensors.rs @@ -1,27 +1,26 @@ use crate::{config::*, error::GenericResult, io::get_input_voltage}; -pub fn get_temperature() -> GenericResult { - const THERMISTOR_NOMINAL_RESISTANCE: f32 = 10_000.; - const THERMISTOR_NOMINAL_TEMPERATURE: f32 = 298.15; - const THERMISTOR_CONSTANT: f32 = 3950.; - - let voltage = get_input_voltage(THERMISTOR_ANALOG_PIN)?; - let resistance = (LOGIC_LEVEL / voltage - 1.) * THERMISTOR_VOLTAGE_DIVIDER_RESISTANCE; +pub fn get_temperature(config: &Configuration) -> GenericResult { + let voltage = get_input_voltage(config.thermistor_settings.pin)?; + let resistance = (config.board_settings.logic_level / voltage - 1.) + * config.thermistor_settings.voltage_divider_resistance; let temperature = 1. - / ((1. / THERMISTOR_NOMINAL_TEMPERATURE) - + (1. / THERMISTOR_CONSTANT * f32::ln(resistance / THERMISTOR_NOMINAL_RESISTANCE))) + / ((1. / config.thermistor_settings.nominal_temperature) + + (1. / config.thermistor_settings.thermal_constant + * f32::ln(resistance / config.thermistor_settings.nominal_resistance))) - 273.15; Ok(temperature) } -pub fn get_soil_moisture() -> GenericResult { - let voltage = get_input_voltage(SOIL_MOISTURE_PIN)?; +pub fn get_soil_moisture(config: &Configuration) -> GenericResult { + let voltage = get_input_voltage(config.soil_moisture_settings.pin)?; - const VOLTAGE_ZERO_HUMIDITY: f32 = (SOIL_NOMINAL_MOISTURE_VOLTAGE - - SOIL_MOISTURE_100_VOLTAGE * SOIL_NOMINAL_MOISTURE) - / (1. - SOIL_NOMINAL_MOISTURE); + let voltage_zero_humidity: f32 = (config.soil_moisture_settings.voltage_nominal + - config.soil_moisture_settings.voltage_100 + * config.soil_moisture_settings.moisture_nominal) + / (1. - config.soil_moisture_settings.moisture_nominal); - let humidity = - (voltage - VOLTAGE_ZERO_HUMIDITY) / (SOIL_MOISTURE_100_VOLTAGE - VOLTAGE_ZERO_HUMIDITY); + let humidity = (voltage - voltage_zero_humidity) + / (config.soil_moisture_settings.voltage_100 - voltage_zero_humidity); Ok(humidity) }