Compare commits

..

No commits in common. "6e0cf26d6a88aa2078a6aa52331d8d9482a89089" and "11f2ebe9614b31bd1436b3b23cb2fbcacb3c1c08" have entirely different histories.

4 changed files with 127 additions and 223 deletions

View File

@ -1,4 +1,4 @@
use serde::{Serialize, Serializer}; use serde::{Serialize, Deserialize};
use smoltcp::time::Instant; use smoltcp::time::Instant;
use stm32f4xx_hal::hal; use stm32f4xx_hal::hal;
use uom::si::{ use uom::si::{
@ -16,7 +16,6 @@ use crate::{
channel_state::ChannelState, channel_state::ChannelState,
command_parser::{CenterPoint, PwmPin}, command_parser::{CenterPoint, PwmPin},
pins, pins,
steinhart_hart,
}; };
pub const CHANNELS: usize = 2; pub const CHANNELS: usize = 2;
@ -418,7 +417,6 @@ impl Channels {
let (i_set, _) = self.get_i(channel); let (i_set, _) = self.get_i(channel);
let i_tec = self.read_itec(channel); let i_tec = self.read_itec(channel);
let tec_i = (i_tec - vref) / ElectricalResistance::new::<ohm>(0.4); let tec_i = (i_tec - vref) / ElectricalResistance::new::<ohm>(0.4);
let (dac_value, _) = self.get_dac(channel);
let state = self.channel_state(channel); let state = self.channel_state(channel);
let pid_output = state.pid.last_output.map(|last_output| let pid_output = state.pid.last_output.map(|last_output|
ElectricCurrent::new::<ampere>(last_output) ElectricCurrent::new::<ampere>(last_output)
@ -433,7 +431,6 @@ impl Channels {
pid_engaged: state.pid_engaged, pid_engaged: state.pid_engaged,
i_set, i_set,
vref, vref,
dac_value,
dac_feedback: self.read_dac_feedback(channel), dac_feedback: self.read_dac_feedback(channel),
i_tec, i_tec,
tec_i, tec_i,
@ -441,33 +438,9 @@ impl Channels {
pid_output, pid_output,
} }
} }
pub fn pwm_summary(&mut self, channel: usize) -> PwmSummary {
PwmSummary {
channel,
center: CenterPointJson(self.channel_state(channel).center.clone()),
i_set: self.get_i(channel).into(),
max_v: self.get_max_v(channel).into(),
max_i_pos: self.get_max_i_pos(channel).into(),
max_i_neg: self.get_max_i_neg(channel).into(),
}
}
pub fn postfilter_summary(&mut self, channel: usize) -> PostFilterSummary {
let rate = self.adc.get_postfilter(channel as u8).unwrap()
.and_then(|filter| filter.output_rate());
PostFilterSummary { channel, rate }
}
pub fn steinhart_hart_summary(&mut self, channel: usize) -> SteinhartHartSummary {
let params = self.channel_state(channel).sh.clone();
SteinhartHartSummary { channel, params }
}
} }
type JsonBuffer = heapless::Vec<u8, heapless::consts::U512>; #[derive(Serialize, Deserialize)]
#[derive(Serialize)]
pub struct Report { pub struct Report {
channel: usize, channel: usize,
time: i64, time: i64,
@ -477,7 +450,6 @@ pub struct Report {
pid_engaged: bool, pid_engaged: bool,
i_set: ElectricCurrent, i_set: ElectricCurrent,
vref: ElectricPotential, vref: ElectricPotential,
dac_value: ElectricPotential,
dac_feedback: ElectricPotential, dac_feedback: ElectricPotential,
i_tec: ElectricPotential, i_tec: ElectricPotential,
tec_i: ElectricCurrent, tec_i: ElectricCurrent,
@ -485,81 +457,14 @@ pub struct Report {
pid_output: Option<ElectricCurrent>, pid_output: Option<ElectricCurrent>,
} }
type JsonBuffer = heapless::Vec<u8, heapless::consts::U360>;
impl Report { impl Report {
pub fn to_json(&self) -> Result<JsonBuffer, serde_json_core::ser::Error> { pub fn to_json(&self) -> Result<JsonBuffer, serde_json_core::ser::Error> {
serde_json_core::to_vec(self) serde_json_core::to_vec(self)
} }
} }
pub struct CenterPointJson(CenterPoint);
// used in JSON encoding, not for config
impl Serialize for CenterPointJson {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self.0 {
CenterPoint::Vref =>
serializer.serialize_str("vref"),
CenterPoint::Override(vref) =>
serializer.serialize_f32(vref),
}
}
}
#[derive(Serialize)]
pub struct PwmSummaryField<T: Serialize> {
value: T,
max: T,
}
impl<T: Serialize> From<(T, T)> for PwmSummaryField<T> {
fn from((value, max): (T, T)) -> Self {
PwmSummaryField { value, max }
}
}
#[derive(Serialize)]
pub struct PwmSummary {
channel: usize,
center: CenterPointJson,
i_set: PwmSummaryField<ElectricCurrent>,
max_v: PwmSummaryField<ElectricPotential>,
max_i_pos: PwmSummaryField<ElectricCurrent>,
max_i_neg: PwmSummaryField<ElectricCurrent>,
}
impl PwmSummary {
pub fn to_json(&self) -> Result<JsonBuffer, serde_json_core::ser::Error> {
serde_json_core::to_vec(self)
}
}
#[derive(Serialize)]
pub struct PostFilterSummary {
channel: usize,
rate: Option<f32>,
}
impl PostFilterSummary {
pub fn to_json(&self) -> Result<JsonBuffer, serde_json_core::ser::Error> {
serde_json_core::to_vec(self)
}
}
#[derive(Serialize)]
pub struct SteinhartHartSummary {
channel: usize,
params: steinhart_hart::Parameters,
}
impl SteinhartHartSummary {
pub fn to_json(&self) -> Result<JsonBuffer, serde_json_core::ser::Error> {
serde_json_core::to_vec(self)
}
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
@ -576,7 +481,6 @@ mod test {
pid_engaged: false, pid_engaged: false,
i_set: ElectricCurrent::new::<ampere>(0.5 / 1.1), i_set: ElectricCurrent::new::<ampere>(0.5 / 1.1),
vref: ElectricPotential::new::<volt>(1.5 / 1.1), vref: ElectricPotential::new::<volt>(1.5 / 1.1),
dac_value: ElectricPotential::new::<volt>(2.0 / 1.1),
dac_feedback: ElectricPotential::new::<volt>(2.0 / 1.1), dac_feedback: ElectricPotential::new::<volt>(2.0 / 1.1),
i_tec: ElectricPotential::new::<volt>(2.0 / 1.1), i_tec: ElectricPotential::new::<volt>(2.0 / 1.1),
tec_i: ElectricCurrent::new::<ampere>(0.2 / 1.1), tec_i: ElectricCurrent::new::<ampere>(0.2 / 1.1),
@ -587,34 +491,4 @@ mod test {
assert_eq!(buf[0], b'{'); assert_eq!(buf[0], b'{');
assert_eq!(buf[buf.len() - 1], b'}'); assert_eq!(buf[buf.len() - 1], b'}');
} }
#[test]
fn pwm_summary_to_json() {
let value = 1.0 / 1.1;
let max = 5.0 / 1.1;
let pwm_summary = PwmSummary {
channel: 0,
center: CenterPointJson(CenterPoint::Vref),
i_set: PwmSummaryField {
value: ElectricCurrent::new::<ampere>(value),
max: ElectricCurrent::new::<ampere>(max),
},
max_v: PwmSummaryField {
value: ElectricPotential::new::<volt>(value),
max: ElectricPotential::new::<volt>(max),
},
max_i_pos: PwmSummaryField {
value: ElectricCurrent::new::<ampere>(value),
max: ElectricCurrent::new::<ampere>(max),
},
max_i_neg: PwmSummaryField {
value: ElectricCurrent::new::<ampere>(value),
max: ElectricCurrent::new::<ampere>(max),
},
};
let buf = pwm_summary.to_json().unwrap();
assert_eq!(buf[0], b'{');
assert_eq!(buf[buf.len() - 1], b'}');
}
} }

View File

@ -56,7 +56,7 @@ use server::Server;
mod session; mod session;
use session::{Session, SessionOutput}; use session::{Session, SessionOutput};
mod command_parser; mod command_parser;
use command_parser::{Command, ShowCommand, PwmPin}; use command_parser::{CenterPoint, Command, ShowCommand, PwmPin};
mod timer; mod timer;
mod pid; mod pid;
mod steinhart_hart; mod steinhart_hart;
@ -80,42 +80,37 @@ pub const EEPROM_SIZE: usize = 128;
const TCP_PORT: u16 = 23; const TCP_PORT: u16 = 23;
fn send_line(socket: &mut TcpSocket, data: &[u8]) -> bool { fn report_to(channel: usize, channels: &mut Channels, socket: &mut TcpSocket) -> bool {
let send_free = socket.send_capacity() - socket.send_queue(); let send_free = socket.send_capacity() - socket.send_queue();
if data.len() > send_free + 1 { match channels.report(channel).to_json() {
Ok(buf) if buf.len() > send_free + 1 => {
// Not enough buffer space, skip report for now // Not enough buffer space, skip report for now
warn!( warn!(
"TCP socket has only {}/{} needed {}", "TCP socket has only {}/{} needed {}",
send_free + 1, socket.send_capacity(), data.len(), send_free + 1, socket.send_capacity(), buf.len(),
); );
} else { }
match socket.send_slice(&data) { Ok(buf) => {
Ok(sent) if sent == data.len() => { match socket.send_slice(&buf) {
Ok(sent) if sent == buf.len() => {
let _ = socket.send_slice(b"\n"); let _ = socket.send_slice(b"\n");
// success // success
return true return true
// TODO: session.mark_report_sent(channel);
} }
Ok(sent) => Ok(sent) =>
warn!("sent only {}/{} bytes", sent, data.len()), warn!("sent only {}/{} bytes of report", sent, buf.len()),
Err(e) => Err(e) =>
error!("error sending line: {:?}", e), error!("error sending report: {:?}", e),
} }
} }
Err(e) =>
error!("unable to serialize report: {:?}", e),
}
// not success // not success
false false
} }
fn report_to(channel: usize, channels: &mut Channels, socket: &mut TcpSocket) -> bool {
match channels.report(channel).to_json() {
Ok(buf) =>
send_line(socket, &buf[..]),
Err(e) => {
error!("unable to serialize report: {:?}", e);
false
}
}
}
/// Initialization and main loop /// Initialization and main loop
#[cfg(not(test))] #[cfg(not(test))]
#[entry] #[entry]
@ -213,45 +208,120 @@ fn main() -> ! {
} }
Command::Show(ShowCommand::Pid) => { Command::Show(ShowCommand::Pid) => {
for channel in 0..CHANNELS { for channel in 0..CHANNELS {
match channels.channel_state(channel).pid.summary(channel).to_json() { let state = channels.channel_state(channel);
Ok(buf) => { let _ = writeln!(socket, "PID settings for channel {}", channel);
send_line(&mut socket, &buf); let pid = &state.pid;
let _ = writeln!(socket, "- target={:.4} °C", pid.target);
macro_rules! show_pid_parameter {
($p: tt) => {
let _ = writeln!(
socket, "- {}={:.4}",
stringify!($p), pid.parameters.$p
);
};
} }
Err(e) => show_pid_parameter!(kp);
error!("unable to serialize pid summary: {:?}", e), show_pid_parameter!(ki);
show_pid_parameter!(kd);
show_pid_parameter!(integral_min);
show_pid_parameter!(integral_max);
show_pid_parameter!(output_min);
show_pid_parameter!(output_max);
if let Some(last_output) = pid.last_output {
let _ = writeln!(socket, "- last_output={:.3} A", last_output);
} }
let _ = writeln!(socket, "");
} }
} }
Command::Show(ShowCommand::Pwm) => { Command::Show(ShowCommand::Pwm) => {
for channel in 0..CHANNELS { for channel in 0..CHANNELS {
match channels.pwm_summary(channel).to_json() { let i_set = channels.get_i(channel);
Ok(buf) => { let state = channels.channel_state(channel);
send_line(&mut socket, &buf); let _ = writeln!(
socket, "channel {}: PID={}",
channel,
if state.pid_engaged { "engaged" } else { "disengaged" }
);
let _ = write!(
socket, "- i_set={:.3} / {:.3} ",
i_set.0.into_format_args(ampere, Abbreviation),
i_set.1.into_format_args(ampere, Abbreviation),
);
match state.center {
CenterPoint::Vref => {
let _ = writeln!(
socket, "center=vref vref={:.3}",
state.vref.into_format_args(volt, Abbreviation),
);
} }
Err(e) => CenterPoint::Override(volts) => {
error!("unable to serialize pwm summary: {:?}", e), let _ = writeln!(
socket, "center={:.3} V",
volts,
);
} }
} }
let max_v = channels.get_max_v(channel);
let _ = writeln!(
socket, "- max_v={:.3} / {:.3}",
max_v.0.into_format_args(volt, Abbreviation),
max_v.1.into_format_args(volt, Abbreviation),
);
let max_i_pos = channels.get_max_i_pos(channel);
let _ = writeln!(
socket, "- max_i_pos={:.3} / {:.3}",
max_i_pos.0.into_format_args(ampere, Abbreviation),
max_i_pos.1.into_format_args(ampere, Abbreviation),
);
let max_i_neg = channels.get_max_i_neg(channel);
let _ = writeln!(
socket, "- max_i_neg={:.3} / {:.3}",
max_i_neg.0.into_format_args(ampere, Abbreviation),
max_i_neg.1.into_format_args(ampere, Abbreviation),
);
}
let _ = writeln!(socket, "");
} }
Command::Show(ShowCommand::SteinhartHart) => { Command::Show(ShowCommand::SteinhartHart) => {
for channel in 0..CHANNELS { for channel in 0..CHANNELS {
match channels.steinhart_hart_summary(channel).to_json() { let state = channels.channel_state(channel);
Ok(buf) => { let _ = writeln!(
send_line(&mut socket, &buf); socket, "channel {}: Steinhart-Hart equation parameters",
channel,
);
let _ = writeln!(socket, "- t0={}", state.sh.t0.into_format_args(degree_celsius, Abbreviation));
let _ = writeln!(socket, "- b={}", state.sh.b);
let _ = writeln!(socket, "- r0={}", state.sh.r0.into_format_args(ohm, Abbreviation));
match (state.get_adc(), state.get_sens(), state.get_temperature()) {
(Some(adc), Some(sens), Some(temp)) => {
let _ = writeln!(
socket, "- adc={:.6} r={:.0} temp{}={:.3}",
adc.into_format_args(volt, Abbreviation),
sens.into_format_args(ohm, Abbreviation),
channel,
temp.into_format_args(degree_celsius, Abbreviation),
);
} }
Err(e) => _ => {}
error!("unable to serialize steinhart-hart summary: {:?}", e),
} }
let _ = writeln!(socket, "");
} }
} }
Command::Show(ShowCommand::PostFilter) => { Command::Show(ShowCommand::PostFilter) => {
for channel in 0..CHANNELS { for channel in 0..CHANNELS {
match channels.postfilter_summary(channel).to_json() { match channels.adc.get_postfilter(channel as u8).unwrap() {
Ok(buf) => { Some(filter) => {
send_line(&mut socket, &buf); let _ = writeln!(
socket, "channel {}: postfilter={:.2} SPS",
channel, filter.output_rate().unwrap()
);
}
None => {
let _ = writeln!(
socket, "channel {}: postfilter disabled",
channel
);
} }
Err(e) =>
error!("unable to serialize postfilter summary: {:?}", e),
} }
} }
} }

View File

@ -81,35 +81,11 @@ impl Controller {
output output
} }
#[allow(dead_code)]
pub fn reset(&mut self) { pub fn reset(&mut self) {
self.integral = 0.0; self.integral = 0.0;
self.last_input = None; self.last_input = None;
} }
pub fn summary(&self, channel: usize) -> Summary {
Summary {
channel,
parameters: self.parameters.clone(),
target: self.target,
integral: self.integral,
}
}
}
type JsonBuffer = heapless::Vec<u8, heapless::consts::U360>;
#[derive(Clone, Serialize, Deserialize)]
pub struct Summary {
channel: usize,
parameters: Parameters,
target: f64,
integral: f64,
}
impl Summary {
pub fn to_json(&self) -> Result<JsonBuffer, serde_json_core::ser::Error> {
serde_json_core::to_vec(self)
}
} }
#[cfg(test)] #[cfg(test)]
@ -150,13 +126,4 @@ mod test {
total_t += 1; total_t += 1;
} }
} }
#[test]
fn summary_to_json() {
let mut pid = Controller::new(PARAMETERS.clone());
pid.target = 30.0 / 1.1;
let buf = pid.summary(0).to_json().unwrap();
assert_eq!(buf[0], b'{');
assert_eq!(buf[buf.len() - 1], b'}');
}
} }

View File

@ -8,12 +8,9 @@ use uom::si::{
ratio::ratio, ratio::ratio,
thermodynamic_temperature::{degree_celsius, kelvin}, thermodynamic_temperature::{degree_celsius, kelvin},
}; };
use serde::Serialize;
type JsonBuffer = heapless::Vec<u8, heapless::consts::U200>;
/// Steinhart-Hart equation parameters /// Steinhart-Hart equation parameters
#[derive(Clone, Debug, Serialize)] #[derive(Clone, Debug)]
pub struct Parameters { pub struct Parameters {
/// Base temperature /// Base temperature
pub t0: ThermodynamicTemperature, pub t0: ThermodynamicTemperature,
@ -33,10 +30,6 @@ impl Parameters {
let inv_temp = 1.0 / self.t0.get::<kelvin>() + (r / self.r0).get::<ratio>().ln() / self.b; let inv_temp = 1.0 / self.t0.get::<kelvin>() + (r / self.r0).get::<ratio>().ln() / self.b;
ThermodynamicTemperature::new::<kelvin>(1.0 / inv_temp) ThermodynamicTemperature::new::<kelvin>(1.0 / inv_temp)
} }
pub fn to_json(&self) -> Result<JsonBuffer, serde_json_core::ser::Error> {
serde_json_core::to_vec(self)
}
} }
impl Default for Parameters { impl Default for Parameters {