Nareshkumar Rao 7 months ago
parent
commit
35a5da2c2a
  1. 131
      Cargo.lock
  2. 4
      Cargo.toml
  3. 24
      src/actuators.rs
  4. 48
      src/cli_mode.rs
  5. 98
      src/config.rs
  6. 40
      src/io.rs
  7. 31
      src/sensors.rs

131
Cargo.lock

@ -71,6 +71,12 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
[[package]]
name = "equivalent"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]] [[package]]
name = "errno" name = "errno"
version = "0.3.8" version = "0.3.8"
@ -98,6 +104,12 @@ dependencies = [
"windows-sys", "windows-sys",
] ]
[[package]]
name = "hashbrown"
version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
[[package]] [[package]]
name = "home" name = "home"
version = "0.5.9" version = "0.5.9"
@ -107,6 +119,16 @@ dependencies = [
"windows-sys", "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]] [[package]]
name = "libc" name = "libc"
version = "0.2.153" version = "0.2.153"
@ -167,6 +189,24 @@ dependencies = [
"libc", "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]] [[package]]
name = "radix_trie" name = "radix_trie"
version = "0.2.1" version = "0.2.1"
@ -186,6 +226,8 @@ dependencies = [
"nb 1.1.0", "nb 1.1.0",
"rppal", "rppal",
"rustyline", "rustyline",
"serde",
"toml",
] ]
[[package]] [[package]]
@ -238,6 +280,35 @@ dependencies = [
"windows-sys", "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]] [[package]]
name = "smallvec" name = "smallvec"
version = "1.13.2" version = "1.13.2"
@ -253,6 +324,57 @@ dependencies = [
"windows-sys", "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]] [[package]]
name = "unicode-segmentation" name = "unicode-segmentation"
version = "1.11.0" version = "1.11.0"
@ -349,3 +471,12 @@ name = "windows_x86_64_msvc"
version = "0.52.5" version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
[[package]]
name = "winnow"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0c976aaaa0e1f90dbb21e9587cdaf1d9679a1cde8875c0d6bd83ab96a208352"
dependencies = [
"memchr",
]

4
Cargo.toml

@ -11,4 +11,6 @@ rppal = { version = "0.17", features = ["hal"] }
libc = "0.2" libc = "0.2"
ads1x1x = "0.2" ads1x1x = "0.2"
nb = "1.1.0" nb = "1.1.0"
rustyline = "14.0.0"
rustyline = "14.0.0"
serde = { "version" = "1.0", features = ["derive"] }
toml = "0.8"

24
src/actuators.rs

@ -4,14 +4,26 @@ use crate::{
io::{Relay, RelaySwitchState}, 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)
} }

48
src/cli_mode.rs

@ -3,6 +3,7 @@ use std::{thread, time::Duration};
use rustyline::{config::Configurer, error::ReadlineError, history::FileHistory}; use rustyline::{config::Configurer, error::ReadlineError, history::FileHistory};
use crate::{ use crate::{
config::Configuration,
error::GenericResult, error::GenericResult,
io::{self, get_input_voltage}, io::{self, get_input_voltage},
sensors, sensors,
@ -12,14 +13,18 @@ struct LoopFlags {
exit: bool, exit: bool,
} }
fn process_input(input: String, program_state: &mut ProgramState) -> GenericResult<LoopFlags> {
fn process_input(
input: String,
program_state: &mut ProgramState,
config: &Configuration,
) -> GenericResult<LoopFlags> {
let args = input.split(' ').collect::<Vec<_>>(); let args = input.split(' ').collect::<Vec<_>>();
let main_command = *args.first().ok_or("No main command found.")?; let main_command = *args.first().ok_or("No main command found.")?;
match main_command { match main_command {
"ana" => command_ana(&args)?, "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 }), "exit" => return Ok(LoopFlags { exit: true }),
_ => return Err("Unknown main command".into()), _ => return Err("Unknown main command".into()),
}; };
@ -27,13 +32,13 @@ fn process_input(input: String, program_state: &mut ProgramState) -> GenericResu
Ok(LoopFlags { exit: false }) Ok(LoopFlags { exit: false })
} }
fn command_temp(args: &[&str]) -> GenericResult<()> {
fn command_temp(args: &[&str], config: &Configuration) -> GenericResult<()> {
let show_loop = args let show_loop = args
.get(1) .get(1)
.map(|arg| matches!(*arg, "loop")) .map(|arg| matches!(*arg, "loop"))
.unwrap_or(false); .unwrap_or(false);
loop { loop {
let temperature = sensors::get_temperature()?;
let temperature = sensors::get_temperature(config)?;
println!("Temperature: {}C", temperature); println!("Temperature: {}C", temperature);
if show_loop { if show_loop {
break; break;
@ -43,14 +48,14 @@ fn command_temp(args: &[&str]) -> GenericResult<()> {
Ok(()) Ok(())
} }
fn command_soil(args: &[&str]) -> GenericResult<()> {
fn command_soil(args: &[&str], config: &Configuration) -> GenericResult<()> {
let show_loop = args let show_loop = args
.get(1) .get(1)
.map(|arg| matches!(*arg, "loop")) .map(|arg| matches!(*arg, "loop"))
.unwrap_or(false); .unwrap_or(false);
loop { loop {
let humidity = sensors::get_soil_moisture()?;
let humidity = sensors::get_soil_moisture(config)?;
println!("Soil humidity: {}", humidity); println!("Soil humidity: {}", humidity);
if show_loop { if show_loop {
break; break;
@ -61,7 +66,11 @@ fn command_soil(args: &[&str]) -> GenericResult<()> {
Ok(()) 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 let pin = args
.get(1) .get(1)
.ok_or("Must specify pin number.")? .ok_or("Must specify pin number.")?
@ -81,11 +90,11 @@ fn command_rel(args: &[&str], program_state: &mut ProgramState) -> GenericResult
match switch_state { match switch_state {
Some(state) => { Some(state) => {
println!("Switching relay"); println!("Switching relay");
program_state.relay.switch(pin, state?)?
program_state.relay.switch(pin, state?, config)?
} }
None => { None => {
println!("Toggling relay"); 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(()) Ok(())
} }
fn cli_loop(rl: &mut CLIEditor, program_state: &mut ProgramState) -> GenericResult<LoopFlags> {
fn cli_loop(
rl: &mut CLIEditor,
program_state: &mut ProgramState,
config: &Configuration,
) -> GenericResult<LoopFlags> {
let readline = rl.readline("growpi>> "); let readline = rl.readline("growpi>> ");
match readline { match readline {
Ok(line) => { Ok(line) => {
rl.add_history_entry(line.as_str())?; 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(ReadlineError::Eof) => Ok(LoopFlags { exit: true }),
Err(_) => Err("No input".into()), Err(_) => Err("No input".into()),
@ -139,18 +152,19 @@ struct ProgramState {
relay: io::Relay, relay: io::Relay,
} }
fn init_state() -> GenericResult<ProgramState> {
fn init_state(config: &Configuration) -> GenericResult<ProgramState> {
Ok(ProgramState { Ok(ProgramState {
relay: io::Relay::new()?,
relay: io::Relay::new(config)?,
}) })
} }
pub fn run_cli() { pub fn run_cli() {
let mut rl = init_readline().unwrap(); 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 { 'cli_loop: loop {
match cli_loop(&mut rl, &mut program_state) {
match cli_loop(&mut rl, &mut program_state, &config) {
Ok(loop_flags) => { Ok(loop_flags) => {
if loop_flags.exit { if loop_flags.exit {
println!("Leaving CLI"); println!("Leaving CLI");

98
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<u8>; 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<Option<u8>>,
}
#[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<Configuration> {
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.,
},
}
}
}

40
src/io.rs

@ -25,21 +25,28 @@ pub fn get_input_voltage(pin: u8) -> GenericResult<f32> {
} }
pub struct Relay { pub struct Relay {
relay_pins: [Option<rppal::gpio::OutputPin>; RELAY_GPIO_PINS.len()],
relay_pins: Vec<Option<rppal::gpio::OutputPin>>,
} }
pub enum RelaySwitchState { pub enum RelaySwitchState {
On, On,
Off, Off,
} }
impl Relay { impl Relay {
pub fn new() -> GenericResult<Relay> {
let mut output_pins = RELAY_GPIO_PINS.map(|pin| {
pin.and_then(|pin| {
let result =
(|| -> GenericResult<OutputPin> { Ok(Gpio::new()?.get(pin)?.into_output()) })();
result.ok()
pub fn new(config: &Configuration) -> GenericResult<Relay> {
let mut output_pins = config
.relay_settings
.relay_gpio_pins
.clone()
.into_iter()
.map(|pin| {
pin.and_then(|pin| {
let result = (|| -> GenericResult<OutputPin> {
Ok(Gpio::new()?.get(pin)?.into_output())
})();
result.ok()
})
}) })
});
.collect::<Vec<_>>();
for pin in output_pins.iter_mut().flatten() { for pin in output_pins.iter_mut().flatten() {
// The relay turns ON on LOW // The relay turns ON on LOW
pin.set_high(); pin.set_high();
@ -48,14 +55,19 @@ impl Relay {
relay_pins: output_pins, 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(); pin.toggle();
Ok(()) 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 { match state {
RelaySwitchState::On => pin.set_low(), RelaySwitchState::On => pin.set_low(),
RelaySwitchState::Off => pin.set_high(), RelaySwitchState::Off => pin.set_high(),
@ -63,14 +75,14 @@ impl Relay {
Ok(()) 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 Ok(self
.relay_pins .relay_pins
.get_mut(pin as usize) .get_mut(pin as usize)
.ok_or(format!( .ok_or(format!(
"Pin {} not within pin array with length {}", "Pin {} not within pin array with length {}",
pin, pin,
RELAY_GPIO_PINS.len()
config.relay_settings.relay_gpio_pins.len()
))? ))?
.as_mut() .as_mut()
.ok_or("Pin not configured.")?) .ok_or("Pin not configured.")?)

31
src/sensors.rs

@ -1,27 +1,26 @@
use crate::{config::*, error::GenericResult, io::get_input_voltage}; use crate::{config::*, error::GenericResult, io::get_input_voltage};
pub fn get_temperature() -> GenericResult<f32> {
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<f32> {
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. 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; - 273.15;
Ok(temperature) Ok(temperature)
} }
pub fn get_soil_moisture() -> GenericResult<f32> {
let voltage = get_input_voltage(SOIL_MOISTURE_PIN)?;
pub fn get_soil_moisture(config: &Configuration) -> GenericResult<f32> {
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) Ok(humidity)
} }

Loading…
Cancel
Save