use core::{fmt::Debug, marker::PhantomData}; use log::{debug, info}; use miniconf::{JsonCoreSlash, Tree}; use serde::{Deserialize, Serialize}; use smoltcp::iface::SocketHandle; use uom::si::{electric_current::{ampere, ElectricCurrent}, electric_potential::{volt, ElectricPotential}, electrical_conductance::{siemens, ElectricalConductance}, electrical_resistance::{ohm, ElectricalResistance}, power::{watt, Power}, thermodynamic_temperature::{degree_celsius, ThermodynamicTemperature}}; use crate::{device::{dfu, sys_timer}, laser_diode::{laser_diode::{LdDrive, LdSettingsSummary, StatusReport as LdStatusReport}, pd_mon_params::{self, ResponsitivityUnit}}, net::net, thermostat::{ad7172::FilterType, pid_state::PidSettings::*, thermostat::{StatusReport as TecStatusReport, TempAdcFilter, Thermostat, ThermostatSettingsSummary}}, DeviceSettings, IpSettings, State}; #[derive(Deserialize, Serialize, Copy, Clone, Default, Debug)] pub enum ResponseEnum { #[default] Reserved, Settings, Report, 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, SetPdFinGain, SetPdTransconductance, 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, thermostat_cmd: Option, device_cmd: Option, data_bool: Option, data_f32: Option, data_f64: Option, temp_adc_filter: Option, ip_settings: Option, } #[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, msg_type: ResponseEnum, 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 { msg_type: ResponseEnum, 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 { msg_type: ResponseEnum::Settings, 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(), msg_type: ResponseEnum::Report, 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::(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::SetPdFinGain) => match cmd.json.data_f32 { Some(val) => { send_response(buffer, ResponseEnum::Acknowledge, None, socket); device_settings.pd_mon_fin_gain = val; laser.set_pd_transconductance( device_settings.pd_mon_fin_gain * device_settings.pd_mon_transconductance, ); *state = State::SaveDeviceSettings; } None => { send_response( buffer, ResponseEnum::InvalidDatatype, Some(ERR_MSG_MISSING_DATA_F32), socket, ); } }, Some(DeviceCmd::SetPdTransconductance) => match cmd.json.data_f32 { Some(val) => { send_response(buffer, ResponseEnum::Acknowledge, None, socket); device_settings.pd_mon_transconductance = ElectricalConductance::new::(val); laser.set_pd_transconductance( device_settings.pd_mon_fin_gain * device_settings.pd_mon_transconductance, ); *state = State::SaveDeviceSettings; } None => { send_response( buffer, ResponseEnum::InvalidDatatype, Some(ERR_MSG_MISSING_DATA_F32), socket, ); } }, Some(DeviceCmd::GetStatusReport) => { send_status_report(buffer, laser, thermostat, socket); } Some(DeviceCmd::GetSettingsSummary) => { 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::(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::(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::(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::(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::(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::(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_neg(ElectricCurrent::new::(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::(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::(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::(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::(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::(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::(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(_) => { info!("cmd_recv: {:?}", buffer); send_response(buffer, ResponseEnum::InvalidCmd, None, socket); } } }