diff --git a/.gitignore b/.gitignore index 2eb09f3..eb27e8b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /kicad/*-backups/ -/kicad/*.lck \ No newline at end of file +/kicad/*.lck +/firmware/cfg.toml \ No newline at end of file diff --git a/firmware/.cargo/config.toml b/firmware/.cargo/config.toml index e79536e..92e323a 100644 --- a/firmware/.cargo/config.toml +++ b/firmware/.cargo/config.toml @@ -3,7 +3,7 @@ target = "xtensa-esp32s2-espidf" [target.xtensa-esp32s2-espidf] linker = "ldproxy" -runner = "espflash flash --monitor --before no-reset -a no-reset --no-stub" +runner = "espflash flash --monitor --before no-reset -a no-reset --no-stub -T part_table.csv" rustflags = [ "--cfg", "espidf_time64"] [unstable] diff --git a/firmware/Cargo.toml b/firmware/Cargo.toml index d2478f5..4add632 100644 --- a/firmware/Cargo.toml +++ b/firmware/Cargo.toml @@ -28,6 +28,8 @@ esp-idf-svc = { version = "0.51", features = ["critical-section", "embassy-time- anyhow = "1.0.97" embedded-graphics = "0.8.1" ssd1306 = "0.9.0" +toml-cfg = "0.2.0" [build-dependencies] embuild = "0.33" +toml-cfg = "0.2.0" diff --git a/firmware/build.rs b/firmware/build.rs index 112ec3f..eae393f 100644 --- a/firmware/build.rs +++ b/firmware/build.rs @@ -1,3 +1,15 @@ +#[toml_cfg::toml_config] +pub struct Config { + #[default("")] + wifi_ssid: &'static str, + #[default("")] + wifi_psk: &'static str, +} + fn main() { + if !std::path::Path::new("cfg.toml").exists() { + panic!("You need to create a `cfg.toml` file with your Wi-Fi credentials! Use `cfg.toml.template` as a template."); + } + embuild::espidf::sysenv::output(); } diff --git a/firmware/cfg.toml.template b/firmware/cfg.toml.template new file mode 100644 index 0000000..4e8a9f4 --- /dev/null +++ b/firmware/cfg.toml.template @@ -0,0 +1,3 @@ +[http-server] +wifi_ssid = "beans32" +wifi_psk = "12345678" \ No newline at end of file diff --git a/firmware/part_table.csv b/firmware/part_table.csv new file mode 100644 index 0000000..e12db40 --- /dev/null +++ b/firmware/part_table.csv @@ -0,0 +1,5 @@ +# ESP-IDF Partition Table +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x6000, +phy_init, data, phy, 0xf000, 0x1000, +factory, app, factory, 0x10000, 2M, \ No newline at end of file diff --git a/firmware/src/main.rs b/firmware/src/main.rs index ffb3026..43f6157 100644 --- a/firmware/src/main.rs +++ b/firmware/src/main.rs @@ -1,21 +1,61 @@ mod hid; +mod wifi; -use std::{thread, time::{Duration, Instant}}; +use std::{sync::{atomic::{AtomicBool, AtomicPtr, Ordering}, Arc, Mutex, RwLock}, thread, time::{Duration, Instant}}; use anyhow::Result; use embedded_graphics::{ mono_font::{ascii::FONT_6X10, MonoTextStyleBuilder}, pixelcolor::BinaryColor, }; -use esp_idf_svc::hal::{ +use esp_idf_svc::{eventloop::EspSystemEventLoop, hal::{ adc::{attenuation::DB_11, oneshot::{config::AdcChannelConfig, AdcChannelDriver, AdcDriver}}, gpio::{InterruptType, PinDriver}, i2c::{I2cConfig, I2cDriver}, prelude::Peripherals, units::* -}; +}, http::{server::{Configuration, EspHttpServer}, Method}, io::{EspIOError, Write}}; use hid::{Button, GPIOIndicatedButton, Indicator}; use ssd1306::{mode::DisplayConfig, prelude::DisplayRotation, size::DisplaySize128x32, I2CDisplayInterface, Ssd1306}; +#[toml_cfg::toml_config] +pub struct Config { + #[default("")] + wifi_ssid: &'static str, + #[default("")] + wifi_psk: &'static str, +} + +struct PIDController { + p_gain: f32, + i_gain: f32, + d_gain: f32, + prev_err: f32, + sum_err: f32, + target: f32, +} + +impl PIDController { + pub fn new(p_gain: f32, i_gain: f32, d_gain: f32, target: f32) -> Self { + return Self { + p_gain, + i_gain, + d_gain, + prev_err: 0_f32, + sum_err: 0_f32, + target, + }; + } + + pub fn get_control(&mut self, measured: f32) -> f32 { + let ctrl_err = self.target - measured; + let delta_err = ctrl_err - self.prev_err; + let control = self.p_gain * ctrl_err + self.i_gain * self.sum_err + self.d_gain * delta_err; + self.sum_err += ctrl_err; + self.prev_err = ctrl_err; + + return control; + } +} fn main() -> Result<()> { // It is necessary to call this function once. Otherwise some patches to the runtime @@ -63,6 +103,22 @@ fn main() -> Result<()> { log::info!("Hello, world!"); + let sysloop = EspSystemEventLoop::take()?; + let app_config = CONFIG; + + // Connect to the Wi-Fi network + let _wifi = wifi::wifi( + app_config.wifi_ssid, + app_config.wifi_psk, + peripherals.modem, + sysloop, + )?; + let mut server = EspHttpServer::new(&Configuration::default())?; + + const PID_TARGET: f32 = 70_f32; + + let mut pid = PIDController::new(0.5, 0.01, 0.3, PID_TARGET); + const R1: f32 = 100000_f32; const R0: f32 = 100000_f32; const V1: f32 = 5_f32; @@ -70,12 +126,40 @@ fn main() -> Result<()> { const B: f32 = 4285_f32; let mut samples_sum = 0_f32; - let mut temperature = 0_f32; + let temperature = Arc::new(Mutex::new(0_f32)); + let c_temperature = Arc::clone(&temperature); + + let pid_control = Arc::new(Mutex::new(0_f32)); + let c_pid_control = Arc::clone(&pid_control); + + let heater_state = Arc::new(AtomicBool::new(heater_drv_pin.is_set_high())); + let c_heater_state = Arc::clone(&heater_state); + let mut i = 0_u32; + let headers = &[("Content-Type", "text/plain")]; + + server.fn_handler( + "/metrics", + Method::Get, + move |request| -> core::result::Result<(), EspIOError> { + let mut response = request.into_response(200, Option::None, headers)?; + let (pidv, tempv) = { + (*pid_control.lock().unwrap(), *temperature.lock().unwrap()) + }; + response.write(format!("pid_controller_action {}\n", pidv).as_bytes())?; + response.write(format!("pid_controller_target {}\n", PID_TARGET).as_bytes())?; + response.write(format!("boiler_temperature {}\n", tempv).as_bytes())?; + response.write(format!("boiler_heater_state {}\n", heater_state.load(Ordering::Relaxed) as i32 as f32).as_bytes())?; + Ok(()) + }, + )?; + let mut blink_time = Instant::now(); let mut measure_time = Instant::now(); let mut input_time = Option::from(Instant::now()); + + let mut is_meltdown_state = false; loop { // when we receive button event, button interrupt will be automatically disabled until we enable it manually @@ -88,8 +172,12 @@ fn main() -> Result<()> { heater_drv_pin.set_low()?; btn1.indicator.set_state(false)?; } else { - heater_drv_pin.set_high()?; - btn1.indicator.set_state(true)?; + if !is_meltdown_state { + heater_drv_pin.set_high()?; + btn1.indicator.set_state(true)?; + } else { + println!("DON'T CLICK WE'RE ABOUT TO EXPLODE!"); + } } input_time = Option::from(Instant::now()); @@ -111,8 +199,37 @@ fn main() -> Result<()> { samples_sum += temp; i += 1; } else { - temperature = samples_sum / 64_f32; - println!("Temperature value: {}", temperature); + let tempv = samples_sum / 64_f32; + let pidv = pid.get_control(tempv); + { + let mut t = c_temperature.lock().unwrap(); + let mut p = c_pid_control.lock().unwrap(); + *t = tempv; + *p = pidv; + } + + println!("Temperature value: {}", tempv); + println!("PID Control Value: {}", pidv); + + if !is_meltdown_state { + if pidv > 12_f32 && !heater_drv_pin.is_set_high() { + println!("[PID] Set heater to high"); + heater_drv_pin.set_high()?; + } else if pidv < 10_f32 && !heater_drv_pin.is_set_low(){ + println!("[PID] Set heater to low"); + heater_drv_pin.set_low()?; + } + } + + if tempv > 85_f32 { + println!("CRITICAL TEMPERATURE SHUTTING HEATER DOWN"); + println!("CRITICAL TEMPERATURE SHUTTING HEATER DOWN"); + println!("CRITICAL TEMPERATURE SHUTTING HEATER DOWN"); + println!("CRITICAL TEMPERATURE SHUTTING HEATER DOWN"); + heater_drv_pin.set_low()?; + is_meltdown_state = true; + } + samples_sum = 0_f32; i = 0; } @@ -142,6 +259,7 @@ fn main() -> Result<()> { blink_time = Instant::now(); } + c_heater_state.store(heater_drv_pin.is_set_high(), Ordering::Relaxed); thread::sleep(Duration::from_millis(10)); } diff --git a/firmware/src/wifi.rs b/firmware/src/wifi.rs new file mode 100644 index 0000000..2275d49 --- /dev/null +++ b/firmware/src/wifi.rs @@ -0,0 +1,78 @@ +use anyhow::{bail, Result}; +use esp_idf_svc::{ + eventloop::EspSystemEventLoop, + hal::peripheral, + wifi::{AuthMethod, BlockingWifi, ClientConfiguration, Configuration, EspWifi}, +}; +use log::info; + +pub fn wifi( + ssid: &str, + pass: &str, + modem: impl peripheral::Peripheral
+ 'static,
+ sysloop: EspSystemEventLoop,
+) -> Result