1
0
Fork 0

Sloppily implement scale/offset

gh-action
Bo Jeanes 2022-08-14 18:05:48 +10:00
parent 78d08623a6
commit 9e421b91b3
5 changed files with 201 additions and 80 deletions

28
Cargo.lock generated
View File

@ -41,6 +41,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "arrayvec"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6"
[[package]]
name = "async-trait"
version = "0.1.56"
@ -504,6 +510,7 @@ dependencies = [
"humantime-serde",
"itertools",
"rumqttc",
"rust_decimal",
"serde",
"serde_json",
"serialport",
@ -547,6 +554,15 @@ dependencies = [
"libc",
]
[[package]]
name = "num-traits"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [
"autocfg",
]
[[package]]
name = "num_cpus"
version = "1.13.1"
@ -710,6 +726,18 @@ dependencies = [
"url",
]
[[package]]
name = "rust_decimal"
version = "1.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee9164faf726e4f3ece4978b25ca877ddc6802fa77f38cdccb32c7f805ecd70c"
dependencies = [
"arrayvec",
"num-traits",
"serde",
"serde_json",
]
[[package]]
name = "rustls"
version = "0.20.6"

View File

@ -11,6 +11,7 @@ clap = { version = "3.2.12", features = ["derive", "env"] }
humantime-serde = "1.1.1"
itertools = "0.10.3"
rumqttc = { version = "0.13.0", features = ["url"], git = "https://github.com/bytebeamio/rumqtt" }
rust_decimal = { version = "1.26.1", features = ["serde-arbitrary-precision", "serde-float", "serde_json", "maths"] }
serde = { version = "1.0.139", features = ["serde_derive"] }
serde_json = "1.0.82"
serialport = { version = "4.2.0", features = ["serde"] }

View File

@ -375,92 +375,38 @@ async fn watch_registers(
r.address.checked_sub(address_offset.unsigned_abs() as u16)
};
if let Some(address) = address {
let size = r.parse.value_type.size();
debug!(
"Polling {:?} {} {}",
read_type,
name = r.name.as_ref().unwrap_or(&"".to_string()),
address,
&r.name.as_ref().unwrap_or(&"".to_string())
size,
register_type = ?read_type,
value_type = r.parse.value_type.type_name(),
"Polling register",
);
let (tx, rx) = oneshot::channel();
modbus
.send(ModbusCommand::Read(
read_type,
address,
r.parse.value_type.size(),
tx,
))
.send(ModbusCommand::Read(read_type, address, size, tx))
.await
.unwrap();
let values = rx.await.unwrap().unwrap();
let words = rx.await.unwrap().unwrap();
let swapped_values = if r.parse.swap_bytes.0 {
values.iter().map(|v| v.swap_bytes()).collect()
} else {
values.clone()
};
let swapped_words = r.apply_swaps(&words);
let swapped_values = if r.parse.swap_words.0 {
swapped_values
.chunks_exact(2)
.flat_map(|chunk| vec![chunk[1], chunk[0]])
.collect()
} else {
swapped_values
};
let value = r.from_words(&swapped_words);
let bytes: Vec<u8> = swapped_values
.iter()
.flat_map(|v| v.to_ne_bytes())
.collect();
debug!(
name = r.name.as_ref().unwrap_or(&"".to_string()),
address,
%value,
raw = ?words,
"Received value",
);
use crate::modbus::config::RegisterValueType as T;
use crate::modbus::config::{RegisterArray, RegisterNumeric as N, RegisterString};
let value = match r.parse.value_type {
T::Numeric {
ref of,
adjust: ref _adjust,
} => match of {
N::U8 => json!(bytes[1]), // or is it 0?
N::U16 => json!(swapped_values[0]),
N::U32 => {
json!(bytes.try_into().map(|bytes| u32::from_le_bytes(bytes)).ok())
}
N::U64 => {
json!(bytes.try_into().map(|bytes| u64::from_le_bytes(bytes)).ok())
}
N::I8 => json!(vec![bytes[1]]
.try_into()
.map(|bytes| i8::from_le_bytes(bytes))),
N::I16 => {
json!(bytes.try_into().map(|bytes| i16::from_le_bytes(bytes)).ok())
}
N::I32 => {
json!(bytes.try_into().map(|bytes| i32::from_le_bytes(bytes)).ok())
}
N::I64 => {
json!(bytes.try_into().map(|bytes| i64::from_le_bytes(bytes)).ok())
}
N::F32 => {
json!(bytes.try_into().map(|bytes| f32::from_le_bytes(bytes)).ok())
}
N::F64 => {
json!(bytes.try_into().map(|bytes| f64::from_le_bytes(bytes)).ok())
}
},
T::String(RegisterString { .. }) => {
json!(String::from_utf16_lossy(&swapped_values))
}
T::Array(RegisterArray { .. }) => todo!(),
};
let payload = serde_json::to_vec(
&json!({ "raw": values, "swapped": swapped_values, "value": value }),
)
.unwrap();
let payload = serde_json::to_vec(&json!({ "value": value, "raw": words })).unwrap();
dispatcher
.send(DispatchCommand::Publish {

View File

@ -54,20 +54,17 @@ fn default_modbus_parity() -> tokio_serial::Parity {
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
#[serde(rename_all = "lowercase", default)]
pub struct RegisterNumericAdjustment {
#[serde(default)]
scale: i8, // powers of 10 (0 = no adjustment, 1 = x10, -1 = /10)
#[serde(default)]
offset: i8,
pub scale: i8, // powers of 10 (0 = no adjustment, 1 = x10, -1 = /10)
pub offset: i8,
// precision: Option<u8>,
}
impl Default for RegisterNumericAdjustment {
fn default() -> Self {
Self {
scale: 1,
scale: 0,
offset: 0,
}
}
@ -107,6 +104,10 @@ impl RegisterNumeric {
U64 | I64 | F64 => 4,
}
}
fn type_name(&self) -> String {
format!("{:?}", *self).to_lowercase()
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
@ -152,6 +153,16 @@ pub enum RegisterValueType {
String(RegisterString),
}
impl RegisterValueType {
pub fn type_name(&self) -> String {
match *self {
RegisterValueType::Numeric { ref of, .. } => of.type_name(),
RegisterValueType::Array(_) => "array".to_owned(),
RegisterValueType::String(_) => "string".to_owned(),
}
}
}
impl Default for RegisterValueType {
fn default() -> Self {
RegisterValueType::Numeric {
@ -175,6 +186,7 @@ impl RegisterValueType {
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(transparent)]
pub struct Swap(pub bool);
impl Default for Swap {
@ -404,7 +416,10 @@ fn parse_empty_register_parser_defaults() {
swap_words: Swap(false),
value_type: RegisterValueType::Numeric {
of: RegisterNumeric::U16,
..
adjust: RegisterNumericAdjustment {
scale: 0,
offset: 0,
}
}
}
));

View File

@ -1,5 +1,10 @@
use rust_decimal::{prelude::FromPrimitive, Decimal};
use serde::Serialize;
use crate::modbus::config::RegisterNumericAdjustment;
use self::config::{Register, RegisterValueType};
pub mod config;
#[derive(Serialize)]
@ -19,3 +24,129 @@ pub struct ConnectStatus {
pub type UnitId = tokio_modbus::prelude::SlaveId;
pub type Unit = tokio_modbus::prelude::Slave;
impl RegisterValueType {
pub fn from_words(&self, words: &[u16]) -> serde_json::Value {
use self::config::RegisterValueType as T;
use self::config::{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 from_words(&self, words: &[u16]) -> serde_json::Value {
self.parse.value_type.from_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
}
}
}
#[test]
fn test_parse_1() {
use self::config::{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: config::RegisterNumeric::I32,
adjust: RegisterNumericAdjustment {
scale: 0,
offset: 0,
},
},
},
};
assert_eq!(reg.from_words(&vec![843, 0]), json!(843));
}