use serde::{Deserialize, Serialize};
use std::io::{Error, ErrorKind, Result};
use std::str::FromStr;
use strum::IntoEnumIterator;
use strum_macros::{Display, EnumIter, FromRepr};
#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize, FromRepr)]
pub enum StateOfOperation {
Off = 0,
LowPower = 1,
Fault = 2,
Bulk = 3,
Absorption = 4,
Float = 5,
Storage = 6,
Equalize = 7,
Inverting = 9,
PowerSupply = 11,
StartingUp = 245,
RepeatedAbsorption = 246,
AutoEqualize = 247,
BatterySafe = 248,
ExternalControl = 252,
}
impl Default for StateOfOperation {
fn default() -> Self {
StateOfOperation::Off
}
}
#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize, FromRepr)]
pub enum ErrorCode {
None = 0,
BatteryVoltageTooHigh = 2,
ChargerTemperatureTooHigh = 17,
ChargerOverCurrent = 18,
ChargerCurrentReversed = 19,
BulkTimeLimitExceeded = 20,
CurrentSensorIssue = 21,
TerminalsOverheated = 26,
ConverterIssue = 28,
InputVoltageTooHigh = 33,
InputCurrentTooHigh = 34,
InputShutdownBatVoltage = 38,
InputShutdownCurrentFlow = 39,
LostComWithDevices = 65,
SynchronisedChargingIssue = 66,
BMSConnectionLost = 67,
NetworkMisconfigured = 68,
FactoryCalibrationDataLost = 116,
InvalidFirmware = 117,
UserSettingsInvalid = 119,
}
impl Default for ErrorCode {
fn default() -> Self {
ErrorCode::None
}
}
#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize, FromRepr, EnumIter, Copy)]
pub enum OffReason {
None = 0,
NoInputPower = 1,
SwitchedOffPowerSwitch = 2,
SwitchedOffDMR = 4,
RemoteInput = 8,
ProtectionActive = 16,
Paygo = 32,
BMS = 64,
EngineShutdownDetection = 128,
AnalysingInputVoltage = 256,
}
impl Default for OffReason {
fn default() -> Self {
OffReason::None
}
}
#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize, FromRepr, EnumIter, Copy)]
pub enum AlarmReason {
None = 0,
LowVoltage = 1,
HighVoltage = 2,
LowSOC = 4,
LowStarterVoltage = 8,
HighStarterVoltage = 16,
LowTemperature = 32,
HighTemperature = 64,
MidVoltage = 128,
Overload = 256,
DCripple = 512,
LowVACout = 1024,
HighVACout = 2048,
}
impl Default for AlarmReason {
fn default() -> Self {
AlarmReason::None
}
}
#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize, FromRepr, EnumIter, Copy)]
pub enum WarningReason {
None = 0,
LowVoltage = 1,
HighVoltage = 2,
LowSOC = 4,
LowStarterVoltage = 8,
HighStarterVoltage = 16,
LowTemperature = 32,
HighTemperature = 64,
MidVoltage = 128,
Overload = 256,
DCripple = 512,
LowVACout = 1024,
HighVACout = 2048,
}
impl Default for WarningReason {
fn default() -> Self {
WarningReason::None
}
}
#[allow(non_camel_case_types)]
#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize, FromRepr)]
pub enum DeviceMode {
None = 0,
VE_REG_MODE_INVERTER = 2,
VE_REG_MODE_OFF = 4,
VE_REG_MODE_ECO = 5,
}
impl Default for DeviceMode {
fn default() -> Self {
DeviceMode::None
}
}
#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize, FromRepr)]
pub enum BluetoothStatus {
Off = 0,
On = 1,
}
impl Default for BluetoothStatus {
fn default() -> Self {
BluetoothStatus::Off
}
}
#[allow(non_camel_case_types)]
#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize, FromRepr)]
pub enum BluetoothCapBle {
None = 0,
BLE_Supports_Switching_Off = 1,
BLE_Switching_Off_Is_Permanent = 2,
}
impl Default for BluetoothCapBle {
fn default() -> Self {
BluetoothCapBle::None
}
}
#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize, FromRepr)]
pub enum Load {
Off = 0,
On = 1,
}
impl Default for Load {
fn default() -> Self {
Load::Off
}
}
#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize, FromRepr)]
pub enum Alarm {
Off = 0,
On = 1,
}
impl Default for Alarm {
fn default() -> Self {
Alarm::Off
}
}
#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize, FromRepr)]
pub enum Relay {
Off = 0,
On = 1,
}
impl Default for Relay {
fn default() -> Self {
Relay::Off
}
}
#[allow(non_camel_case_types)]
#[derive(Display)]
pub enum Labels {
V, VS, VM, DM, VPV, PPV, I, IL, LOAD, T, P, CE, SOC, TTG, Alarm, Relay, AR, OR, H1, H2, H3, H4, H5, H6, H7, H8, H9, H10, H11, H12, H13, H14, H15, H16, H17, H18, H19, H20, H21, H22, H23, ERR, CS, BMV, FW, FWE, PID, #[strum(serialize = "SER#")]
SER, HSDS, MODE, AC_OUT_V, AC_OUT_I, AC_OUT_S, WARN, BLE, CAP_BLE, Checksum, Calc_sum, Unknown, Time, }
#[allow(non_snake_case)]
#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
pub struct VEDirectData {
pub V: Option<f64>,
pub VS: Option<f64>,
pub VM: Option<f64>,
pub DM: Option<f64>,
pub VPV: Option<f64>,
pub PPV: Option<f64>,
pub I: Option<f64>,
pub IL: Option<f64>,
pub LOAD: Option<Load>,
pub T: Option<f64>,
pub P: Option<f64>,
pub CE: Option<f64>,
pub SOC: Option<f64>,
pub TTG: Option<f64>,
pub Alarm: Option<Alarm>,
pub Relay: Option<Relay>,
pub AR: Option<Vec<AlarmReason>>,
pub OR: Option<Vec<OffReason>>,
pub H1: Option<f64>,
pub H2: Option<f64>,
pub H3: Option<f64>,
pub H4: Option<f64>,
pub H5: Option<f64>,
pub H6: Option<f64>,
pub H7: Option<f64>,
pub H8: Option<f64>,
pub H9: Option<f64>,
pub H10: Option<f64>,
pub H11: Option<f64>,
pub H12: Option<f64>,
pub H13: Option<f64>,
pub H14: Option<f64>,
pub H15: Option<f64>,
pub H16: Option<f64>,
pub H17: Option<f64>,
pub H18: Option<f64>,
pub H19: Option<f64>,
pub H20: Option<f64>,
pub H21: Option<f64>,
pub H22: Option<f64>,
pub H23: Option<f64>,
pub ERR: Option<ErrorCode>,
pub CS: Option<StateOfOperation>,
pub BMV: Option<String>,
pub FW: Option<String>,
pub FWE: Option<String>,
pub PID: Option<String>,
pub SER: Option<String>,
pub HSDS: Option<f64>,
pub MODE: Option<DeviceMode>,
pub AC_OUT_V: Option<f64>,
pub AC_OUT_I: Option<f64>,
pub AC_OUT_S: Option<f64>,
pub WARN: Option<Vec<WarningReason>>,
pub Calc_sum: Option<u8>,
pub Checksum: Option<u8>,
pub BLE: Option<BluetoothStatus>,
pub CAP_BLE: Option<BluetoothCapBle>,
pub Time: Option<i64>,
pub Unknown: Option<Vec<String>>,
}
pub type V = f64; pub type A = f64; pub type Ah = f64; pub type Wh = f64;
#[allow(non_camel_case_types)]
pub type mV = f64; #[allow(non_camel_case_types)]
pub type cV = f64; #[allow(non_camel_case_types)]
pub type mA = f64; #[allow(non_camel_case_types)]
pub type dA = f64; #[allow(non_camel_case_types)]
pub type mAh = f64; #[allow(non_camel_case_types)]
pub type daWh = f64; #[allow(non_camel_case_types)]
pub type kWh = f64;
pub fn convert_mv(str: String) -> Result<V> {
let val = str.parse::<mV>().map_err(|err| Error::new(ErrorKind::InvalidData, err))?;
Ok(val / 1000_f64)
}
pub fn convert_ma(str: String) -> Result<A> {
let val = str.parse::<mA>().map_err(|err| Error::new(ErrorKind::InvalidData, err))?;
Ok(val / 1000_f64)
}
pub fn convert_mah(str: String) -> Result<Ah> {
let val = str.parse::<mAh>().map_err(|err| Error::new(ErrorKind::InvalidData, err))?;
Ok(val / 1000_f64)
}
pub fn convert_kwh(str: String) -> Result<Wh> {
let val = str.parse::<kWh>().map_err(|err| Error::new(ErrorKind::InvalidData, err))?;
Ok(val * 1000_f64)
}
pub fn convert_dawh(str: String) -> Result<Wh> {
let val = str.parse::<daWh>().map_err(|err| Error::new(ErrorKind::InvalidData, err))?;
Ok(val * 10_f64)
}
pub fn convert_cv(str: String) -> Result<V> {
let val = str.parse::<cV>().map_err(|err| Error::new(ErrorKind::InvalidData, err))?;
Ok(val / 100_f64)
}
pub fn convert_da(str: String) -> Result<A> {
let val = str.parse::<dA>().map_err(|err| Error::new(ErrorKind::InvalidData, err))?;
Ok(val / 10_f64)
}
pub fn convert_none<T>(val: T) -> Result<T> {
Ok(val)
}
pub fn convert_parse<T>(str: String) -> Result<T>
where
T: FromStr,
{
str.parse::<T>().map_err(|_| Error::from(ErrorKind::InvalidData))
}
pub fn convert_minutes(str: String) -> Result<f64> {
let val = str.parse::<f64>().map_err(|err| Error::new(ErrorKind::InvalidData, err))?;
Ok(val * 60_f64)
}
pub fn convert_state_of_operation(field: String) -> Result<StateOfOperation> {
match StateOfOperation::from_repr(field.parse::<usize>().map_err(|_| Error::from(ErrorKind::InvalidData))?) {
Some(v) => Ok(v),
None => Err(Error::from(ErrorKind::InvalidData)),
}
}
pub fn convert_error_code(field: String) -> Result<ErrorCode> {
match ErrorCode::from_repr(field.parse::<usize>().map_err(|_| Error::from(ErrorKind::InvalidData))?) {
Some(v) => Ok(v),
None => Err(Error::from(ErrorKind::InvalidData)),
}
}
pub fn convert_alarm_reason(field: String) -> Result<Vec<AlarmReason>> {
let val = field.parse::<usize>().map_err(|_| Error::from(ErrorKind::InvalidData))?;
let mut ret = Vec::<AlarmReason>::new();
if val == 0 {
ret.push(AlarmReason::None);
return Ok(ret);
}
for reason in AlarmReason::iter() {
if val & (reason as usize) > 0 {
ret.push(reason)
}
}
if !ret.is_empty() {
Ok(ret)
} else {
Err(Error::from(ErrorKind::InvalidData))
}
}
pub fn convert_warning_reason(field: String) -> Result<Vec<WarningReason>> {
let val = field.parse::<usize>().map_err(|_| Error::from(ErrorKind::InvalidData))?;
let mut ret = Vec::<WarningReason>::new();
if val == 0 {
ret.push(WarningReason::None);
return Ok(ret);
}
for reason in WarningReason::iter() {
if val & (reason as usize) > 0 {
ret.push(reason)
}
}
if !ret.is_empty() {
Ok(ret)
} else {
Err(Error::from(ErrorKind::InvalidData))
}
}
pub fn convert_device_mode(field: String) -> Result<DeviceMode> {
match DeviceMode::from_repr(field.parse::<usize>().map_err(|_| Error::from(ErrorKind::InvalidData))?) {
Some(v) => Ok(v),
None => Err(Error::from(ErrorKind::InvalidData)),
}
}
pub fn convert_off_reason(field: String) -> Result<Vec<OffReason>> {
let without_prefix = field.trim_start_matches("0x");
let val = usize::from_str_radix(without_prefix, 16).map_err(|_| Error::from(ErrorKind::InvalidData))?;
let mut ret = Vec::<OffReason>::new();
if val == 0 {
ret.push(OffReason::None);
return Ok(ret);
}
for reason in OffReason::iter() {
if val & (reason as usize) > 0 {
ret.push(reason)
}
}
if !ret.is_empty() {
Ok(ret)
} else {
Err(Error::from(ErrorKind::InvalidData))
}
}
pub fn convert_ble(field: String) -> Result<BluetoothStatus> {
let without_prefix = field.trim_start_matches("0x");
let val = usize::from_str_radix(without_prefix, 16).map_err(|_| Error::from(ErrorKind::InvalidData))?;
match BluetoothStatus::from_repr(val) {
Some(v) => Ok(v),
None => Err(Error::from(ErrorKind::InvalidData)),
}
}
pub fn convert_capble(field: String) -> Result<BluetoothCapBle> {
let without_prefix = field.trim_start_matches("0x");
let val = usize::from_str_radix(without_prefix, 16).map_err(|_| Error::from(ErrorKind::InvalidData))?;
match BluetoothCapBle::from_repr(val) {
Some(v) => Ok(v),
None => Err(Error::from(ErrorKind::InvalidData)),
}
}
pub fn convert_alarm(field: String) -> Result<Alarm> {
let lower = field.to_lowercase();
if lower == "on" {
Ok(Alarm::On)
} else if lower == "off" {
Ok(Alarm::Off)
} else {
Err(Error::from(ErrorKind::InvalidData))
}
}
pub fn convert_relay(field: String) -> Result<Relay> {
let lower = field.to_lowercase();
if lower == "on" {
Ok(Relay::On)
} else if lower == "off" {
Ok(Relay::Off)
} else {
Err(Error::from(ErrorKind::InvalidData))
}
}
pub fn convert_load(field: String) -> Result<Load> {
let lower = field.to_lowercase();
if lower == "on" {
Ok(Load::On)
} else if lower == "off" {
Ok(Load::Off)
} else {
Err(Error::from(ErrorKind::InvalidData))
}
}