Compare commits

..

3 Commits

Author SHA1 Message Date
0940520ded README: Document swap status in report 2024-09-23 18:04:06 +08:00
2c6b8e8186 add swap status to the report
Signed-off-by: Egor Savkin <es@m-labs.hk>
2024-09-23 18:04:06 +08:00
8f8baa0cee Add command for flipping output polarity
Effectively switches all values related to the directionality of current
through the TEC, including current maximums. The swapped status is
stored in the flash store.

To swap current directions, use the command "pwm <ch> polarity_swapped
<bool>", where <bool> is true when TEC current direction is reversed
from the front panel markings.

This is needed for IDC cable connections, since the IDC connector and
front panel connectors have flipped polarities.
2024-09-23 18:04:04 +08:00
9 changed files with 245 additions and 145 deletions

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,7 +95,7 @@ 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` | Show current report mode |
| `report mode <off/on>` | Set report mode | | `report mode <off/on>` | Set report mode |
@ -102,7 +104,7 @@ formatted as line-delimited JSON.
| `pwm <0/1> max_i_neg <amp>` | Set maximum negative output current | | `pwm <0/1> max_i_neg <amp>` | Set maximum negative output current |
| `pwm <0/1> max_v <volt>` | Set maximum output voltage | | `pwm <0/1> max_v <volt>` | Set maximum output voltage |
| `pwm <0/1> i_set <amp>` | Disengage PID, set fixed output current | | `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> polarity_swapped <false/true>` | Swap output current polarity on channel |
| `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 |
@ -185,8 +187,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
@ -252,7 +252,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 |
@ -263,6 +264,7 @@ with the following keys.
| `sens` | Ohms | Thermistor resistance derived from `adc` | | `sens` | Ohms | Thermistor resistance derived from `adc` |
| `temperature` | Degrees Celsius | Steinhart-Hart conversion result derived from `sens` | | `temperature` | Degrees Celsius | Steinhart-Hart conversion result derived from `sens` |
| `pid_engaged` | Boolean | `true` if in closed-loop mode | | `pid_engaged` | Boolean | `true` if in closed-loop mode |
| `current_swapped` | Boolean | `true` if TEC current direction is swapped relative to front panel |
| `i_set` | Amperes | TEC output current | | `i_set` | Amperes | TEC output current |
| `dac_value` | Volts | AD5680 output derived from `i_set` | | `dac_value` | Volts | AD5680 output derived from `i_set` |
| `dac_feedback` | Volts | ADC measurement of the AD5680 output | | `dac_feedback` | Volts | ADC measurement of the AD5680 output |

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
@ -57,14 +56,12 @@ class Client:
'max_i_neg': {'max': 3.0, 'value': 3.0}, 'max_i_neg': {'max': 3.0, 'value': 3.0},
'max_v': {'max': 5.988, 'value': 5.988}, 'max_v': {'max': 5.988, 'value': 5.988},
'max_i_pos': {'max': 3.0, 'value': 3.0}}, 'max_i_pos': {'max': 3.0, 'value': 3.0}},
'polarity': 'normal',
{'channel': 1, {'channel': 1,
'center': 'vref', 'center': 'vref',
'i_set': {'max': 2.9802790335151985, 'value': -0.02002179650216762}, 'i_set': {'max': 2.9802790335151985, 'value': -0.02002179650216762},
'max_i_neg': {'max': 3.0, 'value': 3.0}, 'max_i_neg': {'max': 3.0, 'value': 3.0},
'max_v': {'max': 5.988, 'value': 5.988}, 'max_v': {'max': 5.988, 'value': 5.988},
'max_i_pos': {'max': 3.0, 'value': 3.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

@ -17,7 +17,7 @@ use crate::{
ad7172, ad7172,
pid, pid,
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;
@ -35,7 +35,7 @@ pub struct ChannelState {
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, pub polarity_swapped: bool,
} }
impl ChannelState { impl ChannelState {
@ -52,7 +52,7 @@ impl ChannelState {
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, polarity_swapped: false,
} }
} }

View File

@ -17,7 +17,7 @@ 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,
@ -45,11 +45,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,
@ -159,9 +155,10 @@ 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; self.channel_state(channel).i_set = i_set;
let negate = match self.channel_state(channel).polarity { let negate = if self.channel_state(channel).polarity_swapped {
Polarity::Normal => 1.0, -1.0
Polarity::Reversed => -1.0, } else {
1.0
}; };
let vref_meas = match channel.into() { let vref_meas = match channel.into() {
0 => self.channel0.vref_meas, 0 => self.channel0.vref_meas,
@ -305,6 +302,7 @@ impl Channels {
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 (5..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 => {
@ -323,7 +321,7 @@ impl Channels {
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 +330,8 @@ impl Channels {
_ => unreachable!(), _ => unreachable!(),
} }
} }
prev_value = value;
} }
} }
@ -390,27 +390,32 @@ impl Channels {
} }
pub fn get_max_i_pos(&mut self, channel: usize) -> (ElectricCurrent, ElectricCurrent) { pub fn get_max_i_pos(&mut self, channel: usize) -> (ElectricCurrent, ElectricCurrent) {
let duty = match self.channel_state(channel).polarity { let max = ElectricCurrent::new::<ampere>(3.0);
Polarity::Normal => self.get_pwm(channel, PwmPin::MaxIPos), let duty = if self.channel_state(channel).polarity_swapped {
Polarity::Reversed => self.get_pwm(channel, PwmPin::MaxINeg), self.get_pwm(channel, PwmPin::MaxINeg)
} else {
self.get_pwm(channel, PwmPin::MaxIPos)
}; };
(duty * MAX_TEC_I_DUTY_TO_CURRENT_RATE, MAX_TEC_I) (duty * max, MAX_TEC_I)
} }
pub fn get_max_i_neg(&mut self, channel: usize) -> (ElectricCurrent, ElectricCurrent) { pub fn get_max_i_neg(&mut self, channel: usize) -> (ElectricCurrent, ElectricCurrent) {
let duty = match self.channel_state(channel).polarity { let max = ElectricCurrent::new::<ampere>(3.0);
Polarity::Normal => self.get_pwm(channel, PwmPin::MaxINeg), let duty = if self.channel_state(channel).polarity_swapped {
Polarity::Reversed => self.get_pwm(channel, PwmPin::MaxIPos), self.get_pwm(channel, PwmPin::MaxIPos)
} else {
self.get_pwm(channel, PwmPin::MaxINeg)
}; };
(duty * MAX_TEC_I_DUTY_TO_CURRENT_RATE, MAX_TEC_I) (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); let tec_i = (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 { if self.channel_state(channel).polarity_swapped {
Polarity::Normal => tec_i, -tec_i
Polarity::Reversed => -tec_i, } else {
tec_i
} }
} }
@ -455,31 +460,33 @@ impl Channels {
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 duty = (max_i_pos.min(MAX_TEC_I).max(ElectricCurrent::zero()) / MAX_TEC_I_DUTY_TO_CURRENT_RATE).get::<ratio>(); let duty = (max_i_pos.min(MAX_TEC_I).max(ElectricCurrent::zero()) / max).get::<ratio>();
let duty = match self.channel_state(channel).polarity { let duty = if self.channel_state(channel).polarity_swapped {
Polarity::Normal => self.set_pwm(channel, PwmPin::MaxIPos, duty), self.set_pwm(channel, PwmPin::MaxINeg, duty)
Polarity::Reversed => self.set_pwm(channel, PwmPin::MaxINeg, duty), } else {
self.set_pwm(channel, PwmPin::MaxIPos, duty)
}; };
(duty * MAX_TEC_I_DUTY_TO_CURRENT_RATE, max) (duty * max, 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 duty = (max_i_neg.min(MAX_TEC_I).max(ElectricCurrent::zero()) / MAX_TEC_I_DUTY_TO_CURRENT_RATE).get::<ratio>(); let duty = (max_i_neg.min(MAX_TEC_I).max(ElectricCurrent::zero()) / max).get::<ratio>();
let duty = match self.channel_state(channel).polarity { let duty = if self.channel_state(channel).polarity_swapped {
Polarity::Normal => self.set_pwm(channel, PwmPin::MaxINeg, duty), self.set_pwm(channel, PwmPin::MaxIPos, duty)
Polarity::Reversed => self.set_pwm(channel, PwmPin::MaxIPos, duty), } else {
self.set_pwm(channel, PwmPin::MaxINeg, duty)
}; };
(duty * MAX_TEC_I_DUTY_TO_CURRENT_RATE, max) (duty * max, max)
} }
pub fn set_polarity(&mut self, channel: usize, polarity: Polarity) { pub fn swap_polarity(&mut self, channel: usize, swapped: bool) {
if self.channel_state(channel).polarity != polarity { if self.channel_state(channel).polarity_swapped != swapped {
let i_set = self.channel_state(channel).i_set; let i_set = self.channel_state(channel).i_set;
let max_i_pos = self.get_max_i_pos(channel).0; let max_i_pos = self.get_max_i_pos(channel).0;
let max_i_neg = self.get_max_i_neg(channel).0; let max_i_neg = self.get_max_i_neg(channel).0;
self.channel_state(channel).polarity = polarity;
self.channel_state(channel).polarity_swapped = swapped;
self.set_i(channel, i_set); self.set_i(channel, i_set);
self.set_max_i_pos(channel, max_i_pos); self.set_max_i_pos(channel, max_i_pos);
self.set_max_i_neg(channel, max_i_neg); self.set_max_i_neg(channel, max_i_neg);
@ -502,6 +509,7 @@ impl Channels {
temperature: state.get_temperature() temperature: state.get_temperature()
.map(|temperature| temperature.get::<degree_celsius>()), .map(|temperature| temperature.get::<degree_celsius>()),
pid_engaged: state.pid_engaged, pid_engaged: state.pid_engaged,
current_swapped: state.polarity_swapped,
i_set, i_set,
dac_value, dac_value,
dac_feedback: self.adc_read(channel, PinsAdcReadTarget::DacVfb, 1), dac_feedback: self.adc_read(channel, PinsAdcReadTarget::DacVfb, 1),
@ -545,7 +553,6 @@ impl Channels {
max_v: self.get_max_v(channel).into(), max_v: self.get_max_v(channel).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(),
polarity: PolarityJson(self.channel_state(channel).polarity.clone()),
} }
} }
@ -601,6 +608,7 @@ pub struct Report {
sens: Option<ElectricalResistance>, sens: Option<ElectricalResistance>,
temperature: Option<f64>, temperature: Option<f64>,
pid_engaged: bool, pid_engaged: bool,
current_swapped: bool,
i_set: ElectricCurrent, i_set: ElectricCurrent,
dac_value: ElectricPotential, dac_value: ElectricPotential,
dac_feedback: ElectricPotential, dac_feedback: ElectricPotential,
@ -627,21 +635,6 @@ impl Serialize for CenterPointJson {
} }
} }
pub struct PolarityJson(Polarity);
// used in JSON encoding, not for config
impl Serialize for PolarityJson {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(match self.0 {
Polarity::Normal => "normal",
Polarity::Reversed => "reversed",
})
}
}
#[derive(Serialize)] #[derive(Serialize)]
pub struct PwmSummaryField<T: Serialize> { pub struct PwmSummaryField<T: Serialize> {
value: T, value: T,
@ -662,7 +655,6 @@ pub struct PwmSummary {
max_v: PwmSummaryField<ElectricPotential>, max_v: PwmSummaryField<ElectricPotential>,
max_i_pos: PwmSummaryField<ElectricCurrent>, max_i_pos: PwmSummaryField<ElectricCurrent>,
max_i_neg: PwmSummaryField<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,8 +181,8 @@ impl Handler {
Ok(Handler::Handled) Ok(Handler::Handled)
} }
fn set_polarity(socket: &mut TcpSocket, channels: &mut Channels, channel: usize, polarity: Polarity) -> Result<Handler, Error> { fn swap_polarity (socket: &mut TcpSocket, channels: &mut Channels, channel: usize, swapped: bool) -> Result<Handler, Error> {
channels.set_polarity(channel, polarity); channels.swap_polarity(channel, swapped);
send_line(socket, b"{}"); send_line(socket, b"{}");
Ok(Handler::Handled) Ok(Handler::Handled)
} }
@ -408,9 +418,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 +430,7 @@ 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::PwmPolaritySwapped { channel, swapped } => Handler::swap_polarity(socket, channels, channel, swapped),
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,9 +159,9 @@ pub enum Command {
PwmPid { PwmPid {
channel: usize, channel: usize,
}, },
PwmPolarity { PwmPolaritySwapped {
channel: usize, channel: usize,
polarity: Polarity, swapped: bool,
}, },
CenterPoint { CenterPoint {
channel: usize, channel: usize,
@ -241,6 +237,18 @@ 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 boolean(input: &[u8]) -> IResult<&[u8], bool> {
alt((value(false, tag("false")),
value(true, tag("true"))
))(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 +256,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,14 +331,12 @@ fn pwm_pid(input: &[u8]) -> IResult<&[u8], ()> {
value((), tag("pid"))(input) value((), tag("pid"))(input)
} }
fn pwm_polarity(input: &[u8]) -> IResult<&[u8], Polarity> { fn pwm_polarity_swapped(input: &[u8]) -> IResult<&[u8], bool> {
preceded( preceded(
tag("polarity"), tag("polarity_swapped"),
preceded( preceded(
whitespace, whitespace,
alt((value(Polarity::Normal, tag("normal")), boolean,
value(Polarity::Reversed, tag("reversed"))
))
) )
)(input) )(input)
} }
@ -332,8 +354,8 @@ fn pwm(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
Ok((input, Ok(Command::PwmPid { channel }))) Ok((input, Ok(Command::PwmPid { channel })))
}, },
|input| { |input| {
let (input, polarity) = pwm_polarity(input)?; let (input, swapped) = pwm_polarity_swapped(input)?;
Ok((input, Ok(Command::PwmPolarity { channel, polarity }))) Ok((input, Ok(Command::PwmPolaritySwapped { channel, swapped })))
}, },
|input| { |input| {
let (input, config) = pwm_setup(input)?; let (input, config) = pwm_setup(input)?;
@ -684,6 +706,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");

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,7 @@ pub struct ChannelConfig {
pid_target: f32, pid_target: f32,
pid_engaged: bool, pid_engaged: bool,
i_set: ElectricCurrent, i_set: ElectricCurrent,
polarity: Polarity, polarity_swapped: bool,
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
@ -47,7 +47,7 @@ impl ChannelConfig {
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,
polarity: state.polarity.clone(), polarity_swapped: state.polarity_swapped,
sh: state.sh.clone(), sh: state.sh.clone(),
pwm, pwm,
adc_postfilter, adc_postfilter,
@ -70,7 +70,7 @@ 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()); channels.swap_polarity(channel, self.polarity_swapped);
} }
} }

View File

@ -58,7 +58,7 @@ mod hw_rev;
const HSE: MegaHertz = MegaHertz(8); const HSE: MegaHertz = MegaHertz(8);
#[cfg(not(feature = "semihosting"))] #[cfg(not(feature = "semihosting"))]
const WATCHDOG_INTERVAL: u32 = 1_000; const WATCHDOG_INTERVAL: u32 = 2_000;
#[cfg(feature = "semihosting")] #[cfg(feature = "semihosting")]
const WATCHDOG_INTERVAL: u32 = 30_000; const WATCHDOG_INTERVAL: u32 = 30_000;
@ -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 => {}