From 27f24f26407cf84ba271c0231eeabfafbc53b387 Mon Sep 17 00:00:00 2001 From: Mikhail Kantur Date: Sat, 17 Sep 2022 12:47:42 +1000 Subject: [PATCH 1/2] Added enc client (encapsulated RTU over TCP) --- encclient.go | 232 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 232 insertions(+) create mode 100644 encclient.go diff --git a/encclient.go b/encclient.go new file mode 100644 index 0000000..06b00bf --- /dev/null +++ b/encclient.go @@ -0,0 +1,232 @@ +package modbus + +import ( + "fmt" + "io" + "log" + "net" + "sync" + "time" +) + +const ( + encMaxSize = 256 + encHeaderSize = 3 + encCrcSize = 2 + encTimeout = 10 * time.Second + encIdleTimeout = 60 * time.Second +) + +// region - Enc Client + +// EncClient creates TCP-RTU client with default handler and given connect string. +func EncClient(address string) Client { + handler := NewEncClientHandler(address) + return NewClient(handler) +} + +// endregion + +// region - Enc Client Handler + +type EncClientHandler struct { + rtuPackager + encTransporter +} + +func NewEncClientHandler(address string) *EncClientHandler { + h := &EncClientHandler{} + h.Address = address + h.Timeout = encTimeout + h.IdleTimeout = encIdleTimeout + return h +} + +// endregion + +// region - Enc Transporter + +type encTransporter struct { + + // Connect string + Address string + + // Connect & Read timeout + Timeout time.Duration + + // Idle timeout to close the connection + IdleTimeout time.Duration + + // Transmission logger + Logger *log.Logger + + // TCP connection + mu sync.Mutex + conn net.Conn + closeTimer *time.Timer + lastActivity time.Time +} + +// region = public API + +func (mb *encTransporter) Send(aduRequest []byte) (aduResponse []byte, err error) { + + mb.mu.Lock() + defer mb.mu.Unlock() + + // Establish a new connection if not connected + if err = mb.connect(); err != nil { + return + } + + // Set timer to close when idle + mb.lastActivity = time.Now() + mb.startCloseTimer() + + // Set write and read timeout + var timeout time.Time + if mb.Timeout > 0 { + timeout = mb.lastActivity.Add(mb.Timeout) + } + if err = mb.conn.SetDeadline(timeout); err != nil { + return + } + + // Send data + mb.logf("modbus: sending % x", aduRequest) + if _, err = mb.conn.Write(aduRequest); err != nil { + mb.logf("modbus: could not send request: %s", err) + return + } + + //time.Sleep(mb.calculateDelay(len(aduRequest) + bytesToRead)) + //time.Sleep(1000) + + var data [encMaxSize]byte + + // read header + if _, err = io.ReadFull(mb.conn, data[:encHeaderSize]); err != nil { + return + } + mb.logf("modbus: header % x", data[:encHeaderSize]) + + length := data[2] + if length <= 0 { + mb.flush(data[:]) + err = fmt.Errorf("modbus: length in response header '%v' must not be zero", length) + return + } + if length > (encMaxSize - encHeaderSize) { + mb.flush(data[:]) + err = fmt.Errorf("modbus: length in response header '%v' must not greater than '%v'", length, encMaxSize-encHeaderSize) + return + } + + // read data + length += encHeaderSize + if _, err = io.ReadFull(mb.conn, data[encHeaderSize:length]); err != nil { + return + } + mb.logf("modbus: data % x", data[encHeaderSize:length]) + + //read CRC + if _, err = io.ReadFull(mb.conn, data[length:length+encCrcSize]); err != nil { + return + } + mb.logf("modbus: crc % x", data[length:length+encCrcSize]) + + aduResponse = data[:length+encCrcSize] + mb.logf("modbus: received % x\n", aduResponse) + return + +} + +// Connect establishes a new connection to the address in Address. +// Connect and Close are exported so that multiple requests can be done with one session +func (mb *encTransporter) Connect() error { + mb.mu.Lock() + defer mb.mu.Unlock() + return mb.connect() +} + +// Close closes current connection. +func (mb *encTransporter) Close() error { + mb.mu.Lock() + defer mb.mu.Unlock() + return mb.close() +} + +// endregion + +// region = helper methods + +func (mb *encTransporter) connect() error { + if mb.conn == nil { + dialer := net.Dialer{Timeout: mb.Timeout} + conn, err := dialer.Dial("tcp", mb.Address) + if err != nil { + return err + } + mb.conn = conn + } + return nil +} +func (mb *encTransporter) startCloseTimer() { + if mb.IdleTimeout <= 0 { + return + } + if mb.closeTimer == nil { + mb.closeTimer = time.AfterFunc(mb.IdleTimeout, mb.closeIdle) + } else { + mb.closeTimer.Reset(mb.IdleTimeout) + } +} + +// flush flushes pending data in the connection, +// returns io.EOF if connection is closed. +func (mb *encTransporter) flush(b []byte) (err error) { + if err = mb.conn.SetReadDeadline(time.Now()); err != nil { + return + } + // Timeout setting will be reset when reading + if _, err = mb.conn.Read(b); err != nil { + // Ignore timeout error + if netError, ok := err.(net.Error); ok && netError.Timeout() { + err = nil + } + } + return +} + +func (mb *encTransporter) logf(format string, v ...interface{}) { + if mb.Logger != nil { + mb.Logger.Printf(format, v...) + } +} + +// closeLocked closes current connection. Caller must hold the mutex before calling this method. +func (mb *encTransporter) close() (err error) { + if mb.conn != nil { + err = mb.conn.Close() + mb.conn = nil + } + return +} + +// closeIdle closes the connection if last activity is passed behind IdleTimeout. +func (mb *encTransporter) closeIdle() { + mb.mu.Lock() + defer mb.mu.Unlock() + if mb.IdleTimeout <= 0 { + return + } + idle := time.Now().Sub(mb.lastActivity) + if idle >= mb.IdleTimeout { + mb.logf("modbus: closing connection due to idle timeout: %v", idle) + mb.close() + } +} + +// endregion + +// endregion From fdba5fe8a96571d78e86c8be94fe104142d3868c Mon Sep 17 00:00:00 2001 From: Mikhail Kantur Date: Sat, 17 Sep 2022 21:35:00 +1000 Subject: [PATCH 2/2] added support for multiple slaves --- api.go | 22 ++-- asciiclient.go | 19 ++-- asciiclient_test.go | 9 +- client.go | 239 +++++++++++++++++++++++++------------------- encclient.go | 6 +- modbus.go | 7 +- rtuclient.go | 14 +-- rtuclient_test.go | 13 +-- tcpclient.go | 28 +++--- tcpclient_test.go | 13 +-- 10 files changed, 200 insertions(+), 170 deletions(-) diff --git a/api.go b/api.go index fe06ab0..78b859f 100644 --- a/api.go +++ b/api.go @@ -9,41 +9,41 @@ type Client interface { // ReadCoils reads from 1 to 2000 contiguous status of coils in a // remote device and returns coil status. - ReadCoils(address, quantity uint16) (results []byte, err error) + ReadCoils(slaveId uint8, address, quantity uint16) (results []byte, err error) // ReadDiscreteInputs reads from 1 to 2000 contiguous status of // discrete inputs in a remote device and returns input status. - ReadDiscreteInputs(address, quantity uint16) (results []byte, err error) + ReadDiscreteInputs(slaveId uint8, address, quantity uint16) (results []byte, err error) // WriteSingleCoil write a single output to either ON or OFF in a // remote device and returns output value. - WriteSingleCoil(address, value uint16) (results []byte, err error) + WriteSingleCoil(slaveId uint8, address, value uint16) (results []byte, err error) // WriteMultipleCoils forces each coil in a sequence of coils to either // ON or OFF in a remote device and returns quantity of outputs. - WriteMultipleCoils(address, quantity uint16, value []byte) (results []byte, err error) + WriteMultipleCoils(slaveId uint8, address, quantity uint16, value []byte) (results []byte, err error) // 16-bit access // ReadInputRegisters reads from 1 to 125 contiguous input registers in // a remote device and returns input registers. - ReadInputRegisters(address, quantity uint16) (results []byte, err error) + ReadInputRegisters(slaveId uint8, address, quantity uint16) (results []byte, err error) // ReadHoldingRegisters reads the contents of a contiguous block of // holding registers in a remote device and returns register value. - ReadHoldingRegisters(address, quantity uint16) (results []byte, err error) + ReadHoldingRegisters(slaveId uint8, address, quantity uint16) (results []byte, err error) // WriteSingleRegister writes a single holding register in a remote // device and returns register value. - WriteSingleRegister(address, value uint16) (results []byte, err error) + WriteSingleRegister(slaveId uint8, address, value uint16) (results []byte, err error) // WriteMultipleRegisters writes a block of contiguous registers // (1 to 123 registers) in a remote device and returns quantity of // registers. - WriteMultipleRegisters(address, quantity uint16, value []byte) (results []byte, err error) + WriteMultipleRegisters(slaveId uint8, address, quantity uint16, value []byte) (results []byte, err error) // ReadWriteMultipleRegisters performs a combination of one read // operation and one write operation. It returns read registers value. - ReadWriteMultipleRegisters(readAddress, readQuantity, writeAddress, writeQuantity uint16, value []byte) (results []byte, err error) + ReadWriteMultipleRegisters(slaveId uint8, readAddress, readQuantity, writeAddress, writeQuantity uint16, value []byte) (results []byte, err error) // MaskWriteRegister modify the contents of a specified holding // register using a combination of an AND mask, an OR mask, and the // register's current contents. The function returns // AND-mask and OR-mask. - MaskWriteRegister(address, andMask, orMask uint16) (results []byte, err error) + MaskWriteRegister(slaveId uint8, address, andMask, orMask uint16) (results []byte, err error) //ReadFIFOQueue reads the contents of a First-In-First-Out (FIFO) queue // of register in a remote device and returns FIFO value register. - ReadFIFOQueue(address uint16) (results []byte, err error) + ReadFIFOQueue(slaveId uint8, address uint16) (results []byte, err error) } diff --git a/asciiclient.go b/asciiclient.go index 98d9c60..a95d24b 100644 --- a/asciiclient.go +++ b/asciiclient.go @@ -47,19 +47,20 @@ type asciiPackager struct { } // Encode encodes PDU in a ASCII frame: -// Start : 1 char -// Address : 2 chars -// Function : 2 chars -// Data : 0 up to 2x252 chars -// LRC : 2 chars -// End : 2 chars -func (mb *asciiPackager) Encode(pdu *ProtocolDataUnit) (adu []byte, err error) { +// +// Start : 1 char +// Address : 2 chars +// Function : 2 chars +// Data : 0 up to 2x252 chars +// LRC : 2 chars +// End : 2 chars +func (mb *asciiPackager) Encode(slaveId uint8, pdu *ProtocolDataUnit) (adu []byte, err error) { var buf bytes.Buffer if _, err = buf.WriteString(asciiStart); err != nil { return } - if err = writeHex(&buf, []byte{mb.SlaveId, pdu.FunctionCode}); err != nil { + if err = writeHex(&buf, []byte{slaveId, pdu.FunctionCode}); err != nil { return } if err = writeHex(&buf, pdu.Data); err != nil { @@ -68,7 +69,7 @@ func (mb *asciiPackager) Encode(pdu *ProtocolDataUnit) (adu []byte, err error) { // Exclude the beginning colon and terminating CRLF pair characters var lrc lrc lrc.reset() - lrc.pushByte(mb.SlaveId).pushByte(pdu.FunctionCode).pushBytes(pdu.Data) + lrc.pushByte(slaveId).pushByte(pdu.FunctionCode).pushBytes(pdu.Data) if err = writeHex(&buf, []byte{lrc.value()}); err != nil { return } diff --git a/asciiclient_test.go b/asciiclient_test.go index db48bac..1a6c0fd 100644 --- a/asciiclient_test.go +++ b/asciiclient_test.go @@ -11,13 +11,12 @@ import ( func TestASCIIEncoding(t *testing.T) { encoder := asciiPackager{} - encoder.SlaveId = 17 pdu := ProtocolDataUnit{} pdu.FunctionCode = 3 pdu.Data = []byte{0, 107, 0, 3} - adu, err := encoder.Encode(&pdu) + adu, err := encoder.Encode(17, &pdu) if err != nil { t.Fatal(err) } @@ -47,15 +46,13 @@ func TestASCIIDecoding(t *testing.T) { } func BenchmarkASCIIEncoder(b *testing.B) { - encoder := asciiPackager{ - SlaveId: 10, - } + encoder := asciiPackager{} pdu := ProtocolDataUnit{ FunctionCode: 1, Data: []byte{2, 3, 4, 5, 6, 7, 8, 9}, } for i := 0; i < b.N; i++ { - _, err := encoder.Encode(&pdu) + _, err := encoder.Encode(10, &pdu) if err != nil { b.Fatal(err) } diff --git a/client.go b/client.go index ac3ee2e..d4fc6b5 100644 --- a/client.go +++ b/client.go @@ -31,14 +31,17 @@ func NewClient2(packager Packager, transporter Transporter) Client { } // Request: -// Function code : 1 byte (0x01) -// Starting address : 2 bytes -// Quantity of coils : 2 bytes +// +// Function code : 1 byte (0x01) +// Starting address : 2 bytes +// Quantity of coils : 2 bytes +// // Response: -// Function code : 1 byte (0x01) -// Byte count : 1 byte -// Coil status : N* bytes (=N or N+1) -func (mb *client) ReadCoils(address, quantity uint16) (results []byte, err error) { +// +// Function code : 1 byte (0x01) +// Byte count : 1 byte +// Coil status : N* bytes (=N or N+1) +func (mb *client) ReadCoils(slaveId uint8, address, quantity uint16) (results []byte, err error) { if quantity < 1 || quantity > 2000 { err = fmt.Errorf("modbus: quantity '%v' must be between '%v' and '%v',", quantity, 1, 2000) return @@ -47,7 +50,7 @@ func (mb *client) ReadCoils(address, quantity uint16) (results []byte, err error FunctionCode: FuncCodeReadCoils, Data: dataBlock(address, quantity), } - response, err := mb.send(&request) + response, err := mb.send(slaveId, &request) if err != nil { return } @@ -62,14 +65,17 @@ func (mb *client) ReadCoils(address, quantity uint16) (results []byte, err error } // Request: -// Function code : 1 byte (0x02) -// Starting address : 2 bytes -// Quantity of inputs : 2 bytes +// +// Function code : 1 byte (0x02) +// Starting address : 2 bytes +// Quantity of inputs : 2 bytes +// // Response: -// Function code : 1 byte (0x02) -// Byte count : 1 byte -// Input status : N* bytes (=N or N+1) -func (mb *client) ReadDiscreteInputs(address, quantity uint16) (results []byte, err error) { +// +// Function code : 1 byte (0x02) +// Byte count : 1 byte +// Input status : N* bytes (=N or N+1) +func (mb *client) ReadDiscreteInputs(slaveId uint8, address, quantity uint16) (results []byte, err error) { if quantity < 1 || quantity > 2000 { err = fmt.Errorf("modbus: quantity '%v' must be between '%v' and '%v',", quantity, 1, 2000) return @@ -78,7 +84,7 @@ func (mb *client) ReadDiscreteInputs(address, quantity uint16) (results []byte, FunctionCode: FuncCodeReadDiscreteInputs, Data: dataBlock(address, quantity), } - response, err := mb.send(&request) + response, err := mb.send(slaveId, &request) if err != nil { return } @@ -93,14 +99,17 @@ func (mb *client) ReadDiscreteInputs(address, quantity uint16) (results []byte, } // Request: -// Function code : 1 byte (0x03) -// Starting address : 2 bytes -// Quantity of registers : 2 bytes +// +// Function code : 1 byte (0x03) +// Starting address : 2 bytes +// Quantity of registers : 2 bytes +// // Response: -// Function code : 1 byte (0x03) -// Byte count : 1 byte -// Register value : Nx2 bytes -func (mb *client) ReadHoldingRegisters(address, quantity uint16) (results []byte, err error) { +// +// Function code : 1 byte (0x03) +// Byte count : 1 byte +// Register value : Nx2 bytes +func (mb *client) ReadHoldingRegisters(slaveId uint8, address, quantity uint16) (results []byte, err error) { if quantity < 1 || quantity > 125 { err = fmt.Errorf("modbus: quantity '%v' must be between '%v' and '%v',", quantity, 1, 125) return @@ -109,7 +118,7 @@ func (mb *client) ReadHoldingRegisters(address, quantity uint16) (results []byte FunctionCode: FuncCodeReadHoldingRegisters, Data: dataBlock(address, quantity), } - response, err := mb.send(&request) + response, err := mb.send(slaveId, &request) if err != nil { return } @@ -124,14 +133,17 @@ func (mb *client) ReadHoldingRegisters(address, quantity uint16) (results []byte } // Request: -// Function code : 1 byte (0x04) -// Starting address : 2 bytes -// Quantity of registers : 2 bytes +// +// Function code : 1 byte (0x04) +// Starting address : 2 bytes +// Quantity of registers : 2 bytes +// // Response: -// Function code : 1 byte (0x04) -// Byte count : 1 byte -// Input registers : N bytes -func (mb *client) ReadInputRegisters(address, quantity uint16) (results []byte, err error) { +// +// Function code : 1 byte (0x04) +// Byte count : 1 byte +// Input registers : N bytes +func (mb *client) ReadInputRegisters(slaveId uint8, address, quantity uint16) (results []byte, err error) { if quantity < 1 || quantity > 125 { err = fmt.Errorf("modbus: quantity '%v' must be between '%v' and '%v',", quantity, 1, 125) return @@ -140,7 +152,7 @@ func (mb *client) ReadInputRegisters(address, quantity uint16) (results []byte, FunctionCode: FuncCodeReadInputRegisters, Data: dataBlock(address, quantity), } - response, err := mb.send(&request) + response, err := mb.send(slaveId, &request) if err != nil { return } @@ -155,14 +167,17 @@ func (mb *client) ReadInputRegisters(address, quantity uint16) (results []byte, } // Request: -// Function code : 1 byte (0x05) -// Output address : 2 bytes -// Output value : 2 bytes +// +// Function code : 1 byte (0x05) +// Output address : 2 bytes +// Output value : 2 bytes +// // Response: -// Function code : 1 byte (0x05) -// Output address : 2 bytes -// Output value : 2 bytes -func (mb *client) WriteSingleCoil(address, value uint16) (results []byte, err error) { +// +// Function code : 1 byte (0x05) +// Output address : 2 bytes +// Output value : 2 bytes +func (mb *client) WriteSingleCoil(slaveId uint8, address, value uint16) (results []byte, err error) { // The requested ON/OFF state can only be 0xFF00 and 0x0000 if value != 0xFF00 && value != 0x0000 { err = fmt.Errorf("modbus: state '%v' must be either 0xFF00 (ON) or 0x0000 (OFF)", value) @@ -172,7 +187,7 @@ func (mb *client) WriteSingleCoil(address, value uint16) (results []byte, err er FunctionCode: FuncCodeWriteSingleCoil, Data: dataBlock(address, value), } - response, err := mb.send(&request) + response, err := mb.send(slaveId, &request) if err != nil { return } @@ -196,19 +211,22 @@ func (mb *client) WriteSingleCoil(address, value uint16) (results []byte, err er } // Request: -// Function code : 1 byte (0x06) -// Register address : 2 bytes -// Register value : 2 bytes +// +// Function code : 1 byte (0x06) +// Register address : 2 bytes +// Register value : 2 bytes +// // Response: -// Function code : 1 byte (0x06) -// Register address : 2 bytes -// Register value : 2 bytes -func (mb *client) WriteSingleRegister(address, value uint16) (results []byte, err error) { +// +// Function code : 1 byte (0x06) +// Register address : 2 bytes +// Register value : 2 bytes +func (mb *client) WriteSingleRegister(slaveId uint8, address, value uint16) (results []byte, err error) { request := ProtocolDataUnit{ FunctionCode: FuncCodeWriteSingleRegister, Data: dataBlock(address, value), } - response, err := mb.send(&request) + response, err := mb.send(slaveId, &request) if err != nil { return } @@ -232,16 +250,19 @@ func (mb *client) WriteSingleRegister(address, value uint16) (results []byte, er } // Request: -// Function code : 1 byte (0x0F) -// Starting address : 2 bytes -// Quantity of outputs : 2 bytes -// Byte count : 1 byte -// Outputs value : N* bytes +// +// Function code : 1 byte (0x0F) +// Starting address : 2 bytes +// Quantity of outputs : 2 bytes +// Byte count : 1 byte +// Outputs value : N* bytes +// // Response: -// Function code : 1 byte (0x0F) -// Starting address : 2 bytes -// Quantity of outputs : 2 bytes -func (mb *client) WriteMultipleCoils(address, quantity uint16, value []byte) (results []byte, err error) { +// +// Function code : 1 byte (0x0F) +// Starting address : 2 bytes +// Quantity of outputs : 2 bytes +func (mb *client) WriteMultipleCoils(slaveId uint8, address, quantity uint16, value []byte) (results []byte, err error) { if quantity < 1 || quantity > 1968 { err = fmt.Errorf("modbus: quantity '%v' must be between '%v' and '%v',", quantity, 1, 1968) return @@ -250,7 +271,7 @@ func (mb *client) WriteMultipleCoils(address, quantity uint16, value []byte) (re FunctionCode: FuncCodeWriteMultipleCoils, Data: dataBlockSuffix(value, address, quantity), } - response, err := mb.send(&request) + response, err := mb.send(slaveId, &request) if err != nil { return } @@ -274,16 +295,19 @@ func (mb *client) WriteMultipleCoils(address, quantity uint16, value []byte) (re } // Request: -// Function code : 1 byte (0x10) -// Starting address : 2 bytes -// Quantity of outputs : 2 bytes -// Byte count : 1 byte -// Registers value : N* bytes +// +// Function code : 1 byte (0x10) +// Starting address : 2 bytes +// Quantity of outputs : 2 bytes +// Byte count : 1 byte +// Registers value : N* bytes +// // Response: -// Function code : 1 byte (0x10) -// Starting address : 2 bytes -// Quantity of registers : 2 bytes -func (mb *client) WriteMultipleRegisters(address, quantity uint16, value []byte) (results []byte, err error) { +// +// Function code : 1 byte (0x10) +// Starting address : 2 bytes +// Quantity of registers : 2 bytes +func (mb *client) WriteMultipleRegisters(slaveId uint8, address, quantity uint16, value []byte) (results []byte, err error) { if quantity < 1 || quantity > 123 { err = fmt.Errorf("modbus: quantity '%v' must be between '%v' and '%v',", quantity, 1, 123) return @@ -292,7 +316,7 @@ func (mb *client) WriteMultipleRegisters(address, quantity uint16, value []byte) FunctionCode: FuncCodeWriteMultipleRegisters, Data: dataBlockSuffix(value, address, quantity), } - response, err := mb.send(&request) + response, err := mb.send(slaveId, &request) if err != nil { return } @@ -316,21 +340,24 @@ func (mb *client) WriteMultipleRegisters(address, quantity uint16, value []byte) } // Request: -// Function code : 1 byte (0x16) -// Reference address : 2 bytes -// AND-mask : 2 bytes -// OR-mask : 2 bytes +// +// Function code : 1 byte (0x16) +// Reference address : 2 bytes +// AND-mask : 2 bytes +// OR-mask : 2 bytes +// // Response: -// Function code : 1 byte (0x16) -// Reference address : 2 bytes -// AND-mask : 2 bytes -// OR-mask : 2 bytes -func (mb *client) MaskWriteRegister(address, andMask, orMask uint16) (results []byte, err error) { +// +// Function code : 1 byte (0x16) +// Reference address : 2 bytes +// AND-mask : 2 bytes +// OR-mask : 2 bytes +func (mb *client) MaskWriteRegister(slaveId uint8, address, andMask, orMask uint16) (results []byte, err error) { request := ProtocolDataUnit{ FunctionCode: FuncCodeMaskWriteRegister, Data: dataBlock(address, andMask, orMask), } - response, err := mb.send(&request) + response, err := mb.send(slaveId, &request) if err != nil { return } @@ -359,18 +386,21 @@ func (mb *client) MaskWriteRegister(address, andMask, orMask uint16) (results [] } // Request: -// Function code : 1 byte (0x17) -// Read starting address : 2 bytes -// Quantity to read : 2 bytes -// Write starting address: 2 bytes -// Quantity to write : 2 bytes -// Write byte count : 1 byte -// Write registers value : N* bytes +// +// Function code : 1 byte (0x17) +// Read starting address : 2 bytes +// Quantity to read : 2 bytes +// Write starting address: 2 bytes +// Quantity to write : 2 bytes +// Write byte count : 1 byte +// Write registers value : N* bytes +// // Response: -// Function code : 1 byte (0x17) -// Byte count : 1 byte -// Read registers value : Nx2 bytes -func (mb *client) ReadWriteMultipleRegisters(readAddress, readQuantity, writeAddress, writeQuantity uint16, value []byte) (results []byte, err error) { +// +// Function code : 1 byte (0x17) +// Byte count : 1 byte +// Read registers value : Nx2 bytes +func (mb *client) ReadWriteMultipleRegisters(slaveId uint8, readAddress, readQuantity, writeAddress, writeQuantity uint16, value []byte) (results []byte, err error) { if readQuantity < 1 || readQuantity > 125 { err = fmt.Errorf("modbus: quantity to read '%v' must be between '%v' and '%v',", readQuantity, 1, 125) return @@ -383,7 +413,7 @@ func (mb *client) ReadWriteMultipleRegisters(readAddress, readQuantity, writeAdd FunctionCode: FuncCodeReadWriteMultipleRegisters, Data: dataBlockSuffix(value, readAddress, readQuantity, writeAddress, writeQuantity), } - response, err := mb.send(&request) + response, err := mb.send(slaveId, &request) if err != nil { return } @@ -397,20 +427,23 @@ func (mb *client) ReadWriteMultipleRegisters(readAddress, readQuantity, writeAdd } // Request: -// Function code : 1 byte (0x18) -// FIFO pointer address : 2 bytes +// +// Function code : 1 byte (0x18) +// FIFO pointer address : 2 bytes +// // Response: -// Function code : 1 byte (0x18) -// Byte count : 2 bytes -// FIFO count : 2 bytes -// FIFO count : 2 bytes (<=31) -// FIFO value register : Nx2 bytes -func (mb *client) ReadFIFOQueue(address uint16) (results []byte, err error) { +// +// Function code : 1 byte (0x18) +// Byte count : 2 bytes +// FIFO count : 2 bytes +// FIFO count : 2 bytes (<=31) +// FIFO value register : Nx2 bytes +func (mb *client) ReadFIFOQueue(slaveId uint8, address uint16) (results []byte, err error) { request := ProtocolDataUnit{ FunctionCode: FuncCodeReadFIFOQueue, Data: dataBlock(address), } - response, err := mb.send(&request) + response, err := mb.send(slaveId, &request) if err != nil { return } @@ -435,8 +468,12 @@ func (mb *client) ReadFIFOQueue(address uint16) (results []byte, err error) { // Helpers // send sends request and checks possible exception in the response. -func (mb *client) send(request *ProtocolDataUnit) (response *ProtocolDataUnit, err error) { - aduRequest, err := mb.packager.Encode(request) +func (mb *client) send(slaveId uint8, request *ProtocolDataUnit) (response *ProtocolDataUnit, err error) { + if slaveId > SlaveIdMax /*|| slaveId < SlaveIdMin*/ { + err = fmt.Errorf("modbus: slaveId should be between '%v' and '%v'", SlaveIdMin, SlaveIdMax) + return + } + aduRequest, err := mb.packager.Encode(slaveId, request) if err != nil { return } diff --git a/encclient.go b/encclient.go index 06b00bf..d57a79e 100644 --- a/encclient.go +++ b/encclient.go @@ -108,7 +108,7 @@ func (mb *encTransporter) Send(aduRequest []byte) (aduResponse []byte, err error if _, err = io.ReadFull(mb.conn, data[:encHeaderSize]); err != nil { return } - mb.logf("modbus: header % x", data[:encHeaderSize]) + //mb.logf("modbus: header % x", data[:encHeaderSize]) length := data[2] if length <= 0 { @@ -127,13 +127,13 @@ func (mb *encTransporter) Send(aduRequest []byte) (aduResponse []byte, err error if _, err = io.ReadFull(mb.conn, data[encHeaderSize:length]); err != nil { return } - mb.logf("modbus: data % x", data[encHeaderSize:length]) + //mb.logf("modbus: data % x", data[encHeaderSize:length]) //read CRC if _, err = io.ReadFull(mb.conn, data[length:length+encCrcSize]); err != nil { return } - mb.logf("modbus: crc % x", data[length:length+encCrcSize]) + //mb.logf("modbus: crc % x", data[length:length+encCrcSize]) aduResponse = data[:length+encCrcSize] mb.logf("modbus: received % x\n", aduResponse) diff --git a/modbus.go b/modbus.go index 869473c..43b2f2d 100644 --- a/modbus.go +++ b/modbus.go @@ -40,6 +40,11 @@ const ( ExceptionCodeGatewayTargetDeviceFailedToRespond = 11 ) +const ( + SlaveIdMin = 1 + SlaveIdMax = 247 +) + // ModbusError implements error interface. type ModbusError struct { FunctionCode byte @@ -82,7 +87,7 @@ type ProtocolDataUnit struct { // Packager specifies the communication layer. type Packager interface { - Encode(pdu *ProtocolDataUnit) (adu []byte, err error) + Encode(slaveId uint8, pdu *ProtocolDataUnit) (adu []byte, err error) Decode(adu []byte) (pdu *ProtocolDataUnit, err error) Verify(aduRequest []byte, aduResponse []byte) (err error) } diff --git a/rtuclient.go b/rtuclient.go index ad6a31a..252f121 100644 --- a/rtuclient.go +++ b/rtuclient.go @@ -41,15 +41,15 @@ func RTUClient(address string) Client { // 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) { +// +// Slave Address : 1 byte +// Function : 1 byte +// Data : 0 up to 252 bytes +// CRC : 2 byte +func (mb *rtuPackager) Encode(slaveId uint8, 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) @@ -57,7 +57,7 @@ func (mb *rtuPackager) Encode(pdu *ProtocolDataUnit) (adu []byte, err error) { } adu = make([]byte, length) - adu[0] = mb.SlaveId + adu[0] = slaveId adu[1] = pdu.FunctionCode copy(adu[2:], pdu.Data) diff --git a/rtuclient_test.go b/rtuclient_test.go index 5a0bcc2..c60a338 100644 --- a/rtuclient_test.go +++ b/rtuclient_test.go @@ -11,13 +11,12 @@ import ( func TestRTUEncoding(t *testing.T) { encoder := rtuPackager{} - encoder.SlaveId = 0x01 pdu := ProtocolDataUnit{} pdu.FunctionCode = 0x03 pdu.Data = []byte{0x50, 0x00, 0x00, 0x18} - adu, err := encoder.Encode(&pdu) + adu, err := encoder.Encode(0x01, &pdu) if err != nil { t.Fatal(err) } @@ -69,15 +68,13 @@ func TestCalculateResponseLength(t *testing.T) { } func BenchmarkRTUEncoder(b *testing.B) { - encoder := rtuPackager{ - SlaveId: 10, - } + encoder := rtuPackager{} pdu := ProtocolDataUnit{ FunctionCode: 1, Data: []byte{2, 3, 4, 5, 6, 7, 8, 9}, } for i := 0; i < b.N; i++ { - _, err := encoder.Encode(&pdu) + _, err := encoder.Encode(10, &pdu) if err != nil { b.Fatal(err) } @@ -85,9 +82,7 @@ func BenchmarkRTUEncoder(b *testing.B) { } func BenchmarkRTUDecoder(b *testing.B) { - decoder := rtuPackager{ - SlaveId: 10, - } + decoder := rtuPackager{} adu := []byte{0x01, 0x10, 0x8A, 0x00, 0x00, 0x03, 0xAA, 0x10} for i := 0; i < b.N; i++ { _, err := decoder.Decode(adu) diff --git a/tcpclient.go b/tcpclient.go index 4e53c73..6449a4d 100644 --- a/tcpclient.go +++ b/tcpclient.go @@ -51,18 +51,17 @@ func TCPClient(address string) Client { type tcpPackager struct { // For synchronization between messages of server & client transactionId uint32 - // Broadcast address is 0 - SlaveId byte } // Encode adds modbus application protocol header: -// Transaction identifier: 2 bytes -// Protocol identifier: 2 bytes -// Length: 2 bytes -// Unit identifier: 1 byte -// Function code: 1 byte -// Data: n bytes -func (mb *tcpPackager) Encode(pdu *ProtocolDataUnit) (adu []byte, err error) { +// +// Transaction identifier: 2 bytes +// Protocol identifier: 2 bytes +// Length: 2 bytes +// Unit identifier: 1 byte +// Function code: 1 byte +// Data: n bytes +func (mb *tcpPackager) Encode(slaveId uint8, pdu *ProtocolDataUnit) (adu []byte, err error) { adu = make([]byte, tcpHeaderSize+1+len(pdu.Data)) // Transaction identifier @@ -74,7 +73,7 @@ func (mb *tcpPackager) Encode(pdu *ProtocolDataUnit) (adu []byte, err error) { length := uint16(1 + 1 + len(pdu.Data)) binary.BigEndian.PutUint16(adu[4:], length) // Unit identifier - adu[6] = mb.SlaveId + adu[6] = slaveId // PDU adu[tcpHeaderSize] = pdu.FunctionCode @@ -107,10 +106,11 @@ func (mb *tcpPackager) Verify(aduRequest []byte, aduResponse []byte) (err error) } // Decode extracts PDU from TCP frame: -// Transaction identifier: 2 bytes -// Protocol identifier: 2 bytes -// Length: 2 bytes -// Unit identifier: 1 byte +// +// Transaction identifier: 2 bytes +// Protocol identifier: 2 bytes +// Length: 2 bytes +// Unit identifier: 1 byte func (mb *tcpPackager) Decode(adu []byte) (pdu *ProtocolDataUnit, err error) { // Read length value in the header length := binary.BigEndian.Uint16(adu[4:]) diff --git a/tcpclient_test.go b/tcpclient_test.go index 465a8f6..daaafde 100644 --- a/tcpclient_test.go +++ b/tcpclient_test.go @@ -18,7 +18,7 @@ func TestTCPEncoding(t *testing.T) { pdu.FunctionCode = 3 pdu.Data = []byte{0, 4, 0, 3} - adu, err := packager.Encode(&pdu) + adu, err := packager.Encode(0, &pdu) if err != nil { t.Fatal(err) } @@ -32,7 +32,6 @@ func TestTCPEncoding(t *testing.T) { func TestTCPDecoding(t *testing.T) { packager := tcpPackager{} packager.transactionId = 1 - packager.SlaveId = 17 adu := []byte{0, 1, 0, 0, 0, 6, 17, 3, 0, 120, 0, 3} pdu, err := packager.Decode(adu) @@ -89,15 +88,13 @@ func TestTCPTransporter(t *testing.T) { } func BenchmarkTCPEncoder(b *testing.B) { - encoder := tcpPackager{ - SlaveId: 10, - } + encoder := tcpPackager{} pdu := ProtocolDataUnit{ FunctionCode: 1, Data: []byte{2, 3, 4, 5, 6, 7, 8, 9}, } for i := 0; i < b.N; i++ { - _, err := encoder.Encode(&pdu) + _, err := encoder.Encode(10, &pdu) if err != nil { b.Fatal(err) } @@ -105,9 +102,7 @@ func BenchmarkTCPEncoder(b *testing.B) { } func BenchmarkTCPDecoder(b *testing.B) { - decoder := tcpPackager{ - SlaveId: 10, - } + decoder := tcpPackager{} adu := []byte{0, 1, 0, 0, 0, 6, 17, 3, 0, 120, 0, 3} for i := 0; i < b.N; i++ { _, err := decoder.Decode(adu)