Nareshkumar Rao 7 months ago
parent
commit
a3fa6d0599
  1. 2
      growpi.history.csv
  2. 12
      growpi.service
  3. 9
      src/actuators.rs
  4. 6
      src/config.rs
  5. 30
      src/control/soil.rs
  6. 49
      src/data_logging.rs
  7. 5
      src/history.rs
  8. 3
      src/main.rs

2
growpi.history.csv

@ -1,2 +0,0 @@
time,amount
1714687536,456
1 time amount
2 1714687536 456

12
growpi.service

@ -0,0 +1,12 @@
[Unit]
Description=GrowPi Server and Control
After=network.target
[Service]
Type=simple
Restart=always
RestartSec=10
ExecStart=/opt/growpi/growpi
[Install]
WantedBy=mutli-user.target

9
src/actuators.rs

@ -1,7 +1,8 @@
use std::{thread, time::Duration};
use crate::{
error::GenericResult, history::WateringRecord, io::RelaySwitchState, state::ProgramState,
error::GenericResult, history::WateringRecord, io::RelaySwitchState, sensors,
state::ProgramState,
};
pub fn switch_lights(
@ -49,6 +50,7 @@ pub fn pump_water(water_mass_g: u16, program_state: &mut ProgramState) -> Generi
.grams_per_millisecond;
let duration_ms = duration_ms.round() as u64;
let duration = Duration::from_millis(duration_ms);
let moisture_before_watering = sensors::get_soil_moisture(&program_state.config)?;
switch_water_pump(RelaySwitchState::On, program_state)?;
thread::sleep(duration);
switch_water_pump(RelaySwitchState::Off, program_state)?;
@ -56,7 +58,10 @@ pub fn pump_water(water_mass_g: u16, program_state: &mut ProgramState) -> Generi
program_state
.history
.watering_records
.push(WateringRecord::new(water_mass_g.into()));
.push(WateringRecord::new(
water_mass_g.into(),
moisture_before_watering,
));
program_state.history.save()?;
Ok(())

6
src/config.rs

@ -51,6 +51,9 @@ pub struct ControllerSettings {
pub temperature_loop_mins: u64,
pub sunlight_hours: u64,
pub soil_loop_hours: u64,
pub max_water_over_window_grams: u64,
pub max_water_window_hours: u64,
pub ideal_soil_moisture_pct: u64,
}
#[derive(Serialize, Deserialize)]
@ -109,6 +112,9 @@ impl Default for Configuration {
temperature_loop_mins: 60,
soil_loop_hours: 12,
sunlight_hours: 24,
max_water_over_window_grams: 1000,
max_water_window_hours: 24,
ideal_soil_moisture_pct: 70,
},
}
}

30
src/control/soil.rs

@ -1,6 +1,11 @@
use std::time::Duration;
use crate::state::ProgramStateShared;
use chrono::{DateTime, Utc};
use crate::{
error::{lock_err, GenericResult},
state::ProgramStateShared,
};
pub async fn soil_moisture_control_loop(program_state: ProgramStateShared) {
let loop_duration = program_state
@ -9,6 +14,29 @@ pub async fn soil_moisture_control_loop(program_state: ProgramStateShared) {
.unwrap_or(1);
loop {
let _ = soil_moisture_control(program_state.clone());
tokio::time::sleep(Duration::from_hours(loop_duration)).await;
}
}
fn soil_moisture_control(program_state: ProgramStateShared) -> GenericResult<()> {
let program_state = program_state.lock().map_err(lock_err)?;
let config = &program_state.config.controller_settings;
let history = &program_state.history.watering_records;
let water_amount_over_window: u64 = history
.iter()
.filter(|record| {
if let Some(time) = DateTime::from_timestamp(record.time, 0) {
let delta = Utc::now() - time;
let delta = delta.num_hours();
delta <= config.max_water_window_hours.try_into().unwrap_or(24)
} else {
false
}
})
.map(|record| record.amount)
.sum();
Ok(())
}

49
src/data_logging.rs

@ -0,0 +1,49 @@
use chrono::Utc;
use serde::{Deserialize, Serialize};
use crate::{
error::{lock_err, GenericResult},
sensors,
state::ProgramStateShared,
};
#[derive(Serialize, Deserialize)]
pub struct DataRecord {
pub timestamp: i64,
pub temperature: f32,
pub soil_mositure: f32,
}
#[derive(Serialize, Deserialize)]
pub struct DataRecords {
pub records: Vec<DataRecord>,
}
const FILE_PATH: &str = "./growpi.datalog.csv";
impl DataRecords {
fn load() -> GenericResult<DataRecords> {
let mut data = csv::ReaderBuilder::new()
.has_headers(true)
.from_path(FILE_PATH)?;
let mut result = Vec::new();
for record in data.deserialize() {
result.push(record?);
}
Ok(DataRecords { records: result })
}
pub fn push(program_state: ProgramStateShared) -> GenericResult<()> {
let program_state = program_state.lock().map_err(lock_err)?;
let config = &program_state.config;
let record = DataRecord {
timestamp: Utc::now().timestamp(),
temperature: sensors::get_temperature(config)?,
soil_mositure: sensors::get_soil_moisture(config)?,
};
let mut writer = csv::WriterBuilder::new()
.has_headers(true)
.from_path(FILE_PATH)?;
writer.serialize(record)?;
writer.flush()?;
Ok(())
}
}

5
src/history.rs

@ -7,13 +7,15 @@ use crate::error::GenericResult;
pub struct WateringRecord {
pub time: i64,
pub amount: u64,
pub moisture_before_watering: f32,
}
impl WateringRecord {
pub fn new(amount: u64) -> WateringRecord {
pub fn new(amount: u64, moisture_before_watering: f32) -> WateringRecord {
WateringRecord {
time: Utc::now().timestamp(),
amount,
moisture_before_watering,
}
}
}
@ -64,6 +66,7 @@ mod tests {
history.watering_records.push(WateringRecord {
time: Local::now().timestamp(),
amount: 456,
moisture_before_watering: 71.1,
});
history.save().unwrap();
}

3
src/main.rs

@ -10,12 +10,13 @@ mod actuators;
mod cli_mode;
mod config;
mod control;
mod data_logging;
mod error;
mod history;
mod io;
mod sensors;
mod server;
mod state;
mod history;
fn load_config() -> config::Configuration {
let config = Configuration::from_file(std::path::Path::new("./growpi.toml"));

Loading…
Cancel
Save