Compare commits

..

14 Commits

Author SHA1 Message Date
ba01138246 TCP Command: pwm -> output
Current limit pins are driven by PWM inputs to the MAX1968 driver, but
this is an implementation detail, and should not be exposed in the form
of the command interface. Rename it to "output" in all instances.

See M-Labs/thermostat#62 (comment).
2024-10-14 12:54:50 +08:00
fcb5cf1d4e Add command parser test for polarity command 2024-10-14 12:46:13 +08:00
d517087e10 README: Purge all traces of report mode 2024-10-14 10:08:08 +08:00
798b400aa5 Show and save set value for PWM limits
Show in PwmSummary the set value, before all PWM duty calculations
instead of the machine value after the calculations. Also save the set
value into the flash store instead of the machine value.
2024-10-07 10:41:31 +08:00
93dc39e943 README: Document PWM value clamping 2024-10-07 10:41:31 +08:00
5c3b759d0c PwmSummary: Don't show max PWM values
Putting the maximum in PwmSummary has little use - the maximum never
changes throughout the runtime of the firmware and can be replaced by
documentation elsewhere.
2024-10-07 10:41:31 +08:00
6224486662 Show polarity in pwm command 2024-09-30 17:36:50 +08:00
32bd49b258 Add a command to reverse the output polarity
Effectively flips all values related to the directionality of current
through the TEC, including current maximums. The reversed status is
stored in the flash store and will be loaded on reset once saved.

The command is "pwm <ch> polarity <normal/reversed>", where the "normal"
polarity is indicated by the front panel markings.

This is needed for IDC cable connections to the Sinara 5432 DAC
"Zotino", since the TEC pins of the 10-pin connector on the Thermostat
and Zotino have opposite polarities.
2024-09-30 17:36:47 +08:00
ad54842c43 Fix wrong current limit duty cycle calculation
- prev commit assumed setting 3.3V -> 3A current limit which is wrong
- Please refer to the MAX1968 datasheet for the duty cycle
calculation equation
2024-09-25 17:20:29 +08:00
b336c4f993 Remove report mode
As the report mode command breaks the send-receive architecture model of
the command interface, and is deemed an anti-feature, making clients
difficult to write, remove it from the firmware entirely.
2024-09-25 17:19:34 +08:00
680193b34b Fix incorrect dac calibration algo
- Fix abnormally long calibration time
2024-09-20 21:21:14 +08:00
ae4bea0c8a gitignore: Ignore .bin files and __pycache__ 2024-09-19 10:08:58 +08:00
1f2de942e4 flake: Add rlwrap to devShell 2024-09-19 10:06:45 +08:00
1041d3ecbb Improve the VREF calibration routine
* Fix wrong calibration of VREF on startup. Caused new v2.2.2 boards to
wrongly calibrate the zero-point to ~2.2 V instead of 1.5 V.

* Fix bootloop on some boards.

* Adjust watchdog interval accordingly.
2024-09-16 18:14:47 +08:00
11 changed files with 210 additions and 265 deletions

3
.gitignore vendored
View File

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

View File

@ -84,9 +84,7 @@ invalidate the first line of input.
### Reading ADC input
Set report mode to `on` for a continuous stream of input data.
The scope of this setting is per TCP session.
ADC input data is provided in reports. Query for the latest report with the command `report`. See the *Reports* section below.
### TCP commands
@ -94,42 +92,41 @@ The scope of this setting is per TCP session.
Send commands as simple text string terminated by `\n`. Responses are
formatted as line-delimited JSON.
| Syntax | Function |
|----------------------------------|-------------------------------------------------------------------------------|
| `report` | Show current input |
| `report mode` | Show current report mode |
| `report mode <off/on>` | Set report mode |
| `output` | Show current output settings |
| `output <0/1> max_i_pos <amp>` | Set maximum positive output current |
| `output <0/1> max_i_neg <amp>` | Set maximum negative output current |
| `output <0/1> max_v <volt>` | Set maximum output voltage |
| `output <0/1> i_set <amp>` | Disengage PID, set fixed output current |
| `output <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> vref` | Set the MAX1968 0A-centerpoint to measure from VREF |
| `pid` | Show PID configuration |
| `pid <0/1> target <deg_celsius>` | Set the PID controller target temperature |
| `pid <0/1> kp <value>` | Set proportional gain |
| `pid <0/1> ki <value>` | Set integral gain |
| `pid <0/1> kd <value>` | Set differential gain |
| `pid <0/1> output_min <amp>` | Set mininum output |
| `pid <0/1> output_max <amp>` | Set maximum output |
| `s-h` | Show Steinhart-Hart equation parameters |
| `s-h <0/1> <t0/b/r0> <value>` | Set Steinhart-Hart parameter for a channel |
| `postfilter` | Show postfilter settings |
| `postfilter <0/1> off` | Disable postfilter |
| `postfilter <0/1> rate <rate>` | Set postfilter output data rate |
| `load [0/1]` | Restore configuration for channel all/0/1 from flash |
| `save [0/1]` | Save configuration for channel all/0/1 to flash |
| `reset` | Reset the device |
| `dfu` | Reset device and enters USB device firmware update (DFU) mode |
| `ipv4 <X.X.X.X/L> [Y.Y.Y.Y]` | Configure IPv4 address, netmask length, and optional default gateway |
| `fan` | Show current fan settings and sensors' measurements |
| `fan <value>` | Set fan power with values from 1 to 100 |
| `fan auto` | Enable automatic fan speed control |
| `fcurve <a> <b> <c>` | Set fan controller curve coefficients (see *Fan control* section) |
| `fcurve default` | Set fan controller curve coefficients to defaults (see *Fan control* section) |
| `hwrev` | Show hardware revision, and settings related to it |
| Syntax | Function |
|-------------------------------------------|-------------------------------------------------------------------------------|
| `report` | Show current input |
| `output` | Show current output settings |
| `output <0/1> max_i_pos <amp>` | Set maximum positive output current, clamped to [0, 2] |
| `output <0/1> max_i_neg <amp>` | Set maximum negative output current, clamped to [0, 2] |
| `output <0/1> max_v <volt>` | Set maximum output voltage, clamped to [0, 4] |
| `output <0/1> i_set <amp>` | Disengage PID, set fixed output current, clamped to [-2, 2] |
| `output <0/1> polarity <normal/reversed>` | Set output current polarity, with 'normal' being the front panel polarity |
| `output <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> vref` | Set the MAX1968 0A-centerpoint to measure from VREF |
| `pid` | Show PID configuration |
| `pid <0/1> target <deg_celsius>` | Set the PID controller target temperature |
| `pid <0/1> kp <value>` | Set proportional gain |
| `pid <0/1> ki <value>` | Set integral gain |
| `pid <0/1> kd <value>` | Set differential gain |
| `pid <0/1> output_min <amp>` | Set mininum output |
| `pid <0/1> output_max <amp>` | Set maximum output |
| `s-h` | Show Steinhart-Hart equation parameters |
| `s-h <0/1> <t0/b/r0> <value>` | Set Steinhart-Hart parameter for a channel |
| `postfilter` | Show postfilter settings |
| `postfilter <0/1> off` | Disable postfilter |
| `postfilter <0/1> rate <rate>` | Set postfilter output data rate |
| `load [0/1]` | Restore configuration for channel all/0/1 from flash |
| `save [0/1]` | Save configuration for channel all/0/1 to flash |
| `reset` | Reset the device |
| `dfu` | Reset device and enters USB device firmware update (DFU) mode |
| `ipv4 <X.X.X.X/L> [Y.Y.Y.Y]` | Configure IPv4 address, netmask length, and optional default gateway |
| `fan` | Show current fan settings and sensors' measurements |
| `fan <value>` | Set fan power with values from 1 to 100 |
| `fan auto` | Enable automatic fan speed control |
| `fcurve <a> <b> <c>` | Set fan controller curve coefficients (see *Fan control* section) |
| `fcurve default` | Set fan controller curve coefficients to defaults (see *Fan control* section) |
| `hwrev` | Show hardware revision, and settings related to it |
## USB
@ -186,6 +183,8 @@ 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.
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.
### Limits
@ -251,8 +250,7 @@ output 0 pid
## Reports
Use the bare `report` command to obtain a single report. Enable
continuous reporting with `report mode on`. Reports are JSON objects
Use the bare `report` command to obtain a single report. Reports are JSON objects
with the following keys.
| Key | Unit | Description |

View File

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

View File

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

View File

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

View File

@ -17,11 +17,12 @@ use crate::{
ad7172,
channel::{Channel, Channel0, Channel1},
channel_state::ChannelState,
command_parser::{CenterPoint, PwmPin},
command_parser::{CenterPoint, PwmPin, Polarity},
command_handler::JsonBuffer,
pins::{self, Channel0VRef, Channel1VRef},
steinhart_hart,
};
use crate::timer::sleep;
pub enum PinsAdcReadTarget {
VREF,
@ -44,7 +45,11 @@ pub const MAX_TEC_V: ElectricPotential = ElectricPotential {
units: PhantomData,
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
const DAC_OUT_V_MAX: ElectricPotential = ElectricPotential {
dimension: PhantomData,
@ -153,6 +158,11 @@ impl Channels {
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);
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() {
0 => self.channel0.vref_meas,
1 => self.channel1.vref_meas,
@ -160,10 +170,9 @@ impl Channels {
};
let center_point = vref_meas;
let r_sense = ElectricalResistance::new::<ohm>(R_SENSE);
let voltage = i_set * 10.0 * r_sense + center_point;
let voltage = negate * i_set * 10.0 * r_sense + center_point;
let voltage = self.set_dac(channel, voltage);
let i_set = (voltage - center_point) / (10.0 * r_sense);
self.channel_state(channel).i_set = i_set;
let i_set = negate * (voltage - center_point) / (10.0 * r_sense);
i_set
}
@ -269,17 +278,6 @@ 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.
///
/// The thermostat DAC applies a control voltage signal to the CTLI pin of MAX driver chip to control its output current.
@ -306,8 +304,7 @@ impl Channels {
let mut start_value = 1;
let mut best_error = ElectricPotential::new::<volt>(100.0);
for step in (0..18).rev() {
let mut prev_value = start_value;
for step in (5..18).rev() {
for value in (start_value..=ad5680::MAX_VALUE).step_by(1 << step) {
match channel {
0 => {
@ -318,14 +315,15 @@ impl Channels {
}
_ => unreachable!(),
}
sleep(10);
let dac_feedback = self.read_dac_feedback_until_stable(channel, ElectricPotential::new::<volt>(0.001));
let dac_feedback = self.adc_read(channel, PinsAdcReadTarget::DacVfb, 64);
let error = target_voltage - dac_feedback;
if error < ElectricPotential::new::<volt>(0.0) {
break;
} else if error < best_error {
best_error = error;
start_value = prev_value;
start_value = value;
let vref = (value as f64 / ad5680::MAX_VALUE as f64) * DAC_OUT_V_MAX;
match channel {
@ -334,8 +332,6 @@ impl Channels {
_ => unreachable!(),
}
}
prev_value = value;
}
}
@ -361,53 +357,25 @@ impl Channels {
}
}
fn get_pwm(&self, channel: usize, pin: PwmPin) -> f64 {
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_v(&mut self, channel: usize) -> ElectricPotential {
ElectricPotential::new::<volt>(self.channel_state(channel).pwm_limits.max_v)
}
pub fn get_max_v(&mut self, channel: usize) -> (ElectricPotential, ElectricPotential) {
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_pos(&mut self, channel: usize) -> ElectricCurrent {
ElectricCurrent::new::<ampere>(self.channel_state(channel).pwm_limits.max_i_pos)
}
pub fn get_max_i_pos(&mut self, channel: usize) -> (ElectricCurrent, ElectricCurrent) {
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)
pub fn get_max_i_neg(&mut self, channel: usize) -> ElectricCurrent {
ElectricCurrent::new::<ampere>(self.channel_state(channel).pwm_limits.max_i_neg)
}
// Get current passing through TEC
pub fn get_tec_i(&mut self, channel: usize) -> ElectricCurrent {
(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 {
Polarity::Normal => tec_i,
Polarity::Reversed => -tec_i,
}
}
// Get voltage across TEC
@ -444,23 +412,48 @@ impl Channels {
pub fn set_max_v(&mut self, channel: usize, max_v: ElectricPotential) -> (ElectricPotential, ElectricPotential) {
let max = 4.0 * ElectricPotential::new::<volt>(3.3);
let duty = (max_v.min(MAX_TEC_V).max(ElectricPotential::zero()) / max).get::<ratio>();
let max_v = max_v.min(MAX_TEC_V).max(ElectricPotential::zero());
let duty = (max_v / max).get::<ratio>();
let duty = self.set_pwm(channel, PwmPin::MaxV, duty);
self.channel_state(channel).pwm_limits.max_v = max_v.get::<volt>();
(duty * max, max)
}
pub fn set_max_i_pos(&mut self, channel: usize, max_i_pos: ElectricCurrent) -> (ElectricCurrent, ElectricCurrent) {
let max = ElectricCurrent::new::<ampere>(3.0);
let duty = (max_i_pos.min(MAX_TEC_I).max(ElectricCurrent::zero()) / max).get::<ratio>();
let duty = self.set_pwm(channel, PwmPin::MaxIPos, duty);
(duty * max, max)
let max_i_pos = max_i_pos.min(MAX_TEC_I).max(ElectricCurrent::zero());
let duty = (max_i_pos / MAX_TEC_I_DUTY_TO_CURRENT_RATE).get::<ratio>();
let duty = match self.channel_state(channel).polarity {
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) {
let max = ElectricCurrent::new::<ampere>(3.0);
let duty = (max_i_neg.min(MAX_TEC_I).max(ElectricCurrent::zero()) / max).get::<ratio>();
let duty = self.set_pwm(channel, PwmPin::MaxINeg, duty);
(duty * max, max)
let max_i_neg = max_i_neg.min(MAX_TEC_I).max(ElectricCurrent::zero());
let duty = (max_i_neg / MAX_TEC_I_DUTY_TO_CURRENT_RATE).get::<ratio>();
let duty = match self.channel_state(channel).polarity {
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 {
@ -518,10 +511,11 @@ impl Channels {
OutputSummary {
channel,
center: CenterPointJson(self.channel_state(channel).center.clone()),
i_set: (self.get_i(channel), MAX_TEC_I).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(),
i_set: self.get_i(channel),
max_v: self.get_max_v(channel),
max_i_pos: self.get_max_i_pos(channel),
max_i_neg: self.get_max_i_neg(channel),
polarity: PolarityJson(self.channel_state(channel).polarity.clone()),
}
}
@ -603,15 +597,18 @@ impl Serialize for CenterPointJson {
}
}
#[derive(Serialize)]
pub struct PwmSummaryField<T: Serialize> {
value: T,
max: T,
}
pub struct PolarityJson(Polarity);
impl<T: Serialize> From<(T, T)> for PwmSummaryField<T> {
fn from((value, max): (T, T)) -> Self {
PwmSummaryField { value, max }
// 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",
})
}
}
@ -619,10 +616,11 @@ impl<T: Serialize> From<(T, T)> for PwmSummaryField<T> {
pub struct OutputSummary {
channel: usize,
center: CenterPointJson,
i_set: PwmSummaryField<ElectricCurrent>,
max_v: PwmSummaryField<ElectricPotential>,
max_i_pos: PwmSummaryField<ElectricCurrent>,
max_i_neg: PwmSummaryField<ElectricCurrent>,
i_set: ElectricCurrent,
max_v: ElectricPotential,
max_i_pos: ElectricCurrent,
max_i_neg: ElectricCurrent,
polarity: PolarityJson,
}
#[derive(Serialize)]

View File

@ -11,7 +11,8 @@ use super::{
CenterPoint,
PidParameter,
PwmPin,
ShParameter
ShParameter,
Polarity
},
ad7172,
CHANNEL_CONFIG_KEY,
@ -22,7 +23,6 @@ use super::{
config::ChannelConfig,
dfu,
flash_store::FlashStore,
session::Session,
FanCtrl,
hw_rev::HWRev,
};
@ -87,16 +87,6 @@ fn send_line(socket: &mut TcpSocket, data: &[u8]) -> bool {
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> {
match channels.reports_json() {
Ok(buf) => {
@ -181,6 +171,12 @@ impl Handler {
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> {
match pin {
PwmPin::ISet => {
@ -412,11 +408,9 @@ impl Handler {
}
}
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> {
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> {
match command {
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::Pid) => Handler::show_pid(socket, channels),
Command::Show(ShowCommand::Output) => Handler::show_pwm(socket, channels),
@ -424,6 +418,7 @@ impl Handler {
Command::Show(ShowCommand::PostFilter) => Handler::show_post_filter(socket, channels),
Command::Show(ShowCommand::Ipv4) => Handler::show_ipv4(socket, ipv4_config),
Command::OutputPid { channel } => Handler::engage_pid(socket, channels, channel),
Command::OutputPolarity { channel, polarity } => Handler::set_polarity(socket, channels, channel, polarity),
Command::Output { channel, pin, value } => Handler::set_pwm(socket, channels, channel, pin, value),
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),

View File

@ -96,7 +96,6 @@ pub struct Ipv4Config {
#[derive(Debug, Clone, PartialEq)]
pub enum ShowCommand {
Input,
Reporting,
Output,
Pid,
SteinhartHart,
@ -136,6 +135,12 @@ pub enum CenterPoint {
Override(f32),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Polarity {
Normal,
Reversed,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Command {
Quit,
@ -148,7 +153,6 @@ pub enum Command {
Reset,
Ipv4(Ipv4Config),
Show(ShowCommand),
Reporting(bool),
/// PWM parameter setting
Output {
channel: usize,
@ -159,6 +163,10 @@ pub enum Command {
OutputPid {
channel: usize,
},
OutputPolarity {
channel: usize,
polarity: Polarity,
},
CenterPoint {
channel: usize,
center: CenterPoint,
@ -233,12 +241,6 @@ fn float(input: &[u8]) -> IResult<&[u8], Result<f64, Error>> {
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> {
map(one_of("01"), |c| (c as usize) - ('0' as usize))(input)
}
@ -246,24 +248,8 @@ fn channel(input: &[u8]) -> IResult<&[u8], usize> {
fn report(input: &[u8]) -> IResult<&[u8], Command> {
preceded(
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
value(Command::Show(ShowCommand::Input), end)
))
// `report` - Report once
value(Command::Show(ShowCommand::Input), end)
)(input)
}
@ -321,6 +307,18 @@ fn output_pid(input: &[u8]) -> IResult<&[u8], ()> {
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 output(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
let (input, _) = tag("output")(input)?;
alt((
@ -333,6 +331,10 @@ fn output(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
let (input, ()) = output_pid(input)?;
Ok((input, Ok(Command::OutputPid { channel })))
},
|input| {
let (input, polarity) = pwm_polarity(input)?;
Ok((input, Ok(Command::OutputPolarity { channel, polarity })))
},
|input| {
let (input, config) = pwm_setup(input)?;
match config {
@ -682,24 +684,6 @@ mod test {
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]
fn parse_pwm_i_set() {
let command = Command::parse(b"output 1 i_set 16383");
@ -710,6 +694,15 @@ 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]
fn parse_pwm_pid() {
let command = Command::parse(b"output 0 pid");

View File

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

View File

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

View File

@ -1,5 +1,4 @@
use super::command_parser::{Command, Error as ParserError};
use super::channels::CHANNELS;
const MAX_LINE_LEN: usize = 64;
@ -53,8 +52,6 @@ impl From<Result<Command, ParserError>> for SessionInput {
pub struct Session {
reader: LineReader,
reporting: bool,
report_pending: [bool; CHANNELS],
}
impl Default for Session {
@ -67,43 +64,11 @@ impl Session {
pub fn new() -> Self {
Session {
reader: LineReader::new(),
reporting: false,
report_pending: [false; CHANNELS],
}
}
pub fn reset(&mut self) {
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) {
@ -114,12 +79,6 @@ impl Session {
match line {
Some(line) => {
let command = Command::parse(&line);
match command {
Ok(Command::Reporting(reporting)) => {
self.reporting = reporting;
}
_ => {}
}
return (buf_bytes, command.into());
}
None => {}