#[derive(Debug, Clone, Copy, Default)]
pub struct GridMeasurement {
pub frequency_hz: f64,
pub voltage_rms: f64,
pub active_power_w: f64,
pub reactive_power_var: f64,
pub power_factor: f64,
pub time_us: u64,
}
#[derive(Debug, Clone, Copy, Default)]
pub struct GridCommand {
pub active_power_sp: f64,
pub reactive_power_sp: f64,
pub voltage_sp: f64,
pub enabled: bool,
}
pub trait OxigridInterface {
fn read_measurement(&self) -> GridMeasurement;
fn send_command(&mut self, cmd: GridCommand);
fn is_connected(&self) -> bool;
}
pub struct NullOxigridInterface {
pub nominal_frequency_hz: f64,
pub nominal_voltage_rms: f64,
last_cmd: GridCommand,
}
impl NullOxigridInterface {
pub fn new(nominal_frequency_hz: f64, nominal_voltage_rms: f64) -> Self {
Self {
nominal_frequency_hz,
nominal_voltage_rms,
last_cmd: GridCommand::default(),
}
}
pub fn last_command(&self) -> GridCommand {
self.last_cmd
}
}
impl OxigridInterface for NullOxigridInterface {
fn read_measurement(&self) -> GridMeasurement {
GridMeasurement {
frequency_hz: self.nominal_frequency_hz,
voltage_rms: self.nominal_voltage_rms,
active_power_w: 0.0,
reactive_power_var: 0.0,
power_factor: 1.0,
time_us: 0,
}
}
fn send_command(&mut self, cmd: GridCommand) {
self.last_cmd = cmd;
}
fn is_connected(&self) -> bool {
true
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn null_interface_returns_nominal() {
let iface = NullOxigridInterface::new(50.0, 230.0);
let m = iface.read_measurement();
assert!((m.frequency_hz - 50.0).abs() < 1e-10);
assert!((m.voltage_rms - 230.0).abs() < 1e-10);
assert!(iface.is_connected());
}
#[test]
fn send_command_stored() {
let mut iface = NullOxigridInterface::new(50.0, 230.0);
let cmd = GridCommand {
active_power_sp: 1000.0,
enabled: true,
..Default::default()
};
iface.send_command(cmd);
assert!((iface.last_command().active_power_sp - 1000.0).abs() < 1e-10);
assert!(iface.last_command().enabled);
}
}