1
0
Fork 0

Fix tests

refactor
Bo Jeanes 2022-09-08 11:17:16 +10:00
parent 32cd2b2e78
commit 1698959de9
3 changed files with 369 additions and 365 deletions

View File

@ -196,3 +196,145 @@ pub(crate) fn default_modbus_flow_control() -> tokio_serial::FlowControl {
pub(crate) fn default_modbus_parity() -> tokio_serial::Parity { pub(crate) fn default_modbus_parity() -> tokio_serial::Parity {
tokio_serial::Parity::None tokio_serial::Parity::None
} }
#[test]
fn parse_minimal_tcp_connect_config() {
use serde_json::json;
let result = serde_json::from_value::<Config>(json!({
"proto": "tcp",
"host": "1.1.1.1"
}));
let connect = result.unwrap();
assert!(matches!(
connect.settings,
ModbusProto::Tcp {
ref host,
port: 502
} if host == "1.1.1.1"
))
}
#[test]
fn parse_full_tcp_connect_config() {
use serde_json::json;
let _ = serde_json::from_value::<Config>(json!({
"proto": "tcp",
"host": "10.10.10.219",
"unit": 1,
"address_offset": -1,
"input": [
{
"address": 5017,
"type": "u32",
"name": "dc_power",
"swap_words": false,
"period": "3s"
},
{
"address": 5008,
"type": "s16",
"name": "internal_temperature",
"period": "1m"
},
{
"address": 13008,
"type": "s32",
"name": "load_power",
"swap_words": false,
"period": "3s"
},
{
"address": 13010,
"type": "s32",
"name": "export_power",
"swap_words": false,
"period": "3s"
},
{
"address": 13022,
"name": "battery_power",
"period": "3s"
},
{
"address": 13023,
"name": "battery_level",
"period": "1m"
},
{
"address": 13024,
"name": "battery_health",
"period": "10m"
}
],
"hold": [
{
"address": 13058,
"name": "max_soc",
"period": "90s"
},
{
"address": 13059,
"name": "min_soc",
"period": "90s"
}
]
}))
.unwrap();
}
#[test]
fn parse_minimal_rtu_connect_config() {
use serde_json::json;
let result = serde_json::from_value::<Config>(json!({
"proto": "rtu",
"tty": "/dev/ttyUSB0",
"baud_rate": 9600,
}));
let connect = result.unwrap();
use tokio_serial::*;
assert!(matches!(
connect.settings,
ModbusProto::Rtu {
ref tty,
baud_rate: 9600,
data_bits: DataBits::Eight,
stop_bits: StopBits::One,
flow_control: FlowControl::None,
parity: Parity::None,
..
} if tty == "/dev/ttyUSB0"
))
}
#[test]
fn parse_complete_rtu_connect_config() {
use serde_json::json;
let result = serde_json::from_value::<Config>(json!({
"proto": "rtu",
"tty": "/dev/ttyUSB0",
"baud_rate": 12800,
// TODO: make lowercase words work
"data_bits": "Seven", // TODO: make 7 work
"stop_bits": "Two", // TODO: make 2 work
"flow_control": "Software",
"parity": "Even",
}));
let connect = result.unwrap();
use tokio_serial::*;
assert!(matches!(
connect.settings,
ModbusProto::Rtu {
ref tty,
baud_rate: 12800,
data_bits: DataBits::Seven,
stop_bits: StopBits::Two,
flow_control: FlowControl::Software,
parity: Parity::Even,
..
} if tty == "/dev/ttyUSB0"
),);
}

View File

@ -15,139 +15,5 @@ pub enum ConnectState {
Errored, Errored,
} }
// #[derive(Serialize)]
// pub struct ConnectStatus {
// #[serde(flatten)]
// pub connect: config::Connect,
// pub status: ConnectState,
// }
pub type UnitId = tokio_modbus::prelude::SlaveId; pub type UnitId = tokio_modbus::prelude::SlaveId;
pub type Unit = tokio_modbus::prelude::Slave; pub type Unit = tokio_modbus::prelude::Slave;
impl RegisterValueType {
pub fn parse_words(&self, words: &[u16]) -> serde_json::Value {
use self::register::RegisterValueType as T;
use self::register::{RegisterArray, RegisterNumeric as N, RegisterString};
use serde_json::json;
let bytes: Vec<u8> = words.iter().flat_map(|v| v.to_ne_bytes()).collect();
match *self {
T::Numeric { ref of, ref adjust } => {
use rust_decimal::MathematicalOps;
let scale: Decimal = Decimal::TEN.powi(adjust.scale.into()).normalize();
let offset = Decimal::from(adjust.offset);
match of {
N::U8 => json!(scale * Decimal::from(bytes[1]) + offset), // or is it 0?
N::U16 => json!(scale * Decimal::from(words[0]) + offset),
N::U32 => {
json!(bytes
.try_into()
.map(|bytes| scale * Decimal::from(u32::from_le_bytes(bytes)) + offset)
.ok())
}
N::U64 => {
json!(bytes
.try_into()
.map(|bytes| scale * Decimal::from(u64::from_le_bytes(bytes)) + offset)
.ok())
}
N::I8 => {
json!(vec![bytes[1]]
.try_into()
.map(|bytes| scale * Decimal::from(i8::from_le_bytes(bytes)) + offset)
.ok())
}
N::I16 => {
json!(bytes
.try_into()
.map(|bytes| scale * Decimal::from(i16::from_le_bytes(bytes)) + offset)
.ok())
}
N::I32 => {
json!(bytes
.try_into()
.map(|bytes| scale * Decimal::from(i32::from_le_bytes(bytes)) + offset)
.ok())
}
N::I64 => {
json!(bytes
.try_into()
.map(|bytes| scale * Decimal::from(i64::from_le_bytes(bytes)) + offset)
.ok())
}
N::F32 => {
json!(bytes
.try_into()
.map(|bytes| scale
* Decimal::from_f32(f32::from_le_bytes(bytes)).unwrap()
+ offset)
.ok())
}
N::F64 => {
json!(bytes
.try_into()
.map(|bytes| scale
* Decimal::from_f64(f64::from_le_bytes(bytes)).unwrap()
+ offset)
.ok())
}
}
}
T::String(RegisterString { .. }) => {
json!(String::from_utf16_lossy(words))
}
T::Array(RegisterArray { .. }) => todo!(),
}
}
}
impl Register {
pub fn parse_words(&self, words: &[u16]) -> serde_json::Value {
self.parse.value_type.parse_words(words)
}
pub fn apply_swaps(&self, words: &[u16]) -> Vec<u16> {
let words: Vec<u16> = if self.parse.swap_bytes.0 {
words.iter().map(|v| v.swap_bytes()).collect()
} else {
words.into()
};
if self.parse.swap_words.0 {
words
.chunks_exact(2)
.flat_map(|chunk| vec![chunk[1], chunk[0]])
.collect()
} else {
words
}
}
}
#[cfg(test)]
use pretty_assertions::assert_eq;
#[test]
fn test_parse_1() {
use self::register::{RegisterParse, Swap};
use serde_json::json;
let reg = Register {
address: 42,
name: None,
interval: Default::default(),
parse: RegisterParse {
swap_bytes: Swap(false),
swap_words: Swap(false),
value_type: RegisterValueType::Numeric {
of: register::RegisterNumeric::I32,
adjust: register::RegisterNumericAdjustment {
scale: 0,
offset: 0,
},
},
},
};
assert_eq!(reg.parse_words(&[843, 0]), json!(843));
}

View File

@ -1,5 +1,5 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::time::Duration; use std::{ops::Add, time::Duration};
#[derive(Clone, Default, Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Default, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase", default)] #[serde(rename_all = "lowercase", default)]
@ -180,249 +180,245 @@ fn default_register_interval() -> Duration {
Duration::from_secs(60) Duration::from_secs(60)
} }
// #[test] #[test]
// fn parse_minimal_tcp_connect_config() { fn parse_empty_register_parser_defaults() {
// let result = serde_json::from_value::<Connect>(json!({ use serde_json::json;
// "proto": "tcp", let empty = serde_json::from_value::<RegisterParse>(json!({}));
// "host": "1.1.1.1" assert!(matches!(
// })); empty.unwrap(),
RegisterParse {
swap_bytes: Swap(false),
swap_words: Swap(false),
value_type: RegisterValueType::Numeric {
of: RegisterNumeric::U16,
adjust: RegisterNumericAdjustment {
scale: 0,
offset: 0,
}
}
}
));
}
// let connect = result.unwrap(); #[test]
// assert!(matches!( fn parse_register_parser_type() {
// connect.settings, use serde_json::json;
// ModbusProto::Tcp { let result = serde_json::from_value::<RegisterParse>(json!({
// ref host, "type": "s32"
// port: 502 }));
// } if host == "1.1.1.1" assert!(matches!(
// )) result.unwrap().value_type,
// } RegisterValueType::Numeric {
of: RegisterNumeric::I32,
..
}
));
}
// #[test] #[test]
// fn parse_full_tcp_connect_config() { fn parse_register_parser_array() {
// let _ = serde_json::from_value::<Connect>(json!({ use serde_json::json;
// "proto": "tcp", let result = serde_json::from_value::<RegisterParse>(json!({
// "host": "10.10.10.219", "type": "array",
// "unit": 1, "of": "s32",
// "address_offset": -1, "count": 10,
// "input": [ }));
// { let payload = result.unwrap();
// "address": 5017, // println!("{:?}", payload);
// "type": "u32", // println!("{}", serde_json::to_string_pretty(&payload).unwrap());
// "name": "dc_power",
// "swap_words": false,
// "period": "3s"
// },
// {
// "address": 5008,
// "type": "s16",
// "name": "internal_temperature",
// "period": "1m"
// },
// {
// "address": 13008,
// "type": "s32",
// "name": "load_power",
// "swap_words": false,
// "period": "3s"
// },
// {
// "address": 13010,
// "type": "s32",
// "name": "export_power",
// "swap_words": false,
// "period": "3s"
// },
// {
// "address": 13022,
// "name": "battery_power",
// "period": "3s"
// },
// {
// "address": 13023,
// "name": "battery_level",
// "period": "1m"
// },
// {
// "address": 13024,
// "name": "battery_health",
// "period": "10m"
// }
// ],
// "hold": [
// {
// "address": 13058,
// "name": "max_soc",
// "period": "90s"
// },
// {
// "address": 13059,
// "name": "min_soc",
// "period": "90s"
// }
// ]
// }))
// .unwrap();
// }
// #[test] assert!(matches!(
// fn parse_minimal_rtu_connect_config() { payload.value_type,
// let result = serde_json::from_value::<Connect>(json!({ RegisterValueType::Array(RegisterArray {
// "proto": "rtu", of: RegisterNumeric::I32,
// "tty": "/dev/ttyUSB0", count: 10,
// "baud_rate": 9600, ..
// })); })
));
}
// let connect = result.unwrap(); #[test]
// use tokio_serial::*; fn parse_register_parser_array_implicit_u16() {
// assert!(matches!( use serde_json::json;
// connect.settings, let result = serde_json::from_value::<RegisterParse>(json!({
// ModbusProto::Rtu { "type": "array",
// ref tty, "count": 10,
// baud_rate: 9600, }));
// data_bits: DataBits::Eight, let payload = result.unwrap();
// stop_bits: StopBits::One, // println!("{:?}", payload);
// flow_control: FlowControl::None, // println!("{}", serde_json::to_string_pretty(&payload).unwrap());
// parity: Parity::None,
// ..
// } if tty == "/dev/ttyUSB0"
// ))
// }
// #[test] assert!(matches!(
// fn parse_complete_rtu_connect_config() { payload.value_type,
// let result = serde_json::from_value::<Connect>(json!({ RegisterValueType::Array(RegisterArray {
// "proto": "rtu", of: RegisterNumeric::U16,
// "tty": "/dev/ttyUSB0", count: 10,
// "baud_rate": 12800, ..
})
));
}
// // TODO: make lowercase words work #[test]
// "data_bits": "Seven", // TODO: make 7 work fn parse_register_parser_string() {
// "stop_bits": "Two", // TODO: make 2 work use serde_json::json;
// "flow_control": "Software", let result = serde_json::from_value::<RegisterParse>(json!({
// "parity": "Even", "type": "string",
// })); "length": 10,
}));
let payload = result.unwrap();
// println!("{:?}", payload);
// println!("{}", serde_json::to_string_pretty(&payload).unwrap());
// let connect = result.unwrap(); assert!(matches!(
// use tokio_serial::*; payload.value_type,
// assert!(matches!( RegisterValueType::String(RegisterString { length: 10, .. })
// connect.settings, ));
// ModbusProto::Rtu { }
// ref tty,
// baud_rate: 12800,
// data_bits: DataBits::Seven,
// stop_bits: StopBits::Two,
// flow_control: FlowControl::Software,
// parity: Parity::Even,
// ..
// } if tty == "/dev/ttyUSB0"
// ),);
// }
// #[test] #[test]
// fn parse_empty_register_parser_defaults() { fn parse_register_parser_scale_etc() {
// let empty = serde_json::from_value::<RegisterParse>(json!({})); use serde_json::json;
// assert!(matches!( let result = serde_json::from_value::<RegisterParse>(json!({
// empty.unwrap(), "type": "s32",
// RegisterParse { "scale": -1,
// swap_bytes: Swap(false), "offset": 20,
// swap_words: Swap(false), }));
// value_type: RegisterValueType::Numeric { assert!(matches!(
// of: RegisterNumeric::U16, result.unwrap().value_type,
// adjust: RegisterNumericAdjustment { RegisterValueType::Numeric {
// scale: 0, of: RegisterNumeric::I32,
// offset: 0, adjust: RegisterNumericAdjustment {
// } scale: -1,
// } offset: 20
// } }
// )); }
// } ));
}
// #[test] impl RegisterValueType {
// fn parse_register_parser_type() { pub fn parse_words(&self, words: &[u16]) -> serde_json::Value {
// let result = serde_json::from_value::<RegisterParse>(json!({ use self::RegisterNumeric as N;
// "type": "s32" use rust_decimal::{prelude::FromPrimitive, Decimal, MathematicalOps};
// })); use serde_json::json;
// assert!(matches!( use RegisterValueType as T;
// result.unwrap().value_type,
// RegisterValueType::Numeric {
// of: RegisterNumeric::I32,
// ..
// }
// ));
// }
// #[test] let bytes: Vec<u8> = words.iter().flat_map(|v| v.to_ne_bytes()).collect();
// fn parse_register_parser_array() {
// let result = serde_json::from_value::<RegisterParse>(json!({
// "type": "array",
// "of": "s32",
// "count": 10,
// }));
// let payload = result.unwrap();
// // println!("{:?}", payload);
// // println!("{}", serde_json::to_string_pretty(&payload).unwrap());
// assert!(matches!( match *self {
// payload.value_type, T::Numeric { ref of, ref adjust } => {
// RegisterValueType::Array(RegisterArray { let scale: Decimal = Decimal::TEN.powi(adjust.scale.into()).normalize();
// of: RegisterNumeric::I32, let offset = Decimal::from(adjust.offset);
// count: 10, match of {
// .. N::U8 => json!(scale * Decimal::from(bytes[1]) + offset), // or is it 0?
// }) N::U16 => json!(scale * Decimal::from(words[0]) + offset),
// )); N::U32 => {
// } json!(bytes
.try_into()
.map(|bytes| scale * Decimal::from(u32::from_le_bytes(bytes)) + offset)
.ok())
}
N::U64 => {
json!(bytes
.try_into()
.map(|bytes| scale * Decimal::from(u64::from_le_bytes(bytes)) + offset)
.ok())
}
N::I8 => {
json!(vec![bytes[1]]
.try_into()
.map(|bytes| scale * Decimal::from(i8::from_le_bytes(bytes)) + offset)
.ok())
}
N::I16 => {
json!(bytes
.try_into()
.map(|bytes| scale * Decimal::from(i16::from_le_bytes(bytes)) + offset)
.ok())
}
N::I32 => {
json!(bytes
.try_into()
.map(|bytes| scale * Decimal::from(i32::from_le_bytes(bytes)) + offset)
.ok())
}
N::I64 => {
json!(bytes
.try_into()
.map(|bytes| scale * Decimal::from(i64::from_le_bytes(bytes)) + offset)
.ok())
}
N::F32 => {
json!(bytes
.try_into()
.map(|bytes| scale
* Decimal::from_f32(f32::from_le_bytes(bytes)).unwrap()
+ offset)
.ok())
}
N::F64 => {
json!(bytes
.try_into()
.map(|bytes| scale
* Decimal::from_f64(f64::from_le_bytes(bytes)).unwrap()
+ offset)
.ok())
}
}
}
T::String(RegisterString { .. }) => {
json!(String::from_utf16_lossy(words))
}
T::Array(RegisterArray { .. }) => todo!(),
}
}
}
// #[test] impl Register {
// fn parse_register_parser_array_implicit_u16() { pub fn parse_words(&self, words: &[u16]) -> serde_json::Value {
// let result = serde_json::from_value::<RegisterParse>(json!({ self.parse.value_type.parse_words(words)
// "type": "array", }
// "count": 10,
// }));
// let payload = result.unwrap();
// // println!("{:?}", payload);
// // println!("{}", serde_json::to_string_pretty(&payload).unwrap());
// assert!(matches!( pub fn apply_swaps(&self, words: &[u16]) -> Vec<u16> {
// payload.value_type, let words: Vec<u16> = if self.parse.swap_bytes.0 {
// RegisterValueType::Array(RegisterArray { words.iter().map(|v| v.swap_bytes()).collect()
// of: RegisterNumeric::U16, } else {
// count: 10, words.into()
// .. };
// })
// ));
// }
// #[test] if self.parse.swap_words.0 {
// fn parse_register_parser_string() { words
// let result = serde_json::from_value::<RegisterParse>(json!({ .chunks_exact(2)
// "type": "string", .flat_map(|chunk| vec![chunk[1], chunk[0]])
// "length": 10, .collect()
// })); } else {
// let payload = result.unwrap(); words
// // println!("{:?}", payload); }
// // println!("{}", serde_json::to_string_pretty(&payload).unwrap()); }
}
#[cfg(test)]
use pretty_assertions::assert_eq;
#[test]
fn test_parse_1() {
use serde_json::json;
// assert!(matches!( let reg = AddressedRegister {
// payload.value_type, address: 42,
// RegisterValueType::String(RegisterString { length: 10, .. }) register: Register {
// )); name: None,
// } interval: Default::default(),
parse: RegisterParse {
swap_bytes: Swap(false),
swap_words: Swap(false),
value_type: RegisterValueType::Numeric {
of: RegisterNumeric::I32,
adjust: RegisterNumericAdjustment {
scale: 0,
offset: 0,
},
},
},
},
};
// #[test] assert_eq!(reg.register.parse_words(&[843, 0]), json!(843));
// fn parse_register_parser_scale_etc() { }
// let result = serde_json::from_value::<RegisterParse>(json!({
// "type": "s32",
// "scale": -1,
// "offset": 20,
// }));
// assert!(matches!(
// result.unwrap().value_type,
// RegisterValueType::Numeric {
// of: RegisterNumeric::I32,
// adjust: RegisterNumericAdjustment {
// scale: -1,
// offset: 20
// }
// }
// ));
// }