Compare commits

..

1 Commits

Author SHA1 Message Date
865366a3fc channels: get_i -> get_i_set 2024-08-13 17:28:15 +08:00
11 changed files with 265 additions and 210 deletions

3
.gitignore vendored
View File

@ -1,5 +1,2 @@
target/ target/
result result
*.bin
__pycache__/

View File

@ -84,7 +84,9 @@ invalidate the first line of input.
### Reading ADC input ### Reading ADC input
ADC input data is provided in reports. Query for the latest report with the command `report`. See the *Reports* section below. Set report mode to `on` for a continuous stream of input data.
The scope of this setting is per TCP session.
### TCP commands ### TCP commands
@ -93,14 +95,15 @@ Send commands as simple text string terminated by `\n`. Responses are
formatted as line-delimited JSON. formatted as line-delimited JSON.
| Syntax | Function | | Syntax | Function |
|------------------------------------------- |-------------------------------------------------------------------------------| |----------------------------------|-------------------------------------------------------------------------------|
| `report` | Show current input | | `report` | Show current input |
| `report mode` | Show current report mode |
| `report mode <off/on>` | Set report mode |
| `pwm` | Show current PWM settings | | `pwm` | Show current PWM settings |
| `pwm <0/1> max_i_pos <amp>` | Set maximum positive output current, clamped to [0, 2] | | `pwm <0/1> max_i_pos <amp>` | Set maximum positive output current |
| `pwm <0/1> max_i_neg <amp>` | Set maximum negative output current, clamped to [0, 2] | | `pwm <0/1> max_i_neg <amp>` | Set maximum negative output current |
| `pwm <0/1> max_v <volt>` | Set maximum output voltage, clamped to [0, 4] | | `pwm <0/1> max_v <volt>` | Set maximum output voltage |
| `pwm <0/1> i_set <amp>` | Disengage PID, set fixed output current, clamped to [-2, 2] | | `pwm <0/1> i_set <amp>` | Disengage PID, set fixed output current |
| `pwm <0/1> polarity <normal/reversed>` | Set output current polarity, with 'normal' being the front panel polarity |
| `pwm <0/1> pid` | Let output current to be controlled by the PID | | `pwm <0/1> pid` | Let output current to be controlled by the PID |
| `center <0/1> <volt>` | Set the MAX1968 0A-centerpoint to the specified fixed voltage | | `center <0/1> <volt>` | Set the MAX1968 0A-centerpoint to the specified fixed voltage |
| `center <0/1> vref` | Set the MAX1968 0A-centerpoint to measure from VREF | | `center <0/1> vref` | Set the MAX1968 0A-centerpoint to measure from VREF |
@ -183,8 +186,6 @@ postfilter rate can be tuned with the `postfilter` command.
When using a TEC module with the Thermostat, the Thermostat expects the thermal load (where the thermistor is located) to cool down with a positive software current set point, and heat up with a negative current set point. When using a TEC module with the Thermostat, the Thermostat expects the thermal load (where the thermistor is located) to cool down with a positive software current set point, and heat up with a negative current set point.
If the Thermostat is used for temperature control with the Sinara 5432 DAC "Zotino", and is connected via an IDC cable, the TEC polarity may need to be reversed with the `pwm <ch> polarity reversed` TCP command.
Testing heat flow direction with a low set current is recommended before installation of the TEC module. Testing heat flow direction with a low set current is recommended before installation of the TEC module.
### Limits ### Limits
@ -250,7 +251,8 @@ pwm 0 pid
## Reports ## Reports
Use the bare `report` command to obtain a single report. Reports are JSON objects Use the bare `report` command to obtain a single report. Enable
continuous reporting with `report mode on`. Reports are JSON objects
with the following keys. with the following keys.
| Key | Unit | Description | | Key | Unit | Description |

View File

@ -63,7 +63,7 @@
name = "thermostat-dev-shell"; name = "thermostat-dev-shell";
packages = with pkgs; [ packages = with pkgs; [
rust llvm rust llvm
openocd dfu-util rlwrap openocd dfu-util
] ++ (with python3Packages; [ ] ++ (with python3Packages; [
numpy matplotlib numpy matplotlib
]); ]);

View File

@ -1,7 +1,6 @@
import socket import socket
import json import json
import logging import logging
import time
class CommandError(Exception): class CommandError(Exception):
pass pass
@ -16,7 +15,7 @@ class Client:
pwm_report = self.get_pwm() pwm_report = self.get_pwm()
for pwm_channel in pwm_report: for pwm_channel in pwm_report:
for limit in ["max_i_neg", "max_i_pos", "max_v"]: for limit in ["max_i_neg", "max_i_pos", "max_v"]:
if pwm_channel[limit] == 0.0: if pwm_channel[limit]["value"] == 0.0:
logging.warning("`{}` limit is set to zero on channel {}".format(limit, pwm_channel["channel"])) logging.warning("`{}` limit is set to zero on channel {}".format(limit, pwm_channel["channel"]))
def _read_line(self): def _read_line(self):
@ -53,18 +52,16 @@ class Client:
Example:: Example::
[{'channel': 0, [{'channel': 0,
'center': 'vref', 'center': 'vref',
'i_set': -0.02002179650216762, 'i_set': {'max': 2.9802790335151985, 'value': -0.02002179650216762},
'max_i_neg': 2.0, 'max_i_neg': {'max': 3.0, 'value': 3.0},
'max_v': 3.988, 'max_v': {'max': 5.988, 'value': 5.988},
'max_i_pos': 2.0, 'max_i_pos': {'max': 3.0, 'value': 3.0}},
'polarity': 'normal',
{'channel': 1, {'channel': 1,
'center': 'vref', 'center': 'vref',
'i_set': -0.02002179650216762, 'i_set': {'max': 2.9802790335151985, 'value': -0.02002179650216762},
'max_i_neg': 2.0, 'max_i_neg': {'max': 3.0, 'value': 3.0},
'max_v': 3.988, 'max_v': {'max': 5.988, 'value': 5.988},
'max_i_pos': 2.0} 'max_i_pos': {'max': 3.0, 'value': 3.0}}
'polarity': 'normal',
] ]
""" """
return self._get_conf("pwm") return self._get_conf("pwm")
@ -129,8 +126,9 @@ class Client:
'tec_u_meas': 2.5340000000000003, 'tec_u_meas': 2.5340000000000003,
'pid_output': 2.067581958092247} 'pid_output': 2.067581958092247}
""" """
self._command("report mode", "on")
while True: while True:
self._socket.sendall("report\n".encode('utf-8'))
line = self._read_line() line = self._read_line()
if not line: if not line:
break break
@ -138,7 +136,6 @@ class Client:
yield json.loads(line) yield json.loads(line)
except json.decoder.JSONDecodeError: except json.decoder.JSONDecodeError:
pass pass
time.sleep(0.05)
def set_param(self, topic, channel, field="", value=""): def set_param(self, topic, channel, field="", value=""):
"""Set configuration parameters """Set configuration parameters

View File

@ -16,9 +16,8 @@ use uom::si::{
use crate::{ use crate::{
ad7172, ad7172,
pid, pid,
config::PwmLimits,
steinhart_hart as sh, steinhart_hart as sh,
command_parser::{CenterPoint, Polarity}, command_parser::CenterPoint,
}; };
const R_INNER: f64 = 2.0 * 5100.0; const R_INNER: f64 = 2.0 * 5100.0;
@ -33,11 +32,9 @@ pub struct ChannelState {
pub center: CenterPoint, pub center: CenterPoint,
pub dac_value: ElectricPotential, pub dac_value: ElectricPotential,
pub i_set: ElectricCurrent, pub i_set: ElectricCurrent,
pub pwm_limits: PwmLimits,
pub pid_engaged: bool, pub pid_engaged: bool,
pub pid: pid::Controller, pub pid: pid::Controller,
pub sh: sh::Parameters, pub sh: sh::Parameters,
pub polarity: Polarity,
} }
impl ChannelState { impl ChannelState {
@ -51,15 +48,9 @@ impl ChannelState {
center: CenterPoint::Vref, center: CenterPoint::Vref,
dac_value: ElectricPotential::new::<volt>(0.0), dac_value: ElectricPotential::new::<volt>(0.0),
i_set: ElectricCurrent::new::<ampere>(0.0), i_set: ElectricCurrent::new::<ampere>(0.0),
pwm_limits: PwmLimits {
max_v: 0.0,
max_i_pos: 0.0,
max_i_neg: 0.0,
},
pid_engaged: false, pid_engaged: false,
pid: pid::Controller::new(pid::Parameters::default()), pid: pid::Controller::new(pid::Parameters::default()),
sh: sh::Parameters::default(), sh: sh::Parameters::default(),
polarity: Polarity::Normal,
} }
} }

View File

@ -17,12 +17,11 @@ use crate::{
ad7172, ad7172,
channel::{Channel, Channel0, Channel1}, channel::{Channel, Channel0, Channel1},
channel_state::ChannelState, channel_state::ChannelState,
command_parser::{CenterPoint, PwmPin, Polarity}, command_parser::{CenterPoint, PwmPin},
command_handler::JsonBuffer, command_handler::JsonBuffer,
pins::{self, Channel0VRef, Channel1VRef}, pins::{self, Channel0VRef, Channel1VRef},
steinhart_hart, steinhart_hart,
}; };
use crate::timer::sleep;
pub enum PinsAdcReadTarget { pub enum PinsAdcReadTarget {
VREF, VREF,
@ -45,11 +44,7 @@ pub const MAX_TEC_V: ElectricPotential = ElectricPotential {
units: PhantomData, units: PhantomData,
value: 4.0, value: 4.0,
}; };
const MAX_TEC_I_DUTY_TO_CURRENT_RATE: ElectricCurrent = ElectricCurrent {
dimension: PhantomData,
units: PhantomData,
value: 1.0 / (10.0 * R_SENSE / 3.3),
};
// DAC chip outputs 0-5v, which is then passed through a resistor dividor to provide 0-3v range // DAC chip outputs 0-5v, which is then passed through a resistor dividor to provide 0-3v range
const DAC_OUT_V_MAX: ElectricPotential = ElectricPotential { const DAC_OUT_V_MAX: ElectricPotential = ElectricPotential {
dimension: PhantomData, dimension: PhantomData,
@ -158,11 +153,6 @@ impl Channels {
pub fn set_i(&mut self, channel: usize, i_set: ElectricCurrent) -> ElectricCurrent { pub fn set_i(&mut self, channel: usize, i_set: ElectricCurrent) -> ElectricCurrent {
let i_set = i_set.min(MAX_TEC_I).max(-MAX_TEC_I); let i_set = i_set.min(MAX_TEC_I).max(-MAX_TEC_I);
self.channel_state(channel).i_set = i_set;
let negate = match self.channel_state(channel).polarity {
Polarity::Normal => 1.0,
Polarity::Reversed => -1.0,
};
let vref_meas = match channel.into() { let vref_meas = match channel.into() {
0 => self.channel0.vref_meas, 0 => self.channel0.vref_meas,
1 => self.channel1.vref_meas, 1 => self.channel1.vref_meas,
@ -170,9 +160,10 @@ impl Channels {
}; };
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 = negate * i_set * 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);
let i_set = negate * (voltage - center_point) / (10.0 * r_sense); let i_set = (voltage - center_point) / (10.0 * r_sense);
self.channel_state(channel).i_set = i_set;
i_set i_set
} }
@ -278,6 +269,17 @@ impl Channels {
} }
} }
pub fn read_dac_feedback_until_stable(&mut self, channel: usize, tolerance: ElectricPotential) -> ElectricPotential {
let mut prev = self.adc_read(channel, PinsAdcReadTarget::DacVfb, 1);
loop {
let current = self.adc_read(channel, PinsAdcReadTarget::DacVfb, 1);
if (current - prev).abs() < tolerance {
return current;
}
prev = current;
}
}
/// Calibrates the DAC output to match vref of the MAX driver to reduce zero-current offset of the MAX driver output. /// Calibrates the DAC output to match vref of the MAX driver to reduce zero-current offset of the MAX driver output.
/// ///
/// The thermostat DAC applies a control voltage signal to the CTLI pin of MAX driver chip to control its output current. /// The thermostat DAC applies a control voltage signal to the CTLI pin of MAX driver chip to control its output current.
@ -304,7 +306,8 @@ impl Channels {
let mut start_value = 1; let mut start_value = 1;
let mut best_error = ElectricPotential::new::<volt>(100.0); let mut best_error = ElectricPotential::new::<volt>(100.0);
for step in (5..18).rev() { for step in (0..18).rev() {
let mut prev_value = start_value;
for value in (start_value..=ad5680::MAX_VALUE).step_by(1 << step) { for value in (start_value..=ad5680::MAX_VALUE).step_by(1 << step) {
match channel { match channel {
0 => { 0 => {
@ -315,15 +318,14 @@ impl Channels {
} }
_ => unreachable!(), _ => unreachable!(),
} }
sleep(10);
let dac_feedback = self.adc_read(channel, PinsAdcReadTarget::DacVfb, 64); let dac_feedback = self.read_dac_feedback_until_stable(channel, ElectricPotential::new::<volt>(0.001));
let error = target_voltage - dac_feedback; let error = target_voltage - dac_feedback;
if error < ElectricPotential::new::<volt>(0.0) { if error < ElectricPotential::new::<volt>(0.0) {
break; break;
} else if error < best_error { } else if error < best_error {
best_error = error; best_error = error;
start_value = value; start_value = prev_value;
let vref = (value as f64 / ad5680::MAX_VALUE as f64) * DAC_OUT_V_MAX; let vref = (value as f64 / ad5680::MAX_VALUE as f64) * DAC_OUT_V_MAX;
match channel { match channel {
@ -332,6 +334,8 @@ impl Channels {
_ => unreachable!(), _ => unreachable!(),
} }
} }
prev_value = value;
} }
} }
@ -357,25 +361,53 @@ impl Channels {
} }
} }
pub fn get_max_v(&mut self, channel: usize) -> ElectricPotential { fn get_pwm(&self, channel: usize, pin: PwmPin) -> f64 {
ElectricPotential::new::<volt>(self.channel_state(channel).pwm_limits.max_v) fn get<P: hal::PwmPin<Duty=u16>>(pin: &P) -> f64 {
let duty = pin.get_duty();
let max = pin.get_max_duty();
duty as f64 / (max as f64)
}
match (channel, pin) {
(_, PwmPin::ISet) =>
panic!("i_set is no pwm pin"),
(0, PwmPin::MaxIPos) =>
get(&self.pwm.max_i_pos0),
(0, PwmPin::MaxINeg) =>
get(&self.pwm.max_i_neg0),
(0, PwmPin::MaxV) =>
get(&self.pwm.max_v0),
(1, PwmPin::MaxIPos) =>
get(&self.pwm.max_i_pos1),
(1, PwmPin::MaxINeg) =>
get(&self.pwm.max_i_neg1),
(1, PwmPin::MaxV) =>
get(&self.pwm.max_v1),
_ =>
unreachable!(),
}
} }
pub fn get_max_i_pos(&mut self, channel: usize) -> ElectricCurrent { pub fn get_max_v(&mut self, channel: usize) -> (ElectricPotential, ElectricPotential) {
ElectricCurrent::new::<ampere>(self.channel_state(channel).pwm_limits.max_i_pos) let max = 4.0 * ElectricPotential::new::<volt>(3.3);
let duty = self.get_pwm(channel, PwmPin::MaxV);
(duty * max, MAX_TEC_V)
} }
pub fn get_max_i_neg(&mut self, channel: usize) -> ElectricCurrent { pub fn get_max_i_pos(&mut self, channel: usize) -> (ElectricCurrent, ElectricCurrent) {
ElectricCurrent::new::<ampere>(self.channel_state(channel).pwm_limits.max_i_neg) let max = ElectricCurrent::new::<ampere>(3.0);
let duty = self.get_pwm(channel, PwmPin::MaxIPos);
(duty * max, MAX_TEC_I)
}
pub fn get_max_i_neg(&mut self, channel: usize) -> (ElectricCurrent, ElectricCurrent) {
let max = ElectricCurrent::new::<ampere>(3.0);
let duty = self.get_pwm(channel, PwmPin::MaxINeg);
(duty * max, MAX_TEC_I)
} }
// Get current passing through TEC // Get current passing through TEC
pub fn get_tec_i(&mut self, channel: usize) -> ElectricCurrent { pub fn get_tec_i(&mut self, channel: usize) -> ElectricCurrent {
let tec_i = (self.adc_read(channel, PinsAdcReadTarget::ITec, 16) - self.adc_read(channel, PinsAdcReadTarget::VREF, 16)) / ElectricalResistance::new::<ohm>(0.4); (self.adc_read(channel, PinsAdcReadTarget::ITec, 16) - self.adc_read(channel, PinsAdcReadTarget::VREF, 16)) / ElectricalResistance::new::<ohm>(0.4)
match self.channel_state(channel).polarity {
Polarity::Normal => tec_i,
Polarity::Reversed => -tec_i,
}
} }
// Get voltage across TEC // Get voltage across TEC
@ -412,48 +444,23 @@ impl Channels {
pub fn set_max_v(&mut self, channel: usize, max_v: ElectricPotential) -> (ElectricPotential, ElectricPotential) { pub fn set_max_v(&mut self, channel: usize, max_v: ElectricPotential) -> (ElectricPotential, ElectricPotential) {
let max = 4.0 * ElectricPotential::new::<volt>(3.3); let max = 4.0 * ElectricPotential::new::<volt>(3.3);
let max_v = max_v.min(MAX_TEC_V).max(ElectricPotential::zero()); let duty = (max_v.min(MAX_TEC_V).max(ElectricPotential::zero()) / max).get::<ratio>();
let duty = (max_v / max).get::<ratio>();
let duty = self.set_pwm(channel, PwmPin::MaxV, duty); let duty = self.set_pwm(channel, PwmPin::MaxV, duty);
self.channel_state(channel).pwm_limits.max_v = max_v.get::<volt>();
(duty * max, max) (duty * max, max)
} }
pub fn set_max_i_pos(&mut self, channel: usize, max_i_pos: ElectricCurrent) -> (ElectricCurrent, ElectricCurrent) { pub fn set_max_i_pos(&mut self, channel: usize, max_i_pos: ElectricCurrent) -> (ElectricCurrent, ElectricCurrent) {
let max = ElectricCurrent::new::<ampere>(3.0); let max = ElectricCurrent::new::<ampere>(3.0);
let max_i_pos = max_i_pos.min(MAX_TEC_I).max(ElectricCurrent::zero()); let duty = (max_i_pos.min(MAX_TEC_I).max(ElectricCurrent::zero()) / max).get::<ratio>();
let duty = (max_i_pos / MAX_TEC_I_DUTY_TO_CURRENT_RATE).get::<ratio>(); let duty = self.set_pwm(channel, PwmPin::MaxIPos, duty);
let duty = match self.channel_state(channel).polarity { (duty * max, max)
Polarity::Normal => self.set_pwm(channel, PwmPin::MaxIPos, duty),
Polarity::Reversed => self.set_pwm(channel, PwmPin::MaxINeg, duty),
};
self.channel_state(channel).pwm_limits.max_i_pos = max_i_pos.get::<ampere>();
(duty * MAX_TEC_I_DUTY_TO_CURRENT_RATE, max)
} }
pub fn set_max_i_neg(&mut self, channel: usize, max_i_neg: ElectricCurrent) -> (ElectricCurrent, ElectricCurrent) { pub fn set_max_i_neg(&mut self, channel: usize, max_i_neg: ElectricCurrent) -> (ElectricCurrent, ElectricCurrent) {
let max = ElectricCurrent::new::<ampere>(3.0); let max = ElectricCurrent::new::<ampere>(3.0);
let max_i_neg = max_i_neg.min(MAX_TEC_I).max(ElectricCurrent::zero()); let duty = (max_i_neg.min(MAX_TEC_I).max(ElectricCurrent::zero()) / max).get::<ratio>();
let duty = (max_i_neg / MAX_TEC_I_DUTY_TO_CURRENT_RATE).get::<ratio>(); let duty = self.set_pwm(channel, PwmPin::MaxINeg, duty);
let duty = match self.channel_state(channel).polarity { (duty * max, max)
Polarity::Normal => self.set_pwm(channel, PwmPin::MaxINeg, duty),
Polarity::Reversed => self.set_pwm(channel, PwmPin::MaxIPos, duty),
};
self.channel_state(channel).pwm_limits.max_i_neg = max_i_neg.get::<ampere>();
(duty * MAX_TEC_I_DUTY_TO_CURRENT_RATE, max)
}
pub fn set_polarity(&mut self, channel: usize, polarity: Polarity) {
if self.channel_state(channel).polarity != polarity {
let i_set = self.channel_state(channel).i_set;
let max_i_pos = self.get_max_i_pos(channel);
let max_i_neg = self.get_max_i_neg(channel);
self.channel_state(channel).polarity = polarity;
self.set_i(channel, i_set);
self.set_max_i_pos(channel, max_i_pos);
self.set_max_i_neg(channel, max_i_neg);
}
} }
fn report(&mut self, channel: usize) -> Report { fn report(&mut self, channel: usize) -> Report {
@ -511,11 +518,10 @@ impl Channels {
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_set(channel), i_set: (self.get_i_set(channel), MAX_TEC_I).into(),
max_v: self.get_max_v(channel), max_v: self.get_max_v(channel).into(),
max_i_pos: self.get_max_i_pos(channel), max_i_pos: self.get_max_i_pos(channel).into(),
max_i_neg: self.get_max_i_neg(channel), max_i_neg: self.get_max_i_neg(channel).into(),
polarity: PolarityJson(self.channel_state(channel).polarity.clone()),
} }
} }
@ -597,18 +603,15 @@ impl Serialize for CenterPointJson {
} }
} }
pub struct PolarityJson(Polarity); #[derive(Serialize)]
pub struct PwmSummaryField<T: Serialize> {
value: T,
max: T,
}
// used in JSON encoding, not for config impl<T: Serialize> From<(T, T)> for PwmSummaryField<T> {
impl Serialize for PolarityJson { fn from((value, max): (T, T)) -> Self {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> PwmSummaryField { value, max }
where
S: Serializer,
{
serializer.serialize_str(match self.0 {
Polarity::Normal => "normal",
Polarity::Reversed => "reversed",
})
} }
} }
@ -616,11 +619,10 @@ impl Serialize for PolarityJson {
pub struct PwmSummary { pub struct PwmSummary {
channel: usize, channel: usize,
center: CenterPointJson, center: CenterPointJson,
i_set: ElectricCurrent, i_set: PwmSummaryField<ElectricCurrent>,
max_v: ElectricPotential, max_v: PwmSummaryField<ElectricPotential>,
max_i_pos: ElectricCurrent, max_i_pos: PwmSummaryField<ElectricCurrent>,
max_i_neg: ElectricCurrent, max_i_neg: PwmSummaryField<ElectricCurrent>,
polarity: PolarityJson,
} }
#[derive(Serialize)] #[derive(Serialize)]

View File

@ -11,8 +11,7 @@ use super::{
CenterPoint, CenterPoint,
PidParameter, PidParameter,
PwmPin, PwmPin,
ShParameter, ShParameter
Polarity
}, },
ad7172, ad7172,
CHANNEL_CONFIG_KEY, CHANNEL_CONFIG_KEY,
@ -23,6 +22,7 @@ use super::{
config::ChannelConfig, config::ChannelConfig,
dfu, dfu,
flash_store::FlashStore, flash_store::FlashStore,
session::Session,
FanCtrl, FanCtrl,
hw_rev::HWRev, hw_rev::HWRev,
}; };
@ -87,6 +87,16 @@ fn send_line(socket: &mut TcpSocket, data: &[u8]) -> bool {
impl Handler { impl Handler {
fn reporting(socket: &mut TcpSocket) -> Result<Handler, Error> {
send_line(socket, b"{}");
Ok(Handler::Handled)
}
fn show_report_mode(socket: &mut TcpSocket, session: &Session) -> Result<Handler, Error> {
let _ = writeln!(socket, "{{ \"report\": {:?} }}", session.reporting());
Ok(Handler::Handled)
}
fn show_report(socket: &mut TcpSocket, channels: &mut Channels) -> Result<Handler, Error> { fn show_report(socket: &mut TcpSocket, channels: &mut Channels) -> Result<Handler, Error> {
match channels.reports_json() { match channels.reports_json() {
Ok(buf) => { Ok(buf) => {
@ -171,12 +181,6 @@ impl Handler {
Ok(Handler::Handled) Ok(Handler::Handled)
} }
fn set_polarity(socket: &mut TcpSocket, channels: &mut Channels, channel: usize, polarity: Polarity) -> Result<Handler, Error> {
channels.set_polarity(channel, polarity);
send_line(socket, b"{}");
Ok(Handler::Handled)
}
fn set_pwm (socket: &mut TcpSocket, channels: &mut Channels, channel: usize, pin: PwmPin, value: f64) -> Result<Handler, Error> { fn set_pwm (socket: &mut TcpSocket, channels: &mut Channels, channel: usize, pin: PwmPin, value: f64) -> Result<Handler, Error> {
match pin { match pin {
PwmPin::ISet => { PwmPin::ISet => {
@ -408,9 +412,11 @@ impl Handler {
} }
} }
pub fn handle_command(command: Command, socket: &mut TcpSocket, channels: &mut Channels, store: &mut FlashStore, ipv4_config: &mut Ipv4Config, fan_ctrl: &mut FanCtrl, hwrev: HWRev) -> Result<Self, Error> { pub fn handle_command(command: Command, socket: &mut TcpSocket, channels: &mut Channels, session: &Session, store: &mut FlashStore, ipv4_config: &mut Ipv4Config, fan_ctrl: &mut FanCtrl, hwrev: HWRev) -> Result<Self, Error> {
match command { match command {
Command::Quit => Ok(Handler::CloseSocket), Command::Quit => Ok(Handler::CloseSocket),
Command::Reporting(_reporting) => Handler::reporting(socket),
Command::Show(ShowCommand::Reporting) => Handler::show_report_mode(socket, session),
Command::Show(ShowCommand::Input) => Handler::show_report(socket, channels), Command::Show(ShowCommand::Input) => Handler::show_report(socket, channels),
Command::Show(ShowCommand::Pid) => Handler::show_pid(socket, channels), Command::Show(ShowCommand::Pid) => Handler::show_pid(socket, channels),
Command::Show(ShowCommand::Pwm) => Handler::show_pwm(socket, channels), Command::Show(ShowCommand::Pwm) => Handler::show_pwm(socket, channels),
@ -418,7 +424,6 @@ impl Handler {
Command::Show(ShowCommand::PostFilter) => Handler::show_post_filter(socket, channels), Command::Show(ShowCommand::PostFilter) => Handler::show_post_filter(socket, channels),
Command::Show(ShowCommand::Ipv4) => Handler::show_ipv4(socket, ipv4_config), Command::Show(ShowCommand::Ipv4) => Handler::show_ipv4(socket, ipv4_config),
Command::PwmPid { channel } => Handler::engage_pid(socket, channels, channel), Command::PwmPid { channel } => Handler::engage_pid(socket, channels, channel),
Command::PwmPolarity { channel, polarity } => Handler::set_polarity(socket, channels, channel, polarity),
Command::Pwm { channel, pin, value } => Handler::set_pwm(socket, channels, channel, pin, value), Command::Pwm { channel, pin, value } => Handler::set_pwm(socket, channels, channel, pin, value),
Command::CenterPoint { channel, center } => Handler::set_center_point(socket, channels, channel, center), Command::CenterPoint { channel, center } => Handler::set_center_point(socket, channels, channel, center),
Command::Pid { channel, parameter, value } => Handler::set_pid(socket, channels, channel, parameter, value), Command::Pid { channel, parameter, value } => Handler::set_pid(socket, channels, channel, parameter, value),

View File

@ -96,6 +96,7 @@ pub struct Ipv4Config {
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum ShowCommand { pub enum ShowCommand {
Input, Input,
Reporting,
Pwm, Pwm,
Pid, Pid,
SteinhartHart, SteinhartHart,
@ -135,12 +136,6 @@ pub enum CenterPoint {
Override(f32), Override(f32),
} }
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Polarity {
Normal,
Reversed,
}
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum Command { pub enum Command {
Quit, Quit,
@ -153,6 +148,7 @@ pub enum Command {
Reset, Reset,
Ipv4(Ipv4Config), Ipv4(Ipv4Config),
Show(ShowCommand), Show(ShowCommand),
Reporting(bool),
/// PWM parameter setting /// PWM parameter setting
Pwm { Pwm {
channel: usize, channel: usize,
@ -163,10 +159,6 @@ pub enum Command {
PwmPid { PwmPid {
channel: usize, channel: usize,
}, },
PwmPolarity {
channel: usize,
polarity: Polarity,
},
CenterPoint { CenterPoint {
channel: usize, channel: usize,
center: CenterPoint, center: CenterPoint,
@ -241,6 +233,12 @@ fn float(input: &[u8]) -> IResult<&[u8], Result<f64, Error>> {
Ok((input, result)) Ok((input, result))
} }
fn off_on(input: &[u8]) -> IResult<&[u8], bool> {
alt((value(false, tag("off")),
value(true, tag("on"))
))(input)
}
fn channel(input: &[u8]) -> IResult<&[u8], usize> { fn channel(input: &[u8]) -> IResult<&[u8], usize> {
map(one_of("01"), |c| (c as usize) - ('0' as usize))(input) map(one_of("01"), |c| (c as usize) - ('0' as usize))(input)
} }
@ -248,8 +246,24 @@ fn channel(input: &[u8]) -> IResult<&[u8], usize> {
fn report(input: &[u8]) -> IResult<&[u8], Command> { fn report(input: &[u8]) -> IResult<&[u8], Command> {
preceded( preceded(
tag("report"), tag("report"),
alt((
preceded(
whitespace,
preceded(
tag("mode"),
alt((
preceded(
whitespace,
// `report mode <on | off>` - Switch repoting mode
map(off_on, Command::Reporting)
),
// `report mode` - Show current reporting state
value(Command::Show(ShowCommand::Reporting), end)
))
)),
// `report` - Report once // `report` - Report once
value(Command::Show(ShowCommand::Input), end) value(Command::Show(ShowCommand::Input), end)
))
)(input) )(input)
} }
@ -307,18 +321,6 @@ fn pwm_pid(input: &[u8]) -> IResult<&[u8], ()> {
value((), tag("pid"))(input) value((), tag("pid"))(input)
} }
fn pwm_polarity(input: &[u8]) -> IResult<&[u8], Polarity> {
preceded(
tag("polarity"),
preceded(
whitespace,
alt((value(Polarity::Normal, tag("normal")),
value(Polarity::Reversed, tag("reversed"))
))
)
)(input)
}
fn pwm(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> { fn pwm(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
let (input, _) = tag("pwm")(input)?; let (input, _) = tag("pwm")(input)?;
alt(( alt((
@ -331,10 +333,6 @@ fn pwm(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
let (input, ()) = pwm_pid(input)?; let (input, ()) = pwm_pid(input)?;
Ok((input, Ok(Command::PwmPid { channel }))) Ok((input, Ok(Command::PwmPid { channel })))
}, },
|input| {
let (input, polarity) = pwm_polarity(input)?;
Ok((input, Ok(Command::PwmPolarity { channel, polarity })))
},
|input| { |input| {
let (input, config) = pwm_setup(input)?; let (input, config) = pwm_setup(input)?;
match config { match config {
@ -684,6 +682,24 @@ mod test {
assert_eq!(command, Ok(Command::Show(ShowCommand::Input))); assert_eq!(command, Ok(Command::Show(ShowCommand::Input)));
} }
#[test]
fn parse_report_mode() {
let command = Command::parse(b"report mode");
assert_eq!(command, Ok(Command::Show(ShowCommand::Reporting)));
}
#[test]
fn parse_report_mode_on() {
let command = Command::parse(b"report mode on");
assert_eq!(command, Ok(Command::Reporting(true)));
}
#[test]
fn parse_report_mode_off() {
let command = Command::parse(b"report mode off");
assert_eq!(command, Ok(Command::Reporting(false)));
}
#[test] #[test]
fn parse_pwm_i_set() { fn parse_pwm_i_set() {
let command = Command::parse(b"pwm 1 i_set 16383"); let command = Command::parse(b"pwm 1 i_set 16383");
@ -694,15 +710,6 @@ mod test {
})); }));
} }
#[test]
fn parse_pwm_polarity() {
let command = Command::parse(b"pwm 0 polarity reversed");
assert_eq!(command, Ok(Command::PwmPolarity {
channel: 0,
polarity: Polarity::Reversed,
}));
}
#[test] #[test]
fn parse_pwm_pid() { fn parse_pwm_pid() {
let command = Command::parse(b"pwm 0 pid"); let command = Command::parse(b"pwm 0 pid");

View File

@ -8,7 +8,7 @@ use uom::si::{
use crate::{ use crate::{
ad7172::PostFilter, ad7172::PostFilter,
channels::Channels, channels::Channels,
command_parser::{CenterPoint, Polarity}, command_parser::CenterPoint,
pid, pid,
steinhart_hart, steinhart_hart,
}; };
@ -20,7 +20,6 @@ pub struct ChannelConfig {
pid_target: f32, pid_target: f32,
pid_engaged: bool, pid_engaged: bool,
i_set: ElectricCurrent, i_set: ElectricCurrent,
polarity: Polarity,
sh: steinhart_hart::Parameters, sh: steinhart_hart::Parameters,
pwm: PwmLimits, pwm: PwmLimits,
/// uses variant `PostFilter::Invalid` instead of `None` to save space /// uses variant `PostFilter::Invalid` instead of `None` to save space
@ -46,8 +45,7 @@ impl ChannelConfig {
pid: state.pid.parameters.clone(), pid: state.pid.parameters.clone(),
pid_target: state.pid.target as f32, pid_target: state.pid.target as f32,
pid_engaged: state.pid_engaged, pid_engaged: state.pid_engaged,
i_set, i_set: i_set,
polarity: state.polarity.clone(),
sh: state.sh.clone(), sh: state.sh.clone(),
pwm, pwm,
adc_postfilter, adc_postfilter,
@ -70,22 +68,21 @@ impl ChannelConfig {
}; };
let _ = channels.adc.set_postfilter(channel as u8, adc_postfilter); let _ = channels.adc.set_postfilter(channel as u8, adc_postfilter);
let _ = channels.set_i(channel, self.i_set); let _ = channels.set_i(channel, self.i_set);
channels.set_polarity(channel, self.polarity.clone());
} }
} }
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct PwmLimits { struct PwmLimits {
pub max_v: f64, max_v: f64,
pub max_i_pos: f64, max_i_pos: f64,
pub max_i_neg: f64, max_i_neg: f64,
} }
impl PwmLimits { impl PwmLimits {
pub fn new(channels: &mut Channels, channel: usize) -> Self { pub fn new(channels: &mut Channels, channel: usize) -> Self {
let max_v = channels.get_max_v(channel); let (max_v, _) = channels.get_max_v(channel);
let max_i_pos = channels.get_max_i_pos(channel); let (max_i_pos, _) = channels.get_max_i_pos(channel);
let max_i_neg = channels.get_max_i_neg(channel); let (max_i_neg, _) = channels.get_max_i_neg(channel);
PwmLimits { PwmLimits {
max_v: max_v.get::<volt>(), max_v: max_v.get::<volt>(),
max_i_pos: max_i_pos.get::<ampere>(), max_i_pos: max_i_pos.get::<ampere>(),

View File

@ -180,7 +180,10 @@ fn main() -> ! {
loop { loop {
let mut new_ipv4_config = None; let mut new_ipv4_config = None;
let instant = Instant::from_millis(i64::from(timer::now())); let instant = Instant::from_millis(i64::from(timer::now()));
channels.poll_adc(instant); let updated_channel = channels.poll_adc(instant);
if let Some(channel) = updated_channel {
server.for_each(|_, session| session.set_report_pending(channel.into()));
}
fan_ctrl.cycle(channels.current_abs_max_tec_i()); fan_ctrl.cycle(channels.current_abs_max_tec_i());
@ -213,7 +216,7 @@ fn main() -> ! {
// Do nothing and feed more data to the line reader in the next loop cycle. // Do nothing and feed more data to the line reader in the next loop cycle.
Ok(SessionInput::Nothing) => {} Ok(SessionInput::Nothing) => {}
Ok(SessionInput::Command(command)) => { Ok(SessionInput::Command(command)) => {
match Handler::handle_command(command, &mut socket, &mut channels, &mut store, &mut ipv4_config, &mut fan_ctrl, hwrev) { match Handler::handle_command(command, &mut socket, &mut channels, session, &mut store, &mut ipv4_config, &mut fan_ctrl, hwrev) {
Ok(Handler::NewIPV4(ip)) => new_ipv4_config = Some(ip), Ok(Handler::NewIPV4(ip)) => new_ipv4_config = Some(ip),
Ok(Handler::Handled) => {}, Ok(Handler::Handled) => {},
Ok(Handler::CloseSocket) => socket.close(), Ok(Handler::CloseSocket) => socket.close(),
@ -228,6 +231,19 @@ fn main() -> ! {
Err(_) => Err(_) =>
socket.close(), socket.close(),
} }
} else if socket.can_send() {
if let Some(channel) = session.is_report_pending() {
match channels.reports_json() {
Ok(buf) => {
send_line(&mut socket, &buf[..]);
session.mark_report_sent(channel);
}
Err(e) => {
error!("unable to serialize report: {:?}", e);
}
}
}
} }
}); });
} else { } else {

View File

@ -1,4 +1,5 @@
use super::command_parser::{Command, Error as ParserError}; use super::command_parser::{Command, Error as ParserError};
use super::channels::CHANNELS;
const MAX_LINE_LEN: usize = 64; const MAX_LINE_LEN: usize = 64;
@ -52,6 +53,8 @@ impl From<Result<Command, ParserError>> for SessionInput {
pub struct Session { pub struct Session {
reader: LineReader, reader: LineReader,
reporting: bool,
report_pending: [bool; CHANNELS],
} }
impl Default for Session { impl Default for Session {
@ -64,11 +67,43 @@ impl Session {
pub fn new() -> Self { pub fn new() -> Self {
Session { Session {
reader: LineReader::new(), reader: LineReader::new(),
reporting: false,
report_pending: [false; CHANNELS],
} }
} }
pub fn reset(&mut self) { pub fn reset(&mut self) {
self.reader = LineReader::new(); self.reader = LineReader::new();
self.reporting = false;
self.report_pending = [false; CHANNELS];
}
pub fn reporting(&self) -> bool {
self.reporting
}
pub fn set_report_pending(&mut self, channel: usize) {
if self.reporting {
self.report_pending[channel] = true;
}
}
pub fn is_report_pending(&self) -> Option<usize> {
if ! self.reporting {
None
} else {
self.report_pending.iter()
.enumerate()
.fold(None, |result, (channel, report_pending)| {
result.or_else(|| {
if *report_pending { Some(channel) } else { None }
})
})
}
}
pub fn mark_report_sent(&mut self, channel: usize) {
self.report_pending[channel] = false;
} }
pub fn feed(&mut self, buf: &[u8]) -> (usize, SessionInput) { pub fn feed(&mut self, buf: &[u8]) -> (usize, SessionInput) {
@ -79,6 +114,12 @@ impl Session {
match line { match line {
Some(line) => { Some(line) => {
let command = Command::parse(&line); let command = Command::parse(&line);
match command {
Ok(Command::Reporting(reporting)) => {
self.reporting = reporting;
}
_ => {}
}
return (buf_bytes, command.into()); return (buf_bytes, command.into());
} }
None => {} None => {}