rust_snap7/utils/
getters.rs

1use chrono::{DateTime, NaiveDate, Utc};
2use std::convert::TryInto;
3use std::time::Duration;
4
5pub fn get_bool(bytearray: &[u8], byte_index: usize, bool_index: usize) -> Result<bool, String> {
6    if bytearray.len() < byte_index + 2 || bool_index > 7 {
7        return Err("Buffer has no enough data to decoding".to_string());
8    }
9    let index_value = 1 << bool_index;
10    let byte_value = bytearray[byte_index];
11    let current_value = byte_value & index_value;
12    Ok(current_value == index_value)
13}
14
15pub fn get_byte(bytearray: &[u8], byte_index: usize) -> u8 {
16    bytearray[byte_index]
17}
18
19pub fn get_word(bytearray: &[u8], byte_index: usize) -> u16 {
20    let data: [u8; 2] = bytearray[byte_index..byte_index + 2].try_into().unwrap();
21    u16::from_be_bytes(data)
22}
23
24pub fn get_int(bytearray: &[u8], byte_index: usize) -> i16 {
25    let data: [u8; 2] = bytearray[byte_index..byte_index + 2].try_into().unwrap();
26    i16::from_be_bytes(data)
27}
28
29pub fn get_uint(bytearray: &[u8], byte_index: usize) -> u16 {
30    get_word(bytearray, byte_index)
31}
32
33pub fn get_real(bytearray: &[u8], byte_index: usize) -> f32 {
34    let data: [u8; 4] = bytearray[byte_index..byte_index + 4].try_into().unwrap();
35    f32::from_bits(u32::from_be_bytes(data))
36}
37
38pub fn get_fstring(
39    bytearray: &[u8],
40    byte_index: usize,
41    max_length: usize,
42    remove_padding: bool,
43) -> String {
44    let data = &bytearray[byte_index..byte_index + max_length];
45    let string = String::from_utf8(data.to_vec()).unwrap();
46
47    if remove_padding {
48        string.trim_end().to_string()
49    } else {
50        string
51    }
52}
53
54pub fn get_string(bytearray: &[u8], byte_index: usize) -> Result<String, String> {
55    let max_string_size = bytearray[byte_index] as usize;
56    let str_length = bytearray[byte_index + 1] as usize;
57
58    if str_length > max_string_size || max_string_size > 254 {
59        return Err("String length not match!".to_string());
60    }
61
62    if bytearray.len() < byte_index + str_length + 1 {
63        return Err("Buffer has no enough data to decoding".to_string());
64    }
65
66    let data = &bytearray[byte_index + 2..byte_index + 2 + str_length];
67    let str_data = String::from_utf8(data.to_vec()).map_err(|e| e.to_string())?;
68    Ok(str_data)
69}
70
71pub fn get_dword(bytearray: &[u8], byte_index: usize) -> u32 {
72    let data: [u8; 4] = bytearray[byte_index..byte_index + 4].try_into().unwrap();
73    u32::from_be_bytes(data)
74}
75
76pub fn get_dint(bytearray: &[u8], byte_index: usize) -> i32 {
77    let data: [u8; 4] = bytearray[byte_index..byte_index + 4].try_into().unwrap();
78    i32::from_be_bytes(data)
79}
80
81pub fn get_udint(bytearray: &[u8], byte_index: usize) -> u32 {
82    get_dword(bytearray, byte_index)
83}
84
85pub fn get_s5time(bytearray: &[u8], byte_index: usize) -> String {
86    let data_bytearray = &bytearray[byte_index..byte_index + 2];
87    let s5time_data_int_like = format!("{:02X}{:02X}", data_bytearray[0], data_bytearray[1]);
88
89    let time_base = match &s5time_data_int_like[0..1] {
90        "0" => 10,
91        "1" => 100,
92        "2" => 1000,
93        "3" => 10000,
94        _ => panic!("This value should not be greater than 3"),
95    };
96
97    let mut s5time_bcd: i32 = 0;
98
99    for (i, digit) in s5time_data_int_like.chars().enumerate() {
100        if i > 0 {
101            s5time_bcd *= 10;
102            s5time_bcd += digit.to_digit(10).unwrap() as i32;
103        }
104    }
105
106    let s5time_microseconds = time_base * s5time_bcd;
107    let s5time = Duration::from_micros(s5time_microseconds as u64 * 1000);
108    format!("{:?}", s5time)
109}
110
111pub fn get_dt(bytearray: &[u8], byte_index: usize) -> String {
112    get_date_time_object(bytearray, byte_index).to_string()
113}
114
115pub fn get_date_time_object(bytearray: &[u8], byte_index: usize) -> DateTime<Utc> {
116    fn bcd_to_byte(byte: u8) -> u8 {
117        (byte >> 4) * 10 + (byte & 0xF)
118    }
119    let year = bcd_to_byte(bytearray[byte_index]) as i32;
120    let year = if year < 90 { 2000 + year } else { 1900 + year };
121    let month = bcd_to_byte(bytearray[byte_index + 1]);
122    let day = bcd_to_byte(bytearray[byte_index + 2]);
123    let hour = bcd_to_byte(bytearray[byte_index + 3]);
124    let min = bcd_to_byte(bytearray[byte_index + 4]);
125    let sec = bcd_to_byte(bytearray[byte_index + 5]);
126    let microsec = (bcd_to_byte(bytearray[byte_index + 6]) as u32 * 10
127        + (bytearray[byte_index + 7] >> 4) as u32)
128        * 1000;
129
130    NaiveDate::from_ymd_opt(year, month.into(), day.into())
131        .expect("failed to parse date")
132        .and_hms_micro_opt(hour.into(), min.into(), sec.into(), microsec)
133        .expect("failed to parse time")
134        .and_utc()
135}
136
137pub fn get_time(bytearray: &[u8], byte_index: usize) -> String {
138    let data_bytearray = &bytearray[byte_index..byte_index + 4];
139    let mut val = i32::from_be_bytes(data_bytearray.try_into().unwrap());
140
141    let sign_str = if val < 0 {
142        val = -val;
143        "-"
144    } else {
145        ""
146    };
147
148    let milli_seconds = val % 1000;
149    let seconds = (val / 1000) % 60;
150    let minutes = (val / (1000 * 60)) % 60;
151    let hours = (val / (1000 * 60 * 60)) % 24;
152    let days = val / (1000 * 60 * 60 * 24);
153
154    format!(
155        "{}{}:{}:{}:{}.{}",
156        sign_str,
157        days,
158        hours % 24,
159        minutes % 60,
160        seconds % 60,
161        milli_seconds
162    )
163}
164
165pub fn get_usint(bytearray: &[u8], byte_index: usize) -> u8 {
166    bytearray[byte_index]
167}
168
169pub fn get_sint(bytearray: &[u8], byte_index: usize) -> i8 {
170    bytearray[byte_index] as i8
171}
172
173pub fn get_lint(bytearray: &[u8], byte_index: usize) -> i64 {
174    let data: [u8; 8] = bytearray[byte_index..byte_index + 8].try_into().unwrap();
175    i64::from_be_bytes(data)
176}
177
178pub fn get_lreal(bytearray: &[u8], byte_index: usize) -> f64 {
179    let data: [u8; 8] = bytearray[byte_index..byte_index + 8].try_into().unwrap();
180    f64::from_bits(u64::from_be_bytes(data))
181}
182
183pub fn get_lword(bytearray: &[u8], byte_index: usize) -> u64 {
184    let data: [u8; 8] = bytearray[byte_index..byte_index + 8].try_into().unwrap();
185    u64::from_be_bytes(data)
186}
187
188pub fn get_ulint(bytearray: &[u8], byte_index: usize) -> u64 {
189    get_lword(bytearray, byte_index)
190}
191
192pub fn get_tod(bytearray: &[u8], byte_index: usize) -> Duration {
193    let len_bytearray = bytearray.len();
194    let byte_range = byte_index + 4;
195    if len_bytearray < byte_range {
196        panic!("Date can't be extracted from bytearray. bytearray_[Index:Index+16] would cause overflow.");
197    }
198    let time_val = Duration::from_millis(u32::from_be_bytes(
199        bytearray[byte_index..byte_range].try_into().unwrap(),
200    ) as u64);
201    if time_val.as_secs() >= 86400 {
202        panic!(
203            "Time_Of_Date can't be extracted from bytearray. Bytearray contains unexpected values."
204        );
205    }
206    time_val
207}
208
209pub fn get_date(bytearray: &[u8], byte_index: usize) -> chrono::NaiveDate {
210    use chrono::NaiveDate;
211
212    let len_bytearray = bytearray.len();
213    let byte_range = byte_index + 2;
214    if len_bytearray < byte_range {
215        panic!("Date can't be extracted from bytearray. bytearray_[Index:Index+16] would cause overflow.");
216    }
217    let date_val = NaiveDate::from_ymd_opt(1990, 1, 1).expect("failed to parse date.")
218        + chrono::Duration::days(u16::from_be_bytes(
219            bytearray[byte_index..byte_range].try_into().unwrap(),
220        ) as i64);
221    if date_val > NaiveDate::from_ymd_opt(2168, 12, 31).expect("failed to parse date.") {
222        panic!("date_val is higher than specification allows.");
223    }
224    date_val
225}
226
227#[cfg(test)]
228mod getters_tests {
229    use super::*;
230    use chrono::NaiveDate;
231
232    #[test]
233    fn test_get_bool() {
234        let bytearray = [0b10101010];
235        assert!(get_bool(&bytearray, 0, 1));
236        assert!(!get_bool(&bytearray, 0, 0));
237    }
238
239    #[test]
240    fn test_get_byte() {
241        let bytearray = [0x12];
242        assert_eq!(get_byte(&bytearray, 0), 0x12);
243    }
244
245    #[test]
246    fn test_get_word() {
247        let bytearray = [0x12, 0x34];
248        assert_eq!(get_word(&bytearray, 0), 0x1234);
249    }
250
251    #[test]
252    fn test_get_int() {
253        let bytearray = [0xFF, 0xD6];
254        assert_eq!(get_int(&bytearray, 0), -42);
255    }
256
257    #[test]
258    fn test_get_uint() {
259        let bytearray = [0x12, 0x34];
260        assert_eq!(get_uint(&bytearray, 0), 0x1234);
261    }
262
263    #[test]
264    fn test_get_real() {
265        let bytearray = [0x41, 0x20, 0x00, 0x00];
266        assert_eq!(get_real(&bytearray, 0), 10.0);
267    }
268
269    #[test]
270    fn test_get_fstring() {
271        let bytearray = b"hello";
272        assert_eq!(get_fstring(bytearray, 0, 5, true), "hello");
273    }
274
275    #[test]
276    fn test_get_string() {
277        let bytearray = [5, 4, b'h', b'e', b'l', b'l', b'o'];
278        assert_eq!(get_string(&bytearray, 0), "hell");
279    }
280
281    #[test]
282    fn test_get_dword() {
283        let bytearray = [0x12, 0x34, 0x56, 0x78];
284        assert_eq!(get_dword(&bytearray, 0), 0x12345678);
285    }
286
287    #[test]
288    fn test_get_dint() {
289        let bytearray = [0xFF, 0xFF, 0xFF, 0xC6];
290        assert_eq!(get_dint(&bytearray, 0), -58);
291    }
292
293    #[test]
294    fn test_get_udint() {
295        let bytearray = [0x12, 0x34, 0x56, 0x78];
296        assert_eq!(get_udint(&bytearray, 0), 0x12345678);
297    }
298
299    #[test]
300    fn test_get_s5time() {
301        let bytearray = [0x12, 0x34];
302        assert_eq!(get_s5time(&bytearray, 0), "23.4s");
303    }
304
305    #[test]
306    fn test_get_dt() {
307        let bytearray = [0x24, 0x12, 0x12, 0x12, 0x30, 0x30, 0x30, 0x00];
308        assert_eq!(get_dt(&bytearray, 0), "2024-12-12 12:30:30.300 UTC");
309    }
310
311    #[test]
312
313    fn test_get_time() {
314        let bytearray = [0x7f, 0xff, 0xff, 0xff];
315        assert_eq!(get_time(&bytearray, 0), "24:20:31:23.647");
316    }
317
318    #[test]
319    fn test_get_usint() {
320        let bytearray = [0x12];
321        assert_eq!(get_usint(&bytearray, 0), 0x12);
322    }
323
324    #[test]
325    fn test_get_sint() {
326        let bytearray = [0xF6];
327        assert_eq!(get_sint(&bytearray, 0), -10);
328    }
329
330    #[test]
331    fn test_get_lint() {
332        let bytearray = [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC6];
333        assert_eq!(get_lint(&bytearray, 0), -58);
334    }
335
336    #[test]
337    fn test_get_lreal() {
338        let bytearray = [0x40, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
339        assert_eq!(get_lreal(&bytearray, 0), 10.0);
340    }
341
342    #[test]
343    fn test_get_lword() {
344        let bytearray = [0x12, 0x34, 0x56, 0x78, 0x90, 0xAB, 0xCD, 0xEF];
345        assert_eq!(get_lword(&bytearray, 0), 0x1234567890ABCDEF);
346    }
347
348    #[test]
349    fn test_get_ulint() {
350        let bytearray = [0x12, 0x34, 0x56, 0x78, 0x90, 0xAB, 0xCD, 0xEF];
351        assert_eq!(get_ulint(&bytearray, 0), 0x1234567890ABCDEF);
352    }
353
354    #[test]
355    fn test_get_tod() {
356        let bytearray = [0x00, 0x01, 0x51, 0x80];
357        assert_eq!(get_tod(&bytearray, 0), Duration::from_millis(86400));
358    }
359
360    #[test]
361    fn test_get_date() {
362        let days_since_1990 = (NaiveDate::from_ymd_opt(2024, 1, 1).unwrap()
363            - NaiveDate::from_ymd_opt(1990, 1, 1).unwrap())
364        .num_days() as u16;
365        let bytearray = days_since_1990.to_be_bytes();
366        assert_eq!(
367            get_date(&bytearray, 0),
368            NaiveDate::from_ymd_opt(2024, 1, 1).expect("failed to parse date")
369        );
370    }
371}