jsy_mk_194/
lib.rs

1//! JSY-MK-194 is hardware to read power of line.
2//! Please see [official website](https://jsy-tek.com/products/ac-electric-energy-metering-module/single-phase-2-way-power-metering-module-modbus-ttl-electric-energy-metering-pcba).
3//!
4//! This crate was inspered by [jsy-mk-194.cpp](https://github.com/clankgeek/JSY-MK-194/blob/main/src/jsy-mk-194.cpp) library.
5//!
6//! JSY-MK-194 return  data in 61 bytes array:
7//!
8//! | Order of data | Data               | byte index     |
9//! |---------------|--------------------|----------------|
10//! |             1 | header of response | 0, 1, 2        |
11//! |             2 | voltage1           | 3, 4, 5, 6     |
12//! |             3 | current1           | 7, 8, 9, 10    |
13//! |             4 | power1             | 11, 12, 13, 14 |
14//! |             5 | positive kwh1      | 15, 16, 17, 18 |
15//! |             6 | power factor1      | 19, 20, 21, 22 |
16//! |             7 | negative kwh1      | 23, 24, 25, 26 |
17//! |             8 | negative1          | 27             |
18//! |               | negative2          | 28             |
19//! |               | not used           | 29, 30         |
20//! |             9 | frequency          | 31, 32, 33, 34 |
21//! |            10 | voltage2           | 35, 36, 37, 38 |
22//! |            11 | current2           | 39, 40, 41, 42 |
23//! |            12 | power2             | 43, 44, 45, 46 |
24//! |            13 | positive kwh2      | 47, 48, 49, 50 |
25//! |            14 | power factor2      | 51, 52, 53, 54 |
26//! |            15 | negative kwh2      | 55, 56, 57, 58 |
27//! |            16 | crc                | 59, 60         |
28//!
29use embedded_hal::delay::DelayNs;
30use error::UartError;
31
32pub mod error;
33#[cfg(test)]
34mod tests;
35
36// Maximum message to read
37const SEGMENT_READ: usize = 64;
38/// Maximum size of message to send
39const SEGMENT_WRITE: usize = 8;
40/// Size of message to read
41const READ_DATA_SIZE: usize = 61;
42/// Size of message to write change bitrate
43const SEGMENT_WRITE_CHANGE_BIT_RATE: usize = 11;
44
45/// Channel 1 offset
46const CHANNEL_1_OFFSET: usize = 3;
47/// Channel 1 offset
48const CHANNEL_2_OFFSET: usize = 35;
49
50/// Voltage position in data
51const VOLTAGE: usize = 0;
52/// Current position in data
53const CURRENT: usize = 4;
54/// Power
55const POWER: usize = 8;
56/// Positive energy
57const POSITIVE_ENERGY: usize = 12;
58/// Factor
59const FACTOR: usize = 16;
60/// Negative energy
61const NEGATIVE_ENERGY: usize = 20;
62
63/// Frequency position in data
64const FREQUENCY: usize = 31;
65/// Power sign 1
66const POWER_SIGN_1: usize = 27;
67/// Power sign 2
68const POWER_SIGN_2: usize = 28;
69
70type CrcCheck = fn(&[u8]) -> bool;
71
72// From https://ctlsys.com/support/how_to_compute_the_modbus_rtu_message_crc/
73fn is_crc_ok(buf: &[u8]) -> bool {
74    let mut crc: u16 = 0xFFFF;
75    let low = buf.len() - 2;
76    let hi = buf.len() - 1;
77    let buf_crc: u16 = (buf[hi] as u16) * 256 + (buf[low] as u16);
78
79    for current_byte in buf.iter().take(buf.len() - 2) {
80        crc ^= *current_byte as u16; // XOR byte into least sig. byte of crc
81
82        for _ in (0..8).rev() {
83            // Loop over each bit
84            if (crc & 0x0001) != 0 {
85                // If the LSB is set
86                crc >>= 1; // Shift right and XOR 0xA001
87                crc ^= 0xA001;
88            } else {
89                // Else LSB is not set
90                crc >>= 1; // Just shift right
91            }
92        }
93    }
94    // Note, this number has low and high bytes swapped, so use it accordingly (or swap bytes)
95    crc == buf_crc
96}
97
98fn crc_always_ok(_buf: &[u8]) -> bool {
99    true
100}
101
102/// Convert a 32 bits value returned in 4 bytes, to a 32 bit
103#[inline(always)]
104fn conv8to32(hi_byte: u8, mid_byte_2: u8, mid_byte_1: u8, lo_byte: u8) -> u32 {
105    lo_byte as u32
106        + ((mid_byte_1 as u32) << 8)
107        + ((mid_byte_2 as u32) << 16)
108        + ((hi_byte as u32) << 24)
109}
110
111/// Get data number X (see crate doc)
112fn get_data(segment_read: &[u8; SEGMENT_READ], n: usize) -> u32 {
113    conv8to32(
114        segment_read[n],
115        segment_read[n + 1],
116        segment_read[n + 2],
117        segment_read[n + 3],
118    )
119}
120
121/// Get power with right sign.
122#[inline(always)]
123fn power(segment_read: &[u8; SEGMENT_READ], power: usize, sign: usize) -> f32 {
124    let p = (get_data(segment_read, power) as f32) * 0.0001;
125
126    if segment_read[sign] == 1 && p > 0.0 {
127        return -p;
128    }
129
130    p
131}
132
133/// Value to change bitrate
134pub enum ChangeBitrate {
135    B4800,
136    B9600,
137    B19200,
138    B38400,
139}
140
141/// Uart trait that must be impremented for specific hardware
142pub trait Uart {
143    /// Read multiple bytes into a slice
144    fn read(&mut self, buf: &mut [u8], timeout: u32) -> Result<usize, error::UartError>;
145
146    /// Write multiple bytes from a slice
147    fn write(&mut self, bytes: &[u8]) -> Result<usize, error::UartError>;
148
149    /// Allow change uart config
150    fn change_baudrate(&mut self, f: u32) -> Result<(), error::UartError> ;
151}
152
153/// Channel struct to get information. JSY MK 194 has 2 channels
154pub struct Channel {
155    data_offset: usize,
156    power_sign: usize,
157    voltage: f32,
158    current: f32,
159    positive_energy: f32,
160    negative_energy: f32,
161    power: f32,
162    factor: f32,
163}
164
165impl Channel {
166    pub fn new(data_offset: usize, power_sign: usize) -> Self {
167        Self {
168            data_offset,
169            power_sign,
170            voltage: 0.0,
171            current: 0.0,
172            positive_energy: 0.0,
173            negative_energy: 0.0,
174            power: 0.0,
175            factor: 0.0,
176        }
177    }
178
179    /// Return the voltage of first channel in volt.
180    pub fn voltage(&self) -> f32 {
181        self.voltage
182    }
183
184    /// Return current in A of channel.
185    pub fn current(&self) -> f32 {
186        self.current
187    }
188
189    /// Return positive energy in kW/h of channel.
190    pub fn positive_energy(&self) -> f32 {
191        self.positive_energy
192    }
193
194    /// Return negative energy in kW/h of channel.
195    pub fn negative_energy(&self) -> f32 {
196        self.negative_energy
197    }
198
199    /// Return the power of channel in watt.
200    pub fn power(&self) -> f32 {
201        self.power
202    }
203
204    /// Return the power of channel in watt.
205    pub fn factor(&self) -> f32 {
206        self.factor
207    }
208
209    /// Update all data
210    fn update(&mut self, segment_read: &[u8; SEGMENT_READ]) {
211        self.voltage = (get_data(segment_read, self.data_offset + VOLTAGE) as f32) * 0.0001;
212        self.current = (get_data(segment_read, self.data_offset + CURRENT) as f32) * 0.0001;
213        self.positive_energy =
214            (get_data(segment_read, self.data_offset + POSITIVE_ENERGY) as f32) * 0.0001;
215        self.negative_energy =
216            (get_data(segment_read, self.data_offset + NEGATIVE_ENERGY) as f32) * 0.0001;
217        self.factor = (get_data(segment_read, self.data_offset + FACTOR) as f32) * 0.001;
218        self.power = power(segment_read, self.data_offset + POWER, self.power_sign);
219    }
220}
221
222/// Global struct to communicate with JSY MK 194
223pub struct JsyMk194<U, D>
224where
225    U: Uart,
226    D: DelayNs,
227{
228    uart: U,
229    delay: D,
230    segment_write: [u8; SEGMENT_WRITE], //= {0x01, 0x03, 0x00, 0x48, 0x00, 0x0E, 0x44, 0x18};
231    segment_read: [u8; SEGMENT_READ],
232    is_crc_valid: CrcCheck,
233    frequency: f32,
234
235    pub channel1: Channel,
236    pub channel2: Channel,
237}
238
239impl<U, D> JsyMk194<U, D>
240where
241    U: Uart,
242    D: DelayNs,
243{
244    /// Create a new struct of JsyMk194.
245    pub fn new(uart: U, delay: D) -> Self {
246        Self {
247            uart,
248            delay,
249            segment_write: [0x01, 0x03, 0x00, 0x48, 0x00, 0x0e, 0x44, 0x18],
250            segment_read: [0; SEGMENT_READ],
251            is_crc_valid: is_crc_ok,
252            channel1: Channel::new(CHANNEL_1_OFFSET, POWER_SIGN_1),
253            channel2: Channel::new(CHANNEL_2_OFFSET, POWER_SIGN_2),
254            frequency: 0.0,
255        }
256    }
257
258    /// Create a new struct of JsyMk194.
259    pub fn new_without_crc_check(uart: U, delay: D) -> Self {
260        Self {
261            uart,
262            delay,
263            segment_write: [0x01, 0x03, 0x00, 0x48, 0x00, 0x0e, 0x44, 0x18],
264            segment_read: [0; SEGMENT_READ],
265            is_crc_valid: crc_always_ok,
266            channel1: Channel::new(CHANNEL_1_OFFSET, POWER_SIGN_1),
267            channel2: Channel::new(CHANNEL_2_OFFSET, POWER_SIGN_2),
268            frequency: 0.0,
269        }
270    }
271
272    // Read and wait 100ms
273    pub fn read(&mut self) -> Result<(), error::UartError> {
274        self.read_with_timeout(100)
275    }
276
277    /// Read data.
278    pub fn read_with_timeout(&mut self, timeout_ms: u32) -> Result<(), error::UartError> {
279        // send segment to JSY-MK-194
280        self.uart.write(&self.segment_write)?;
281
282        let is_read_data = self.uart.read(&mut self.segment_read, timeout_ms);
283
284        match is_read_data {
285            Ok(data_size) => {
286                if data_size != READ_DATA_SIZE {
287                    return Err(error::UartError::new(
288                        error::UartErrorKind::ReadInsuffisantBytes,
289                        format!(
290                            "Try to read {} bytes, but Uart read only {} bytes",
291                            READ_DATA_SIZE, data_size
292                        ),
293                    ));
294                }
295
296                if (self.is_crc_valid)(&self.segment_read[0..data_size]) {
297                    self.channel1.update(&self.segment_read);
298                    self.channel2.update(&self.segment_read);
299                    self.frequency = (get_data(&self.segment_read, FREQUENCY) as f32) * 0.01;
300                    Ok(())
301                } else {
302                    Err(error::UartError::from(error::UartErrorKind::BadCrc))
303                }
304            }
305            Err(e) => Err(e),
306        }
307    }
308
309    /// Return frequency in hz.
310    pub fn frequency(&self) -> f32 {
311        self.frequency
312    }
313
314    /// Default bitrate is 4800, you can update the bitrate of module
315    /// the available values are : 4800, 9600, 19200, 38400.
316    /// Return true if success.
317    pub fn change_bitrate(
318        &mut self,
319        new_bitrate: ChangeBitrate,
320    ) -> Result<(), error::ChangeBitrateError> {
321        let mut segment: [u8; SEGMENT_WRITE_CHANGE_BIT_RATE] = [
322            0x00, 0x10, 0x00, 0x04, 0x00, 0x01, 0x02, 0x01, 0x00, 0x00, 0x00,
323        ];
324
325        match new_bitrate {
326            ChangeBitrate::B9600 => self.update_segment(&mut segment, 0x06, 0x2b, 0xd6),
327            ChangeBitrate::B19200 => self.update_segment(&mut segment, 0x07, 0xea, 0x16),
328            ChangeBitrate::B38400 => self.update_segment(&mut segment, 0x08, 0xaa, 0x12),
329            _ => self.update_segment(&mut segment, 0x05, 0x6B, 0xD7),
330        }
331
332        self.delay.delay_ms(1000);
333
334        let result = self.uart.write(&segment);
335
336        match result {
337            Ok(write_size) => {
338                if write_size == segment.len() {
339                    return Ok(());
340                }
341
342                Err(error::ChangeBitrateError::new(error::UartError::new(
343                    error::UartErrorKind::WriteInsuffisantBytes,
344                    format!(
345                        "Try to write {} bytes, but Uart write only {} bytes",
346                        segment.len(),
347                        write_size
348                    ),
349                )))
350            }
351            Err(e) => Err(error::ChangeBitrateError::new(e)),
352        }
353    }
354
355    pub fn change_baudrate(&mut self, f: u32) -> Result<(), UartError> {
356        self.uart.change_baudrate(f)
357    }
358
359    fn update_segment(
360        &self,
361        data: &mut [u8; SEGMENT_WRITE_CHANGE_BIT_RATE],
362        value: u8,
363        crc1: u8,
364        crc2: u8,
365    ) {
366        data[8] = value;
367        data[9] = crc1;
368        data[10] = crc2;
369    }
370
371    #[cfg(test)]
372    fn get_uart(&self) -> &U {
373        &self.uart
374    }
375}