use crate::flicker::FlickerMeter;
use crate::harmonics::{FftCache, InterharmonicAccumulator};
use crate::urms::UrmsHalfCycle;
use alloc::boxed::Box;
use serde::{Deserialize, Serialize};
pub const FREQ_NOMINAL_50: f32 = 50.0;
pub const FREQ_NOMINAL_60: f32 = 60.0;
pub const ADC_SAMPLES_50HZ_CYCLE: f32 = 160.0;
pub const ADC_SAMPLES_60HZ_CYCLE: usize = 133;
pub const NUMBER_HARMONICS: usize = 50;
pub const NUMBER_INTERHARMONICS: usize = 49;
#[derive(Clone, Debug)]
pub struct TimeModel {
pub utc_at_boot_ns: u64,
pub ktime_at_boot_ns: u64,
pub drift_factor: f32,
pub last_calibration_ktime_ns: u64,
}
impl TimeModel {
pub fn ktime_to_utc(&self, ktime_ns: u64) -> u64 {
let delta_ktime = (ktime_ns as i64) - (self.ktime_at_boot_ns as i64);
let delta_utc = (delta_ktime as f64) * (self.drift_factor as f64);
(self.utc_at_boot_ns as i64 + delta_utc as i64) as u64
}
pub fn init_from_system(utc_now_ns: u64, ktime_now_ns: u64) -> Self {
TimeModel {
utc_at_boot_ns: utc_now_ns,
ktime_at_boot_ns: ktime_now_ns,
drift_factor: 1.0,
last_calibration_ktime_ns: ktime_now_ns,
}
}
pub fn recalibrate(&mut self, utc_new_ns: u64, ktime_new_ns: u64) {
let delta_ktime = (ktime_new_ns as i64 - self.last_calibration_ktime_ns as i64) as f64;
let delta_utc = (utc_new_ns as i64 - self.utc_at_boot_ns as i64) as f64;
if delta_ktime.abs() > 1e6 {
self.drift_factor = (delta_utc / delta_ktime) as f32;
self.last_calibration_ktime_ns = ktime_new_ns;
}
}
}
impl Default for TimeModel {
fn default() -> Self {
TimeModel {
utc_at_boot_ns: 0,
ktime_at_boot_ns: 0,
drift_factor: 1.0,
last_calibration_ktime_ns: 0,
}
}
}
#[derive(Clone, Debug)]
pub struct PllConfig {
pub kp: f32,
pub ki: f32,
pub freq_min: f32,
pub freq_max: f32,
pub lock_threshold: f32,
pub norm_threshold: f32,
pub integrator_clamp: f32,
pub lock_ema_alpha: f32,
}
impl Default for PllConfig {
fn default() -> Self {
Self {
kp: 0.002,
ki: 0.00005,
freq_min: 40.0,
freq_max: 60.0,
lock_threshold: 0.5,
norm_threshold: crate::pll::PLL_NORM_THRESHOLD,
integrator_clamp: crate::pll::PLL_INTEGRATOR_CLAMP,
lock_ema_alpha: crate::pll::PLL_LOCK_EMA_ALPHA,
}
}
}
#[derive(Clone)]
pub struct MetrologyInsightConfig {
pub avg_sec: f32,
pub adc_samples_seconds: f32,
pub adc_samples_per_cycle: f64,
#[allow(dead_code)]
pub num_harmonics: usize,
pub calibration: CalibrationFactors,
pub time_model: TimeModel,
pub nominal_freq: f32,
pub min_amplitude_voltage: f32,
pub min_amplitude_current: f32,
pub pll: PllConfig,
pub event_config: crate::events::PqEventConfig,
pub rvc_config: crate::rvc::RvcConfig,
pub flicker: FlickerConfig,
pub phase: PhaseConfig,
pub signal: SignalConfig,
pub standard_values: StandardElectricalValues,
}
impl Default for MetrologyInsightConfig {
fn default() -> Self {
Self {
avg_sec: 0.0,
adc_samples_seconds: 7812.5,
adc_samples_per_cycle: 156.25,
num_harmonics: NUMBER_HARMONICS,
calibration: CalibrationFactors::default(),
time_model: TimeModel::default(),
nominal_freq: FREQ_NOMINAL_50,
min_amplitude_voltage: 10.0,
min_amplitude_current: 0.001,
pll: PllConfig::default(),
event_config: crate::events::PqEventConfig::default(),
rvc_config: crate::rvc::RvcConfig::default(),
flicker: FlickerConfig::default(),
phase: PhaseConfig::default(),
signal: SignalConfig::default(),
standard_values: StandardElectricalValues::default(),
}
}
}
#[derive(Clone, Debug)]
pub struct FlickerConfig {
pub nominal_voltage: f32,
pub rms_tc_seconds: f32,
pub smooth_tc_seconds: f32,
pub seed_threshold_sq: f32,
pub min_rms_guard: f32,
pub pst_min_samples: u32,
}
impl Default for FlickerConfig {
fn default() -> Self {
use crate::flicker::{
FLICKER_MIN_RMS_GUARD, FLICKER_PST_MIN_SAMPLES, FLICKER_RMS_TC_SECONDS,
FLICKER_SEED_THRESHOLD_SQ, FLICKER_SMOOTH_TC_SECONDS,
};
Self {
nominal_voltage: 230.0,
rms_tc_seconds: FLICKER_RMS_TC_SECONDS,
smooth_tc_seconds: FLICKER_SMOOTH_TC_SECONDS,
seed_threshold_sq: FLICKER_SEED_THRESHOLD_SQ,
min_rms_guard: FLICKER_MIN_RMS_GUARD,
pst_min_samples: FLICKER_PST_MIN_SAMPLES,
}
}
}
#[derive(Clone, Debug)]
pub struct PhaseConfig {
pub direction_deadband_deg: f32,
}
impl Default for PhaseConfig {
fn default() -> Self {
Self {
direction_deadband_deg: crate::phase::PHASE_DIRECTION_DEADBAND_DEG,
}
}
}
#[derive(Clone, Debug)]
pub struct SignalConfig {
pub half_cycle_min_factor: f32,
pub rms_consistency_min_guard: f32,
pub pll_error_accum_threshold: f32,
pub sync_consistency_threshold: f32,
}
impl Default for SignalConfig {
fn default() -> Self {
use crate::pll::PLL_ERROR_ACCUM_THRESHOLD;
use crate::signal::{
HALF_CYCLE_MIN_FACTOR, RMS_CONSISTENCY_MIN_GUARD, SYNC_CONSISTENCY_THRESHOLD,
};
Self {
half_cycle_min_factor: HALF_CYCLE_MIN_FACTOR,
rms_consistency_min_guard: RMS_CONSISTENCY_MIN_GUARD,
pll_error_accum_threshold: PLL_ERROR_ACCUM_THRESHOLD,
sync_consistency_threshold: SYNC_CONSISTENCY_THRESHOLD,
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct StandardElectricalValues {
pub un_v: f32,
pub in_a: f32,
pub imax_a: f32,
pub imin_a: f32,
pub ist_a: f32,
pub fn_hz: f32,
}
impl Default for StandardElectricalValues {
fn default() -> Self {
Self {
un_v: 230.0,
in_a: 5.0,
imax_a: 10.0,
imin_a: 0.02 * 5.0, ist_a: 0.002 * 5.0, fn_hz: crate::FREQ_NOMINAL_50,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct CalibrationFactors {
pub v_gain: f32,
pub i_gain: [f32; 3],
pub phase_offset: [f32; 3],
pub phase_delay_us: [f32; 3],
pub temp_coeff: f64,
pub v_lsb_to_phys: f32,
pub i_lsb_to_phys: f32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
pub enum SystemMode {
#[default]
SinglePhase,
SinglePhaseN,
ThreePhase3Wire,
ThreePhase4Wire,
}
impl SystemMode {
pub const fn active_phases(self) -> usize {
match self {
SystemMode::SinglePhase => 1,
SystemMode::SinglePhaseN => 2,
SystemMode::ThreePhase3Wire => 3,
SystemMode::ThreePhase4Wire => 4,
}
}
pub const fn has_neutral(self) -> bool {
matches!(self, SystemMode::SinglePhaseN | SystemMode::ThreePhase4Wire)
}
}
#[derive(Debug, Clone)]
pub struct PhaseData {
pub voltage: MetrologyInsightSignal,
pub current: MetrologyInsightSignal,
pub phase_angles: PhaseAngleMetrics,
pub power_metrics: PowerMetrics,
pub flicker_meter: FlickerMeter,
pub event_detector: crate::events::PowerQualityEventDetector,
pub rvc_detector: crate::rvc::RvcDetector,
pub interharm_acc: InterharmonicAccumulator,
}
impl Default for PhaseData {
fn default() -> Self {
Self {
voltage: MetrologyInsightSignal::default(),
current: MetrologyInsightSignal {
signal_type: MetrologyInsightSignalType::Current,
..Default::default()
},
phase_angles: PhaseAngleMetrics::default(),
power_metrics: PowerMetrics::default(),
flicker_meter: FlickerMeter::default(),
event_detector: crate::events::PowerQualityEventDetector::default(),
rvc_detector: crate::rvc::RvcDetector::default(),
interharm_acc: InterharmonicAccumulator::new(
crate::harmonics::FFT_RESOLUTION as f32 * crate::FREQ_NOMINAL_50,
),
}
}
}
#[derive(Debug, Clone)]
pub struct MetrologyInsightSocket {
pub phases: [Box<PhaseData>; 4],
pub power_metrics_total: PowerMetrics,
pub energy_metrics: EnergyMetrics,
pub unbalance_metrics: crate::unbalance::UnbalanceMetrics,
}
impl Default for MetrologyInsightSocket {
fn default() -> Self {
Self {
phases: [
Box::new(PhaseData::default()),
Box::new(PhaseData::default()),
Box::new(PhaseData::default()),
Box::new(PhaseData::default()),
],
power_metrics_total: PowerMetrics::default(),
energy_metrics: EnergyMetrics::default(),
unbalance_metrics: crate::unbalance::UnbalanceMetrics::default(),
}
}
}
pub struct MetrologyInsight {
pub socket: Box<MetrologyInsightSocket>,
pub config: MetrologyInsightConfig,
pub fft_cache: Option<FftCache>,
pub active_phases: usize,
}
impl MetrologyInsight {
pub fn new(config: MetrologyInsightConfig) -> Self {
let mut instance = Self {
socket: Box::new(MetrologyInsightSocket::default()),
config,
fft_cache: None,
active_phases: 1,
};
instance.apply_config();
instance
}
pub fn apply_config(&mut self) {
let nominal_v = self.config.flicker.nominal_voltage;
for phase in &mut self.socket.phases {
phase.flicker_meter.set_nominal_voltage(nominal_v);
}
}
pub fn set_nominal_voltage(&mut self, voltage_v: f32) {
self.config.event_config.nominal_voltage = voltage_v;
self.config.flicker.nominal_voltage = voltage_v;
self.apply_config();
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MetrologyInsightSignalType {
Voltage,
Current,
}
impl MetrologyInsightSignalType {
pub fn min_amplitude(&self, config: &MetrologyInsightConfig) -> f32 {
match self {
MetrologyInsightSignalType::Voltage => config.min_amplitude_voltage,
MetrologyInsightSignalType::Current => config.min_amplitude_current,
}
}
}
impl Default for MetrologyInsightSignalType {
fn default() -> Self {
MetrologyInsightSignalType::Voltage
}
}
pub const MAX_SIGNAL_SAMPLES: usize = 160;
#[derive(Clone, Copy, Debug)]
pub struct MetrologyInsightSignal {
pub real_wave: [f32; MAX_SIGNAL_SAMPLES],
pub real_wave_len: usize,
pub length: usize,
pub length_cycle: usize,
pub calc_freq: bool,
pub peak: f32,
pub rms: f32,
pub rms_10cycle: f32,
pub cycle_10_sq_sum: f32,
pub cycle_10_count: u8,
pub freq_nominal: f32,
pub freq_zc: f32,
pub harmonics: [f32; NUMBER_HARMONICS],
pub interharmonics: [f32; NUMBER_INTERHARMONICS],
pub thd: f32,
pub sc_thres: f32,
pub signal_type: MetrologyInsightSignalType,
pub adc_factor: f32,
pub adc_scale: f32,
pub dc_offset: f32,
pub pll_state: PllState,
pub quality_flags: u32,
pub rms_sync: f32,
pub consistency_error: f32,
pub frame_start_ns: u64,
pub urms_half_cycle: UrmsHalfCycle,
}
pub const Q_FLAG_OK: u32 = 0x0000;
pub const Q_FLAG_PLL_UNSETTLED: u32 = 0x0001;
pub const Q_FLAG_SYNC_INCONSISTENT: u32 = 0x0002;
pub const Q_FLAG_OUT_OF_RANGE: u32 = 0x0004;
pub const Q_FLAG_EVENT_MARKED: u32 = 0x0008;
fn _dummy_flags() {}
#[derive(Clone, Copy, Debug, Default)]
pub struct PllState {
pub phase: f32,
pub freq_est: f32,
pub freq_10s: f32,
pub integrator: f32,
pub locked: bool,
pub error_accum: f32,
pub freq_buf: [f32; 10],
pub freq_buf_idx: usize,
pub freq_buf_count: usize,
pub cycle_freq_sum: f32,
pub cycle_freq_count: usize,
}
impl MetrologyInsightSignal {
pub fn is_current(&self) -> bool {
matches!(self.signal_type, MetrologyInsightSignalType::Current)
}
pub fn real_wave_slice(&self) -> &[f32] {
&self.real_wave[..self.real_wave_len.min(MAX_SIGNAL_SAMPLES)]
}
pub fn push_real_sample(&mut self, val: f32) {
if self.real_wave_len < MAX_SIGNAL_SAMPLES {
self.real_wave[self.real_wave_len] = val;
self.real_wave_len += 1;
}
}
pub fn clear_samples(&mut self) {
self.real_wave_len = 0;
}
}
impl Default for MetrologyInsightSignal {
fn default() -> Self {
Self {
real_wave: [0.0; MAX_SIGNAL_SAMPLES],
real_wave_len: 0,
length: 0,
length_cycle: 0,
calc_freq: false,
peak: 0.0,
rms: 0.0,
rms_10cycle: 0.0,
cycle_10_sq_sum: 0.0,
cycle_10_count: 0,
freq_nominal: FREQ_NOMINAL_50,
freq_zc: 0.0,
harmonics: [0.0; NUMBER_HARMONICS],
interharmonics: [0.0; NUMBER_INTERHARMONICS],
thd: 0.0,
sc_thres: 0.0,
signal_type: MetrologyInsightSignalType::Voltage,
adc_factor: 1.0,
adc_scale: 1.0,
dc_offset: 0.0,
pll_state: PllState::default(),
quality_flags: Q_FLAG_OK,
rms_sync: 0.0,
consistency_error: 0.0,
frame_start_ns: 0,
urms_half_cycle: UrmsHalfCycle::default(),
}
}
}
#[derive(Debug, Clone)]
pub enum PhaseDirection {
Inductive,
Capacitive,
InPhase,
}
impl Default for PhaseDirection {
fn default() -> Self {
PhaseDirection::InPhase
}
}
impl PhaseDirection {
pub fn as_str(&self) -> &'static str {
match self {
PhaseDirection::Inductive => "Inductive (current lags voltage)",
PhaseDirection::Capacitive => "Capacitive (current leads voltage)",
PhaseDirection::InPhase => "In phase (no phase difference)",
}
}
}
#[derive(Debug, Clone, Default)]
pub struct PhaseAngleMetrics {
pub c2v_angle: f32,
pub v_angle: f32,
pub c_angle: f32,
pub direction: PhaseDirection,
}
impl PhaseAngleMetrics {
pub fn direction_description(&self) -> &'static str {
self.direction.as_str()
}
}
#[derive(Debug, Clone, Default)]
pub struct PowerMetrics {
pub real_power: f32,
pub reactive_power: f32,
pub apparent_power: f32,
pub power_factor: f32,
pub displacement_pf: f32,
}
#[derive(Debug, Clone, Default)]
pub struct ActiveEnergyMetrics {
pub imported: f64,
pub exported: f64,
pub balance: f64,
pub q1: f64,
pub q2: f64,
pub q3: f64,
pub q4: f64,
pub q1_uj: i128,
pub q2_uj: i128,
pub q3_uj: i128,
pub q4_uj: i128,
}
impl ActiveEnergyMetrics {
pub fn imported(&self) -> f64 {
self.q1 + self.q4
}
pub fn exported(&self) -> f64 {
self.q2 + self.q3
}
pub fn balance(&self) -> f64 {
self.imported() - self.exported()
}
}
#[derive(Debug, Clone, Default)]
pub struct ReactiveEnergyMetrics {
pub capacitive: f64,
pub inductive: f64,
pub balance: f64,
pub q1: f64,
pub q2: f64,
pub q3: f64,
pub q4: f64,
pub q1_uj: i128,
pub q2_uj: i128,
pub q3_uj: i128,
pub q4_uj: i128,
}
impl ReactiveEnergyMetrics {
pub fn inductive(&self) -> f64 {
self.q1 + self.q3
}
pub fn capacitive(&self) -> f64 {
self.q2 + self.q4
}
pub fn balance(&self) -> f64 {
(self.q1 + self.q2) - (self.q3 + self.q4)
}
}
#[derive(Debug, Clone, Default)]
pub struct EnergyMetrics {
pub active: ActiveEnergyMetrics,
pub reactive: ReactiveEnergyMetrics,
}
#[derive(Debug, Clone, Copy, serde::Serialize)]
pub struct PqAggregationRecord {
pub timestamp_ms: u64,
pub aggregation_type: u8, pub v_rms: [f32; 3],
pub i_rms: [f32; 3],
pub frequency: f32,
pub v_peak: [f32; 3],
pub i_peak: [f32; 3],
pub active_power: f32,
pub reactive_power: f32,
pub apparent_power: f32,
pub power_factor: f32,
pub active_energy_imp: f32,
pub active_energy_exp: f32,
pub flicker: [f32; 3],
pub flicker_pst: [f32; 3],
pub v_rms_10cycle: [f32; 3],
pub freq_10s: f32,
pub u2_unbalance: f32,
pub u0_unbalance: f32,
pub u2_i_ratio_pct: f32,
pub u0_i_ratio_pct: f32,
pub i0_zero_seq: f32,
pub i1_pos_seq: f32,
pub i2_neg_seq: f32,
pub v_rms_min: [f32; 3],
pub v_rms_max: [f32; 3],
pub freq_min: f32,
pub freq_max: f32,
pub dip_count: u32,
pub swell_count: u32,
pub interrupt_count: u32,
pub rvc_count: u32,
pub v_thd: [f32; 3],
pub i_thd: [f32; 3],
pub clean_windows: u16,
pub total_windows: u16,
pub rvc_delta_u_max_pct: [f32; 3],
pub rvc_delta_u_ss_pct: [f32; 3],
#[serde(skip)]
pub padding: [u8; 3],
}
impl PqAggregationRecord {
pub fn empty() -> Self {
Self {
timestamp_ms: 0,
aggregation_type: 0,
v_rms: [0.0; 3],
i_rms: [0.0; 3],
frequency: 0.0,
v_peak: [0.0; 3],
i_peak: [0.0; 3],
active_power: 0.0,
reactive_power: 0.0,
apparent_power: 0.0,
power_factor: 0.0,
active_energy_imp: 0.0,
active_energy_exp: 0.0,
flicker: [0.0; 3],
flicker_pst: [0.0; 3],
v_rms_10cycle: [0.0; 3],
freq_10s: 0.0,
u2_unbalance: 0.0,
u0_unbalance: 0.0,
u2_i_ratio_pct: 0.0,
u0_i_ratio_pct: 0.0,
i0_zero_seq: 0.0,
i1_pos_seq: 0.0,
i2_neg_seq: 0.0,
v_rms_min: [f32::MAX; 3],
v_rms_max: [f32::MIN; 3],
freq_min: f32::MAX,
freq_max: f32::MIN,
dip_count: 0,
swell_count: 0,
interrupt_count: 0,
rvc_count: 0,
v_thd: [0.0; 3],
i_thd: [0.0; 3],
clean_windows: 0,
total_windows: 0,
rvc_delta_u_max_pct: [0.0; 3],
rvc_delta_u_ss_pct: [0.0; 3],
padding: [0u8; 3],
}
}
pub fn to_bytes(&self) -> [u8; 256] {
let mut buf = [0u8; 256];
let mut off = 0usize;
fn write_u64(buf: &mut [u8; 256], off: &mut usize, v: u64) {
buf[*off..*off + 8].copy_from_slice(&v.to_le_bytes());
*off += 8;
}
fn write_u8(buf: &mut [u8; 256], off: &mut usize, v: u8) {
buf[*off] = v;
*off += 1;
}
fn write_f32(buf: &mut [u8; 256], off: &mut usize, v: f32) {
buf[*off..*off + 4].copy_from_slice(&v.to_le_bytes());
*off += 4;
}
fn write_f32x3(buf: &mut [u8; 256], off: &mut usize, v: &[f32; 3]) {
write_f32(buf, off, v[0]);
write_f32(buf, off, v[1]);
write_f32(buf, off, v[2]);
}
fn write_u32(buf: &mut [u8; 256], off: &mut usize, v: u32) {
buf[*off..*off + 4].copy_from_slice(&v.to_le_bytes());
*off += 4;
}
fn write_u16(buf: &mut [u8; 256], off: &mut usize, v: u16) {
buf[*off..*off + 2].copy_from_slice(&v.to_le_bytes());
*off += 2;
}
write_u64(&mut buf, &mut off, self.timestamp_ms);
write_u8(&mut buf, &mut off, self.aggregation_type);
write_f32x3(&mut buf, &mut off, &self.v_rms);
write_f32x3(&mut buf, &mut off, &self.i_rms);
write_f32(&mut buf, &mut off, self.frequency);
write_f32x3(&mut buf, &mut off, &self.v_peak);
write_f32x3(&mut buf, &mut off, &self.i_peak);
write_f32(&mut buf, &mut off, self.active_power);
write_f32(&mut buf, &mut off, self.reactive_power);
write_f32(&mut buf, &mut off, self.apparent_power);
write_f32(&mut buf, &mut off, self.power_factor);
write_f32(&mut buf, &mut off, self.active_energy_imp);
write_f32(&mut buf, &mut off, self.active_energy_exp);
write_f32x3(&mut buf, &mut off, &self.flicker);
write_f32x3(&mut buf, &mut off, &self.flicker_pst);
write_f32x3(&mut buf, &mut off, &self.v_rms_10cycle);
write_f32(&mut buf, &mut off, self.freq_10s);
write_f32(&mut buf, &mut off, self.u2_unbalance);
write_f32(&mut buf, &mut off, self.u0_unbalance);
write_f32(&mut buf, &mut off, self.u2_i_ratio_pct);
write_f32(&mut buf, &mut off, self.u0_i_ratio_pct);
write_f32(&mut buf, &mut off, self.i0_zero_seq);
write_f32(&mut buf, &mut off, self.i1_pos_seq);
write_f32(&mut buf, &mut off, self.i2_neg_seq);
write_f32x3(&mut buf, &mut off, &self.v_rms_min);
write_f32x3(&mut buf, &mut off, &self.v_rms_max);
write_f32(&mut buf, &mut off, self.freq_min);
write_f32(&mut buf, &mut off, self.freq_max);
write_u32(&mut buf, &mut off, self.dip_count);
write_u32(&mut buf, &mut off, self.swell_count);
write_u32(&mut buf, &mut off, self.interrupt_count);
write_u32(&mut buf, &mut off, self.rvc_count);
write_f32x3(&mut buf, &mut off, &self.v_thd);
write_f32x3(&mut buf, &mut off, &self.i_thd);
write_u16(&mut buf, &mut off, self.clean_windows);
write_u16(&mut buf, &mut off, self.total_windows);
write_f32x3(&mut buf, &mut off, &self.rvc_delta_u_max_pct);
write_f32x3(&mut buf, &mut off, &self.rvc_delta_u_ss_pct);
buf[off..].copy_from_slice(&self.padding);
buf
}
pub fn from_bytes(bytes: &[u8; 256]) -> Self {
let mut off = 0usize;
fn read_u64(buf: &[u8; 256], off: &mut usize) -> u64 {
let v = u64::from_le_bytes(buf[*off..*off + 8].try_into().unwrap());
*off += 8;
v
}
fn read_u8(buf: &[u8; 256], off: &mut usize) -> u8 {
let v = buf[*off];
*off += 1;
v
}
fn read_f32(buf: &[u8; 256], off: &mut usize) -> f32 {
let v = f32::from_le_bytes(buf[*off..*off + 4].try_into().unwrap());
*off += 4;
v
}
fn read_f32x3(buf: &[u8; 256], off: &mut usize) -> [f32; 3] {
[read_f32(buf, off), read_f32(buf, off), read_f32(buf, off)]
}
fn read_u32(buf: &[u8; 256], off: &mut usize) -> u32 {
let v = u32::from_le_bytes(buf[*off..*off + 4].try_into().unwrap());
*off += 4;
v
}
fn read_u16(buf: &[u8; 256], off: &mut usize) -> u16 {
let v = u16::from_le_bytes(buf[*off..*off + 2].try_into().unwrap());
*off += 2;
v
}
Self {
timestamp_ms: read_u64(bytes, &mut off),
aggregation_type: read_u8(bytes, &mut off),
v_rms: read_f32x3(bytes, &mut off),
i_rms: read_f32x3(bytes, &mut off),
frequency: read_f32(bytes, &mut off),
v_peak: read_f32x3(bytes, &mut off),
i_peak: read_f32x3(bytes, &mut off),
active_power: read_f32(bytes, &mut off),
reactive_power: read_f32(bytes, &mut off),
apparent_power: read_f32(bytes, &mut off),
power_factor: read_f32(bytes, &mut off),
active_energy_imp: read_f32(bytes, &mut off),
active_energy_exp: read_f32(bytes, &mut off),
flicker: read_f32x3(bytes, &mut off),
flicker_pst: read_f32x3(bytes, &mut off),
v_rms_10cycle: read_f32x3(bytes, &mut off),
freq_10s: read_f32(bytes, &mut off),
u2_unbalance: read_f32(bytes, &mut off),
u0_unbalance: read_f32(bytes, &mut off),
u2_i_ratio_pct: read_f32(bytes, &mut off),
u0_i_ratio_pct: read_f32(bytes, &mut off),
i0_zero_seq: read_f32(bytes, &mut off),
i1_pos_seq: read_f32(bytes, &mut off),
i2_neg_seq: read_f32(bytes, &mut off),
v_rms_min: read_f32x3(bytes, &mut off),
v_rms_max: read_f32x3(bytes, &mut off),
freq_min: read_f32(bytes, &mut off),
freq_max: read_f32(bytes, &mut off),
dip_count: read_u32(bytes, &mut off),
swell_count: read_u32(bytes, &mut off),
interrupt_count: read_u32(bytes, &mut off),
rvc_count: read_u32(bytes, &mut off),
v_thd: read_f32x3(bytes, &mut off),
i_thd: read_f32x3(bytes, &mut off),
clean_windows: read_u16(bytes, &mut off),
total_windows: read_u16(bytes, &mut off),
rvc_delta_u_max_pct: read_f32x3(bytes, &mut off),
rvc_delta_u_ss_pct: read_f32x3(bytes, &mut off),
padding: {
let mut p = [0u8; 3];
p.copy_from_slice(&bytes[off..off + 3]);
p
},
}
}
}