channels: Start to make reports make sense

Names in the report make no sense, and expose implementation details.
Start simplifying the interface.
This commit is contained in:
atse 2024-01-18 17:30:39 +08:00
parent fb4333e177
commit 3a8e0bddec
5 changed files with 57 additions and 65 deletions

View File

@ -255,21 +255,18 @@ Use the bare `report` command to obtain a single report. Enable
continuous reporting with `report mode on`. Reports are JSON objects continuous reporting with `report mode on`. Reports are JSON objects
with the following keys. with the following keys.
| Key | Unit | Description | | Key | Unit | Description |
| --- | :---: | --- | | --- | :---: | --- |
| `channel` | Integer | Channel `0`, or `1` | | `channel` | Integer | Channel `0`, or `1` |
| `time` | Seconds | Temperature measurement time | | `time` | Seconds | Report timestamp (since thermostat reset) |
| `adc` | Volts | AD7172 input | | `interval` | Seconds | ADC Sampling Interval |
| `sens` | Ohms | Thermistor resistance derived from `adc` | | `sens` | Ohms | Thermistor resistance |
| `temperature` | Degrees Celsius | Steinhart-Hart conversion result derived from `sens` | | `temperature` | Degrees Celsius | Temperature derived from thermistor resistance |
| `pid_engaged` | Boolean | `true` if in closed-loop mode | | `pid_engaged` | Boolean | `true` if in closed-loop mode |
| `i_set` | Amperes | TEC output current | | `pid_output` | Amperes | PID control output |
| `dac_value` | Volts | AD5680 output derived from `i_set` | | `i_set` | Amperes | Set TEC output current |
| `dac_feedback` | Volts | ADC measurement of the AD5680 output | | `i_measured` | Amperes | Measured current passing through TEC |
| `i_tec` | Volts | MAX1968 TEC current monitor | | `v_measured` | Volts | Measured voltage across TEC |
| `tec_i` | Amperes | TEC output current feedback derived from `i_tec` |
| `tec_u_meas` | Volts | Measurement of the voltage across the TEC |
| `pid_output` | Amperes | PID control output |
Note: With Thermostat v2 and below, the voltage and current readouts `i_tec` and `tec_i` are disabled and null due to faulty hardware that introduces a lot of noise in the signal. Note: With Thermostat v2 and below, the voltage and current readouts `i_tec` and `tec_i` are disabled and null due to faulty hardware that introduces a lot of noise in the signal.
@ -280,10 +277,10 @@ The thermostat implements a PID control loop for each of the TEC channels, more
## Fan control ## Fan control
Fan control commands are available for thermostat revisions with an integrated fan system: Fan control commands are available for thermostat revisions with an integrated fan system:
1. `fan` - show fan stats: `fan_pwm`, `abs_max_tec_i`, `auto_mode`, `k_a`, `k_b`, `k_c`. 1. `fan` - show fan stats: `fan_pwm`, `max_abs_i_measured`, `auto_mode`, `k_a`, `k_b`, `k_c`.
2. `fan auto` - enable auto speed controller mode, where fan speed is controlled by the fan curve `fcurve`. 2. `fan auto` - enable auto speed controller mode, where fan speed is controlled by the fan curve `fcurve`.
3. `fan <value>` - set the fan power with the value from `1` to `100` and disable auto mode. There is no way to completely disable the fan. 3. `fan <value>` - set the fan power with the value from `1` to `100` and disable auto mode. There is no way to completely disable the fan.
Please note that power doesn't correlate with the actual speed linearly. Please note that power doesn't correlate with the actual speed linearly.
4. `fcurve <a> <b> <c>` - set coefficients of the controlling curve `a*x^2 + b*x + c`, where `x` is `abs_max_tec_i/MAX_TEC_I`, a normalized value in range [0,1], 4. `fcurve <a> <b> <c>` - set coefficients of the controlling curve `a*x^2 + b*x + c`, where `x` is `max_abs_i_measured/MAX_I`, a normalized value in range [0,1],
i.e. the (linear) proportion of current output capacity used, on the channel with the largest current flow. The controlling curve is also clamped to [0,1]. i.e. the (linear) proportion of current output capacity used, on the channel with the largest current flow. The controlling curve is also clamped to [0,1].
5. `fcurve default` - restore fan curve coefficients to defaults: `a = 1.0, b = 0.0, c = 0.0`. 5. `fcurve default` - restore fan curve coefficients to defaults: `a = 1.0, b = 0.0, c = 0.0`.

View File

@ -4,7 +4,7 @@ use crate::{
channel_state::ChannelState, channel_state::ChannelState,
command_handler::JsonBuffer, command_handler::JsonBuffer,
command_parser::{CenterPoint, PwmPin}, command_parser::{CenterPoint, PwmPin},
pins, steinhart_hart, hw_rev hw_rev, pins, steinhart_hart,
}; };
use core::cmp::max_by; use core::cmp::max_by;
use heapless::{consts::U2, Vec}; use heapless::{consts::U2, Vec};
@ -55,7 +55,14 @@ impl<'a> Channels<'a> {
let channel1 = Channel::new(pins.channel1, adc_calibration1); let channel1 = Channel::new(pins.channel1, adc_calibration1);
let pins_adc = pins.pins_adc; let pins_adc = pins.pins_adc;
let pwm = pins.pwm; let pwm = pins.pwm;
let mut channels = Channels { channel0, channel1, adc, pins_adc, pwm, hwrev }; let mut channels = Channels {
channel0,
channel1,
adc,
pins_adc,
pwm,
hwrev,
};
for channel in 0..CHANNELS { for channel in 0..CHANNELS {
channels.calibrate_dac_value(channel); channels.calibrate_dac_value(channel);
channels.set_i(channel, ElectricCurrent::new::<ampere>(0.0)); channels.set_i(channel, ElectricCurrent::new::<ampere>(0.0));
@ -109,7 +116,7 @@ impl<'a> Channels<'a> {
voltage voltage
} }
pub fn get_i(&mut self, channel: usize) -> ElectricCurrent { pub fn get_i_set(&mut self, channel: usize) -> ElectricCurrent {
let center_point = self.get_center(channel); let center_point = self.get_center(channel);
let r_sense = ElectricalResistance::new::<ohm>(R_SENSE); let r_sense = ElectricalResistance::new::<ohm>(R_SENSE);
let voltage = self.get_dac(channel); let voltage = self.get_dac(channel);
@ -129,7 +136,7 @@ impl<'a> Channels<'a> {
voltage voltage
} }
pub fn set_i(&mut self, channel: usize, i_tec: ElectricCurrent) -> ElectricCurrent { pub fn set_i(&mut self, channel: usize, i_set: ElectricCurrent) -> ElectricCurrent {
let vref_meas = match channel { let vref_meas = match channel {
0 => self.channel0.vref_meas, 0 => self.channel0.vref_meas,
1 => self.channel1.vref_meas, 1 => self.channel1.vref_meas,
@ -137,7 +144,7 @@ impl<'a> Channels<'a> {
}; };
let center_point = vref_meas; let center_point = vref_meas;
let r_sense = ElectricalResistance::new::<ohm>(R_SENSE); let r_sense = ElectricalResistance::new::<ohm>(R_SENSE);
let voltage = i_tec * 10.0 * r_sense + center_point; let voltage = i_set * 10.0 * r_sense + center_point;
let voltage = self.set_dac(channel, voltage); let voltage = self.set_dac(channel, voltage);
(voltage - center_point) / (10.0 * r_sense) (voltage - center_point) / (10.0 * r_sense)
} }
@ -365,13 +372,13 @@ impl<'a> Channels<'a> {
(duty * max, max) (duty * max, max)
} }
// Get current passing through TEC // Measure current passing through TEC
pub fn get_tec_i(&mut self, channel: usize) -> ElectricCurrent { pub fn get_i_measured(&mut self, channel: usize) -> ElectricCurrent {
(self.read_itec(channel) - self.read_vref(channel)) / ElectricalResistance::new::<ohm>(0.4) (self.read_itec(channel) - self.read_vref(channel)) / ElectricalResistance::new::<ohm>(0.4)
} }
// Get voltage across TEC // Measure voltage across TEC
pub fn get_tec_v(&mut self, channel: usize) -> ElectricPotential { pub fn get_v_measured(&mut self, channel: usize) -> ElectricPotential {
(self.read_tec_u_meas(channel) - ElectricPotential::new::<volt>(1.5)) * 4.0 (self.read_tec_u_meas(channel) - ElectricPotential::new::<volt>(1.5)) * 4.0
} }
@ -428,29 +435,24 @@ impl<'a> Channels<'a> {
} }
fn report(&mut self, channel: usize) -> Report { fn report(&mut self, channel: usize) -> Report {
let i_set = self.get_i(channel);
let i_tec = if self.hwrev.major > 2 {Some(self.read_itec(channel))} else {None};
let tec_i = if self.hwrev.major > 2 {Some(self.get_tec_i(channel))} else {None};
let dac_value = self.get_dac(channel);
let state = self.channel_state(channel); let state = self.channel_state(channel);
let pid_output = ElectricCurrent::new::<ampere>(state.pid.y1);
Report { Report {
channel, channel,
time: state.get_adc_time(), time: state.get_adc_time(),
interval: state.get_adc_interval(), interval: state.get_adc_interval(),
adc: state.get_adc(),
sens: state.get_sens(), sens: state.get_sens(),
temperature: state temperature: state
.get_temperature() .get_temperature()
.map(|temperature| temperature.get::<degree_celsius>()), .map(|temperature| temperature.get::<degree_celsius>()),
pid_engaged: state.pid_engaged, pid_engaged: state.pid_engaged,
i_set, pid_output: ElectricCurrent::new::<ampere>(state.pid.y1),
dac_value, i_set: self.get_i_set(channel),
dac_feedback: self.read_dac_feedback(channel), i_measured: if self.hwrev.major > 2 {
i_tec, Some(self.get_i_measured(channel))
tec_i, } else {
tec_u_meas: self.get_tec_v(channel), None
pid_output, },
v_measured: self.get_v_measured(channel),
} }
} }
@ -483,7 +485,7 @@ impl<'a> Channels<'a> {
PwmSummary { PwmSummary {
channel, channel,
center: CenterPointJson(self.channel_state(channel).center.clone()), center: CenterPointJson(self.channel_state(channel).center.clone()),
i_set: (self.get_i(channel), ElectricCurrent::new::<ampere>(3.0)).into(), i_set: (self.get_i_set(channel), ElectricCurrent::new::<ampere>(3.0)).into(),
max_v: (self.get_max_v(channel), ElectricPotential::new::<volt>(5.0)).into(), max_v: (self.get_max_v(channel), ElectricPotential::new::<volt>(5.0)).into(),
max_i_pos: self.get_max_i_pos(channel).into(), max_i_pos: self.get_max_i_pos(channel).into(),
max_i_neg: self.get_max_i_neg(channel).into(), max_i_neg: self.get_max_i_neg(channel).into(),
@ -530,10 +532,10 @@ impl<'a> Channels<'a> {
serde_json_core::to_vec(&summaries) serde_json_core::to_vec(&summaries)
} }
pub fn current_abs_max_tec_i(&mut self) -> ElectricCurrent { pub fn max_abs_i_measured(&mut self) -> ElectricCurrent {
max_by( max_by(
self.get_tec_i(0).abs(), self.get_i_measured(0).abs(),
self.get_tec_i(1).abs(), self.get_i_measured(1).abs(),
|a, b| a.partial_cmp(b).unwrap_or(core::cmp::Ordering::Equal), |a, b| a.partial_cmp(b).unwrap_or(core::cmp::Ordering::Equal),
) )
} }
@ -544,17 +546,13 @@ pub struct Report {
channel: usize, channel: usize,
time: Time, time: Time,
interval: Time, interval: Time,
adc: Option<ElectricPotential>,
sens: Option<ElectricalResistance>, sens: Option<ElectricalResistance>,
temperature: Option<f64>, temperature: Option<f64>,
pid_engaged: bool, pid_engaged: bool,
i_set: ElectricCurrent,
dac_value: ElectricPotential,
dac_feedback: ElectricPotential,
i_tec: Option<ElectricPotential>,
tec_i: Option<ElectricCurrent>,
tec_u_meas: ElectricPotential,
pid_output: ElectricCurrent, pid_output: ElectricCurrent,
i_set: ElectricCurrent,
i_measured: Option<ElectricCurrent>,
v_measured: ElectricPotential,
} }
pub struct CenterPointJson(CenterPoint); pub struct CenterPointJson(CenterPoint);

View File

@ -207,7 +207,7 @@ impl Handler {
channel: usize, channel: usize,
center: CenterPoint, center: CenterPoint,
) -> Result<Handler, Error> { ) -> Result<Handler, Error> {
let i_tec = channels.get_i(channel); let i_tec = channels.get_i_set(channel);
let state = channels.channel_state(channel); let state = channels.channel_state(channel);
state.center = center; state.center = center;
if !state.pid_engaged { if !state.pid_engaged {

View File

@ -1,19 +1,16 @@
use crate::{command_handler::JsonBuffer, hw_rev::HWSettings};
use num_traits::Float; use num_traits::Float;
use serde::Serialize; use serde::Serialize;
use stm32f4xx_hal::{ use stm32f4xx_hal::{
pac::TIM8, pac::TIM8,
pwm::{self, PwmChannels}, pwm::{self, PwmChannels},
}; };
use uom::si::{ use uom::si::{electric_current::ampere, f64::ElectricCurrent};
f64::ElectricCurrent,
electric_current::ampere,
};
use crate::{command_handler::JsonBuffer, hw_rev::HWSettings};
pub type FanPin = PwmChannels<TIM8, pwm::C4>; pub type FanPin = PwmChannels<TIM8, pwm::C4>;
// as stated in the schematics // as stated in the schematics
const MAX_TEC_I: f32 = 3.0; const MAX_I: f32 = 3.0;
const MAX_USER_FAN_PWM: f32 = 100.0; const MAX_USER_FAN_PWM: f32 = 100.0;
const MIN_USER_FAN_PWM: f32 = 1.0; const MIN_USER_FAN_PWM: f32 = 1.0;
@ -25,7 +22,7 @@ pub struct FanCtrl {
k_a: f32, k_a: f32,
k_b: f32, k_b: f32,
k_c: f32, k_c: f32,
abs_max_tec_i: f32, max_abs_i_measured: f32,
hw_settings: HWSettings, hw_settings: HWSettings,
} }
@ -40,7 +37,7 @@ impl FanCtrl {
k_a: hw_settings.fan_k_a, k_a: hw_settings.fan_k_a,
k_b: hw_settings.fan_k_b, k_b: hw_settings.fan_k_b,
k_c: hw_settings.fan_k_c, k_c: hw_settings.fan_k_c,
abs_max_tec_i: 0f32, max_abs_i_measured: 0f32,
hw_settings, hw_settings,
}; };
if fan_ctrl.fan_auto { if fan_ctrl.fan_auto {
@ -49,10 +46,10 @@ impl FanCtrl {
fan_ctrl fan_ctrl
} }
pub fn cycle(&mut self, abs_max_tec_i: ElectricCurrent) { pub fn cycle(&mut self, max_abs_i_measured: ElectricCurrent) {
self.abs_max_tec_i = abs_max_tec_i.get::<ampere>() as f32; self.max_abs_i_measured = max_abs_i_measured.get::<ampere>() as f32;
if self.fan_auto && self.hw_settings.fan_available { if self.fan_auto && self.hw_settings.fan_available {
let scaled_current = self.abs_max_tec_i / MAX_TEC_I; let scaled_current = self.max_abs_i_measured / MAX_I;
// do not limit upper bound, as it will be limited in the set_pwm() // do not limit upper bound, as it will be limited in the set_pwm()
let pwm = (MAX_USER_FAN_PWM let pwm = (MAX_USER_FAN_PWM
* (scaled_current * (scaled_current * self.k_a + self.k_b) + self.k_c)) * (scaled_current * (scaled_current * self.k_a + self.k_b) + self.k_c))
@ -65,7 +62,7 @@ impl FanCtrl {
if self.hw_settings.fan_available { if self.hw_settings.fan_available {
let summary = FanSummary { let summary = FanSummary {
fan_pwm: self.get_pwm(), fan_pwm: self.get_pwm(),
abs_max_tec_i: self.abs_max_tec_i, max_abs_i_measured: self.max_abs_i_measured,
auto_mode: self.fan_auto, auto_mode: self.fan_auto,
k_a: self.k_a, k_a: self.k_a,
k_b: self.k_b, k_b: self.k_b,
@ -160,7 +157,7 @@ fn scale_number(unscaled: f32, to_min: f32, to_max: f32, from_min: f32, from_max
#[derive(Serialize)] #[derive(Serialize)]
pub struct FanSummary { pub struct FanSummary {
fan_pwm: u32, fan_pwm: u32,
abs_max_tec_i: f32, max_abs_i_measured: f32,
auto_mode: bool, auto_mode: bool,
k_a: f32, k_a: f32,
k_b: f32, k_b: f32,

View File

@ -196,7 +196,7 @@ fn main() -> ! {
server.for_each(|_, session| session.set_report_pending(channel.into())); server.for_each(|_, session| session.set_report_pending(channel.into()));
} }
fan_ctrl.cycle(channels.current_abs_max_tec_i()); fan_ctrl.cycle(channels.max_abs_i_measured());
if channels.pid_engaged() { if channels.pid_engaged() {
leds.g3.on(); leds.g3.on();