Sloppily implement scale/offset
parent
78d08623a6
commit
9e421b91b3
|
@ -41,6 +41,12 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arrayvec"
|
||||||
|
version = "0.7.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-trait"
|
name = "async-trait"
|
||||||
version = "0.1.56"
|
version = "0.1.56"
|
||||||
|
@ -504,6 +510,7 @@ dependencies = [
|
||||||
"humantime-serde",
|
"humantime-serde",
|
||||||
"itertools",
|
"itertools",
|
||||||
"rumqttc",
|
"rumqttc",
|
||||||
|
"rust_decimal",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serialport",
|
"serialport",
|
||||||
|
@ -547,6 +554,15 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-traits"
|
||||||
|
version = "0.2.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num_cpus"
|
name = "num_cpus"
|
||||||
version = "1.13.1"
|
version = "1.13.1"
|
||||||
|
@ -710,6 +726,18 @@ dependencies = [
|
||||||
"url",
|
"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]]
|
[[package]]
|
||||||
name = "rustls"
|
name = "rustls"
|
||||||
version = "0.20.6"
|
version = "0.20.6"
|
||||||
|
|
|
@ -11,6 +11,7 @@ clap = { version = "3.2.12", features = ["derive", "env"] }
|
||||||
humantime-serde = "1.1.1"
|
humantime-serde = "1.1.1"
|
||||||
itertools = "0.10.3"
|
itertools = "0.10.3"
|
||||||
rumqttc = { version = "0.13.0", features = ["url"], git = "https://github.com/bytebeamio/rumqtt" }
|
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 = { version = "1.0.139", features = ["serde_derive"] }
|
||||||
serde_json = "1.0.82"
|
serde_json = "1.0.82"
|
||||||
serialport = { version = "4.2.0", features = ["serde"] }
|
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)
|
r.address.checked_sub(address_offset.unsigned_abs() as u16)
|
||||||
};
|
};
|
||||||
if let Some(address) = address {
|
if let Some(address) = address {
|
||||||
|
let size = r.parse.value_type.size();
|
||||||
debug!(
|
debug!(
|
||||||
"Polling {:?} {} {}",
|
name = r.name.as_ref().unwrap_or(&"".to_string()),
|
||||||
read_type,
|
|
||||||
address,
|
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();
|
let (tx, rx) = oneshot::channel();
|
||||||
|
|
||||||
modbus
|
modbus
|
||||||
.send(ModbusCommand::Read(
|
.send(ModbusCommand::Read(read_type, address, size, tx))
|
||||||
read_type,
|
|
||||||
address,
|
|
||||||
r.parse.value_type.size(),
|
|
||||||
tx,
|
|
||||||
))
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let values = rx.await.unwrap().unwrap();
|
let words = rx.await.unwrap().unwrap();
|
||||||
|
|
||||||
let swapped_values = if r.parse.swap_bytes.0 {
|
let swapped_words = r.apply_swaps(&words);
|
||||||
values.iter().map(|v| v.swap_bytes()).collect()
|
|
||||||
} else {
|
|
||||||
values.clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
let swapped_values = if r.parse.swap_words.0 {
|
let value = r.from_words(&swapped_words);
|
||||||
swapped_values
|
|
||||||
.chunks_exact(2)
|
|
||||||
.flat_map(|chunk| vec![chunk[1], chunk[0]])
|
|
||||||
.collect()
|
|
||||||
} else {
|
|
||||||
swapped_values
|
|
||||||
};
|
|
||||||
|
|
||||||
let bytes: Vec<u8> = swapped_values
|
debug!(
|
||||||
.iter()
|
name = r.name.as_ref().unwrap_or(&"".to_string()),
|
||||||
.flat_map(|v| v.to_ne_bytes())
|
address,
|
||||||
.collect();
|
%value,
|
||||||
|
raw = ?words,
|
||||||
|
"Received value",
|
||||||
|
);
|
||||||
|
|
||||||
use crate::modbus::config::RegisterValueType as T;
|
let payload = serde_json::to_vec(&json!({ "value": value, "raw": words })).unwrap();
|
||||||
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();
|
|
||||||
|
|
||||||
dispatcher
|
dispatcher
|
||||||
.send(DispatchCommand::Publish {
|
.send(DispatchCommand::Publish {
|
||||||
|
|
|
@ -54,20 +54,17 @@ fn default_modbus_parity() -> tokio_serial::Parity {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase", default)]
|
||||||
pub struct RegisterNumericAdjustment {
|
pub struct RegisterNumericAdjustment {
|
||||||
#[serde(default)]
|
pub scale: i8, // powers of 10 (0 = no adjustment, 1 = x10, -1 = /10)
|
||||||
scale: i8, // powers of 10 (0 = no adjustment, 1 = x10, -1 = /10)
|
pub offset: i8,
|
||||||
|
|
||||||
#[serde(default)]
|
|
||||||
offset: i8,
|
|
||||||
// precision: Option<u8>,
|
// precision: Option<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for RegisterNumericAdjustment {
|
impl Default for RegisterNumericAdjustment {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
scale: 1,
|
scale: 0,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -107,6 +104,10 @@ impl RegisterNumeric {
|
||||||
U64 | I64 | F64 => 4,
|
U64 | I64 | F64 => 4,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn type_name(&self) -> String {
|
||||||
|
format!("{:?}", *self).to_lowercase()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
|
@ -152,6 +153,16 @@ pub enum RegisterValueType {
|
||||||
String(RegisterString),
|
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 {
|
impl Default for RegisterValueType {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
RegisterValueType::Numeric {
|
RegisterValueType::Numeric {
|
||||||
|
@ -175,6 +186,7 @@ impl RegisterValueType {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
|
#[serde(transparent)]
|
||||||
pub struct Swap(pub bool);
|
pub struct Swap(pub bool);
|
||||||
|
|
||||||
impl Default for Swap {
|
impl Default for Swap {
|
||||||
|
@ -404,7 +416,10 @@ fn parse_empty_register_parser_defaults() {
|
||||||
swap_words: Swap(false),
|
swap_words: Swap(false),
|
||||||
value_type: RegisterValueType::Numeric {
|
value_type: RegisterValueType::Numeric {
|
||||||
of: RegisterNumeric::U16,
|
of: RegisterNumeric::U16,
|
||||||
..
|
adjust: RegisterNumericAdjustment {
|
||||||
|
scale: 0,
|
||||||
|
offset: 0,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
|
use rust_decimal::{prelude::FromPrimitive, Decimal};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::modbus::config::RegisterNumericAdjustment;
|
||||||
|
|
||||||
|
use self::config::{Register, RegisterValueType};
|
||||||
|
|
||||||
pub mod config;
|
pub mod config;
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
|
@ -19,3 +24,129 @@ pub struct ConnectStatus {
|
||||||
|
|
||||||
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 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