Compare commits
5 Commits
c5cacb429d
...
a3fa6d0599
Author | SHA1 | Date |
---|---|---|
Nareshkumar Rao | a3fa6d0599 | 7 months ago |
Nareshkumar Rao | b0bdb6a975 | 7 months ago |
Nareshkumar Rao | a6b050cc64 | 7 months ago |
Nareshkumar Rao | 34e8aee41b | 7 months ago |
Nareshkumar Rao | 771f81a9f6 | 7 months ago |
18 changed files with 412 additions and 83 deletions
@ -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 |
@ -1,9 +1,10 @@ |
|||
import 'primevue/resources/themes/aura-light-green/theme.css' |
|||
import 'primevue/resources/themes/aura-dark-green/theme.css' |
|||
|
|||
import { createApp } from 'vue' |
|||
import PrimeVue from 'primevue/config' |
|||
import App from './App.vue' |
|||
|
|||
createApp(App).mount('#app') |
|||
App.use(PrimeVue) |
|||
const app = createApp(App) |
|||
app.use(PrimeVue) |
|||
app.mount('#app') |
|||
|
|||
|
@ -1,64 +0,0 @@ |
|||
use std::time::Duration; |
|||
|
|||
use chrono::{Local, Timelike}; |
|||
use tokio::join; |
|||
|
|||
use crate::{ |
|||
actuators, |
|||
error::{lock_err, GenericResult}, |
|||
sensors, |
|||
state::ProgramStateShared, |
|||
}; |
|||
|
|||
async fn temperature_control(program_state: ProgramStateShared) -> GenericResult<()> { |
|||
let mut program_state = program_state.lock().map_err(lock_err)?; |
|||
let current_temperature = sensors::get_temperature(&program_state.config)?; |
|||
if current_temperature > 28. { |
|||
actuators::switch_fan(crate::io::RelaySwitchState::On, &mut program_state)?; |
|||
} else { |
|||
actuators::switch_fan(crate::io::RelaySwitchState::Off, &mut program_state)?; |
|||
} |
|||
Ok(()) |
|||
} |
|||
|
|||
async fn temperature_control_loop(program_state: ProgramStateShared) { |
|||
loop { |
|||
let _ = temperature_control(program_state.clone()).await; |
|||
tokio::time::sleep(Duration::from_mins(1)).await; |
|||
} |
|||
} |
|||
async fn soil_moisture_control_loop(program_state: ProgramStateShared) { |
|||
loop { |
|||
tokio::time::sleep(Duration::from_days(1)).await; |
|||
} |
|||
} |
|||
|
|||
async fn light_control(program_state: ProgramStateShared) -> GenericResult<()> { |
|||
let program_state = program_state.clone(); |
|||
let mut program_state = program_state.lock().map_err(lock_err)?; |
|||
let local = Local::now(); |
|||
let hour = local.time().hour(); |
|||
const HOURS_ON: u32 = 24; |
|||
if hour <= HOURS_ON { |
|||
actuators::switch_lights(crate::io::RelaySwitchState::On, &mut program_state)?; |
|||
} else { |
|||
actuators::switch_lights(crate::io::RelaySwitchState::Off, &mut program_state)?; |
|||
} |
|||
|
|||
Ok(()) |
|||
} |
|||
|
|||
async fn light_control_loop(program_state: ProgramStateShared) { |
|||
loop { |
|||
let _ = light_control(program_state.clone()).await; |
|||
tokio::time::sleep(Duration::from_hours(1)).await; |
|||
} |
|||
} |
|||
|
|||
pub async fn control_thread(program_state: ProgramStateShared) { |
|||
join!( |
|||
light_control_loop(program_state.clone()), |
|||
temperature_control_loop(program_state.clone()), |
|||
soil_moisture_control_loop(program_state.clone()) |
|||
); |
|||
} |
@ -0,0 +1,30 @@ |
|||
use std::time::Duration; |
|||
|
|||
use chrono::{Local, Timelike}; |
|||
|
|||
use crate::{ |
|||
actuators, |
|||
error::{lock_err, GenericResult}, |
|||
state::ProgramStateShared, |
|||
}; |
|||
|
|||
fn light_control(program_state: ProgramStateShared) -> GenericResult<()> { |
|||
let program_state = program_state.clone(); |
|||
let mut program_state = program_state.lock().map_err(lock_err)?; |
|||
let local = Local::now(); |
|||
let hour = local.time().hour(); |
|||
if hour as u64 <= program_state.config.controller_settings.sunlight_hours { |
|||
actuators::switch_lights(crate::io::RelaySwitchState::On, &mut program_state)?; |
|||
} else { |
|||
actuators::switch_lights(crate::io::RelaySwitchState::Off, &mut program_state)?; |
|||
} |
|||
|
|||
Ok(()) |
|||
} |
|||
|
|||
pub async fn light_control_loop(program_state: ProgramStateShared) { |
|||
loop { |
|||
let _ = light_control(program_state.clone()); |
|||
tokio::time::sleep(Duration::from_hours(1)).await; |
|||
} |
|||
} |
@ -0,0 +1,21 @@ |
|||
use tokio::join; |
|||
|
|||
use crate::{ |
|||
control::{ |
|||
light::light_control_loop, soil::soil_moisture_control_loop, |
|||
temperature::temperature_control_loop, |
|||
}, |
|||
state::ProgramStateShared, |
|||
}; |
|||
|
|||
mod light; |
|||
mod soil; |
|||
mod temperature; |
|||
|
|||
pub async fn control_thread(program_state: ProgramStateShared) { |
|||
join!( |
|||
light_control_loop(program_state.clone()), |
|||
temperature_control_loop(program_state.clone()), |
|||
soil_moisture_control_loop(program_state.clone()) |
|||
); |
|||
} |
@ -0,0 +1,42 @@ |
|||
use std::time::Duration; |
|||
|
|||
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 |
|||
.lock() |
|||
.map(|program_state| program_state.config.controller_settings.soil_loop_hours) |
|||
.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(()) |
|||
} |
@ -0,0 +1,38 @@ |
|||
use std::time::Duration; |
|||
|
|||
use crate::{ |
|||
actuators, |
|||
error::{lock_err, GenericResult}, |
|||
sensors, |
|||
state::ProgramStateShared, |
|||
}; |
|||
|
|||
fn temperature_control(program_state: ProgramStateShared) -> GenericResult<()> { |
|||
let mut program_state = program_state.lock().map_err(lock_err)?; |
|||
let config = &program_state.config.controller_settings; |
|||
|
|||
let current_temperature = sensors::get_temperature(&program_state.config)?; |
|||
if current_temperature > config.temperature_set_point_upper { |
|||
actuators::switch_fan(crate::io::RelaySwitchState::On, &mut program_state)?; |
|||
} else if current_temperature < config.temperature_set_point_lower { |
|||
actuators::switch_fan(crate::io::RelaySwitchState::Off, &mut program_state)?; |
|||
} |
|||
Ok(()) |
|||
} |
|||
|
|||
pub async fn temperature_control_loop(program_state: ProgramStateShared) { |
|||
let loop_duration = program_state |
|||
.lock() |
|||
.map(|program_state| { |
|||
program_state |
|||
.config |
|||
.controller_settings |
|||
.temperature_loop_mins |
|||
}) |
|||
.unwrap_or(1); |
|||
|
|||
loop { |
|||
let _ = temperature_control(program_state.clone()); |
|||
tokio::time::sleep(Duration::from_mins(loop_duration)).await; |
|||
} |
|||
} |
@ -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(()) |
|||
} |
|||
} |
@ -0,0 +1,73 @@ |
|||
use chrono::Utc; |
|||
use serde::{Deserialize, Serialize}; |
|||
|
|||
use crate::error::GenericResult; |
|||
|
|||
#[derive(Serialize, Deserialize)] |
|||
pub struct WateringRecord { |
|||
pub time: i64, |
|||
pub amount: u64, |
|||
pub moisture_before_watering: f32, |
|||
} |
|||
|
|||
impl WateringRecord { |
|||
pub fn new(amount: u64, moisture_before_watering: f32) -> WateringRecord { |
|||
WateringRecord { |
|||
time: Utc::now().timestamp(), |
|||
amount, |
|||
moisture_before_watering, |
|||
} |
|||
} |
|||
} |
|||
|
|||
#[derive(Default)] |
|||
pub struct History { |
|||
pub watering_records: Vec<WateringRecord>, |
|||
} |
|||
|
|||
const FILE_PATH: &str = "./growpi.history.csv"; |
|||
|
|||
impl History { |
|||
pub fn save(&self) -> GenericResult<()> { |
|||
let mut writer = csv::WriterBuilder::new() |
|||
.has_headers(true) |
|||
.from_path(FILE_PATH)?; |
|||
for record in &self.watering_records { |
|||
writer.serialize(record)?; |
|||
} |
|||
writer.flush()?; |
|||
Ok(()) |
|||
} |
|||
|
|||
pub fn load() -> GenericResult<History> { |
|||
let mut history = csv::ReaderBuilder::new() |
|||
.has_headers(true) |
|||
.from_path(FILE_PATH)?; |
|||
let mut result = Vec::new(); |
|||
for record in history.deserialize() { |
|||
result.push(record?); |
|||
} |
|||
Ok(History { |
|||
watering_records: result, |
|||
}) |
|||
} |
|||
} |
|||
|
|||
#[cfg(test)] |
|||
mod tests { |
|||
|
|||
use chrono::Local; |
|||
|
|||
use super::*; |
|||
|
|||
#[test] |
|||
fn test_write_default() { |
|||
let mut history = History::default(); |
|||
history.watering_records.push(WateringRecord { |
|||
time: Local::now().timestamp(), |
|||
amount: 456, |
|||
moisture_before_watering: 71.1, |
|||
}); |
|||
history.save().unwrap(); |
|||
} |
|||
} |
@ -1,14 +1,20 @@ |
|||
use std::sync::{Arc, Mutex}; |
|||
|
|||
use crate::{config::Configuration, error::GenericResult, io}; |
|||
use crate::{config::Configuration, error::GenericResult, history::History, io}; |
|||
|
|||
pub type ProgramStateShared = Arc<Mutex<ProgramState>>; |
|||
pub struct ProgramState { |
|||
pub config: Configuration, |
|||
pub relay: io::Relay, |
|||
pub history: History, |
|||
} |
|||
|
|||
pub fn init_state(config: Configuration) -> GenericResult<ProgramStateShared> { |
|||
let relay = io::Relay::new(&config)?; |
|||
Ok(Arc::new(Mutex::new(ProgramState { config, relay }))) |
|||
let history = History::load().unwrap_or_default(); |
|||
Ok(Arc::new(Mutex::new(ProgramState { |
|||
config, |
|||
relay, |
|||
history, |
|||
}))) |
|||
} |
|||
|
Loading…
Reference in new issue