kirdy/src/net/cmd_handler.rs

672 lines
29 KiB
Rust

use core::{fmt::Debug, marker::PhantomData};
use miniconf::{JsonCoreSlash, Tree};
use serde::{Deserialize, Serialize};
use uom::si::{
electric_current::{ampere, ElectricCurrent},
electric_potential::{volt, ElectricPotential},
electrical_resistance::{ohm, ElectricalResistance},
power::{watt, Power},
thermodynamic_temperature::{degree_celsius, ThermodynamicTemperature}
};
use crate::{laser_diode::{laser_diode::{
LdDrive, LdSettingsSummary, StatusReport as LdStatusReport},
pd_responsitivity::ResponsitivityUnit
},
net::net,
thermostat::{ad7172::FilterType, thermostat::{StatusReport as TecStatusReport, TempAdcFilter}}
};
use crate::thermostat::thermostat::{Thermostat, ThermostatSettingsSummary};
use crate::thermostat::pid_state::PidSettings::*;
use crate::device::{dfu, sys_timer};
use log::{info, debug};
use crate::{DeviceSettings, State, IpSettings};
use smoltcp::iface::SocketHandle;
#[derive(Deserialize, Serialize, Copy, Clone, Default, Debug)]
pub enum ResponseEnum {
#[default]
Reserved,
Acknowledge,
InvalidDatatype,
InvalidCmd,
HardReset,
}
pub type MsgType = Option<&'static str>;
#[derive(Deserialize, Serialize, Copy, Clone, Debug)]
pub struct Response<'a> {
msg_type: ResponseEnum,
msg: Option<&'a str>,
}
impl Default for Response<'static>{
fn default() -> Self {
Response{
msg_type: ResponseEnum:: Reserved,
msg: None,
}
}
}
#[derive(Deserialize, Serialize, Copy, Clone, Debug, Tree)]
pub struct ResponseObj<'a>{
#[serde(borrow)]
json: Response<'a>
}
#[derive(Deserialize, Serialize, Copy, Clone, Default, Debug)]
enum DeviceCmd {
#[default]
Reserved,
SetIPSettings,
SetActiveReportMode,
GetStatusReport,
GetSettingsSummary,
Dfu,
SaveFlashSettings,
LoadFlashSettings,
HardReset,
}
#[derive(Deserialize, Serialize, Copy, Clone, Default, Debug)]
enum LdCmdEnum {
#[default]
Reserved,
// LD Drive Related
SetDefaultPowerOn,
PowerUp,
PowerDown,
LdTermsShort,
LdTermsOpen,
SetI,
SetISoftLimit,
// PD Mon Related
SetPdResponsitivity,
SetPdDarkCurrent,
SetLdPwrLimit,
ClearAlarm,
}
#[derive(Deserialize, Serialize, Copy, Clone, Default, Debug)]
enum ThermostatCmdEnum {
#[default]
Reserved,
SetDefaultPowerOn,
PowerUp,
PowerDown,
// TEC
SetTecMaxV,
SetTecMaxIPos,
SetTecMaxINeg,
SetTecIOut, // Constant Current Mode
SetTemperatureSetpoint,
// PID
SetPidEngage,
SetPidDisEngage,
SetPidKp,
SetPidKi,
SetPidKd,
SetPidOutMin,
SetPidOutMax,
SetPidUpdateInterval, // Update Interval is set based on the sampling rate of ADC
// Temperature ADC
ConfigTempAdcFilter,
// TempMon
SetTempMonUpperLimit,
SetTempMonLowerLimit,
ClearAlarm,
// Steinhart-Hart Equation
SetShT0,
SetShR0,
SetShBeta,
}
const ERR_MSG_MISSING_DATA_F32 : &str = "Required field \"data_f32\" does not exist";
const ERR_MSG_MISSING_DATA_BOOL: &str = "Required field \"bool\" does not exist";
const ERR_MSG_MISSING_IP_SETTINGS: &str = "Required field \"ip_settings\" does not exist";
const ERR_MSG_MISSING_TEMP_ADC_FILTER: &str = "Required field \"temp_adc_filter\" does not exist";
const ERR_MSG_MISSING_SINC5SINC1ODR: &str = "Required field \"sinc5sinc1odr\" does not exist";
const ERR_MSG_MISSING_SINC3ODR: &str = "Required field \"sinc3odr\" does not exist";
const ERR_MSG_MISSING_POSTFILTER: &str = "Required field \"PostFilter\" does not exist";
const ERR_MSG_MISSING_SINC3FINEODR: &str = "Required field \"sinc3fineodr\" does not exist";
#[derive(Deserialize, Serialize, Copy, Clone, Debug, Default, Tree)]
pub struct CmdJsonObj{
laser_diode_cmd: Option<LdCmdEnum>,
thermostat_cmd: Option<ThermostatCmdEnum>,
device_cmd: Option<DeviceCmd>,
data_bool: Option<bool>,
data_f32: Option<f32>,
data_f64: Option<f64>,
temp_adc_filter: Option<TempAdcFilter>,
ip_settings: Option<IpSettings>,
}
#[derive(Deserialize, Serialize, Copy, Clone, Debug, Default, Tree)]
pub struct Cmd {
json: CmdJsonObj
}
#[derive(Deserialize, Serialize, Copy, Clone, Debug, Tree)]
pub struct StatusReport {
ts: u32,
laser: LdStatusReport,
thermostat: TecStatusReport,
}
#[derive(Deserialize, Serialize, Copy, Clone, Debug, Tree)]
pub struct StatusReportObj {
json: StatusReport
}
#[derive(Deserialize, Serialize, Copy, Clone, Debug, Tree)]
pub struct SettingsSummary {
laser: LdSettingsSummary,
thermostat: ThermostatSettingsSummary,
}
#[derive(Deserialize, Serialize, Copy, Clone, Debug, Tree)]
pub struct SettingsSummaryObj {
json: SettingsSummary
}
pub fn send_response(buffer: &mut [u8], msg_type: ResponseEnum, msg: MsgType, socket: &mut SocketHandle){
let response = ResponseObj {
json: Response {
msg_type: msg_type,
msg: msg,
}
};
debug!("{:?}", response.json);
let mut num_bytes = response.get_json("/json", buffer).unwrap();
buffer[num_bytes] = b'\n';
num_bytes += 1;
net::eth_send(buffer, num_bytes, *socket);
}
pub fn send_settings_summary(buffer: &mut [u8], laser: &mut LdDrive, thermostat: &mut Thermostat, socket: &mut SocketHandle){
let settings_summary = SettingsSummaryObj {
json: SettingsSummary {
laser: laser.get_settings_summary(),
thermostat: thermostat.get_settings_summary(),
}
};
let mut num_bytes = settings_summary.get_json("/json", buffer).unwrap();
buffer[num_bytes] = b'\n';
num_bytes += 1;
net::eth_send(buffer, num_bytes, *socket);
}
pub fn send_status_report(buffer: &mut [u8], laser: &mut LdDrive, thermostat: &mut Thermostat, socket: &mut SocketHandle){
let status_report = StatusReportObj {
json: StatusReport {
ts: sys_timer::now(),
laser: laser.get_status_report(),
thermostat: thermostat.get_status_report(),
}
};
let mut num_bytes = status_report.get_json("/json", buffer).unwrap();
buffer[num_bytes] = b'\n';
num_bytes += 1;
net::eth_send(buffer, num_bytes, *socket);
}
// Use a minimal struct for high speed cmd ctrl to reduce processing overhead
#[derive(Deserialize, Serialize, Copy, Clone, Debug, Default, Tree)]
pub struct TecSetICmdJson {
tec_set_i: f32
}
#[derive(Deserialize, Serialize, Copy, Clone, Debug, Default, Tree)]
pub struct TecSetICmd {
json: TecSetICmdJson
}
/// Miniconf is very slow in debug builds (~3-4ms of cmd decoding time).
/// Make sure kirdy's firmware is flashed with release builds.
/// The received message must contain only one json cmd. TCP client should set TCP_NODELAY or equivalent flag in its TCP Socket
/// Settings to avoid unwanted buffering on TX Data and minimize TX latency.
pub fn execute_cmd(buffer: &mut [u8], buffer_size: usize, socket: &mut SocketHandle, laser: &mut LdDrive, thermostat: &mut Thermostat, state: &mut State, device_settings: &mut DeviceSettings, active_report: &mut bool){
let mut cmd = TecSetICmd {
json: TecSetICmdJson::default()
};
match cmd.set_json("/json", &buffer[0..buffer_size]){
Ok(_) => {
thermostat.set_i(ElectricCurrent::new::<ampere>(cmd.json.tec_set_i));
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
return;
}
Err(_) => { /* Do Nothing */}
}
let mut cmd = Cmd {
json: CmdJsonObj::default()
};
match cmd.set_json("/json", &buffer[0..buffer_size]){
Ok(_) => {
info!("############ Laser Diode Command Received {:?}", cmd.json.laser_diode_cmd);
info!("############ Thermostat Command Received {:?}", cmd.json.thermostat_cmd);
info!("############ Device Command Received {:?}", cmd.json.device_cmd);
match cmd.json.device_cmd {
Some(DeviceCmd::SetIPSettings) => {
match cmd.json.ip_settings {
Some(val) => {
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
device_settings.ip_settings = val;
*state = State::SaveDeviceSettings;
}
None => {
send_response(buffer, ResponseEnum::InvalidDatatype, Some(ERR_MSG_MISSING_IP_SETTINGS), socket);
}
}
}
Some(DeviceCmd::Dfu) => {
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
unsafe {
dfu::set_dfu_trigger();
}
*state = State::HardReset;
}
Some(DeviceCmd::SetActiveReportMode) => {
match cmd.json.data_bool{
Some(val) => {
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
*active_report = val;
}
None => {
send_response(buffer, ResponseEnum::InvalidDatatype, Some(ERR_MSG_MISSING_DATA_BOOL), socket);
}
}
}
Some(DeviceCmd::GetStatusReport) => {
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
send_status_report(buffer, laser, thermostat, socket);
}
Some(DeviceCmd::GetSettingsSummary) => {
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
send_settings_summary(buffer, laser, thermostat, socket);
}
Some(DeviceCmd::SaveFlashSettings) => {
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
*state = State::SaveLdThermostatSettings;
}
Some(DeviceCmd::LoadFlashSettings) => {
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
*state = State::LoadFlashSettings;
}
Some(DeviceCmd::HardReset) => {
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
*state = State::PrepareForHardReset;
}
None => { /* Do Nothing */}
_ => {
send_response(buffer, ResponseEnum::InvalidCmd, None, socket);
debug!("Unimplemented Command")
}
}
match cmd.json.laser_diode_cmd {
Some(LdCmdEnum::SetDefaultPowerOn) => {
match cmd.json.data_bool {
Some(val) => {
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
laser.set_default_pwr_on(val);
}
None => {
send_response(buffer, ResponseEnum::InvalidDatatype, Some(ERR_MSG_MISSING_DATA_BOOL), socket);
}
}
}
Some(LdCmdEnum::PowerUp) => {
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
laser.power_up()
}
Some(LdCmdEnum::PowerDown) => {
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
laser.power_down()
}
Some(LdCmdEnum::LdTermsShort) => {
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
laser.ld_short();
}
Some(LdCmdEnum::LdTermsOpen) => {
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
laser.ld_open();
}
Some(LdCmdEnum::SetI) => {
match cmd.json.data_f32 {
Some(val) => {
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
laser.ld_set_i(ElectricCurrent::new::<ampere>(val));
}
None => {
send_response(buffer, ResponseEnum::InvalidDatatype, Some(ERR_MSG_MISSING_DATA_F32), socket);
}
}
}
Some(LdCmdEnum::SetISoftLimit) => {
match cmd.json.data_f32 {
Some(val) => {
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
laser.set_ld_drive_current_limit(ElectricCurrent::new::<ampere>(val))
}
None => {
send_response(buffer, ResponseEnum::InvalidDatatype, Some(ERR_MSG_MISSING_DATA_F32), socket);
}
}
}
Some(LdCmdEnum::SetPdResponsitivity) => {
match cmd.json.data_f32 {
Some(val) => {
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
laser.set_pd_responsitivity(ResponsitivityUnit {dimension: PhantomData, units: PhantomData, value: val})
}
None => {
send_response(buffer, ResponseEnum::InvalidDatatype, Some(ERR_MSG_MISSING_DATA_F32), socket);
}
}
}
Some(LdCmdEnum::SetPdDarkCurrent) => {
match cmd.json.data_f32 {
Some(val) => {
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
laser.set_pd_dark_current(ElectricCurrent::new::<ampere>(val))
}
None => {
send_response(buffer, ResponseEnum::InvalidDatatype, Some(ERR_MSG_MISSING_DATA_F32), socket);
}
}
}
Some(LdCmdEnum::SetLdPwrLimit) => {
match cmd.json.data_f32 {
Some(val) => {
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
laser.set_ld_power_limit(Power::new::<watt>(val))
}
None => {
send_response(buffer, ResponseEnum::InvalidDatatype, Some(ERR_MSG_MISSING_DATA_F32), socket);
}
}
}
Some(LdCmdEnum::ClearAlarm) => {
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
laser.pd_mon_clear_alarm()
}
None => { /* Do Nothing*/ }
_ => {
send_response(buffer, ResponseEnum::InvalidCmd, Some(ERR_MSG_MISSING_DATA_F32), socket);
info!("Unimplemented Command")
}
}
match cmd.json.thermostat_cmd {
Some(ThermostatCmdEnum::SetDefaultPowerOn) => {
match cmd.json.data_bool {
Some(val) => {
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
thermostat.set_default_pwr_on(val);
}
None => {
send_response(buffer, ResponseEnum::InvalidDatatype, Some(ERR_MSG_MISSING_DATA_F32), socket);
}
}
}
Some(ThermostatCmdEnum::PowerUp) => {
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
thermostat.power_up()
}
Some(ThermostatCmdEnum::PowerDown) => {
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
thermostat.power_down()
}
Some(ThermostatCmdEnum::SetTecMaxV) => {
match cmd.json.data_f32 {
Some(val) => {
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
thermostat.set_max_v(ElectricPotential::new::<volt>(val));
}
None => {
send_response(buffer, ResponseEnum::InvalidDatatype, Some(ERR_MSG_MISSING_DATA_F32), socket);
}
}
}
Some(ThermostatCmdEnum::SetTecMaxIPos) => {
match cmd.json.data_f32 {
Some(val) => {
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
thermostat.set_max_i_pos(ElectricCurrent::new::<ampere>(val));
}
None => {
send_response(buffer, ResponseEnum::InvalidDatatype, Some(ERR_MSG_MISSING_DATA_F32), socket);
}
}
}
Some(ThermostatCmdEnum::SetTecMaxINeg) => {
match cmd.json.data_f32 {
Some(val) => {
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
thermostat.set_max_i_pos(ElectricCurrent::new::<ampere>(val));
}
None => {
send_response(buffer, ResponseEnum::InvalidDatatype, Some(ERR_MSG_MISSING_DATA_F32), socket);
}
}
}
Some(ThermostatCmdEnum::SetTecIOut) => {
match cmd.json.data_f32 {
Some(val) => {
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
thermostat.set_i(ElectricCurrent::new::<ampere>(val));
}
None => {
send_response(buffer, ResponseEnum::InvalidDatatype, Some(ERR_MSG_MISSING_DATA_F32), socket);
}
}
}
Some(ThermostatCmdEnum::SetTemperatureSetpoint) => {
match cmd.json.data_f32 {
Some(val) => {
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
thermostat.set_temperature_setpoint(ThermodynamicTemperature::new::<degree_celsius>(val));
}
None => {
send_response(buffer, ResponseEnum::InvalidDatatype, Some(ERR_MSG_MISSING_DATA_F32), socket);
}
}
}
Some(ThermostatCmdEnum::SetPidEngage) => {
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
thermostat.set_pid_engaged(true);
}
Some(ThermostatCmdEnum::SetPidDisEngage) => {
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
thermostat.set_pid_engaged(false);
}
Some(ThermostatCmdEnum::SetPidKp) => {
match cmd.json.data_f32 {
Some(val) => {
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
thermostat.set_pid(Kp, val);
}
None => {
send_response(buffer, ResponseEnum::InvalidDatatype, Some(ERR_MSG_MISSING_DATA_F32), socket);
}
}
}
Some(ThermostatCmdEnum::SetPidKi) => {
match cmd.json.data_f32 {
Some(val) => {
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
thermostat.set_pid(Ki, val);
}
None => {
send_response(buffer, ResponseEnum::InvalidDatatype, Some(ERR_MSG_MISSING_DATA_F32), socket);
}
}
}
Some(ThermostatCmdEnum::SetPidKd) => {
match cmd.json.data_f32 {
Some(val) => {
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
thermostat.set_pid(Kd, val);
}
None => {
send_response(buffer, ResponseEnum::InvalidDatatype, Some(ERR_MSG_MISSING_DATA_F32), socket);
}
}
}
Some(ThermostatCmdEnum::SetPidOutMin) => {
match cmd.json.data_f32 {
Some(val) => {
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
thermostat.set_pid(Min, val);
}
None => {
send_response(buffer, ResponseEnum::InvalidDatatype, Some(ERR_MSG_MISSING_DATA_F32), socket);
}
}
}
Some(ThermostatCmdEnum::SetPidOutMax) => {
match cmd.json.data_f32 {
Some(val) => {
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
thermostat.set_pid(Max, val);
}
None => {
send_response(buffer, ResponseEnum::InvalidDatatype, Some(ERR_MSG_MISSING_DATA_F32), socket);
}
}
}
Some(ThermostatCmdEnum::SetPidUpdateInterval) => {
send_response(buffer, ResponseEnum::InvalidCmd, None, socket);
debug!("Not supported Yet")
}
Some(ThermostatCmdEnum::ConfigTempAdcFilter) => {
match cmd.json.temp_adc_filter {
Some(val) => {
match val.filter_type {
FilterType::Sinc5Sinc1With50hz60HzRejection => {
match val.sinc5sinc1postfilter {
Some(val2) => {
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
thermostat.set_temp_adc_sinc5_sinc1_with_postfilter(0, val2);
}
None => {
send_response(buffer, ResponseEnum::InvalidDatatype, Some(ERR_MSG_MISSING_POSTFILTER), socket);
}
}
}
FilterType::Sinc5Sinc1 => {
match val.sinc5sinc1odr {
Some(val2) => {
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
thermostat.set_temp_adc_sinc5_sinc1_filter(0, val2);
}
None => {
send_response(buffer, ResponseEnum::InvalidDatatype, Some(ERR_MSG_MISSING_SINC5SINC1ODR), socket);
}
}
}
FilterType::Sinc3WithFineODR => {
match val.sinc3fineodr {
Some(val2) => {
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
thermostat.set_temp_adc_sinc3_fine_filter(0, val2);
}
None => {
send_response(buffer, ResponseEnum::InvalidDatatype, Some(ERR_MSG_MISSING_SINC3FINEODR), socket);
}
}
}
FilterType::Sinc3 => {
match val.sinc3odr {
Some(val2) => {
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
thermostat.set_temp_adc_sinc3_filter(0, val2);
}
None => {
send_response(buffer, ResponseEnum::InvalidDatatype, Some(ERR_MSG_MISSING_SINC3ODR), socket);
}
}
}
}
}
None => {
send_response(buffer, ResponseEnum::InvalidDatatype, Some(ERR_MSG_MISSING_TEMP_ADC_FILTER), socket);
}
}
}
Some(ThermostatCmdEnum::SetTempMonUpperLimit) => {
match cmd.json.data_f32 {
Some(val) => {
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
thermostat.set_temp_mon_upper_limit(ThermodynamicTemperature::new::<degree_celsius>(val));
}
None => {
send_response(buffer, ResponseEnum::InvalidDatatype, Some(ERR_MSG_MISSING_DATA_F32), socket);
}
}
}
Some(ThermostatCmdEnum::SetTempMonLowerLimit) => {
match cmd.json.data_f32 {
Some(val) => {
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
thermostat.set_temp_mon_lower_limit(ThermodynamicTemperature::new::<degree_celsius>(val));
}
None => {
send_response(buffer, ResponseEnum::InvalidDatatype, Some(ERR_MSG_MISSING_DATA_F32), socket);
}
}
}
Some(ThermostatCmdEnum::ClearAlarm) => {
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
thermostat.clear_temp_mon_alarm();
}
Some(ThermostatCmdEnum::SetShT0) => {
match cmd.json.data_f32 {
Some(val) => {
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
thermostat.set_sh_t0(ThermodynamicTemperature::new::<degree_celsius>(val));
}
None => {
send_response(buffer, ResponseEnum::InvalidDatatype, Some(ERR_MSG_MISSING_DATA_F32), socket);
}
}
}
Some(ThermostatCmdEnum::SetShR0) => {
match cmd.json.data_f32 {
Some(val) => {
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
thermostat.set_sh_r0(ElectricalResistance::new::<ohm>(val));
}
None => {
send_response(buffer, ResponseEnum::InvalidDatatype, Some(ERR_MSG_MISSING_DATA_F32), socket);
}
}
}
Some(ThermostatCmdEnum::SetShBeta) => {
match cmd.json.data_f32 {
Some(val) => {
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
thermostat.set_sh_beta(val);
}
None => {
send_response(buffer, ResponseEnum::InvalidDatatype, Some(ERR_MSG_MISSING_DATA_F32), socket);
}
}
}
None => { /* Do Nothing*/ }
_ => {
send_response(buffer, ResponseEnum::InvalidCmd, None, socket);
}
}
}
Err(_) => {
send_response(buffer, ResponseEnum::InvalidCmd, None, socket);
}
}
}