| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212 |
- // Copyright 2014 Quoc-Viet Nguyen. All rights reserved.
- // This software may be modified and distributed under the terms
- // of the BSD license. See the LICENSE file for details.
- package modbus
- import (
- "encoding/binary"
- "fmt"
- "io"
- "time"
- )
- const (
- rtuMinSize = 4
- rtuMaxSize = 256
- rtuExceptionSize = 5
- )
- // RTUClientHandler implements Packager and Transporter interface.
- type RTUClientHandler struct {
- rtuPackager
- rtuSerialTransporter
- }
- // NewRTUClientHandler allocates and initializes a RTUClientHandler.
- func NewRTUClientHandler(address string) *RTUClientHandler {
- handler := &RTUClientHandler{}
- handler.Address = address
- handler.Timeout = serialTimeout
- handler.IdleTimeout = serialIdleTimeout
- return handler
- }
- // RTUClient creates RTU client with default handler and given connect string.
- func RTUClient(address string) Client {
- handler := NewRTUClientHandler(address)
- return NewClient(handler)
- }
- // rtuPackager implements Packager interface.
- type rtuPackager struct {
- SlaveId byte
- }
- // Encode encodes PDU in a RTU frame:
- //
- // Slave Address : 1 byte
- // Function : 1 byte
- // Data : 0 up to 252 bytes
- // CRC : 2 byte
- func (mb *rtuPackager) Encode(pdu *ProtocolDataUnit) (adu []byte, err error) {
- length := len(pdu.Data) + 4
- if length > rtuMaxSize {
- err = fmt.Errorf("modbus: length of data '%v' must not be bigger than '%v'", length, rtuMaxSize)
- return
- }
- adu = make([]byte, length)
- adu[0] = mb.SlaveId
- adu[1] = pdu.FunctionCode
- copy(adu[2:], pdu.Data)
- // Append crc
- var crc crc
- crc.reset().pushBytes(adu[0 : length-2])
- checksum := crc.value()
- adu[length-1] = byte(checksum >> 8)
- adu[length-2] = byte(checksum)
- return
- }
- // Verify verifies response length and slave id.
- func (mb *rtuPackager) Verify(aduRequest []byte, aduResponse []byte) (err error) {
- length := len(aduResponse)
- // Minimum size (including address, function and CRC)
- if length < rtuMinSize {
- err = fmt.Errorf("modbus: response length '%v' does not meet minimum '%v'", length, rtuMinSize)
- return
- }
- // Slave address must match
- if aduResponse[0] != aduRequest[0] {
- err = fmt.Errorf("modbus: response slave id '%v' does not match request '%v'", aduResponse[0], aduRequest[0])
- return
- }
- return
- }
- // Decode extracts PDU from RTU frame and verify CRC.
- func (mb *rtuPackager) Decode(adu []byte) (pdu *ProtocolDataUnit, err error) {
- length := len(adu)
- // Calculate checksum
- var crc crc
- crc.reset().pushBytes(adu[0 : length-2])
- checksum := uint16(adu[length-1])<<8 | uint16(adu[length-2])
- if checksum != crc.value() {
- fmt.Println("Decode Err:", adu, ";length=", length)
- err = fmt.Errorf("modbus-rtu: response crc '%v' does not match expected '%v'", checksum, crc.value())
- return
- }
- // Function code & data
- pdu = &ProtocolDataUnit{}
- pdu.FunctionCode = adu[1]
- pdu.Data = adu[2 : length-2]
- return
- }
- // rtuSerialTransporter implements Transporter interface.
- type rtuSerialTransporter struct {
- serialPort
- }
- func (mb *rtuSerialTransporter) Send(aduRequest []byte) (aduResponse []byte, err error) {
- // Make sure port is connected
- if err = mb.serialPort.connect(); err != nil {
- return
- }
- // Start the timer to close when idle
- mb.serialPort.lastActivity = time.Now()
- mb.serialPort.startCloseTimer()
- // Send the request
- mb.serialPort.logf("modbus: sending % x\n", aduRequest)
- if _, err = mb.port.Write(aduRequest); err != nil {
- return
- }
- function := aduRequest[1]
- functionFail := aduRequest[1] & 0x80
- bytesToRead := calculateResponseLength(aduRequest)
- time.Sleep(mb.calculateDelay(len(aduRequest) + bytesToRead))
- var n int
- var n1 int
- var data [rtuMaxSize]byte
- //We first read the minimum length and then read either the full package
- //or the error package, depending on the error status (byte 2 of the response)
- n, err = io.ReadAtLeast(mb.port, data[:], rtuMinSize)
- if err != nil {
- return
- }
- //if the function is correct
- if data[1] == function {
- //we read the rest of the bytes
- if n < bytesToRead {
- if bytesToRead > rtuMinSize && bytesToRead <= rtuMaxSize {
- if bytesToRead > n {
- n1, err = io.ReadFull(mb.port, data[n:bytesToRead])
- n += n1
- }
- }
- }
- } else if data[1] == functionFail {
- //for error we need to read 5 bytes
- if n < rtuExceptionSize {
- n1, err = io.ReadFull(mb.port, data[n:rtuExceptionSize])
- }
- n += n1
- }
- if err != nil {
- return
- }
- aduResponse = data[:n]
- mb.serialPort.logf("modbus: received % x\n", aduResponse)
- return
- }
- // calculateDelay roughly calculates time needed for the next frame.
- // See MODBUS over Serial Line - Specification and Implementation Guide (page 13).
- func (mb *rtuSerialTransporter) calculateDelay(chars int) time.Duration {
- var characterDelay, frameDelay int // us
- if mb.BaudRate <= 0 || mb.BaudRate > 19200 {
- characterDelay = 750
- frameDelay = 1750
- } else {
- characterDelay = 15000000 / mb.BaudRate
- frameDelay = 35000000 / mb.BaudRate
- }
- return time.Duration(characterDelay*chars+frameDelay) * time.Microsecond
- }
- func calculateResponseLength(adu []byte) int {
- length := rtuMinSize
- switch adu[1] {
- case FuncCodeReadDiscreteInputs,
- FuncCodeReadCoils:
- count := int(binary.BigEndian.Uint16(adu[4:]))
- length += 1 + count/8
- if count%8 != 0 {
- length++
- }
- case FuncCodeReadInputRegisters,
- FuncCodeReadHoldingRegisters,
- FuncCodeReadWriteMultipleRegisters:
- count := int(binary.BigEndian.Uint16(adu[4:]))
- length += 1 + count*2
- case FuncCodeWriteSingleCoil,
- FuncCodeWriteMultipleCoils,
- FuncCodeWriteSingleRegister,
- FuncCodeWriteMultipleRegisters:
- length += 4
- case FuncCodeMaskWriteRegister:
- length += 6
- case FuncCodeReadFIFOQueue:
- // undetermined
- default:
- }
- return length
- }
|