use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use enum_primitive::FromPrimitive;
use std::borrow::BorrowMut;
use std::io::{self, Cursor, Read, Write};
use std::net::{Shutdown, TcpStream, ToSocketAddrs};
use std::time::Duration;
use {binary, Client, Coil, Error, ExceptionCode, Function, Reason, Result};
const MODBUS_PROTOCOL_TCP: u16 = 0x0000;
const MODBUS_TCP_DEFAULT_PORT: u16 = 502;
const MODBUS_HEADER_SIZE: usize = 7;
const MODBUS_MAX_PACKET_SIZE: usize = 260;
#[derive(Clone, Copy)]
pub struct Config {
pub tcp_port: u16,
pub tcp_connect_timeout: Option<Duration>,
pub tcp_read_timeout: Option<Duration>,
pub tcp_write_timeout: Option<Duration>,
pub modbus_uid: u8,
}
impl Default for Config {
fn default() -> Config {
Config {
tcp_port: MODBUS_TCP_DEFAULT_PORT,
tcp_connect_timeout: None,
tcp_read_timeout: None,
tcp_write_timeout: None,
modbus_uid: 1,
}
}
}
#[derive(Debug, PartialEq)]
struct Header {
tid: u16,
pid: u16,
len: u16,
uid: u8,
}
impl Header {
fn new(transport: &mut Transport, len: u16) -> Header {
Header {
tid: transport.new_tid(),
pid: MODBUS_PROTOCOL_TCP,
len: len - MODBUS_HEADER_SIZE as u16,
uid: transport.uid,
}
}
fn pack(&self) -> Result<Vec<u8>> {
let mut buff = vec![];
buff.write_u16::<BigEndian>(self.tid)?;
buff.write_u16::<BigEndian>(self.pid)?;
buff.write_u16::<BigEndian>(self.len)?;
buff.write_u8(self.uid)?;
Ok(buff)
}
fn unpack(buff: &[u8]) -> Result<Header> {
let mut rdr = Cursor::new(buff);
Ok(Header {
tid: rdr.read_u16::<BigEndian>()?,
pid: rdr.read_u16::<BigEndian>()?,
len: rdr.read_u16::<BigEndian>()?,
uid: rdr.read_u8()?,
})
}
}
pub struct Transport {
tid: u16,
uid: u8,
stream: TcpStream,
}
impl Transport {
pub fn new(addr: &str) -> io::Result<Transport> {
Self::new_with_cfg(addr, Config::default())
}
pub fn new_with_cfg(addr: &str, cfg: Config) -> io::Result<Transport> {
let stream = match cfg.tcp_connect_timeout {
Some(timeout) => {
let mut socket_addrs = (addr, cfg.tcp_port).to_socket_addrs()?;
TcpStream::connect_timeout(&socket_addrs.next().unwrap(), timeout)
}
None => TcpStream::connect((addr, cfg.tcp_port)),
};
match stream {
Ok(s) => {
s.set_read_timeout(cfg.tcp_read_timeout)?;
s.set_write_timeout(cfg.tcp_write_timeout)?;
s.set_nodelay(true)?;
Ok(Transport {
tid: 0,
uid: cfg.modbus_uid,
stream: s,
})
}
Err(e) => Err(e),
}
}
fn new_tid(&mut self) -> u16 {
self.tid = self.tid.wrapping_add(1);
self.tid
}
fn read(self: &mut Self, fun: &Function) -> Result<Vec<u8>> {
let packed_size = |v: u16| v / 8 + if v % 8 > 0 { 1 } else { 0 };
let (addr, count, expected_bytes) = match *fun {
Function::ReadCoils(a, c) | Function::ReadDiscreteInputs(a, c) => {
(a, c, packed_size(c) as usize)
}
Function::ReadHoldingRegisters(a, c) | Function::ReadInputRegisters(a, c) => {
(a, c, 2 * c as usize)
}
_ => return Err(Error::InvalidFunction),
};
if count < 1 {
return Err(Error::InvalidData(Reason::RecvBufferEmpty));
}
if count as usize > MODBUS_MAX_PACKET_SIZE {
return Err(Error::InvalidData(Reason::UnexpectedReplySize));
}
let header = Header::new(self, MODBUS_HEADER_SIZE as u16 + 6u16);
let mut buff = header.pack()?;
buff.write_u8(fun.code())?;
buff.write_u16::<BigEndian>(addr)?;
buff.write_u16::<BigEndian>(count)?;
match self.stream.write_all(&buff) {
Ok(_s) => {
let mut reply = vec![0; MODBUS_HEADER_SIZE + expected_bytes + 2];
match self.stream.read(&mut reply) {
Ok(_s) => {
let resp_hd = Header::unpack(&reply[..MODBUS_HEADER_SIZE])?;
Transport::validate_response_header(&header, &resp_hd)?;
Transport::validate_response_code(&buff, &reply)?;
Transport::get_reply_data(&reply, expected_bytes)
}
Err(e) => Err(Error::Io(e)),
}
}
Err(e) => Err(Error::Io(e)),
}
}
fn validate_response_header(req: &Header, resp: &Header) -> Result<()> {
if req.tid != resp.tid || resp.pid != MODBUS_PROTOCOL_TCP {
Err(Error::InvalidResponse)
} else {
Ok(())
}
}
fn validate_response_code(req: &[u8], resp: &[u8]) -> Result<()> {
if req[7] + 0x80 == resp[7] {
match ExceptionCode::from_u8(resp[8]) {
Some(code) => Err(Error::Exception(code)),
None => Err(Error::InvalidResponse),
}
} else if req[7] == resp[7] {
Ok(())
} else {
Err(Error::InvalidResponse)
}
}
fn get_reply_data(reply: &[u8], expected_bytes: usize) -> Result<Vec<u8>> {
if reply[8] as usize != expected_bytes
|| reply.len() != MODBUS_HEADER_SIZE + expected_bytes + 2
{
Err(Error::InvalidData(Reason::UnexpectedReplySize))
} else {
let mut d = Vec::new();
d.extend_from_slice(&reply[MODBUS_HEADER_SIZE + 2..]);
Ok(d)
}
}
fn write_single(self: &mut Self, fun: &Function) -> Result<()> {
let (addr, value) = match *fun {
Function::WriteSingleCoil(a, v) | Function::WriteSingleRegister(a, v) => (a, v),
_ => return Err(Error::InvalidFunction),
};
let mut buff = vec![0; MODBUS_HEADER_SIZE];
buff.write_u8(fun.code())?;
buff.write_u16::<BigEndian>(addr)?;
buff.write_u16::<BigEndian>(value)?;
self.write(&mut buff)
}
fn write_multiple(self: &mut Self, fun: &Function) -> Result<()> {
let (addr, quantity, values) = match *fun {
Function::WriteMultipleCoils(a, q, v) | Function::WriteMultipleRegisters(a, q, v) => {
(a, q, v)
}
_ => return Err(Error::InvalidFunction),
};
let mut buff = vec![0; MODBUS_HEADER_SIZE];
buff.write_u8(fun.code())?;
buff.write_u16::<BigEndian>(addr)?;
buff.write_u16::<BigEndian>(quantity)?;
buff.write_u8(values.len() as u8)?;
for v in values {
buff.write_u8(*v)?;
}
self.write(&mut buff)
}
fn write(self: &mut Self, buff: &mut [u8]) -> Result<()> {
if buff.is_empty() {
return Err(Error::InvalidData(Reason::SendBufferEmpty));
}
if buff.len() > MODBUS_MAX_PACKET_SIZE {
return Err(Error::InvalidData(Reason::SendBufferTooBig));
}
let header = Header::new(self, buff.len() as u16 + 1u16);
let head_buff = header.pack()?;
{
let mut start = Cursor::new(buff.borrow_mut());
start.write_all(&head_buff)?;
}
match self.stream.write_all(buff) {
Ok(_s) => {
let reply = &mut [0; 12];
match self.stream.read(reply) {
Ok(_s) => {
let resp_hd = Header::unpack(reply)?;
Transport::validate_response_header(&header, &resp_hd)?;
Transport::validate_response_code(buff, reply)
}
Err(e) => Err(Error::Io(e)),
}
}
Err(e) => Err(Error::Io(e)),
}
}
pub fn close(self: &mut Self) -> Result<()> {
self.stream.shutdown(Shutdown::Both).map_err(Error::Io)
}
}
impl Client for Transport {
fn read_coils(self: &mut Self, addr: u16, count: u16) -> Result<Vec<Coil>> {
let bytes = self.read(&Function::ReadCoils(addr, count))?;
Ok(binary::unpack_bits(&bytes, count))
}
fn read_discrete_inputs(self: &mut Self, addr: u16, count: u16) -> Result<Vec<Coil>> {
let bytes = self.read(&Function::ReadDiscreteInputs(addr, count))?;
Ok(binary::unpack_bits(&bytes, count))
}
fn read_holding_registers(self: &mut Self, addr: u16, count: u16) -> Result<Vec<u16>> {
let bytes = self.read(&Function::ReadHoldingRegisters(addr, count))?;
binary::pack_bytes(&bytes[..])
}
fn read_input_registers(self: &mut Self, addr: u16, count: u16) -> Result<Vec<u16>> {
let bytes = self.read(&Function::ReadInputRegisters(addr, count))?;
binary::pack_bytes(&bytes[..])
}
fn write_single_coil(self: &mut Self, addr: u16, value: Coil) -> Result<()> {
self.write_single(&Function::WriteSingleCoil(addr, value.code()))
}
fn write_single_register(self: &mut Self, addr: u16, value: u16) -> Result<()> {
self.write_single(&Function::WriteSingleRegister(addr, value))
}
fn write_multiple_coils(self: &mut Self, addr: u16, values: &[Coil]) -> Result<()> {
let bytes = binary::pack_bits(values);
self.write_multiple(&Function::WriteMultipleCoils(
addr,
values.len() as u16,
&bytes,
))
}
fn write_multiple_registers(self: &mut Self, addr: u16, values: &[u16]) -> Result<()> {
let bytes = binary::unpack_bytes(values);
self.write_multiple(&Function::WriteMultipleRegisters(
addr,
values.len() as u16,
&bytes,
))
}
fn set_uid(&mut self, uid: u8) {
self.uid = uid;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn serialize_header() {
let header = Header {
tid: 12816,
pid: 3930,
len: 99,
uid: 68,
};
let serialized = header.pack().unwrap();
let deserialized = Header::unpack(&vec![50, 16, 15, 90, 0, 99, 68]).unwrap();
let re_deserialized = Header::unpack(&serialized).unwrap();
assert_eq!(serialized, vec![50, 16, 15, 90, 0, 99, 68]);
assert_eq!(deserialized, header);
assert_eq!(re_deserialized, header);
}
}