diff --git a/build.rs b/build.rs index de4aab0..28b141d 100644 --- a/build.rs +++ b/build.rs @@ -15,7 +15,7 @@ */ use csv::StringRecord; -use serde::Deserialize; +use serde::{Deserialize, Deserializer}; use std::collections::HashMap; use std::env; use std::error::Error; @@ -41,6 +41,17 @@ enum FieldType { use FieldType::*; +fn split_str<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let s: &str = Deserialize::deserialize(deserializer)?; + Ok(s.split(' ') + .map(|x| x.to_owned()) + .filter(|x| !x.is_empty()) + .collect()) +} + #[derive(Deserialize, Clone)] struct Field { field_type: FieldType, @@ -48,6 +59,8 @@ struct Field { name: String, id: String, scale: Option, + #[serde(deserialize_with = "split_str")] + sum_of: Vec, } struct Record { @@ -88,7 +101,8 @@ where W: Write, { writeln!(w, "&[")?; - for record in records.iter() { + let mut by_id: HashMap<&str, usize> = HashMap::new(); + for (i, record) in records.iter().enumerate() { let field = &record.field; let default_scale = match field.field_type { Charge | Power | StateOfCharge | Unitless => Some(1.0), @@ -114,6 +128,15 @@ where Unitless => "", }; let scale = field.scale.or(default_scale).unwrap(); + let sum_of: Vec = field + .sum_of + .iter() + .map(|id| { + *by_id + .get(id.as_str()) + .unwrap_or_else(|| panic!("Prior field {id:?} not found")) + }) + .collect(); writeln!( w, r#" crate::fields::Field {{ @@ -124,9 +147,15 @@ where scale: {scale:?}, bias: {bias:?}, unit: {unit:?}, + sum_of: &{:?}, }},"#, - field.field_type, field.group, field.name, field.id + field.field_type, + field.group, + field.name, + field.id, + sum_of.as_slice() )?; + by_id.insert(field.id.as_str(), i); } write!(w, "]")?; Ok(()) diff --git a/fields.csv b/fields.csv index eaa7853..3ba4334 100644 --- a/fields.csv +++ b/fields.csv @@ -1,59 +1,59 @@ -field_type,group,name,id,scale,v292_offset,v292_offset2,v302_offset,v302_offset2,reg,reg2 -Energy,Battery,Total charge,battery_charge_total,,70,72,78,80,72,73 -Energy,Battery,Total discharge,battery_discharge_total,,74,76,82,84,74,75 -Energy,Grid,Total import,grid_import_total,,82,86,90,94,78,80 -Frequency,Grid,Frequency,grid_frequency,,84,,92,,79, -Energy,Grid,Total export,grid_export_total,,88,90,96,98,81,82 -Energy,Load,Total consumption,load_consumption_total,,96,98,104,106,85,86 -Temperature,Inverter,DC Temperature,inverter_temperature_dc,,106,,114,,90, -Temperature,Inverter,AC Temperature,inverter_temperature_ac,,108,,116,,91, -Energy,PV,Total production,pv_production_total,,118,120,126,128,96,97 -Charge,Battery,Capacity,battery_capacity,,140,,148,,107, -Voltage,PV,Voltage 1,pv_voltage_1,0.1,144,,152,,109, -Current,PV,Current 1,pv_current_1,0.1,146,,154,,110, -Voltage,PV,Voltage 2,pv_voltage_2,0.1,148,,156,,111, -Current,PV,Current 2,pv_current_2,0.1,150,,158,,112, -Voltage,Grid,Voltage,grid_voltage,0.1,176,,184,,150, -Voltage,Load,Voltage,load_voltage,0.1,184,,192,,154, -Current,Grid,Current,grid_current,0.01,196,,204,,160, -Current,Load,Current,load_current,0.01,204,,212,,164, -Power,Grid,Power L1,grid_power_l1,,210,,218,,167, -Power,Grid,Power,grid_power,,214,,222,,169, -Power,Inverter,Power,inverter_power,,226,,234,,175, -Power,Load,Power,load_power,,232,,240,,178, -Temperature,Battery,Temperature,battery_temperature,,240,,248,,182, -Voltage,Battery,Voltage,battery_voltage,0.01,242,,250,,183, -StateOfCharge,Battery,SOC,battery_soc,,244,,252,,184, -Power,PV,Power 1,pv_power_1,,248,,256,,186, -Power,PV,Power 2,pv_power_2,,250,,258,,187, -Power,Battery,Power,battery_power,,256,,264,,190, -Current,Battery,Current,battery_current,0.01,258,,266,,191, -Frequency,Load,Frequency,load_frequency,,260,,268,,192, -Unitless,Grid,Connected,grid_connected,,264,,272,,194, -Voltage,BMS,Charge Voltage,bms_charge_voltage,0.01,276,,286,,, -Current,BMS,Charge Limit Current,bms_charge_limit_current,1,280,,290,,, -Current,BMS,Discharge Limit Current,bms_discharge_limit_current,1,282,,292,,, -Voltage,BMS,Voltage,bms_voltage,0.01,286,,296,,, -Current,BMS,Current,bms_current,1,288,,298,,, -Temperature,BMS,Temperature,bms_temperature,,290,,300,,, -Time,Inverter,Program Time 1,inverter_program_time_1,,,,,,250, -Time,Inverter,Program Time 2,inverter_program_time_2,,,,,,251, -Time,Inverter,Program Time 3,inverter_program_time_3,,,,,,252, -Time,Inverter,Program Time 4,inverter_program_time_4,,,,,,253, -Time,Inverter,Program Time 5,inverter_program_time_5,,,,,,254, -Time,Inverter,Program Time 6,inverter_program_time_6,,,,,,255, -Power,Inverter,Program Power 1,inverter_program_power_1,,,,,,256, -Power,Inverter,Program Power 2,inverter_program_power_2,,,,,,257, -Power,Inverter,Program Power 3,inverter_program_power_3,,,,,,258, -Power,Inverter,Program Power 4,inverter_program_power_4,,,,,,259, -Power,Inverter,Program Power 5,inverter_program_power_5,,,,,,260, -Power,Inverter,Program Power 6,inverter_program_power_6,,,,,,261, -StateOfCharge,Inverter,Program SOC 1,inverter_program_soc_1,,,,,,268, -StateOfCharge,Inverter,Program SOC 2,inverter_program_soc_2,,,,,,269, -StateOfCharge,Inverter,Program SOC 3,inverter_program_soc_3,,,,,,270, -StateOfCharge,Inverter,Program SOC 4,inverter_program_soc_4,,,,,,271, -StateOfCharge,Inverter,Program SOC 5,inverter_program_soc_5,,,,,,272, -StateOfCharge,Inverter,Program SOC 6,inverter_program_soc_6,,,,,,273, -Power,Inverter,Program Power,inverter_program_power,,,,,,-1, -StateOfCharge,Inverter,Program SOC,inverter_program_soc,,,,,,-1, -Power,PV,Power,pv_power,,-1,,-1,,-1, +field_type,group,name,id,scale,v292_offset,v292_offset2,v302_offset,v302_offset2,reg,reg2,sum_of +Energy,Battery,Total charge,battery_charge_total,,70,72,78,80,72,73, +Energy,Battery,Total discharge,battery_discharge_total,,74,76,82,84,74,75, +Energy,Grid,Total import,grid_import_total,,82,86,90,94,78,80, +Frequency,Grid,Frequency,grid_frequency,,84,,92,,79,, +Energy,Grid,Total export,grid_export_total,,88,90,96,98,81,82, +Energy,Load,Total consumption,load_consumption_total,,96,98,104,106,85,86, +Temperature,Inverter,DC Temperature,inverter_temperature_dc,,106,,114,,90,, +Temperature,Inverter,AC Temperature,inverter_temperature_ac,,108,,116,,91,, +Energy,PV,Total production,pv_production_total,,118,120,126,128,96,97, +Charge,Battery,Capacity,battery_capacity,,140,,148,,107,, +Voltage,PV,Voltage 1,pv_voltage_1,0.1,144,,152,,109,, +Current,PV,Current 1,pv_current_1,0.1,146,,154,,110,, +Voltage,PV,Voltage 2,pv_voltage_2,0.1,148,,156,,111,, +Current,PV,Current 2,pv_current_2,0.1,150,,158,,112,, +Voltage,Grid,Voltage,grid_voltage,0.1,176,,184,,150,, +Voltage,Load,Voltage,load_voltage,0.1,184,,192,,154,, +Current,Grid,Current,grid_current,0.01,196,,204,,160,, +Current,Load,Current,load_current,0.01,204,,212,,164,, +Power,Grid,Power L1,grid_power_l1,,210,,218,,167,, +Power,Grid,Power,grid_power,,214,,222,,169,, +Power,Inverter,Power,inverter_power,,226,,234,,175,, +Power,Load,Power,load_power,,232,,240,,178,, +Temperature,Battery,Temperature,battery_temperature,,240,,248,,182,, +Voltage,Battery,Voltage,battery_voltage,0.01,242,,250,,183,, +StateOfCharge,Battery,SOC,battery_soc,,244,,252,,184,, +Power,PV,Power 1,pv_power_1,,248,,256,,186,, +Power,PV,Power 2,pv_power_2,,250,,258,,187,, +Power,Battery,Power,battery_power,,256,,264,,190,, +Current,Battery,Current,battery_current,0.01,258,,266,,191,, +Frequency,Load,Frequency,load_frequency,,260,,268,,192,, +Unitless,Grid,Connected,grid_connected,,264,,272,,194,, +Voltage,BMS,Charge Voltage,bms_charge_voltage,0.01,276,,286,,,, +Current,BMS,Charge Limit Current,bms_charge_limit_current,1,280,,290,,,, +Current,BMS,Discharge Limit Current,bms_discharge_limit_current,1,282,,292,,,, +Voltage,BMS,Voltage,bms_voltage,0.01,286,,296,,,, +Current,BMS,Current,bms_current,1,288,,298,,,, +Temperature,BMS,Temperature,bms_temperature,,290,,300,,,, +Time,Inverter,Program Time 1,inverter_program_time_1,,,,,,250,, +Time,Inverter,Program Time 2,inverter_program_time_2,,,,,,251,, +Time,Inverter,Program Time 3,inverter_program_time_3,,,,,,252,, +Time,Inverter,Program Time 4,inverter_program_time_4,,,,,,253,, +Time,Inverter,Program Time 5,inverter_program_time_5,,,,,,254,, +Time,Inverter,Program Time 6,inverter_program_time_6,,,,,,255,, +Power,Inverter,Program Power 1,inverter_program_power_1,,,,,,256,, +Power,Inverter,Program Power 2,inverter_program_power_2,,,,,,257,, +Power,Inverter,Program Power 3,inverter_program_power_3,,,,,,258,, +Power,Inverter,Program Power 4,inverter_program_power_4,,,,,,259,, +Power,Inverter,Program Power 5,inverter_program_power_5,,,,,,260,, +Power,Inverter,Program Power 6,inverter_program_power_6,,,,,,261,, +StateOfCharge,Inverter,Program SOC 1,inverter_program_soc_1,,,,,,268,, +StateOfCharge,Inverter,Program SOC 2,inverter_program_soc_2,,,,,,269,, +StateOfCharge,Inverter,Program SOC 3,inverter_program_soc_3,,,,,,270,, +StateOfCharge,Inverter,Program SOC 4,inverter_program_soc_4,,,,,,271,, +StateOfCharge,Inverter,Program SOC 5,inverter_program_soc_5,,,,,,272,, +StateOfCharge,Inverter,Program SOC 6,inverter_program_soc_6,,,,,,273,, +Power,Inverter,Program Power,inverter_program_power,,,,,,-1,, +StateOfCharge,Inverter,Program SOC,inverter_program_soc,,,,,,-1,, +Power,PV,Power,pv_power,,-1,,-1,,-1,,pv_power_1 pv_power_2 diff --git a/src/fields.rs b/src/fields.rs index 3d58c9c..85efefc 100644 --- a/src/fields.rs +++ b/src/fields.rs @@ -41,6 +41,8 @@ pub struct Field<'a> { /// Amount to add to the value, after scaling pub bias: f64, pub unit: &'a str, + /// Indices of other fields to sum to get this field + pub sum_of: &'a [usize], } impl<'a> Field<'a> { @@ -64,6 +66,10 @@ impl<'a> Field<'a> { } (raw as f64) * self.scale + self.bias } + + pub fn from_sum(&self, values: &[f64]) -> f64 { + self.sum_of.iter().map(|idx| values[*idx]).sum() + } } #[cfg(test)] diff --git a/src/modbus.rs b/src/modbus.rs index 468050b..4c790d8 100644 --- a/src/modbus.rs +++ b/src/modbus.rs @@ -57,16 +57,15 @@ async fn read_values(ctx: &mut Context) -> Result, std::io::Error> { let mut values = Vec::with_capacity(FIELDS.len()); let mut parts = [0u16; 2]; for (field, regs) in FIELDS.iter().zip(REGISTERS.iter()) { - let value; - if !regs.is_empty() { + let value = if !regs.is_empty() { for (i, reg) in regs.iter().enumerate() { // TODO: better error handling parts[i] = ctx.read_holding_registers(*reg, 1).await?[0]; } - value = field.from_u16s(parts[..regs.len()].iter().cloned()); + field.from_u16s(parts[..regs.len()].iter().cloned()) } else { - value = 0.0; - } + field.from_sum(&values) + }; values.push(value); } // Get the inverter time, since that'll determine which program is current @@ -87,9 +86,6 @@ async fn read_values(ctx: &mut Context) -> Result, std::io::Error> { values[field_idx::INVERTER_PROGRAM_POWER] = values[field_idx::INVERTER_PROGRAM_POWER_1 + prog]; values[field_idx::INVERTER_PROGRAM_SOC] = values[field_idx::INVERTER_PROGRAM_SOC_1 + prog]; - // Other computed fields - values[field_idx::PV_POWER] = values[field_idx::PV_POWER_1] + values[field_idx::PV_POWER_2]; - Ok(values) } diff --git a/src/pcap.rs b/src/pcap.rs index 772e64e..8dbef59 100644 --- a/src/pcap.rs +++ b/src/pcap.rs @@ -91,12 +91,16 @@ impl Codec { ); let mut values = Vec::with_capacity(FIELDS.len()); for (&offsets, field) in field_table.offsets.iter().zip(field_table.fields.iter()) { - let parts = offsets.iter().cloned().map(|offset| { - let bytes = &sliced.payload[offset..offset + 2]; - let bytes = <&[u8; 2]>::try_from(bytes).unwrap(); - u16::from_be_bytes(*bytes) - }); - let value = field.from_u16s(parts); + let value = if !offsets.is_empty() { + let parts = offsets.iter().cloned().map(|offset| { + let bytes = &sliced.payload[offset..offset + 2]; + let bytes = <&[u8; 2]>::try_from(bytes).unwrap(); + u16::from_be_bytes(*bytes) + }); + field.from_u16s(parts) + } else { + field.from_sum(&values) + }; values.push(value); } /* unwrapping timestamp_nanos_opt is safe because the encoding