Compare commits
4 Commits
11f2ebe961
...
6e0cf26d6a
Author | SHA1 | Date |
---|---|---|
Astro | 6e0cf26d6a | |
Astro | d4901cbab1 | |
Astro | 62d89a68a1 | |
Astro | 5521563c91 |
134
src/channels.rs
134
src/channels.rs
|
@ -1,4 +1,4 @@
|
|||
use serde::{Serialize, Deserialize};
|
||||
use serde::{Serialize, Serializer};
|
||||
use smoltcp::time::Instant;
|
||||
use stm32f4xx_hal::hal;
|
||||
use uom::si::{
|
||||
|
@ -16,6 +16,7 @@ use crate::{
|
|||
channel_state::ChannelState,
|
||||
command_parser::{CenterPoint, PwmPin},
|
||||
pins,
|
||||
steinhart_hart,
|
||||
};
|
||||
|
||||
pub const CHANNELS: usize = 2;
|
||||
|
@ -417,6 +418,7 @@ impl Channels {
|
|||
let (i_set, _) = self.get_i(channel);
|
||||
let i_tec = self.read_itec(channel);
|
||||
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 pid_output = state.pid.last_output.map(|last_output|
|
||||
ElectricCurrent::new::<ampere>(last_output)
|
||||
|
@ -431,6 +433,7 @@ impl Channels {
|
|||
pid_engaged: state.pid_engaged,
|
||||
i_set,
|
||||
vref,
|
||||
dac_value,
|
||||
dac_feedback: self.read_dac_feedback(channel),
|
||||
i_tec,
|
||||
tec_i,
|
||||
|
@ -438,9 +441,33 @@ impl Channels {
|
|||
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 }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
type JsonBuffer = heapless::Vec<u8, heapless::consts::U512>;
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct Report {
|
||||
channel: usize,
|
||||
time: i64,
|
||||
|
@ -450,6 +477,7 @@ pub struct Report {
|
|||
pid_engaged: bool,
|
||||
i_set: ElectricCurrent,
|
||||
vref: ElectricPotential,
|
||||
dac_value: ElectricPotential,
|
||||
dac_feedback: ElectricPotential,
|
||||
i_tec: ElectricPotential,
|
||||
tec_i: ElectricCurrent,
|
||||
|
@ -457,14 +485,81 @@ pub struct Report {
|
|||
pid_output: Option<ElectricCurrent>,
|
||||
}
|
||||
|
||||
type JsonBuffer = heapless::Vec<u8, heapless::consts::U360>;
|
||||
|
||||
impl Report {
|
||||
pub fn to_json(&self) -> Result<JsonBuffer, serde_json_core::ser::Error> {
|
||||
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)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
@ -481,6 +576,7 @@ mod test {
|
|||
pid_engaged: false,
|
||||
i_set: ElectricCurrent::new::<ampere>(0.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),
|
||||
i_tec: ElectricPotential::new::<volt>(2.0 / 1.1),
|
||||
tec_i: ElectricCurrent::new::<ampere>(0.2 / 1.1),
|
||||
|
@ -491,4 +587,34 @@ mod test {
|
|||
assert_eq!(buf[0], 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'}');
|
||||
}
|
||||
}
|
||||
|
|
170
src/main.rs
170
src/main.rs
|
@ -56,7 +56,7 @@ use server::Server;
|
|||
mod session;
|
||||
use session::{Session, SessionOutput};
|
||||
mod command_parser;
|
||||
use command_parser::{CenterPoint, Command, ShowCommand, PwmPin};
|
||||
use command_parser::{Command, ShowCommand, PwmPin};
|
||||
mod timer;
|
||||
mod pid;
|
||||
mod steinhart_hart;
|
||||
|
@ -80,37 +80,42 @@ pub const EEPROM_SIZE: usize = 128;
|
|||
const TCP_PORT: u16 = 23;
|
||||
|
||||
|
||||
fn report_to(channel: usize, channels: &mut Channels, socket: &mut TcpSocket) -> bool {
|
||||
fn send_line(socket: &mut TcpSocket, data: &[u8]) -> bool {
|
||||
let send_free = socket.send_capacity() - socket.send_queue();
|
||||
match channels.report(channel).to_json() {
|
||||
Ok(buf) if buf.len() > send_free + 1 => {
|
||||
// Not enough buffer space, skip report for now
|
||||
warn!(
|
||||
"TCP socket has only {}/{} needed {}",
|
||||
send_free + 1, socket.send_capacity(), buf.len(),
|
||||
);
|
||||
}
|
||||
Ok(buf) => {
|
||||
match socket.send_slice(&buf) {
|
||||
Ok(sent) if sent == buf.len() => {
|
||||
let _ = socket.send_slice(b"\n");
|
||||
// success
|
||||
return true
|
||||
// TODO: session.mark_report_sent(channel);
|
||||
}
|
||||
Ok(sent) =>
|
||||
warn!("sent only {}/{} bytes of report", sent, buf.len()),
|
||||
Err(e) =>
|
||||
error!("error sending report: {:?}", e),
|
||||
if data.len() > send_free + 1 {
|
||||
// Not enough buffer space, skip report for now
|
||||
warn!(
|
||||
"TCP socket has only {}/{} needed {}",
|
||||
send_free + 1, socket.send_capacity(), data.len(),
|
||||
);
|
||||
} else {
|
||||
match socket.send_slice(&data) {
|
||||
Ok(sent) if sent == data.len() => {
|
||||
let _ = socket.send_slice(b"\n");
|
||||
// success
|
||||
return true
|
||||
}
|
||||
Ok(sent) =>
|
||||
warn!("sent only {}/{} bytes", sent, data.len()),
|
||||
Err(e) =>
|
||||
error!("error sending line: {:?}", e),
|
||||
}
|
||||
Err(e) =>
|
||||
error!("unable to serialize report: {:?}", e),
|
||||
}
|
||||
// not success
|
||||
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
|
||||
#[cfg(not(test))]
|
||||
#[entry]
|
||||
|
@ -208,120 +213,45 @@ fn main() -> ! {
|
|||
}
|
||||
Command::Show(ShowCommand::Pid) => {
|
||||
for channel in 0..CHANNELS {
|
||||
let state = channels.channel_state(channel);
|
||||
let _ = writeln!(socket, "PID settings for channel {}", channel);
|
||||
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
|
||||
);
|
||||
};
|
||||
match channels.channel_state(channel).pid.summary(channel).to_json() {
|
||||
Ok(buf) => {
|
||||
send_line(&mut socket, &buf);
|
||||
}
|
||||
Err(e) =>
|
||||
error!("unable to serialize pid summary: {:?}", e),
|
||||
}
|
||||
show_pid_parameter!(kp);
|
||||
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) => {
|
||||
for channel in 0..CHANNELS {
|
||||
let i_set = channels.get_i(channel);
|
||||
let state = channels.channel_state(channel);
|
||||
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),
|
||||
);
|
||||
}
|
||||
CenterPoint::Override(volts) => {
|
||||
let _ = writeln!(
|
||||
socket, "center={:.3} V",
|
||||
volts,
|
||||
);
|
||||
match channels.pwm_summary(channel).to_json() {
|
||||
Ok(buf) => {
|
||||
send_line(&mut socket, &buf);
|
||||
}
|
||||
Err(e) =>
|
||||
error!("unable to serialize pwm summary: {:?}", e),
|
||||
}
|
||||
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) => {
|
||||
for channel in 0..CHANNELS {
|
||||
let state = channels.channel_state(channel);
|
||||
let _ = writeln!(
|
||||
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),
|
||||
);
|
||||
match channels.steinhart_hart_summary(channel).to_json() {
|
||||
Ok(buf) => {
|
||||
send_line(&mut socket, &buf);
|
||||
}
|
||||
_ => {}
|
||||
Err(e) =>
|
||||
error!("unable to serialize steinhart-hart summary: {:?}", e),
|
||||
}
|
||||
let _ = writeln!(socket, "");
|
||||
}
|
||||
}
|
||||
Command::Show(ShowCommand::PostFilter) => {
|
||||
for channel in 0..CHANNELS {
|
||||
match channels.adc.get_postfilter(channel as u8).unwrap() {
|
||||
Some(filter) => {
|
||||
let _ = writeln!(
|
||||
socket, "channel {}: postfilter={:.2} SPS",
|
||||
channel, filter.output_rate().unwrap()
|
||||
);
|
||||
}
|
||||
None => {
|
||||
let _ = writeln!(
|
||||
socket, "channel {}: postfilter disabled",
|
||||
channel
|
||||
);
|
||||
match channels.postfilter_summary(channel).to_json() {
|
||||
Ok(buf) => {
|
||||
send_line(&mut socket, &buf);
|
||||
}
|
||||
Err(e) =>
|
||||
error!("unable to serialize postfilter summary: {:?}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
35
src/pid.rs
35
src/pid.rs
|
@ -81,11 +81,35 @@ impl Controller {
|
|||
output
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn reset(&mut self) {
|
||||
self.integral = 0.0;
|
||||
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)]
|
||||
|
@ -126,4 +150,13 @@ mod test {
|
|||
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'}');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,9 +8,12 @@ use uom::si::{
|
|||
ratio::ratio,
|
||||
thermodynamic_temperature::{degree_celsius, kelvin},
|
||||
};
|
||||
use serde::Serialize;
|
||||
|
||||
type JsonBuffer = heapless::Vec<u8, heapless::consts::U200>;
|
||||
|
||||
/// Steinhart-Hart equation parameters
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub struct Parameters {
|
||||
/// Base temperature
|
||||
pub t0: ThermodynamicTemperature,
|
||||
|
@ -30,6 +33,10 @@ impl Parameters {
|
|||
let inv_temp = 1.0 / self.t0.get::<kelvin>() + (r / self.r0).get::<ratio>().ln() / self.b;
|
||||
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 {
|
||||
|
|
Loading…
Reference in New Issue