use std::time::Duration;
use crate::drivers::{modbus::ModbusInstrument, InstrumentError, Result};
use crate::logging_utils::device_trace;
use crate::model::{Device, SCADADevice};
use crate::state::BinaryState;
use async_trait::async_trait;
use log::trace;
pub const CN7500_BAUDRATES: [usize; 5] = [2400, 4800, 9600, 19200, 38400];
#[derive(Debug, Clone)]
pub enum Degree {
Fahrenheit,
Celsius,
}
#[derive(Debug)]
pub struct CN7500(ModbusInstrument);
#[async_trait]
impl SCADADevice for CN7500 {
async fn update(device: &mut Device) -> Result<()> {
device_trace!(device, "updating CN7500 device...");
let mut cn = CN7500::connect(
device.conn.controller_addr(),
&device.conn.port(),
*device.conn.baudrate() as u64,
device.conn.timeout(),
)
.await?;
device.state.relay_state = Some(cn.is_running().await?.into());
device.state.pv = Some(cn.get_pv().await?);
device.state.sv = Some(cn.get_sv().await?);
device_trace!(device, "updated");
Ok(())
}
async fn enact(device: &mut Device) -> Result<()> {
device_trace!(device, "enacting CN7500 device...");
let mut cn = CN7500::connect(
device.conn.controller_addr(),
&device.conn.port(),
*device.conn.baudrate() as u64,
device.conn.timeout(),
)
.await?;
match device.state.relay_state {
Some(BinaryState::On) => cn.run().await?,
Some(BinaryState::Off) => cn.stop().await?,
None => {}
}
if let Some(new_sv) = device.state.sv {
cn.set_sv(new_sv).await?;
}
device_trace!(device, "enacted");
Ok(())
}
}
impl CN7500 {
pub async fn connect(
slave_addr: u8,
port_path: &str,
baudrate: u64,
timeout: Duration,
) -> Result<Self> {
trace!("[CN7500 addr: {}] connected", slave_addr);
let mut cn = CN7500(ModbusInstrument::new(slave_addr, port_path, baudrate, timeout).await?);
cn.connected().await.map_err(|instr_err| {
InstrumentError::modbusError(
format!(
"CN7500 connection failed, likely busy. Error: {}",
instr_err
),
Some(slave_addr),
)
})?;
Ok(cn)
}
pub async fn from_device(device: Device) -> Result<Self> {
let c = device.conn;
Self::connect(
c.controller_addr(),
&c.port(),
*c.baudrate() as u64,
c.timeout(),
)
.await
}
pub async fn connected(&mut self) -> Result<()> {
self.software_revision().await?;
Ok(())
}
pub async fn set_sv(&mut self, new_sv: f64) -> Result<()> {
trace!("[CN7500 addr: {}] Setting sv: {new_sv}", self.0.slave_addr);
self.0.write_register(0x1001, (new_sv * 10.0) as u16).await
}
pub async fn get_sv(&mut self) -> Result<f64> {
trace!("[CN7500 addr: {}] getting sv", self.0.slave_addr);
self.0
.read_registers(0x1001, 1)
.await
.map(|vec| (vec[0] as f64) / 10.0)
}
pub async fn get_pv(&mut self) -> Result<f64> {
trace!("[CN7500 addr: {}] getting pv", self.0.slave_addr);
self.0
.read_registers(0x1000, 1)
.await
.map(|vec| (vec[0] as f64) / 10.0)
}
pub async fn is_running(&mut self) -> Result<bool> {
trace!("[CN7500 addr: {}] polled is running", self.0.slave_addr);
self.0.read_coils(0x0814, 1).await.map(|vals| vals[0])
}
pub async fn run(&mut self) -> Result<()> {
trace!("[CN7500 addr: {}] set to run", self.0.slave_addr);
self.0.write_coil(0x0814, true).await
}
pub async fn stop(&mut self) -> Result<()> {
trace!("[CN7500 addr: {}] set to stop", self.0.slave_addr);
self.0.write_coil(0x0814, false).await
}
pub async fn set_degrees(&mut self, degree_mode: Degree) -> Result<()> {
trace!(
"[CN7500 addr: {}] setting degree mode to {:?}",
self.0.slave_addr,
degree_mode
);
match degree_mode {
Degree::Celsius => self.0.write_coil(0x0811, true).await,
Degree::Fahrenheit => self.0.write_coil(0x0811, false).await,
}
}
pub async fn software_revision(&mut self) -> Result<Vec<u16>> {
trace!(
"[CN7500 addr: {}] polled software revision",
self.0.slave_addr
);
self.0.read_registers(0x102F, 1).await.map_err(|_|
InstrumentError::SerialError {
msg: format!("Software revision couldn't be retrieved, the controller likely isn't connected"),
addr: Some(self.0.slave_addr)
}
)
}
}
#[cfg(test)]
mod tests {
use crate::controllers::Controller;
use super::*;
use tokio::test;
async fn instr() -> CN7500 {
let device = crate::tests::test_device_from_type(Controller::CN7500);
CN7500::connect(
device.conn.controller_addr(),
&device.conn.port(),
*device.conn.baudrate() as u64,
device.conn.timeout(),
)
.await
.unwrap()
}
#[test]
async fn test_new_cn7500() {
let cn = instr().await;
assert_eq!(cn.0.port_path, "/dev/ttyUSB0");
}
#[test]
async fn test_set_sv() {
let mut cn = instr().await;
let rsp = cn.set_sv(123.4).await;
assert!(rsp.is_ok());
}
#[test]
async fn test_get_pv() {
let mut cn = instr().await;
assert!(cn.get_pv().await.unwrap() > 0.0);
}
#[test]
async fn test_get_sv() {
let mut cn = instr().await;
assert!(cn.set_sv(145.7).await.is_ok());
assert_eq!(cn.get_sv().await.unwrap(), 145.7);
}
#[test]
async fn test_turn_on_relay() {
let mut cn = instr().await;
assert!(cn.run().await.is_ok());
assert!(cn.is_running().await.unwrap());
assert!(cn.stop().await.is_ok());
}
#[test]
async fn test_cn7500_doesnt_respond_when_bad_conn() {
let cn2 = CN7500::connect(0x18, "/dev/ttyUSB0", 9600, Duration::from_millis(100)).await;
assert!(cn2.is_err());
}
}