Sloppily implement scale/offset
parent
78d08623a6
commit
9e421b91b3
|
@ -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"
|
||||
|
|
|
@ -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"] }
|
||||
|
|
90
src/main.rs
90
src/main.rs
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
));
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue