initial commit
This commit is contained in:
commit
8636825388
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/target
|
||||
Cargo.lock
|
11
Cargo.toml
Normal file
11
Cargo.toml
Normal file
@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "s7_datatypes"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0", features = [ "derive" ] }
|
||||
anyhow = "1.0"
|
||||
bit = "0.1"
|
13
src/errors.rs
Normal file
13
src/errors.rs
Normal file
@ -0,0 +1,13 @@
|
||||
use anyhow::{anyhow, Error};
|
||||
|
||||
pub fn serial_error(name: String, pos: u32) -> Error {
|
||||
anyhow!(
|
||||
"Cannot convert Serial data for type '{}' at position {}.",
|
||||
name,
|
||||
pos
|
||||
)
|
||||
}
|
||||
|
||||
pub fn s7_read_error(name: String, pos: u32) -> Error {
|
||||
anyhow!("Cannot read Byte for type '{}' at position {}.", name, pos)
|
||||
}
|
11
src/lib.rs
Normal file
11
src/lib.rs
Normal file
@ -0,0 +1,11 @@
|
||||
mod errors;
|
||||
pub mod sps_datatypes;
|
||||
pub mod types;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn it_works() {
|
||||
assert_eq!(2 + 2, 4);
|
||||
}
|
||||
}
|
105
src/sps_datatypes.rs
Normal file
105
src/sps_datatypes.rs
Normal file
@ -0,0 +1,105 @@
|
||||
use super::types::boolean::*;
|
||||
use super::types::date_time::*;
|
||||
use super::types::dint::*;
|
||||
use super::types::int::*;
|
||||
use super::types::real::*;
|
||||
use super::types::string::*;
|
||||
use super::types::time::*;
|
||||
use super::types::udint::*;
|
||||
use super::types::uint::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
#[derive(Debug, Serialize, Copy, Clone)]
|
||||
pub enum SPSDataTypes {
|
||||
Boolean(BooleanType),
|
||||
Int(IntType),
|
||||
DInt(DIntType),
|
||||
Real(RealType),
|
||||
Time(TimeType),
|
||||
DateTime(DateTimeType),
|
||||
UDInt(UDIntType),
|
||||
UInt(UIntType),
|
||||
String(StringType),
|
||||
}
|
||||
|
||||
impl SPSDataTypes {
|
||||
fn into(self) -> Box<dyn DataEvaluation> {
|
||||
match self {
|
||||
Self::Boolean(raw) => Box::new(raw),
|
||||
Self::DInt(raw) => Box::new(raw),
|
||||
Self::Int(raw) => Box::new(raw),
|
||||
Self::Real(raw) => Box::new(raw),
|
||||
Self::Time(raw) => Box::new(raw),
|
||||
Self::DateTime(raw) => Box::new(raw),
|
||||
Self::UDInt(raw) => Box::new(raw),
|
||||
Self::UInt(raw) => Box::new(raw),
|
||||
Self::String(raw) => Box::new(raw),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_end_byte(self) -> u32 {
|
||||
self.into().get_end_byte()
|
||||
}
|
||||
|
||||
pub fn get_byte_positon(self) -> u32 {
|
||||
self.into().get_byte_positon()
|
||||
}
|
||||
|
||||
pub fn get_length(self) -> u32 {
|
||||
self.into().get_length()
|
||||
}
|
||||
|
||||
pub fn parse_serial_value(self, data: &[&str]) -> Result<String> {
|
||||
self.into().parse_serial(data)
|
||||
}
|
||||
|
||||
pub fn parse_s7_value(self, data: &[u8]) -> Result<String> {
|
||||
self.into().parse_s7(data)
|
||||
}
|
||||
|
||||
pub fn create_sql_data_type(self) -> SQLDataType {
|
||||
self.into().create_sql_data_type()
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<UnparsedSPSDataType> for SPSDataTypes {
|
||||
fn into(self) -> UnparsedSPSDataType {
|
||||
self.into().into_unparsed()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UnparsedSPSDataType {
|
||||
pub data_type: String,
|
||||
pub data_byte: u32,
|
||||
pub data_bit: Option<u32>,
|
||||
pub data_length: Option<u32>,
|
||||
}
|
||||
|
||||
pub trait DataEvaluation {
|
||||
fn into_unparsed(&self) -> UnparsedSPSDataType;
|
||||
fn get_end_byte(&self) -> u32;
|
||||
fn get_byte_positon(&self) -> u32;
|
||||
fn get_length(&self) -> u32;
|
||||
fn parse_serial(&self, data: &[&str]) -> Result<String>;
|
||||
fn parse_s7(&self, data: &[u8]) -> Result<String>;
|
||||
|
||||
fn sql_equivalent(&self) -> &str;
|
||||
fn create_sql_data_type(&self) -> SQLDataType {
|
||||
SQLDataType {
|
||||
mysql_type: self.sql_equivalent().to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SQLDataType {
|
||||
pub mysql_type: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
|
||||
pub struct BitPosition {
|
||||
pub byte: u32,
|
||||
pub bit: Option<u32>,
|
||||
}
|
95
src/types/boolean.rs
Normal file
95
src/types/boolean.rs
Normal file
@ -0,0 +1,95 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use bit::BitIndex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::super::errors::*;
|
||||
use super::super::sps_datatypes::{BitPosition, DataEvaluation, UnparsedSPSDataType};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
|
||||
pub struct BooleanType {
|
||||
position: BitPosition,
|
||||
}
|
||||
|
||||
impl BooleanType {
|
||||
pub fn new(byte: u32, bit: Option<u32>) -> Self {
|
||||
BooleanType {
|
||||
position: BitPosition { byte, bit },
|
||||
}
|
||||
}
|
||||
fn into_string(self) -> String {
|
||||
"boolean".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl DataEvaluation for BooleanType {
|
||||
fn into_unparsed(&self) -> UnparsedSPSDataType {
|
||||
UnparsedSPSDataType {
|
||||
data_type: self.into_string(),
|
||||
data_byte: self.position.byte,
|
||||
data_bit: self.position.bit,
|
||||
data_length: None,
|
||||
}
|
||||
}
|
||||
fn get_end_byte(&self) -> u32 {
|
||||
self.position.byte
|
||||
}
|
||||
fn get_byte_positon(&self) -> u32 {
|
||||
self.position.byte
|
||||
}
|
||||
fn get_length(&self) -> u32 {
|
||||
1
|
||||
}
|
||||
fn parse_serial(&self, data: &[&str]) -> Result<String> {
|
||||
Ok(data
|
||||
.get((self.position.byte) as usize)
|
||||
.ok_or_else(|| serial_error(self.into_string(), self.position.byte))?
|
||||
.to_string())
|
||||
}
|
||||
fn parse_s7(&self, data: &[u8]) -> Result<String> {
|
||||
let byte = data
|
||||
.get((self.position.byte) as usize)
|
||||
.ok_or_else(|| s7_read_error(self.into_string(), self.position.byte))?;
|
||||
Ok(match byte.bit(self.position.bit.ok_or_else(|| {
|
||||
anyhow!(
|
||||
"Cannot read Bit {}.{}.",
|
||||
self.position.byte,
|
||||
self.position.bit.unwrap()
|
||||
)
|
||||
})? as usize)
|
||||
{
|
||||
true => 1,
|
||||
false => 0,
|
||||
}
|
||||
.to_string())
|
||||
}
|
||||
|
||||
fn sql_equivalent(&self) -> &str {
|
||||
"BOOLEAN DEFAULT false"
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
const BYTEPOS: u32 = 5;
|
||||
const BITPOS: u32 = 4;
|
||||
const VAL: bool = false;
|
||||
|
||||
let test_item = BooleanType::new(BYTEPOS, Some(BITPOS));
|
||||
|
||||
let raw_data: [u8; 50] = [0; 50];
|
||||
let mut test_vec: Vec<u8> = raw_data.to_vec();
|
||||
test_vec[BYTEPOS as usize].set_bit(BITPOS as usize, VAL);
|
||||
|
||||
let result = match test_item.parse_s7(&test_vec) {
|
||||
Ok(res) => res,
|
||||
Err(_) => "Error".to_string(),
|
||||
};
|
||||
assert_eq!(
|
||||
result,
|
||||
match VAL {
|
||||
true => 1,
|
||||
false => 0,
|
||||
}
|
||||
.to_string()
|
||||
)
|
||||
}
|
125
src/types/date_time.rs
Normal file
125
src/types/date_time.rs
Normal file
@ -0,0 +1,125 @@
|
||||
use anyhow::{bail, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::super::errors::*;
|
||||
use super::super::sps_datatypes::{BitPosition, DataEvaluation, UnparsedSPSDataType};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
|
||||
pub struct DateTimeType {
|
||||
length: u32,
|
||||
position: BitPosition,
|
||||
}
|
||||
impl DateTimeType {
|
||||
const LEN: usize = 8;
|
||||
pub fn new(byte: u32, bit: Option<u32>) -> Self {
|
||||
DateTimeType {
|
||||
length: Self::LEN as u32,
|
||||
position: BitPosition { byte, bit },
|
||||
}
|
||||
}
|
||||
fn into_string(self) -> String {
|
||||
"datetime".to_string()
|
||||
}
|
||||
|
||||
fn assert_range_inclusive(&self, input: u8, min: u8, max: u8, field: &str) -> Result<u8> {
|
||||
if input < min {
|
||||
bail!(
|
||||
"Value '{}' is lower than the minimum '{}' allowed for {}.",
|
||||
input,
|
||||
min,
|
||||
field
|
||||
)
|
||||
}
|
||||
if input > max {
|
||||
bail!(
|
||||
"Value '{}' is higher than the maximum '{}' allowed for {}.",
|
||||
input,
|
||||
max,
|
||||
field
|
||||
)
|
||||
}
|
||||
Ok(input)
|
||||
}
|
||||
|
||||
fn parse(&self, data: &[u8]) -> Result<String> {
|
||||
let year: Result<u32> = match data[0] {
|
||||
e if e < 90 => Ok(e as u32 + 2000),
|
||||
e if e < 100 => Ok(e as u32 + 1900),
|
||||
e => bail!(
|
||||
"Value '{}' is higher than the maximum '99' of S7 date and time representation.",
|
||||
e
|
||||
),
|
||||
};
|
||||
let month = self.assert_range_inclusive(data[1], 1, 12, "month")?;
|
||||
let day = self.assert_range_inclusive(data[2], 1, 31, "day of month")?;
|
||||
let hour = self.assert_range_inclusive(data[3], 0, 23, "hour")?;
|
||||
let minute = self.assert_range_inclusive(data[4], 0, 59, "minute")?;
|
||||
let second = self.assert_range_inclusive(data[5], 0, 59, "second")?;
|
||||
let _sec = self.assert_range_inclusive(data[6], 0, 99, "first two millisecond digits")?;
|
||||
let _msec = self.assert_range_inclusive(data[7] >> 4, 0, 9, "third millisecond digit")?;
|
||||
let _day_of_week =
|
||||
self.assert_range_inclusive(data[7] & 0b00001111, 1, 7, "day of week")?;
|
||||
|
||||
Ok(format!(
|
||||
"{}-{:02}-{:02} {:02}:{:02}:{:02}",
|
||||
year?, month, day, hour, minute, second
|
||||
))
|
||||
}
|
||||
}
|
||||
impl DataEvaluation for DateTimeType {
|
||||
fn into_unparsed(&self) -> UnparsedSPSDataType {
|
||||
UnparsedSPSDataType {
|
||||
data_type: self.into_string(),
|
||||
data_byte: self.position.byte,
|
||||
data_bit: self.position.bit,
|
||||
data_length: None,
|
||||
}
|
||||
}
|
||||
fn get_end_byte(&self) -> u32 {
|
||||
self.position.byte + self.length
|
||||
}
|
||||
fn get_byte_positon(&self) -> u32 {
|
||||
self.position.byte
|
||||
}
|
||||
fn get_length(&self) -> u32 {
|
||||
self.length
|
||||
}
|
||||
fn parse_serial(&self, data: &[&str]) -> Result<String> {
|
||||
Ok(data
|
||||
.get((self.position.byte) as usize)
|
||||
.ok_or_else(|| serial_error(self.into_string(), self.position.byte))?
|
||||
.to_string())
|
||||
}
|
||||
fn parse_s7(&self, data: &[u8]) -> Result<String> {
|
||||
let bytes = data
|
||||
.get((self.position.byte) as usize..(self.position.byte + self.length) as usize)
|
||||
.ok_or_else(|| s7_read_error(self.into_string(), self.position.byte))?;
|
||||
self.parse(&bytes.to_vec())
|
||||
}
|
||||
|
||||
fn sql_equivalent(&self) -> &str {
|
||||
r"DATETIME DEFAULT NULL"
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
const DATEPOS: u32 = 5;
|
||||
const LEN: u32 = 8;
|
||||
const VAL: &str = "1993-12-25 08:12:34";
|
||||
|
||||
let test_item = DateTimeType::new(DATEPOS, None);
|
||||
let raw_data: [u8; 50] = [255; 50];
|
||||
let mut test_vec: Vec<u8> = raw_data.to_vec();
|
||||
test_vec.splice(
|
||||
DATEPOS as usize..DATEPOS as usize + LEN as usize,
|
||||
[93, 12, 25, 8, 12, 34, 56, 7 << 4 | 7],
|
||||
);
|
||||
|
||||
let result = match test_item.parse_s7(&test_vec) {
|
||||
Ok(res) => res,
|
||||
Err(_) => "Error".to_string(),
|
||||
};
|
||||
|
||||
assert_eq!(result, VAL.to_string())
|
||||
}
|
85
src/types/dint.rs
Normal file
85
src/types/dint.rs
Normal file
@ -0,0 +1,85 @@
|
||||
use anyhow::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::super::errors::*;
|
||||
use super::super::sps_datatypes::{BitPosition, DataEvaluation, UnparsedSPSDataType};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
|
||||
pub struct DIntType {
|
||||
length: u32,
|
||||
position: BitPosition,
|
||||
}
|
||||
|
||||
impl DIntType {
|
||||
const LEN: usize = 4;
|
||||
|
||||
pub fn new(byte: u32, bit: Option<u32>) -> Self {
|
||||
DIntType {
|
||||
length: Self::LEN as u32,
|
||||
position: BitPosition { byte, bit },
|
||||
}
|
||||
}
|
||||
|
||||
fn into_string(self) -> String {
|
||||
"dint".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl DataEvaluation for DIntType {
|
||||
fn into_unparsed(&self) -> UnparsedSPSDataType {
|
||||
UnparsedSPSDataType {
|
||||
data_type: self.into_string(),
|
||||
data_byte: self.position.byte,
|
||||
data_bit: self.position.bit,
|
||||
data_length: None,
|
||||
}
|
||||
}
|
||||
fn get_end_byte(&self) -> u32 {
|
||||
self.position.byte + self.length
|
||||
}
|
||||
fn get_byte_positon(&self) -> u32 {
|
||||
self.position.byte
|
||||
}
|
||||
fn get_length(&self) -> u32 {
|
||||
self.length
|
||||
}
|
||||
fn parse_serial(&self, data: &[&str]) -> Result<String> {
|
||||
Ok(data
|
||||
.get((self.position.byte) as usize)
|
||||
.ok_or_else(|| serial_error(self.into_string(), self.position.byte))?
|
||||
.to_string())
|
||||
}
|
||||
fn parse_s7(&self, data: &[u8]) -> Result<String> {
|
||||
let bytes = data
|
||||
.get((self.position.byte) as usize..self.position.byte as usize + Self::LEN)
|
||||
.ok_or_else(|| s7_read_error(self.into_string(), self.position.byte))?;
|
||||
let mut slice: [u8; Self::LEN] = Default::default();
|
||||
slice.copy_from_slice(bytes);
|
||||
let parsed = i32::from_be_bytes(slice);
|
||||
Ok(parsed.to_string())
|
||||
}
|
||||
|
||||
fn sql_equivalent(&self) -> &str {
|
||||
r"BIGINT DEFAULT 0"
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
const INTPOS: u32 = 5;
|
||||
const INT: i32 = -2589090;
|
||||
const LEN: usize = 4;
|
||||
let test_item = DIntType::new(INTPOS, None);
|
||||
let raw_data: [u8; 50] = [255; 50];
|
||||
let mut test_vec: Vec<u8> = raw_data.to_vec();
|
||||
test_vec.splice(
|
||||
INTPOS as usize..INTPOS as usize + LEN,
|
||||
INT.to_be_bytes().iter().cloned(),
|
||||
);
|
||||
|
||||
let result = match test_item.parse_s7(&test_vec) {
|
||||
Ok(res) => res,
|
||||
Err(_) => "Error".to_string(),
|
||||
};
|
||||
assert_eq!(result, INT.to_string())
|
||||
}
|
83
src/types/int.rs
Normal file
83
src/types/int.rs
Normal file
@ -0,0 +1,83 @@
|
||||
use anyhow::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::super::errors::*;
|
||||
use super::super::sps_datatypes::{BitPosition, DataEvaluation, UnparsedSPSDataType};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
|
||||
pub struct IntType {
|
||||
length: u32,
|
||||
position: BitPosition,
|
||||
}
|
||||
|
||||
impl IntType {
|
||||
const LEN: usize = 2;
|
||||
pub fn new(byte: u32, bit: Option<u32>) -> Self {
|
||||
IntType {
|
||||
length: Self::LEN as u32,
|
||||
position: BitPosition { byte, bit },
|
||||
}
|
||||
}
|
||||
fn into_string(self) -> String {
|
||||
"int".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl DataEvaluation for IntType {
|
||||
fn into_unparsed(&self) -> UnparsedSPSDataType {
|
||||
UnparsedSPSDataType {
|
||||
data_type: self.into_string(),
|
||||
data_byte: self.position.byte,
|
||||
data_bit: self.position.bit,
|
||||
data_length: None,
|
||||
}
|
||||
}
|
||||
fn get_end_byte(&self) -> u32 {
|
||||
self.position.byte + self.length
|
||||
}
|
||||
fn get_byte_positon(&self) -> u32 {
|
||||
self.position.byte
|
||||
}
|
||||
fn get_length(&self) -> u32 {
|
||||
self.length
|
||||
}
|
||||
fn parse_serial(&self, data: &[&str]) -> Result<String> {
|
||||
Ok(data
|
||||
.get((self.position.byte) as usize)
|
||||
.ok_or_else(|| serial_error(self.into_string(), self.position.byte))?
|
||||
.to_string())
|
||||
}
|
||||
fn parse_s7(&self, data: &[u8]) -> Result<String> {
|
||||
let bytes = data
|
||||
.get((self.position.byte) as usize..self.position.byte as usize + Self::LEN)
|
||||
.ok_or_else(|| s7_read_error(self.into_string(), self.position.byte))?;
|
||||
let mut slice: [u8; Self::LEN] = Default::default();
|
||||
slice.copy_from_slice(bytes);
|
||||
let parsed = i16::from_be_bytes(slice);
|
||||
Ok(parsed.to_string())
|
||||
}
|
||||
|
||||
fn sql_equivalent(&self) -> &str {
|
||||
r"INT DEFAULT 0"
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
const INTPOS: u32 = 3;
|
||||
const INT: i16 = -12;
|
||||
const LEN: usize = 2;
|
||||
let test_item = IntType::new(INTPOS, None);
|
||||
let raw_data: [u8; 50] = [255; 50];
|
||||
let mut test_vec: Vec<u8> = raw_data.to_vec();
|
||||
test_vec.splice(
|
||||
INTPOS as usize..INTPOS as usize + LEN,
|
||||
INT.to_be_bytes().iter().cloned(),
|
||||
);
|
||||
|
||||
let result = match test_item.parse_s7(&test_vec) {
|
||||
Ok(res) => res,
|
||||
Err(_) => "Error".to_string(),
|
||||
};
|
||||
assert_eq!(result, INT.to_string())
|
||||
}
|
9
src/types/mod.rs
Normal file
9
src/types/mod.rs
Normal file
@ -0,0 +1,9 @@
|
||||
pub mod boolean;
|
||||
pub mod date_time;
|
||||
pub mod dint;
|
||||
pub mod int;
|
||||
pub mod real;
|
||||
pub mod string;
|
||||
pub mod time;
|
||||
pub mod udint;
|
||||
pub mod uint;
|
83
src/types/real.rs
Normal file
83
src/types/real.rs
Normal file
@ -0,0 +1,83 @@
|
||||
use anyhow::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::super::errors::*;
|
||||
use super::super::sps_datatypes::{BitPosition, DataEvaluation, UnparsedSPSDataType};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
|
||||
pub struct RealType {
|
||||
length: u32,
|
||||
position: BitPosition,
|
||||
}
|
||||
|
||||
impl RealType {
|
||||
const LEN: usize = 4;
|
||||
pub fn new(byte: u32, bit: Option<u32>) -> Self {
|
||||
RealType {
|
||||
length: Self::LEN as u32,
|
||||
position: BitPosition { byte, bit },
|
||||
}
|
||||
}
|
||||
fn into_string(self) -> String {
|
||||
"real".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl DataEvaluation for RealType {
|
||||
fn into_unparsed(&self) -> UnparsedSPSDataType {
|
||||
UnparsedSPSDataType {
|
||||
data_type: self.into_string(),
|
||||
data_byte: self.position.byte,
|
||||
data_bit: self.position.bit,
|
||||
data_length: None,
|
||||
}
|
||||
}
|
||||
fn get_end_byte(&self) -> u32 {
|
||||
self.position.byte + self.length
|
||||
}
|
||||
fn get_byte_positon(&self) -> u32 {
|
||||
self.position.byte
|
||||
}
|
||||
fn get_length(&self) -> u32 {
|
||||
self.length
|
||||
}
|
||||
fn parse_serial(&self, data: &[&str]) -> Result<String> {
|
||||
Ok(data
|
||||
.get((self.position.byte) as usize)
|
||||
.ok_or_else(|| serial_error(self.into_string(), self.position.byte))?
|
||||
.to_string())
|
||||
}
|
||||
fn parse_s7(&self, data: &[u8]) -> Result<String> {
|
||||
let bytes = data
|
||||
.get((self.position.byte) as usize..(self.position.byte + self.length) as usize)
|
||||
.ok_or_else(|| s7_read_error(self.into_string(), self.position.byte))?;
|
||||
let mut slice: [u8; 4] = Default::default();
|
||||
slice.copy_from_slice(bytes);
|
||||
let parsed = f32::from_be_bytes(slice);
|
||||
Ok(parsed.to_string())
|
||||
}
|
||||
|
||||
fn sql_equivalent(&self) -> &str {
|
||||
r"DOUBLE DEFAULT 0.00"
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
const INTPOS: u32 = 5;
|
||||
const INT: f32 = 2.522529;
|
||||
const LEN: usize = 4;
|
||||
let test_item = RealType::new(INTPOS, None);
|
||||
let raw_data: [u8; 50] = [255; 50];
|
||||
let mut test_vec: Vec<u8> = raw_data.to_vec();
|
||||
test_vec.splice(
|
||||
INTPOS as usize..INTPOS as usize + LEN,
|
||||
INT.to_be_bytes().iter().cloned(),
|
||||
);
|
||||
|
||||
let result = match test_item.parse_s7(&test_vec) {
|
||||
Ok(res) => res,
|
||||
Err(_) => "Error".to_string(),
|
||||
};
|
||||
assert_eq!(result, INT.to_string())
|
||||
}
|
92
src/types/string.rs
Normal file
92
src/types/string.rs
Normal file
@ -0,0 +1,92 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::super::errors::*;
|
||||
use super::super::sps_datatypes::{BitPosition, DataEvaluation, UnparsedSPSDataType};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
|
||||
pub struct StringType {
|
||||
length: u32,
|
||||
position: BitPosition,
|
||||
}
|
||||
impl StringType {
|
||||
pub fn new(length: u32, byte: u32, bit: Option<u32>) -> Self {
|
||||
StringType {
|
||||
length,
|
||||
position: BitPosition { byte, bit },
|
||||
}
|
||||
}
|
||||
fn into_string(self) -> String {
|
||||
"string".to_string()
|
||||
}
|
||||
}
|
||||
impl DataEvaluation for StringType {
|
||||
fn into_unparsed(&self) -> UnparsedSPSDataType {
|
||||
UnparsedSPSDataType {
|
||||
data_type: self.into_string(),
|
||||
data_byte: self.position.byte,
|
||||
data_bit: self.position.bit,
|
||||
data_length: Some(self.length),
|
||||
}
|
||||
}
|
||||
fn get_end_byte(&self) -> u32 {
|
||||
// first two bytes are header bytes
|
||||
self.position.byte + self.length + 2
|
||||
}
|
||||
fn get_byte_positon(&self) -> u32 {
|
||||
self.position.byte
|
||||
}
|
||||
fn get_length(&self) -> u32 {
|
||||
self.length
|
||||
}
|
||||
fn parse_serial(&self, data: &[&str]) -> Result<String> {
|
||||
Ok(data
|
||||
.get((self.position.byte) as usize)
|
||||
.ok_or_else(|| serial_error(self.into_string(), self.position.byte))?
|
||||
.to_string())
|
||||
}
|
||||
fn parse_s7(&self, data: &[u8]) -> Result<String> {
|
||||
// Headerbyte 1: Reservierte Stringlänge in Byte [0…254]
|
||||
// Headerbyte 2: Anzahl der zu interpretierenden Zeichen im String ab Byte 3
|
||||
// Byte 3: 1. Nutzzeichen
|
||||
let bytes = data
|
||||
.get((self.position.byte) as usize..(self.position.byte + self.length) as usize)
|
||||
.ok_or_else(|| s7_read_error(self.into_string(), self.position.byte))?;
|
||||
match String::from_utf8(bytes[2..bytes[1] as usize + 2].to_vec()) {
|
||||
Ok(string) => Ok(string),
|
||||
Err(err) => Err(anyhow!(
|
||||
"Could not convert data at byte {} with length {} into string: {}.",
|
||||
self.position.byte,
|
||||
self.length,
|
||||
err
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn sql_equivalent(&self) -> &str {
|
||||
r"VARCHAR(255)"
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
const BYTEPOS: u32 = 5;
|
||||
const LEN: u32 = 20;
|
||||
const VAL: &str = "ich bin ein pfau";
|
||||
|
||||
let test_item = StringType::new(LEN + 2, BYTEPOS, None);
|
||||
let raw_data: [u8; 50] = [0; 50];
|
||||
let mut test_vec: Vec<u8> = raw_data.to_vec();
|
||||
test_vec[BYTEPOS as usize] = LEN as u8 + 2;
|
||||
test_vec[BYTEPOS as usize + 1] = VAL.len() as u8;
|
||||
test_vec.splice(
|
||||
BYTEPOS as usize + 2..BYTEPOS as usize + LEN as usize,
|
||||
VAL.as_bytes().iter().cloned(),
|
||||
);
|
||||
|
||||
let result = match test_item.parse_s7(&test_vec) {
|
||||
Ok(res) => res,
|
||||
Err(_) => "Error".to_string(),
|
||||
};
|
||||
assert_eq!(result, VAL.to_string())
|
||||
}
|
55
src/types/time.rs
Normal file
55
src/types/time.rs
Normal file
@ -0,0 +1,55 @@
|
||||
use anyhow::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::super::errors::*;
|
||||
use super::super::sps_datatypes::{BitPosition, DataEvaluation, UnparsedSPSDataType};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
|
||||
pub struct TimeType {
|
||||
length: u32,
|
||||
position: BitPosition,
|
||||
}
|
||||
impl TimeType {
|
||||
const LEN: usize = 4;
|
||||
pub fn new(byte: u32, bit: Option<u32>) -> Self {
|
||||
TimeType {
|
||||
length: Self::LEN as u32,
|
||||
position: BitPosition { byte, bit },
|
||||
}
|
||||
}
|
||||
fn into_string(self) -> String {
|
||||
"time".to_string()
|
||||
}
|
||||
}
|
||||
impl DataEvaluation for TimeType {
|
||||
fn into_unparsed(&self) -> UnparsedSPSDataType {
|
||||
UnparsedSPSDataType {
|
||||
data_type: self.into_string(),
|
||||
data_byte: self.position.byte,
|
||||
data_bit: self.position.bit,
|
||||
data_length: None,
|
||||
}
|
||||
}
|
||||
fn get_end_byte(&self) -> u32 {
|
||||
self.position.byte + self.length
|
||||
}
|
||||
fn get_byte_positon(&self) -> u32 {
|
||||
self.position.byte
|
||||
}
|
||||
fn get_length(&self) -> u32 {
|
||||
self.length
|
||||
}
|
||||
fn parse_serial(&self, data: &[&str]) -> Result<String> {
|
||||
Ok(data
|
||||
.get((self.position.byte) as usize)
|
||||
.ok_or_else(|| serial_error(self.into_string(), self.position.byte))?
|
||||
.to_string())
|
||||
}
|
||||
fn parse_s7(&self, _data: &[u8]) -> Result<String> {
|
||||
Ok("time".to_string())
|
||||
}
|
||||
|
||||
fn sql_equivalent(&self) -> &str {
|
||||
r"BIGINT UNSIGNED DEFAULT 0"
|
||||
}
|
||||
}
|
81
src/types/udint.rs
Normal file
81
src/types/udint.rs
Normal file
@ -0,0 +1,81 @@
|
||||
use anyhow::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::super::errors::*;
|
||||
use super::super::sps_datatypes::{BitPosition, DataEvaluation, UnparsedSPSDataType};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
|
||||
pub struct UDIntType {
|
||||
length: u32,
|
||||
position: BitPosition,
|
||||
}
|
||||
impl UDIntType {
|
||||
const LEN: usize = 4;
|
||||
pub fn new(byte: u32, bit: Option<u32>) -> Self {
|
||||
UDIntType {
|
||||
length: Self::LEN as u32,
|
||||
position: BitPosition { byte, bit },
|
||||
}
|
||||
}
|
||||
fn into_string(self) -> String {
|
||||
"udint".to_string()
|
||||
}
|
||||
}
|
||||
impl DataEvaluation for UDIntType {
|
||||
fn into_unparsed(&self) -> UnparsedSPSDataType {
|
||||
UnparsedSPSDataType {
|
||||
data_type: self.into_string(),
|
||||
data_byte: self.position.byte,
|
||||
data_bit: self.position.bit,
|
||||
data_length: None,
|
||||
}
|
||||
}
|
||||
fn get_end_byte(&self) -> u32 {
|
||||
self.position.byte + self.length
|
||||
}
|
||||
fn get_byte_positon(&self) -> u32 {
|
||||
self.position.byte
|
||||
}
|
||||
fn get_length(&self) -> u32 {
|
||||
self.length
|
||||
}
|
||||
fn parse_serial(&self, data: &[&str]) -> Result<String> {
|
||||
Ok(data
|
||||
.get((self.position.byte) as usize)
|
||||
.ok_or_else(|| serial_error(self.into_string(), self.position.byte))?
|
||||
.to_string())
|
||||
}
|
||||
fn parse_s7(&self, data: &[u8]) -> Result<String> {
|
||||
let bytes = data
|
||||
.get((self.position.byte) as usize..(self.position.byte + self.length) as usize)
|
||||
.ok_or_else(|| s7_read_error(self.into_string(), self.position.byte))?;
|
||||
let mut slice: [u8; Self::LEN] = Default::default();
|
||||
slice.copy_from_slice(bytes);
|
||||
let parsed = u32::from_be_bytes(slice);
|
||||
Ok(parsed.to_string())
|
||||
}
|
||||
|
||||
fn sql_equivalent(&self) -> &str {
|
||||
r"BIGINT UNSIGNED DEFAULT 0"
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
const INTPOS: u32 = 5;
|
||||
const INT: u32 = 25890920;
|
||||
const LEN: usize = 4;
|
||||
let test_item = UDIntType::new(INTPOS, None);
|
||||
let raw_data: [u8; 50] = [255; 50];
|
||||
let mut test_vec: Vec<u8> = raw_data.to_vec();
|
||||
test_vec.splice(
|
||||
INTPOS as usize..INTPOS as usize + LEN,
|
||||
INT.to_be_bytes().iter().cloned(),
|
||||
);
|
||||
|
||||
let result = match test_item.parse_s7(&test_vec) {
|
||||
Ok(res) => res,
|
||||
Err(_) => "Error".to_string(),
|
||||
};
|
||||
assert_eq!(result, INT.to_string())
|
||||
}
|
81
src/types/uint.rs
Normal file
81
src/types/uint.rs
Normal file
@ -0,0 +1,81 @@
|
||||
use anyhow::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::super::errors::*;
|
||||
use super::super::sps_datatypes::{BitPosition, DataEvaluation, UnparsedSPSDataType};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
|
||||
pub struct UIntType {
|
||||
length: u32,
|
||||
position: BitPosition,
|
||||
}
|
||||
impl UIntType {
|
||||
const LEN: usize = 2;
|
||||
pub fn new(byte: u32, bit: Option<u32>) -> Self {
|
||||
UIntType {
|
||||
length: Self::LEN as u32,
|
||||
position: BitPosition { byte, bit },
|
||||
}
|
||||
}
|
||||
fn into_string(self) -> String {
|
||||
"uint".to_string()
|
||||
}
|
||||
}
|
||||
impl DataEvaluation for UIntType {
|
||||
fn into_unparsed(&self) -> UnparsedSPSDataType {
|
||||
UnparsedSPSDataType {
|
||||
data_type: self.into_string(),
|
||||
data_byte: self.position.byte,
|
||||
data_bit: self.position.bit,
|
||||
data_length: None,
|
||||
}
|
||||
}
|
||||
fn get_end_byte(&self) -> u32 {
|
||||
self.position.byte + self.length
|
||||
}
|
||||
fn get_byte_positon(&self) -> u32 {
|
||||
self.position.byte
|
||||
}
|
||||
fn get_length(&self) -> u32 {
|
||||
self.length
|
||||
}
|
||||
fn parse_serial(&self, data: &[&str]) -> Result<String> {
|
||||
Ok(data
|
||||
.get((self.position.byte) as usize)
|
||||
.ok_or_else(|| serial_error(self.into_string(), self.position.byte))?
|
||||
.to_string())
|
||||
}
|
||||
fn parse_s7(&self, data: &[u8]) -> Result<String> {
|
||||
let bytes = data
|
||||
.get((self.position.byte) as usize..(self.position.byte + self.length) as usize)
|
||||
.ok_or_else(|| s7_read_error(self.into_string(), self.position.byte))?;
|
||||
let mut slice: [u8; Self::LEN] = Default::default();
|
||||
slice.copy_from_slice(bytes);
|
||||
let parsed = u16::from_be_bytes(slice);
|
||||
Ok(parsed.to_string())
|
||||
}
|
||||
|
||||
fn sql_equivalent(&self) -> &str {
|
||||
r"INT UNSIGNED DEFAULT 0"
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
const INTPOS: u32 = 15;
|
||||
const INT: u16 = 8209;
|
||||
const LEN: usize = 2;
|
||||
let test_item = UIntType::new(INTPOS, None);
|
||||
let raw_data: [u8; 50] = [255; 50];
|
||||
let mut test_vec: Vec<u8> = raw_data.to_vec();
|
||||
test_vec.splice(
|
||||
INTPOS as usize..INTPOS as usize + LEN,
|
||||
INT.to_be_bytes().iter().cloned(),
|
||||
);
|
||||
|
||||
let result = match test_item.parse_s7(&test_vec) {
|
||||
Ok(res) => res,
|
||||
Err(_) => "Error".to_string(),
|
||||
};
|
||||
assert_eq!(result, INT.to_string())
|
||||
}
|
Loading…
Reference in New Issue
Block a user