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}