#![no_std]
extern crate crc16;
extern crate embedded_hal as hal;
#[macro_use(block)]
extern crate nb;
mod io;
use io::*;
mod no_timeout;
pub use no_timeout::NoTimeout;
use core::fmt::Display;
use core::fmt::Formatter;
use hal::serial;
use hal::timer;
const ADDR_DEFAULT: u8 = 0xf8;
const ADDR_MIN: u8 = 0x01;
const ADDR_MAX: u8 = 0xf7;
const CMD_READ: u8 = 0x04;
const CMD_RESET: u8 = 0x42;
const CMD_READ_PARAM: u8 = 0x03;
const CMD_WRITE_PARAM: u8 = 0x06;
const PARAM_THRESHOLD: u16 = 0x0001;
const PARAM_ADDR: u16 = 0x0002;
const REG_COUNT: u16 = 10;
#[derive(Debug, Clone)]
pub enum Error<WriteError, ReadError> {
TimedOut,
CrcMismatch,
PzemError,
IllegalAddress,
WriteError(WriteError),
ReadError(ReadError),
}
impl<WriteError: Display, ReadError: Display> Display for Error<WriteError, ReadError> {
fn fmt(&self, f: &mut Formatter) -> Result<(), core::fmt::Error> {
match self {
Error::TimedOut => write!(f, "Communication timed out"),
Error::CrcMismatch => write!(f, "CRC doesn't match"),
Error::PzemError => write!(f, "Internal PZEM004T error"),
Error::IllegalAddress => write!(f, "Illegal address"),
Error::WriteError(e) => write!(f, "Could not write: {}", e),
Error::ReadError(e) => write!(f, "Could not read: {}", e),
}
}
}
fn crc_write(buf: &mut [u8]) {
let n = buf.len();
let crc = u16::to_be(crc16::State::<crc16::MODBUS>::calculate(&mut buf[0..n - 2]));
buf[n - 2] = (crc >> 8) as u8;
buf[n - 1] = (crc >> 0) as u8;
}
fn crc_check(buf: &[u8]) -> bool {
let n = buf.len();
let crc = u16::from_be(crc16::State::<crc16::MODBUS>::calculate(&buf[0..n - 2]));
(crc >> 8) as u8 == buf[n - 2] && crc as u8 == buf[n - 1]
}
fn result_convert(buf: &[u8; 25], m: &mut Measurement) {
m.voltage = (((buf[3] as u16) << 8) | buf[4] as u16) as f32 / 10.0;
m.current = (((buf[5] as u32) << 8)
| ((buf[6] as u32) << 0)
| ((buf[7] as u32) << 24)
| ((buf[8] as u32) << 16)) as f32
/ 1000.0;
m.power = (((buf[9] as u32) << 8)
| ((buf[10] as u32) << 0)
| ((buf[11] as u32) << 24)
| ((buf[12] as u32) << 16)) as f32
/ 10.0;
m.energy = (((buf[13] as u32) << 8)
| ((buf[14] as u32) << 0)
| ((buf[15] as u32) << 24)
| ((buf[16] as u32) << 16)) as f32
/ 1000.0;
m.frequency = (((buf[17] as u16) << 8) | ((buf[18] as u16) << 0)) as f32 / 10.0;
m.pf = (((buf[19] as u16) << 8) | ((buf[20] as u16) << 0)) as f32 / 100.0;
m.alarm = (((buf[21] as u16) << 8) | ((buf[22] as u16) << 0)) != 0;
}
#[derive(Debug, Default, Copy, Clone)]
pub struct Measurement {
pub voltage: f32,
pub current: f32,
pub power: f32,
pub energy: f32,
pub frequency: f32,
pub pf: f32,
pub alarm: bool,
}
pub struct Pzem<Serial> {
uart: Serial,
addr: u8,
}
impl<Serial, WriteError, ReadError> Pzem<Serial>
where
Serial: serial::Write<u8, Error = WriteError> + serial::Read<u8, Error = ReadError>,
{
pub fn new(uart: Serial, addr: Option<u8>) -> Result<Self, Error<WriteError, ReadError>> {
let addr = addr.unwrap_or(ADDR_DEFAULT);
if addr != ADDR_DEFAULT && (addr < ADDR_MIN || addr > ADDR_MAX) {
return Err(Error::IllegalAddress);
}
Ok(Self { uart, addr })
}
fn communicate<T: timer::CountDown>(
&mut self,
req: &[u8],
resp: &mut [u8],
timeout: Option<(&mut T, T::Time)>,
) -> Result<(), Error<WriteError, ReadError>> {
self.uart.drain().map_err(Error::ReadError)?;
self.uart.write_blocking(&req).map_err(Error::WriteError)?;
block!(self.uart.flush()).map_err(Error::WriteError)?;
if self
.uart
.read_blocking(timeout, resp)
.map_err(Error::ReadError)?
< resp.len() as u8
{
return Err(Error::TimedOut);
}
if resp[0] != req[0] || resp[1] != req[1] {
return Err(Error::PzemError);
}
if resp.len() == 4 && (resp[2] != req[2] || resp[3] != req[3]) {
return Err(Error::CrcMismatch);
}
if !crc_check(&resp) {
return Err(Error::CrcMismatch);
}
Ok(())
}
pub fn read<T: timer::CountDown>(
&mut self,
m: &mut Measurement,
timeout: Option<(&mut T, T::Time)>,
) -> Result<(), Error<WriteError, ReadError>> {
let mut buf = [
self.addr,
CMD_READ,
0,
0,
(REG_COUNT >> 8) as u8,
(REG_COUNT >> 0) as u8,
0,
0,
];
crc_write(&mut buf);
let mut resp: [u8; 25] = unsafe { core::mem::MaybeUninit::uninit().assume_init() };
self.communicate(&buf, &mut resp, timeout)?;
result_convert(&resp, m);
Ok(())
}
pub fn get_threshold<T: timer::CountDown>(
&mut self,
timeout: Option<(&mut T, T::Time)>,
) -> Result<u16, Error<WriteError, ReadError>> {
let mut buf = [
self.addr,
CMD_READ_PARAM,
(PARAM_THRESHOLD >> 8) as u8,
(PARAM_THRESHOLD >> 0) as u8,
0,
1,
0,
0,
];
crc_write(&mut buf);
let mut resp: [u8; 7] = unsafe { core::mem::MaybeUninit::uninit().assume_init() };
self.communicate(&buf, &mut resp, timeout)?;
Ok(((resp[3] as u16) << 8) | ((resp[4] as u16) << 0))
}
pub fn get_addr<T: timer::CountDown>(
&mut self,
timeout: Option<(&mut T, T::Time)>,
) -> Result<u16, Error<WriteError, ReadError>> {
let mut buf = [
self.addr,
CMD_READ_PARAM,
(PARAM_ADDR >> 8) as u8,
(PARAM_ADDR >> 0) as u8,
0,
1,
0,
0,
];
crc_write(&mut buf);
let mut resp: [u8; 7] = unsafe { core::mem::MaybeUninit::uninit().assume_init() };
self.communicate(&buf, &mut resp, timeout)?;
Ok(((resp[3] as u16) << 8) | ((resp[4] as u16) << 0))
}
pub fn set_threshold<T: timer::CountDown>(
&mut self,
threshold: u16,
timeout: Option<(&mut T, T::Time)>,
) -> Result<(), Error<WriteError, ReadError>> {
let mut buf: [u8; 8] = [
self.addr,
CMD_WRITE_PARAM,
(PARAM_THRESHOLD >> 8) as u8,
(PARAM_THRESHOLD >> 0) as u8,
(threshold >> 8) as u8,
(threshold >> 0) as u8,
0,
0,
];
crc_write(&mut buf);
let mut resp: [u8; 8] = unsafe { core::mem::MaybeUninit::uninit().assume_init() };
self.communicate(&buf, &mut resp, timeout)?;
Ok(())
}
pub fn set_addr<T: timer::CountDown>(
&mut self,
addr: u8,
timeout: Option<(&mut T, T::Time)>,
) -> Result<(), Error<WriteError, ReadError>> {
if addr < ADDR_MIN || addr > ADDR_MAX {
return Err(Error::IllegalAddress);
}
let mut buf: [u8; 8] = [
self.addr,
CMD_WRITE_PARAM,
(PARAM_ADDR >> 8) as u8,
(PARAM_ADDR >> 0) as u8,
0,
addr,
0,
0,
];
crc_write(&mut buf);
let mut resp: [u8; 8] = unsafe { core::mem::MaybeUninit::uninit().assume_init() };
self.communicate(&buf, &mut resp, timeout)?;
self.addr = addr;
Ok(())
}
pub fn reset_energy<T: timer::CountDown>(
&mut self,
timeout: Option<(&mut T, T::Time)>,
) -> Result<(), Error<WriteError, ReadError>> {
let mut buf = [self.addr, CMD_RESET, 0, 0];
crc_write(&mut buf);
let mut resp: [u8; 4] = unsafe { core::mem::MaybeUninit::uninit().assume_init() };
self.communicate(&buf, &mut resp, timeout)?;
Ok(())
}
pub fn release(self) -> Serial {
self.uart
}
}