diff --git a/api.go b/api.go index fe06ab0..68ac88c 100644 --- a/api.go +++ b/api.go @@ -35,6 +35,9 @@ type Client interface { // (1 to 123 registers) in a remote device and returns quantity of // registers. WriteMultipleRegisters(address, quantity uint16, value []byte) (results []byte, err error) + // WriteFileRecord writes a block of a Record (1 to 251 registers) + // in a remote device and returns quantity of registers written. + WriteFileRecord(address, quantity uint16, SubReqSize uint16, RecUpdateFileNum []byte, 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) diff --git a/client.go b/client.go index ac3ee2e..c0d3606 100644 --- a/client.go +++ b/client.go @@ -31,13 +31,16 @@ 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) +// +// 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) { if quantity < 1 || quantity > 2000 { err = fmt.Errorf("modbus: quantity '%v' must be between '%v' and '%v',", quantity, 1, 2000) @@ -57,18 +60,25 @@ func (mb *client) ReadCoils(address, quantity uint16) (results []byte, err error err = fmt.Errorf("modbus: response data size '%v' does not match count '%v'", length, count) return } + if count != int(quantity) { + err = fmt.Errorf("modbus: response count '%v' does not match request quantity '%v'", count, quantity) + return + } results = response.Data[1:] return } // 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) +// +// 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) { if quantity < 1 || quantity > 2000 { err = fmt.Errorf("modbus: quantity '%v' must be between '%v' and '%v',", quantity, 1, 2000) @@ -88,18 +98,25 @@ func (mb *client) ReadDiscreteInputs(address, quantity uint16) (results []byte, err = fmt.Errorf("modbus: response data size '%v' does not match count '%v'", length, count) return } + if count != int(quantity) { + err = fmt.Errorf("modbus: response count '%v' does not match request quantity '%v'", count, quantity) + return + } results = response.Data[1:] return } // 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 +// +// Function code : 1 byte (0x03) +// Byte count : 1 byte +// Register value : Nx2 bytes func (mb *client) ReadHoldingRegisters(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) @@ -119,18 +136,25 @@ func (mb *client) ReadHoldingRegisters(address, quantity uint16) (results []byte err = fmt.Errorf("modbus: response data size '%v' does not match count '%v'", length, count) return } + if count != int(quantity)*2 { + err = fmt.Errorf("modbus: response count '%v' does not match request quantity '%v'", count, quantity) + return + } results = response.Data[1:] return } // 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 +// +// Function code : 1 byte (0x04) +// Byte count : 1 byte +// Input registers : N bytes func (mb *client) ReadInputRegisters(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) @@ -150,18 +174,25 @@ func (mb *client) ReadInputRegisters(address, quantity uint16) (results []byte, err = fmt.Errorf("modbus: response data size '%v' does not match count '%v'", length, count) return } + if count != int(quantity)*2 { + err = fmt.Errorf("modbus: response count '%v' does not match request quantity '%v'", count, quantity) + return + } results = response.Data[1:] return } // 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 +// +// Function code : 1 byte (0x05) +// Output address : 2 bytes +// Output value : 2 bytes func (mb *client) WriteSingleCoil(address, value uint16) (results []byte, err error) { // The requested ON/OFF state can only be 0xFF00 and 0x0000 if value != 0xFF00 && value != 0x0000 { @@ -196,13 +227,16 @@ 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 +// +// Function code : 1 byte (0x06) +// Register address : 2 bytes +// Register value : 2 bytes func (mb *client) WriteSingleRegister(address, value uint16) (results []byte, err error) { request := ProtocolDataUnit{ FunctionCode: FuncCodeWriteSingleRegister, @@ -232,15 +266,18 @@ 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 +// +// 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) { if quantity < 1 || quantity > 1968 { err = fmt.Errorf("modbus: quantity '%v' must be between '%v' and '%v',", quantity, 1, 1968) @@ -274,15 +311,18 @@ 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 +// +// 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) { if quantity < 1 || quantity > 123 { err = fmt.Errorf("modbus: quantity '%v' must be between '%v' and '%v',", quantity, 1, 123) @@ -316,15 +356,77 @@ 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 (0x15) +// Starting address : 2 bytes +// Quantity of outputs : 2 bytes +// Byte count : 1 byte +// Registers value : N* bytes +// // Response: -// Function code : 1 byte (0x16) -// Reference address : 2 bytes -// AND-mask : 2 bytes -// OR-mask : 2 bytes +// +// Function code : 1 byte (0x15) +// Starting address : 2 bytes +// Quantity of registers : 2 bytes +func (mb *client) WriteFileRecord(address, quantity uint16, subReqSize uint16, recUpdateFileNum []byte, value []byte) (results []byte, err error) { + wRecControlFieldsLength := uint16(7) + + dataFieldsLength := quantity * subReqSize + totalLength := (quantity * (subReqSize + wRecControlFieldsLength)) + 1 //+1 because of Function Code also included + if totalLength < 1 || totalLength > 253 { + err = fmt.Errorf("modbus: total length per modbus request must be between 1 and 253") + return + } + if len(recUpdateFileNum) != 2 { + err = fmt.Errorf("modbus: sub request file number should be a 2 byte value") + return + } + if len(value) < int(address+dataFieldsLength) { + err = fmt.Errorf("modbus: value input parameter length is not enough given address, quantity and subReqSize requested") + return + } + bffrReq := make([]byte, 0) + bffrReq = append(bffrReq, byte(totalLength-1)) + for i := int(address); i < int(address+dataFieldsLength); i += int(subReqSize) { + bffrReq = append(bffrReq, 6) //Sub Request reference type + bffrReq = append(bffrReq, recUpdateFileNum...) + recordNumber := []byte{byte(i >> 8), byte(i & 0xFF)} + bffrReq = append(bffrReq, recordNumber...) + bSize := []byte{byte(subReqSize / 2 >> 8), byte(subReqSize / 2 & 0xFF)} + bffrReq = append(bffrReq, bSize...) + valueNext := value[i*int(subReqSize) : (i+1)*int(subReqSize)] + bffrReq = append(bffrReq, valueNext...) + } + request := ProtocolDataUnit{ + FunctionCode: FuncCodeWriteFileRecord, + Data: bffrReq, + } + response, err := mb.send(&request) + if err != nil { + return + } + // Fixed response length + if len(response.Data) != int(totalLength) { + err = fmt.Errorf("modbus: response data size '%v' does not match expected '%v'", len(response.Data), int(totalLength)) + return + } + results = response.Data[2:] + return +} + +// Request: +// +// 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) { request := ProtocolDataUnit{ FunctionCode: FuncCodeMaskWriteRegister, @@ -359,17 +461,20 @@ 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 +// +// 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) { if readQuantity < 1 || readQuantity > 125 { err = fmt.Errorf("modbus: quantity to read '%v' must be between '%v' and '%v',", readQuantity, 1, 125) @@ -392,19 +497,26 @@ func (mb *client) ReadWriteMultipleRegisters(readAddress, readQuantity, writeAdd err = fmt.Errorf("modbus: response data size '%v' does not match count '%v'", len(response.Data)-1, count) return } + if count != int(readQuantity)*2 { + err = fmt.Errorf("modbus: response count '%v' does not match request quantity '%v'", count, readQuantity) + return + } results = response.Data[1:] return } // 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 +// +// 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) { request := ProtocolDataUnit{ FunctionCode: FuncCodeReadFIFOQueue, diff --git a/modbus.go b/modbus.go index 869473c..1ffa476 100644 --- a/modbus.go +++ b/modbus.go @@ -23,6 +23,7 @@ const ( FuncCodeReadHoldingRegisters = 3 FuncCodeWriteSingleRegister = 6 FuncCodeWriteMultipleRegisters = 16 + FuncCodeWriteFileRecord = 21 FuncCodeReadWriteMultipleRegisters = 23 FuncCodeMaskWriteRegister = 22 FuncCodeReadFIFOQueue = 24 diff --git a/rtuclient.go b/rtuclient.go index ad6a31a..5860f7c 100644 --- a/rtuclient.go +++ b/rtuclient.go @@ -204,6 +204,8 @@ func calculateResponseLength(adu []byte) int { length += 6 case FuncCodeReadFIFOQueue: // undetermined + case FuncCodeWriteFileRecord: + length = len(adu) default: } return length diff --git a/serial.go b/serial.go index 7d14c67..9eecf14 100644 --- a/serial.go +++ b/serial.go @@ -53,11 +53,17 @@ func (mb *serialPort) connect() error { return nil } -func (mb *serialPort) Close() (err error) { +func (mb *serialPort) Close() error { mb.mu.Lock() defer mb.mu.Unlock() - return mb.close() + err := mb.close() + if err == nil { + if mb.closeTimer != nil { + mb.closeTimer.Stop() + } + } + return err } // close closes the serial port if it is connected. Caller must hold the mutex. diff --git a/tcpclient.go b/tcpclient.go index 4e53c73..849a641 100644 --- a/tcpclient.go +++ b/tcpclient.go @@ -41,6 +41,16 @@ func NewTCPClientHandler(address string) *TCPClientHandler { return h } +// NewTCPConnClientHandler allocates a new TCPClientHandler. +func NewTCPConnClientHandler(address string, conn net.Conn) *TCPClientHandler { + h := &TCPClientHandler{} + h.Address = address + h.Timeout = tcpTimeout + h.IdleTimeout = tcpIdleTimeout + h.conn = conn + return h +} + // TCPClient creates TCP client with default handler and given connect string. func TCPClient(address string) Client { handler := NewTCPClientHandler(address) @@ -233,7 +243,13 @@ func (mb *tcpTransporter) Close() error { mb.mu.Lock() defer mb.mu.Unlock() - return mb.close() + err := mb.close() + if err == nil { + if mb.closeTimer != nil { + mb.closeTimer.Stop() + } + } + return err } // flush flushes pending data in the connection,