From a8787430b1ba2dbb0719dd416ca5b13b0621decb Mon Sep 17 00:00:00 2001 From: linuswck Date: Wed, 21 Feb 2024 15:49:08 +0800 Subject: [PATCH] Add Temperature Monitor to Thermostat - Issue an alarm when temperature goes out of user-defined operating range during Pid Controller startup or reading is outside of +-0.5 Degree from temperature set point after Pid Controller becomes stable - If alarm is observed, power down laser and tec controller and disengage Pid Controller - Add the corresponding cmd for configuring the temperature monitor --- src/main.rs | 6 ++ src/net/cmd_handler.rs | 27 ++++++ src/thermostat/mod.rs | 1 + src/thermostat/temp_mon.rs | 155 +++++++++++++++++++++++++++++++++++ src/thermostat/thermostat.rs | 34 +++++++- 5 files changed, 221 insertions(+), 2 deletions(-) create mode 100644 src/thermostat/temp_mon.rs diff --git a/src/main.rs b/src/main.rs index 9e5b155..3830d2e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -97,6 +97,12 @@ fn main() -> ! { laser.poll_and_update_output_current(); if thermostat.poll_adc_and_update_pid() { + if thermostat.get_temp_mon_status().over_temp_alarm { + laser.power_down(); + thermostat.set_pid_engaged(false); + thermostat.power_down(); + } + info!("curr_dac_vfb: {:?}", volt_fmt.with(thermostat.get_dac_vfb())); info!("curr_vref: {:?}", volt_fmt.with(thermostat.get_vref())); info!("curr_tec_i: {:?}", amp_fmt.with(thermostat.get_tec_i())); diff --git a/src/net/cmd_handler.rs b/src/net/cmd_handler.rs index 7ea19d9..782e5dc 100644 --- a/src/net/cmd_handler.rs +++ b/src/net/cmd_handler.rs @@ -71,6 +71,10 @@ enum ThermostatCmdEnum { SetPidOutMin, SetPidOutMax, SetPidUpdateInterval, // Update Interval is set based on the sampling rate of ADC + // TempMon + SetTempMonUpperLimit, + SetTempMonLowerLimit, + ClearAlarm, // Steinhart-Hart Equation SetShT0, SetShR0, @@ -342,6 +346,29 @@ pub fn execute_cmd(buffer: &mut [u8], buffer_size: usize, mut laser: LdDrive, mu Some(ThermostatCmdEnum::SetPidUpdateInterval) => { info!("Not supported Yet") } + Some(ThermostatCmdEnum::SetTempMonUpperLimit) => { + match cmd.json.data_f64 { + Some(val) => { + tec.set_temp_mon_upper_limit(ThermodynamicTemperature::new::(val)); + } + None => { + info!("Wrong Data type is received") + } + } + } + Some(ThermostatCmdEnum::SetTempMonLowerLimit) => { + match cmd.json.data_f64 { + Some(val) => { + tec.set_temp_mon_lower_limit(ThermodynamicTemperature::new::(val)); + } + None => { + info!("Wrong Data type is received") + } + } + } + Some(ThermostatCmdEnum::ClearAlarm) => { + tec.clear_temp_mon_alarm(); + } Some(ThermostatCmdEnum::SetShT0) => { match cmd.json.data_f64 { Some(val) => { diff --git a/src/thermostat/mod.rs b/src/thermostat/mod.rs index fc8e3b4..d506f91 100644 --- a/src/thermostat/mod.rs +++ b/src/thermostat/mod.rs @@ -4,3 +4,4 @@ pub mod thermostat; pub mod ad7172; pub mod steinhart_hart; pub mod pid_state; +pub mod temp_mon; diff --git a/src/thermostat/temp_mon.rs b/src/thermostat/temp_mon.rs new file mode 100644 index 0000000..1f9f149 --- /dev/null +++ b/src/thermostat/temp_mon.rs @@ -0,0 +1,155 @@ +use serde::{Deserialize, Serialize}; +use miniconf::Tree; +use uom::si::{ + thermodynamic_temperature::degree_celsius, + f64::ThermodynamicTemperature +}; +use num_traits::Float; +#[derive(PartialEq, Deserialize, Serialize, Copy, Clone, Default, Debug)] +pub enum TempStatusEnum { + #[default] + Off, + OverTemp, + Unstable, + Stable, +} + +#[derive(Deserialize, Serialize, Copy, Clone, Debug, Tree)] +pub struct TempStatus { + pub status: TempStatusEnum, + pub over_temp_alarm: bool, +} + +pub struct TempMonSettings { + pub upper_limit: ThermodynamicTemperature, + pub lower_limit: ThermodynamicTemperature, +} + +pub struct TempMon { + pub upper_limit: ThermodynamicTemperature, + pub lower_limit: ThermodynamicTemperature, + pub set_point: ThermodynamicTemperature, + pub status: TempStatus, + state: State, + count: u32, + is_set_point_changed: bool, +} + +impl Default for TempMon { + fn default() -> Self { + Self { + upper_limit: ThermodynamicTemperature::new::(45.0), + lower_limit: ThermodynamicTemperature::new::(0.0), + set_point: ThermodynamicTemperature::new::(0.0), + status: TempStatus { + status: TempStatusEnum::Off, + over_temp_alarm: false + }, + state: State::default(), + count: 0, + // stable_temp_count: 0, + is_set_point_changed: false, + } + } +} + +#[derive(Default, PartialEq, Debug)] +enum State { + #[default] + PidOff, + PidStartUp, + PidStable, + OverTempAlarm, +} + +impl TempMon { + const OVER_TEMP_COUNT_LIMIT: u32 = 25; + const TEMP_STABLE_COUNT_LIMIT: u32 = 100; + + pub fn set_upper_limit(&mut self, upper_limit: ThermodynamicTemperature) { + self.upper_limit = upper_limit; + } + + pub fn set_lower_limit(&mut self, lower_limit: ThermodynamicTemperature) { + self.lower_limit = lower_limit; + } + + pub fn set_setpoint(&mut self, set_point: ThermodynamicTemperature) { + if self.set_point != set_point { + self.is_set_point_changed = true; + self.count = 0; + } + self.set_point = set_point; + } + + pub fn clear_alarm(&mut self) { + self.status.over_temp_alarm = false; + self.state = State::default(); + } + + pub fn update_status(&mut self, pid_engaged: bool, temp: ThermodynamicTemperature) { + match self.state { + State::PidOff => { + self.is_set_point_changed = false; + self.status.status = TempStatusEnum::Off; + self.count = 0; + + // State Transition + if pid_engaged { + self.state = State::PidStartUp; + } + } + State::PidStartUp | State::PidStable => { + let is_over_temp: bool; + if self.state == State::PidStartUp { + is_over_temp = temp > self.upper_limit || temp < self.lower_limit; + } else { + is_over_temp = (temp.value - self.set_point.value).abs() > 0.5; + } + + let is_within_spec: bool = (temp.value - self.set_point.value).abs() < 0.001; + if is_over_temp { + if self.count > TempMon::OVER_TEMP_COUNT_LIMIT { + self.status.status = TempStatusEnum::OverTemp; + } else { + self.count += 1; + } + } else if is_within_spec { + if self.count > TempMon::TEMP_STABLE_COUNT_LIMIT { + self.status.status = TempStatusEnum::Stable; + } else { + self.count += 1; + } + } else { + self.status.status = TempStatusEnum::Unstable; + self.count = 0; + } + + // State Transition + if self.status.status == TempStatusEnum::OverTemp { + self.state = State::OverTempAlarm; + } else if self.is_set_point_changed { + self.is_set_point_changed = false; + self.state = State::PidStartUp; + } else if self.status.status == TempStatusEnum::Stable { + self.state = State::PidStable; + } + } + State::OverTempAlarm => { + self.is_set_point_changed = false; + self.status.over_temp_alarm = true; + } + } + } + + pub fn get_status(&mut self) -> TempStatus { + self.status + } + + pub fn get_settings(&mut self) -> TempMonSettings { + TempMonSettings { + upper_limit: self.upper_limit, + lower_limit: self.lower_limit, + } + } +} diff --git a/src/thermostat/thermostat.rs b/src/thermostat/thermostat.rs index 9b8b76b..bd7718a 100644 --- a/src/thermostat/thermostat.rs +++ b/src/thermostat/thermostat.rs @@ -6,6 +6,7 @@ use crate::thermostat::max1968::{MAX1968, AdcReadTarget, PwmPinsEnum}; use crate::thermostat::ad7172; use crate::thermostat::pid_state::{PidState, PidSettings}; use crate::thermostat::steinhart_hart; +use crate::thermostat::temp_mon::{TempMon, TempStatus, TempMonSettings}; use serde::{Deserialize, Serialize}; use log::debug; use uom::si::{ @@ -103,6 +104,7 @@ pub struct Thermostat { ad7172: ad7172::AdcPhy, pub tec_setting: TecSettings, pid_ctrl_ch0: PidState, + temp_mon: TempMon, } impl Thermostat{ @@ -112,6 +114,7 @@ impl Thermostat{ ad7172: ad7172, tec_setting: TecSettings::default(), pid_ctrl_ch0: PidState::default(), + temp_mon: TempMon::default(), } } pub fn setup(&mut self){ @@ -148,7 +151,8 @@ impl Thermostat{ let state: &mut PidState = &mut self.pid_ctrl_ch0; state.update(data); debug!("state.get_pid_engaged(): {:?}", state.get_pid_engaged()); - if state.get_pid_engaged() { + let pid_engaged = state.get_pid_engaged(); + if pid_engaged { match state.update_pid() { Some(pid_output) => { self.set_i(ElectricCurrent::new::(pid_output)); @@ -157,12 +161,18 @@ impl Thermostat{ None => { } } } - debug!("Temperature: {:?} degree", self.get_temperature().get::()); + let temp = self.get_temperature(); + self.temp_mon.update_status(pid_engaged, temp); + debug!("Temperature: {:?} degree", temp.get::()); data_rdy = true; }); data_rdy } + pub fn get_temp_mon_status(&mut self) -> TempStatus { + self.temp_mon.get_status() + } + pub fn power_up(&mut self){ self.max1968.power_up(); } @@ -278,6 +288,7 @@ impl Thermostat{ StatusReport { ts: sys_timer::now(), pid_engaged: self.get_pid_engaged(), + temp_mon_status: self.temp_mon.get_status(), temperature: self.pid_ctrl_ch0.get_temperature(), i_set: self.tec_setting.i_set, tec_i: self.get_tec_i(), @@ -335,13 +346,32 @@ impl Thermostat{ pub fn set_temperature_setpoint(&mut self, t: ThermodynamicTemperature) { self.pid_ctrl_ch0.set_pid_setpoint(t); + self.temp_mon.set_setpoint(t); } + + pub fn set_temp_mon_upper_limit(&mut self, t: ThermodynamicTemperature) { + self.temp_mon.set_upper_limit(t); + } + + pub fn set_temp_mon_lower_limit(&mut self, t: ThermodynamicTemperature) { + self.temp_mon.set_lower_limit(t); + } + + pub fn clear_temp_mon_alarm(&mut self) { + self.temp_mon.clear_alarm(); + } + + pub fn get_temp_mon_settings(&mut self) -> TempMonSettings { + self.temp_mon.get_settings() + } + } #[derive(Deserialize, Serialize, Copy, Clone, Debug, Tree)] pub struct StatusReport { ts: u32, pid_engaged: bool, + temp_mon_status: TempStatus, temperature: Option, i_set: ElectricCurrent, tec_i: ElectricCurrent,