resol_vbus/
utils.rs

1//! A module containing utitlities functions for processing VBus data.
2use chrono::{DateTime, TimeZone, Utc};
3
4/// Calc checksum according to VBus protocol version x.0.
5///
6/// # Examples
7///
8/// ```rust
9/// use resol_vbus::utils::calc_checksum_v0;
10///
11/// assert_eq!(0x7F, calc_checksum_v0(&[]));
12/// assert_eq!(0x00, calc_checksum_v0(&[ 0x7F ]));
13/// assert_eq!(0x01, calc_checksum_v0(&[ 0x7F, 0x7F ]));
14/// assert_eq!(0x34, calc_checksum_v0(&[ 0x10, 0x00, 0x11, 0x7E, 0x10, 0x00, 0x01, 0x1B ]));
15/// ```
16pub fn calc_checksum_v0(buf: &[u8]) -> u8 {
17    buf.iter().fold(0x7F, |acc, &x| (0x80 + acc - x) & 0x7F)
18}
19
20/// Calc and compare checksum according to VBus protocol version x.0.
21///
22/// This function calculates the checksum over all but the last bytes in the provided slice and
23/// compares the calculated checksum with the last byte in the slice, returning the result of the
24/// comparison.
25///
26/// # Examples
27///
28/// ```rust
29/// use resol_vbus::utils::calc_and_compare_checksum_v0;
30///
31/// assert_eq!(true, calc_and_compare_checksum_v0(&[ 0x10, 0x00, 0x11, 0x7E, 0x10, 0x00, 0x01, 0x1B, 0x34 ]));
32/// assert_eq!(false, calc_and_compare_checksum_v0(&[ 0x10, 0x00, 0x11, 0x7E, 0x10, 0x00, 0x01, 0x1B, 0x00 ]));
33/// ```
34pub fn calc_and_compare_checksum_v0(buf: &[u8]) -> bool {
35    let idx = buf.len() - 1;
36    calc_checksum_v0(&buf[0..idx]) == buf[idx]
37}
38
39/// Calc and set checksum according to VBus protocol version x.0.
40///
41/// This function calculates the checksum over all but the last bytes in the provided slice and
42/// compares the calculated checksum with the last byte in the slice.
43///
44/// # Examples
45///
46/// ```rust
47/// use resol_vbus::utils::calc_and_set_checksum_v0;
48///
49/// let mut buf = [ 0x10, 0x00, 0x11, 0x7E, 0x10, 0x00, 0x01, 0x1B, 0x00 ];
50/// calc_and_set_checksum_v0(&mut buf [..]);
51/// assert_eq!(0x34, buf [8]);
52/// ```
53pub fn calc_and_set_checksum_v0(buf: &mut [u8]) {
54    let idx = buf.len() - 1;
55    buf[idx] = calc_checksum_v0(&buf[0..idx]);
56}
57
58/// Copy bytes from `src` to `dst`, extracting the MSB into a separate byte and appending it at the end of `dst`.
59///
60/// # Panics
61///
62/// The function panics if the `dst` slice is not exactly one byte longer than the `src` slice.
63///
64/// # Examples
65///
66/// ```rust
67/// use resol_vbus::utils::copy_bytes_extracting_septett;
68///
69/// let src = &[ 0x07, 0x01, 0x4c, 0x00, 0x82, 0x01, 0xff, 0x00, 0xb8, 0x22, 0xf6, 0x00, 0x00, 0x00, 0x00, 0x00 ];
70/// let mut dst = [0u8; 20];
71///
72/// copy_bytes_extracting_septett(&mut dst [0..5], &src [0..4]);
73/// copy_bytes_extracting_septett(&mut dst [5..10], &src [4..8]);
74/// copy_bytes_extracting_septett(&mut dst [10..15], &src [8..12]);
75/// copy_bytes_extracting_septett(&mut dst [15..20], &src [12..16]);
76///
77/// assert_eq!(&[ 0x07, 0x01, 0x4c, 0x00, 0x00, 0x02, 0x01, 0x7f, 0x00, 0x05, 0x38, 0x22, 0x76, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00 ], &dst);
78/// ```
79pub fn copy_bytes_extracting_septett(dst: &mut [u8], src: &[u8]) {
80    let septett_idx = src.len();
81    if dst.len() != septett_idx + 1 {
82        panic!("Destination must be one byte larger than source");
83    }
84
85    dst[septett_idx] = src.iter().enumerate().fold(0u8, |acc, (idx, &b)| {
86        dst[idx] = b & 0x7F;
87        let mask = if b >= 0x80 { 1 << idx } else { 0 };
88        acc | mask
89    });
90}
91
92/// Copy bytes from `src` to `dst`, injecting the MSBs stored in a separate byte at the end of `src`.
93///
94/// # Panics
95///
96/// The function panics if the `src` slice is not exactly one byte longer than the `dst` slice.
97///
98/// # Examples
99///
100/// ```rust
101/// use resol_vbus::utils::copy_bytes_injecting_septett;
102///
103/// let src = &[ 0x07, 0x01, 0x4c, 0x00, 0x00, 0x02, 0x01, 0x7f, 0x00, 0x05, 0x38, 0x22, 0x76, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00 ];
104/// let mut dst = [0u8; 16];
105///
106/// copy_bytes_injecting_septett(&mut dst [0..4], &src [0..5]);
107/// copy_bytes_injecting_septett(&mut dst [4..8], &src [5..10]);
108/// copy_bytes_injecting_septett(&mut dst [8..12], &src [10..15]);
109/// copy_bytes_injecting_septett(&mut dst [12..16], &src [15..20]);
110///
111/// assert_eq!(&[ 0x07, 0x01, 0x4c, 0x00, 0x82, 0x01, 0xff, 0x00, 0xb8, 0x22, 0xf6, 0x00, 0x00, 0x00, 0x00, 0x00 ], &dst);
112/// ```
113pub fn copy_bytes_injecting_septett(dst: &mut [u8], src: &[u8]) {
114    let septett_idx = dst.len();
115    if src.len() != septett_idx + 1 {
116        panic!("Source must be one byte larger than destination");
117    }
118
119    let septett = src[septett_idx];
120    for (idx, dst_b) in dst.iter_mut().enumerate() {
121        let b = src[idx];
122        let mask = if (septett & (1 << idx)) != 0 {
123            0x80
124        } else {
125            0x00
126        };
127        *dst_b = b | mask;
128    }
129}
130
131/// Checks a slice of bytes whether one of them has its MSB set.
132///
133/// # Examples
134///
135/// ```rust
136/// use resol_vbus::utils::has_msb_set;
137///
138/// let bytes = &[ 0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80 ];
139///
140/// assert_eq!(false, has_msb_set(&bytes [0..8]));
141/// assert_eq!(true, has_msb_set(&bytes [0..9]));
142/// ```
143pub fn has_msb_set(buf: &[u8]) -> bool {
144    buf.iter().any(|b| b & 0x80 != 0)
145}
146
147const CRC16_TABLE: &[u16] = &[
148    0x0000, 0x1189, 0x2312, 0x329B, 0x4624, 0x57AD, 0x6536, 0x74BF, 0x8C48, 0x9DC1, 0xAF5A, 0xBED3,
149    0xCA6C, 0xDBE5, 0xE97E, 0xF8F7, 0x1081, 0x0108, 0x3393, 0x221A, 0x56A5, 0x472C, 0x75B7, 0x643E,
150    0x9CC9, 0x8D40, 0xBFDB, 0xAE52, 0xDAED, 0xCB64, 0xF9FF, 0xE876, 0x2102, 0x308B, 0x0210, 0x1399,
151    0x6726, 0x76AF, 0x4434, 0x55BD, 0xAD4A, 0xBCC3, 0x8E58, 0x9FD1, 0xEB6E, 0xFAE7, 0xC87C, 0xD9F5,
152    0x3183, 0x200A, 0x1291, 0x0318, 0x77A7, 0x662E, 0x54B5, 0x453C, 0xBDCB, 0xAC42, 0x9ED9, 0x8F50,
153    0xFBEF, 0xEA66, 0xD8FD, 0xC974, 0x4204, 0x538D, 0x6116, 0x709F, 0x0420, 0x15A9, 0x2732, 0x36BB,
154    0xCE4C, 0xDFC5, 0xED5E, 0xFCD7, 0x8868, 0x99E1, 0xAB7A, 0xBAF3, 0x5285, 0x430C, 0x7197, 0x601E,
155    0x14A1, 0x0528, 0x37B3, 0x263A, 0xDECD, 0xCF44, 0xFDDF, 0xEC56, 0x98E9, 0x8960, 0xBBFB, 0xAA72,
156    0x6306, 0x728F, 0x4014, 0x519D, 0x2522, 0x34AB, 0x0630, 0x17B9, 0xEF4E, 0xFEC7, 0xCC5C, 0xDDD5,
157    0xA96A, 0xB8E3, 0x8A78, 0x9BF1, 0x7387, 0x620E, 0x5095, 0x411C, 0x35A3, 0x242A, 0x16B1, 0x0738,
158    0xFFCF, 0xEE46, 0xDCDD, 0xCD54, 0xB9EB, 0xA862, 0x9AF9, 0x8B70, 0x8408, 0x9581, 0xA71A, 0xB693,
159    0xC22C, 0xD3A5, 0xE13E, 0xF0B7, 0x0840, 0x19C9, 0x2B52, 0x3ADB, 0x4E64, 0x5FED, 0x6D76, 0x7CFF,
160    0x9489, 0x8500, 0xB79B, 0xA612, 0xD2AD, 0xC324, 0xF1BF, 0xE036, 0x18C1, 0x0948, 0x3BD3, 0x2A5A,
161    0x5EE5, 0x4F6C, 0x7DF7, 0x6C7E, 0xA50A, 0xB483, 0x8618, 0x9791, 0xE32E, 0xF2A7, 0xC03C, 0xD1B5,
162    0x2942, 0x38CB, 0x0A50, 0x1BD9, 0x6F66, 0x7EEF, 0x4C74, 0x5DFD, 0xB58B, 0xA402, 0x9699, 0x8710,
163    0xF3AF, 0xE226, 0xD0BD, 0xC134, 0x39C3, 0x284A, 0x1AD1, 0x0B58, 0x7FE7, 0x6E6E, 0x5CF5, 0x4D7C,
164    0xC60C, 0xD785, 0xE51E, 0xF497, 0x8028, 0x91A1, 0xA33A, 0xB2B3, 0x4A44, 0x5BCD, 0x6956, 0x78DF,
165    0x0C60, 0x1DE9, 0x2F72, 0x3EFB, 0xD68D, 0xC704, 0xF59F, 0xE416, 0x90A9, 0x8120, 0xB3BB, 0xA232,
166    0x5AC5, 0x4B4C, 0x79D7, 0x685E, 0x1CE1, 0x0D68, 0x3FF3, 0x2E7A, 0xE70E, 0xF687, 0xC41C, 0xD595,
167    0xA12A, 0xB0A3, 0x8238, 0x93B1, 0x6B46, 0x7ACF, 0x4854, 0x59DD, 0x2D62, 0x3CEB, 0x0E70, 0x1FF9,
168    0xF78F, 0xE606, 0xD49D, 0xC514, 0xB1AB, 0xA022, 0x92B9, 0x8330, 0x7BC7, 0x6A4E, 0x58D5, 0x495C,
169    0x3DE3, 0x2C6A, 0x1EF1, 0x0F78,
170];
171
172/// Calculate the CRC16 checksum over a slice of bytes.
173///
174/// # Examples
175///
176/// ```rust
177/// use resol_vbus::utils::calc_crc16;
178///
179/// assert_eq!(0x0000, calc_crc16(&[]));
180/// assert_eq!(0xF078, calc_crc16(&[ 0x00 ]));
181/// ```
182pub fn calc_crc16(buf: &[u8]) -> u16 {
183    let mut crc = 0xFFFFu16;
184    for byte in buf {
185        crc = (crc >> 8) ^ CRC16_TABLE[(crc ^ u16::from(*byte)) as usize & 0xFF];
186    }
187    crc ^ 0xFFFF
188}
189
190/// Return a Utc timestamp for the given UNIX epoch seconds.
191///
192/// # Examples
193///
194/// ```rust
195/// use resol_vbus::utils::utc_timestamp;
196///
197/// assert_eq!("2017-01-29 11:22:13 UTC", utc_timestamp(1485688933).to_string());
198/// ```
199pub fn utc_timestamp(secs: i64) -> DateTime<Utc> {
200    Utc.timestamp(secs, 0)
201}
202
203/// Return the current timestamp if the platform supports that.
204pub fn current_timestamp() -> DateTime<Utc> {
205    current_timestamp_internal()
206}
207
208#[cfg(target_arch = "wasm32")]
209fn current_timestamp_internal() -> DateTime<Utc> {
210    Utc.timestamp(0, 0)
211}
212
213#[cfg(not(target_arch = "wasm32"))]
214fn current_timestamp_internal() -> DateTime<Utc> {
215    Utc::now()
216}