diff --git a/README.md b/README.md index e952636..7197525 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Bit access: * Read/Write Multiple Registers * Mask Write Register * Read FIFO Queue - +* Read Device Identification Supported formats ----------------- * TCP @@ -73,6 +73,25 @@ client := modbus.NewClient(handler) results, err := client.ReadDiscreteInputs(15, 2) ``` +```go +// Read device identification +results, err := client.ReadDeviceIdentification(1, 0) + +if err == nil { + objNumber := results[0] + + currStart := byte(1) + for i := byte(0); i < objNumber; i++ { + currID := results[currStart] + currLen := results[currStart+byte(1)] + currObj := results[currStart+byte(2) : currStart+byte(2)+currLen] + currStart += byte(2) + currLen + fmt.Printf("Object ID % x\n Object LEN: % x\nCurr OBJ = %s\n", currID, currLen, currObj) + } +} +``` + References ---------- - [Modbus Specifications and Implementation Guides](http://www.modbus.org/specs.php) +- [Modbus Application Protocol](https://modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf) \ No newline at end of file diff --git a/api.go b/api.go index fe06ab0..3851fee 100644 --- a/api.go +++ b/api.go @@ -28,6 +28,10 @@ type Client interface { // 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) + + ReadDeviceIdentification(devIdCode byte, objectId byte) (results []byte, err error) + GetDeviceIdentification(devIdCode byte, objectId byte) (results DeviceIdentification, err error) + // WriteSingleRegister writes a single holding register in a remote // device and returns register value. WriteSingleRegister(address, value uint16) (results []byte, err error) diff --git a/client.go b/client.go index ac3ee2e..d6a78cf 100644 --- a/client.go +++ b/client.go @@ -9,6 +9,56 @@ import ( "fmt" ) +const ( + ExceptionCodePDUMaxRequestedQuantity = 1 + ExceptionCodePDUWrongResponseDataSize = 2 + ExceptionCodePDUWrongResponseAddress = 3 + ExceptionCodePDUWrongResponseValue = 4 + ExceptionCodePDUWrongANDMask = 5 + ExceptionCodePDUWrongORMask = 6 + ExceptionCodePDUFifoGreater = 7 + ExceptionCodePDUEmptyResponse = 8 + ExceptionCodePDUWrongDevIDResponse = 9 + ExceptionCodePDUWrongValueSet = 10 +) + +// ModbusPDUError implements error interface for PDU data +type ModbusPDUError struct { + ExceptionCode byte + Request interface{} + Response interface{} +} + +// Error converts known modbus TCP exception code to error message. +func (e *ModbusPDUError) Error() string { + var name string + switch e.ExceptionCode { + case ExceptionCodePDUMaxRequestedQuantity: + name = "quantity '%v' must be between 1 and '%v'" + case ExceptionCodePDUWrongResponseDataSize: + name = "response data size '%v' does not match count '%v'" + case ExceptionCodePDUWrongResponseAddress: + name = "response address '%v' does not match request '%v'" + case ExceptionCodePDUWrongResponseValue: + name = "response value '%v' does not match request '%v'" + case ExceptionCodePDUWrongANDMask: + name = "response AND-mask '%v' does not match request '%v'" + case ExceptionCodePDUWrongORMask: + name = "response OR-mask '%v' does not match request '%v'" + case ExceptionCodePDUFifoGreater: + name = "fifo count '%v' is greater than expected '%v'" + case ExceptionCodePDUEmptyResponse: + return "response data is empty" + case ExceptionCodePDUWrongDevIDResponse: + return fmt.Sprintf("Read Devce ID should response minimum 14 bytes: %v received", e.Request) + case ExceptionCodePDUWrongValueSet: + name = "state '%v' must be either 0xFF00 (ON) or 0x0000 (OFF)" + default: + name = "unknown" + } + return fmt.Sprintf(name, e.Request, e.Response) +} + // ClientHandler is the interface that groups the Packager and Transporter methods. type ClientHandler interface { Packager @@ -31,17 +81,23 @@ 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) - return + return []byte{}, &ModbusPDUError{ + ExceptionCode: ExceptionCodePDUMaxRequestedQuantity, + Request: quantity, + Response: 2000, + } } request := ProtocolDataUnit{ FunctionCode: FuncCodeReadCoils, @@ -54,25 +110,34 @@ func (mb *client) ReadCoils(address, quantity uint16) (results []byte, err error count := int(response.Data[0]) length := len(response.Data) - 1 if count != length { - err = fmt.Errorf("modbus: response data size '%v' does not match count '%v'", length, count) - return + return []byte{}, &ModbusPDUError{ + ExceptionCode: ExceptionCodePDUWrongResponseDataSize, + Request: length, + Response: count, + } } 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) - return + return []byte{}, &ModbusPDUError{ + ExceptionCode: ExceptionCodePDUMaxRequestedQuantity, + Request: quantity, + Response: 2000, + } } request := ProtocolDataUnit{ FunctionCode: FuncCodeReadDiscreteInputs, @@ -85,25 +150,34 @@ func (mb *client) ReadDiscreteInputs(address, quantity uint16) (results []byte, count := int(response.Data[0]) length := len(response.Data) - 1 if count != length { - err = fmt.Errorf("modbus: response data size '%v' does not match count '%v'", length, count) - return + return []byte{}, &ModbusPDUError{ + ExceptionCode: ExceptionCodePDUWrongResponseDataSize, + Request: length, + Response: count, + } } 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) - return + return []byte{}, &ModbusPDUError{ + ExceptionCode: ExceptionCodePDUMaxRequestedQuantity, + Request: quantity, + Response: 125, + } } request := ProtocolDataUnit{ FunctionCode: FuncCodeReadHoldingRegisters, @@ -116,25 +190,34 @@ func (mb *client) ReadHoldingRegisters(address, quantity uint16) (results []byte count := int(response.Data[0]) length := len(response.Data) - 1 if count != length { - err = fmt.Errorf("modbus: response data size '%v' does not match count '%v'", length, count) - return + return response.Data[1:], &ModbusPDUError{ + ExceptionCode: ExceptionCodePDUWrongResponseDataSize, + Request: count, + Response: length, + } } 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) - return + return []byte{}, &ModbusPDUError{ + ExceptionCode: ExceptionCodePDUMaxRequestedQuantity, + Request: quantity, + Response: 125, + } } request := ProtocolDataUnit{ FunctionCode: FuncCodeReadInputRegisters, @@ -147,26 +230,93 @@ func (mb *client) ReadInputRegisters(address, quantity uint16) (results []byte, count := int(response.Data[0]) length := len(response.Data) - 1 if count != length { - err = fmt.Errorf("modbus: response data size '%v' does not match count '%v'", length, count) - return + return []byte{}, &ModbusPDUError{ + ExceptionCode: ExceptionCodePDUWrongResponseDataSize, + Request: length, + Response: count, + } } results = response.Data[1:] return } // Request: -// Function code : 1 byte (0x05) -// Output address : 2 bytes -// Output value : 2 bytes +// +// Device ID Code : 1 byte +// Object ID : 1 byte +// +// Response: +// +// Objects number : 1 byte +// Objects data : N bytes +func (mb *client) ReadDeviceIdentification(devIdCode byte, objectId byte) (results []byte, err error) { + request := ProtocolDataUnit{ + FunctionCode: FuncCodeDevId, + Data: []byte{MEITypeDevId, devIdCode, objectId}, + } + + aduRequest, err := mb.packager.Encode(&request) + if err != nil { + return + } + + aduResponse, err := mb.transporter.Send(aduRequest) + if err != nil { + return + } + + length := len(aduResponse) + if length < 14 { + return []byte{}, &ModbusPDUError{ + ExceptionCode: ExceptionCodePDUWrongDevIDResponse, + Request: length, + } + } + + rxLen := binary.BigEndian.Uint16(aduResponse[4:6]) + length -= 6 + + if int(rxLen) != length { + return []byte{}, &ModbusPDUError{ + ExceptionCode: ExceptionCodePDUWrongResponseDataSize, + Request: length, + Response: rxLen, + } + } + + results = aduResponse[13 : 14+rxLen] + return +} + +func (mb *client) GetDeviceIdentification(devIdCode byte, objectId byte) (results DeviceIdentification, err error) { + raw, err := mb.ReadDeviceIdentification(devIdCode, objectId) + + if err != nil { + return + } + + results = ParseDeviceIdentification(raw) + return +} + +// Request: +// +// 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 { - err = fmt.Errorf("modbus: state '%v' must be either 0xFF00 (ON) or 0x0000 (OFF)", value) - return + return []byte{}, &ModbusPDUError{ + ExceptionCode: ExceptionCodePDUWrongValueSet, + Request: value, + } } request := ProtocolDataUnit{ FunctionCode: FuncCodeWriteSingleCoil, @@ -178,31 +328,43 @@ func (mb *client) WriteSingleCoil(address, value uint16) (results []byte, err er } // Fixed response length if len(response.Data) != 4 { - err = fmt.Errorf("modbus: response data size '%v' does not match expected '%v'", len(response.Data), 4) - return + return []byte{}, &ModbusPDUError{ + ExceptionCode: ExceptionCodePDUWrongResponseDataSize, + Request: len(response.Data), + Response: 4, + } } respValue := binary.BigEndian.Uint16(response.Data) if address != respValue { - err = fmt.Errorf("modbus: response address '%v' does not match request '%v'", respValue, address) - return + return []byte{}, &ModbusPDUError{ + ExceptionCode: ExceptionCodePDUWrongResponseAddress, + Request: respValue, + Response: address, + } } results = response.Data[2:] respValue = binary.BigEndian.Uint16(results) if value != respValue { - err = fmt.Errorf("modbus: response value '%v' does not match request '%v'", respValue, value) - return + return []byte{}, &ModbusPDUError{ + ExceptionCode: ExceptionCodePDUWrongResponseValue, + Request: respValue, + Response: value, + } } return } // 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, @@ -214,37 +376,52 @@ func (mb *client) WriteSingleRegister(address, value uint16) (results []byte, er } // Fixed response length if len(response.Data) != 4 { - err = fmt.Errorf("modbus: response data size '%v' does not match expected '%v'", len(response.Data), 4) - return + return []byte{}, &ModbusPDUError{ + ExceptionCode: ExceptionCodePDUWrongResponseDataSize, + Request: len(response.Data), + Response: 4, + } } respValue := binary.BigEndian.Uint16(response.Data) if address != respValue { - err = fmt.Errorf("modbus: response address '%v' does not match request '%v'", respValue, address) - return + return []byte{}, &ModbusPDUError{ + ExceptionCode: ExceptionCodePDUWrongResponseAddress, + Request: respValue, + Response: address, + } } results = response.Data[2:] respValue = binary.BigEndian.Uint16(results) if value != respValue { - err = fmt.Errorf("modbus: response value '%v' does not match request '%v'", respValue, value) - return + return []byte{}, &ModbusPDUError{ + ExceptionCode: ExceptionCodePDUWrongResponseValue, + Request: respValue, + Response: value, + } } return } // 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) - return + return []byte{}, &ModbusPDUError{ + ExceptionCode: ExceptionCodePDUMaxRequestedQuantity, + Request: quantity, + Response: 1968, + } } request := ProtocolDataUnit{ FunctionCode: FuncCodeWriteMultipleCoils, @@ -256,37 +433,52 @@ func (mb *client) WriteMultipleCoils(address, quantity uint16, value []byte) (re } // Fixed response length if len(response.Data) != 4 { - err = fmt.Errorf("modbus: response data size '%v' does not match expected '%v'", len(response.Data), 4) - return + return []byte{}, &ModbusPDUError{ + ExceptionCode: ExceptionCodePDUWrongResponseDataSize, + Request: len(response.Data), + Response: 4, + } } respValue := binary.BigEndian.Uint16(response.Data) if address != respValue { - err = fmt.Errorf("modbus: response address '%v' does not match request '%v'", respValue, address) - return + return []byte{}, &ModbusPDUError{ + ExceptionCode: ExceptionCodePDUWrongResponseAddress, + Request: respValue, + Response: address, + } } results = response.Data[2:] respValue = binary.BigEndian.Uint16(results) if quantity != respValue { - err = fmt.Errorf("modbus: response quantity '%v' does not match request '%v'", respValue, quantity) - return + return []byte{}, &ModbusPDUError{ + ExceptionCode: ExceptionCodePDUWrongResponseValue, + Request: respValue, + Response: quantity, + } } return } // 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) - return + return []byte{}, &ModbusPDUError{ + ExceptionCode: ExceptionCodePDUMaxRequestedQuantity, + Request: quantity, + Response: 123, + } } request := ProtocolDataUnit{ FunctionCode: FuncCodeWriteMultipleRegisters, @@ -298,33 +490,45 @@ func (mb *client) WriteMultipleRegisters(address, quantity uint16, value []byte) } // Fixed response length if len(response.Data) != 4 { - err = fmt.Errorf("modbus: response data size '%v' does not match expected '%v'", len(response.Data), 4) - return + return []byte{}, &ModbusPDUError{ + ExceptionCode: ExceptionCodePDUWrongResponseDataSize, + Request: len(response.Data), + Response: 4, + } } respValue := binary.BigEndian.Uint16(response.Data) if address != respValue { - err = fmt.Errorf("modbus: response address '%v' does not match request '%v'", respValue, address) - return + return []byte{}, &ModbusPDUError{ + ExceptionCode: ExceptionCodePDUWrongResponseAddress, + Request: respValue, + Response: address, + } } results = response.Data[2:] respValue = binary.BigEndian.Uint16(results) if quantity != respValue { - err = fmt.Errorf("modbus: response quantity '%v' does not match request '%v'", respValue, quantity) - return + return []byte{}, &ModbusPDUError{ + ExceptionCode: ExceptionCodePDUWrongResponseValue, + Request: respValue, + Response: quantity, + } } return } // 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 +// +// 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, @@ -336,48 +540,69 @@ func (mb *client) MaskWriteRegister(address, andMask, orMask uint16) (results [] } // Fixed response length if len(response.Data) != 6 { - err = fmt.Errorf("modbus: response data size '%v' does not match expected '%v'", len(response.Data), 6) - return + return []byte{}, &ModbusPDUError{ + ExceptionCode: ExceptionCodePDUWrongResponseDataSize, + Request: len(response.Data), + Response: 6, + } } respValue := binary.BigEndian.Uint16(response.Data) if address != respValue { - err = fmt.Errorf("modbus: response address '%v' does not match request '%v'", respValue, address) - return + return []byte{}, &ModbusPDUError{ + ExceptionCode: ExceptionCodePDUWrongResponseAddress, + Request: respValue, + Response: address, + } } respValue = binary.BigEndian.Uint16(response.Data[2:]) if andMask != respValue { - err = fmt.Errorf("modbus: response AND-mask '%v' does not match request '%v'", respValue, andMask) - return + return []byte{}, &ModbusPDUError{ + ExceptionCode: ExceptionCodePDUWrongANDMask, + Request: respValue, + Response: andMask, + } } respValue = binary.BigEndian.Uint16(response.Data[4:]) if orMask != respValue { - err = fmt.Errorf("modbus: response OR-mask '%v' does not match request '%v'", respValue, orMask) - return + return []byte{}, &ModbusPDUError{ + ExceptionCode: ExceptionCodePDUWrongORMask, + Request: respValue, + Response: orMask, + } } results = response.Data[2:] return } // 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) - return + return []byte{}, &ModbusPDUError{ + ExceptionCode: ExceptionCodePDUMaxRequestedQuantity, + Request: readQuantity, + Response: 125, + } } if writeQuantity < 1 || writeQuantity > 121 { - err = fmt.Errorf("modbus: quantity to write '%v' must be between '%v' and '%v',", writeQuantity, 1, 121) - return + return []byte{}, &ModbusPDUError{ + ExceptionCode: ExceptionCodePDUMaxRequestedQuantity, + Request: writeQuantity, + Response: 121, + } } request := ProtocolDataUnit{ FunctionCode: FuncCodeReadWriteMultipleRegisters, @@ -389,22 +614,28 @@ func (mb *client) ReadWriteMultipleRegisters(readAddress, readQuantity, writeAdd } count := int(response.Data[0]) if count != (len(response.Data) - 1) { - err = fmt.Errorf("modbus: response data size '%v' does not match count '%v'", len(response.Data)-1, count) - return + return []byte{}, &ModbusPDUError{ + ExceptionCode: ExceptionCodePDUWrongResponseDataSize, + Request: len(response.Data) - 1, + Response: count, + } } 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, @@ -415,18 +646,27 @@ func (mb *client) ReadFIFOQueue(address uint16) (results []byte, err error) { return } if len(response.Data) < 4 { - err = fmt.Errorf("modbus: response data size '%v' is less than expected '%v'", len(response.Data), 4) - return + return []byte{}, &ModbusPDUError{ + ExceptionCode: ExceptionCodePDUWrongResponseDataSize, + Request: len(response.Data), + Response: 4, + } } count := int(binary.BigEndian.Uint16(response.Data)) if count != (len(response.Data) - 1) { - err = fmt.Errorf("modbus: response data size '%v' does not match count '%v'", len(response.Data)-1, count) - return + return []byte{}, &ModbusPDUError{ + ExceptionCode: ExceptionCodePDUWrongResponseDataSize, + Request: len(response.Data) - 1, + Response: count, + } } count = int(binary.BigEndian.Uint16(response.Data[2:])) if count > 31 { - err = fmt.Errorf("modbus: fifo count '%v' is greater than expected '%v'", count, 31) - return + return []byte{}, &ModbusPDUError{ + ExceptionCode: ExceptionCodePDUFifoGreater, + Request: count, + Response: 31, + } } results = response.Data[4:] return @@ -458,8 +698,9 @@ func (mb *client) send(request *ProtocolDataUnit) (response *ProtocolDataUnit, e } if response.Data == nil || len(response.Data) == 0 { // Empty response - err = fmt.Errorf("modbus: response data is empty") - return + return response, &ModbusPDUError{ + ExceptionCode: ExceptionCodePDUEmptyResponse, + } } return } diff --git a/device_identification.go b/device_identification.go new file mode 100644 index 0000000..af8be5e --- /dev/null +++ b/device_identification.go @@ -0,0 +1,55 @@ +package modbus + +const ( + VendorName byte = iota + ProductCode + MinorMajorRevision + VendorUrl + ProductName + ModelNumber + UserApplicationMame +) + +type DeviceIdentification struct { + VendorName string + ProductCode string + MinorMajorRevision string + VendorUrl *string + ProductName *string + ModelNumber *string + UserApplicationMame *string +} + +func ParseDeviceIdentification(raw []byte) DeviceIdentification { + devId := DeviceIdentification{} + + objNumber := raw[0] + + currStart := byte(1) + for i := byte(0); i < objNumber; i++ { + currLen := raw[currStart+byte(1)] + currObj := raw[currStart+byte(2) : currStart+byte(2)+currLen] + currStart += byte(2) + currLen + + strObj := string(currObj) + + switch i { + case VendorName: + devId.VendorName = strObj + case ProductCode: + devId.ProductCode = strObj + case MinorMajorRevision: + devId.MinorMajorRevision = strObj + case VendorUrl: + devId.VendorUrl = &strObj + case ProductName: + devId.ProductName = &strObj + case ModelNumber: + devId.ModelNumber = &strObj + case UserApplicationMame: + devId.UserApplicationMame = &strObj + } + } + + return devId +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..e70b2bb --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module github.com/pierreyves258/modbus + +go 1.18 + +require ( + github.com/goburrow/modbus v0.1.0 + github.com/goburrow/serial v0.1.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..e702ea7 --- /dev/null +++ b/go.sum @@ -0,0 +1,4 @@ +github.com/goburrow/modbus v0.1.0 h1:DejRZY73nEM6+bt5JSP6IsFolJ9dVcqxsYbpLbeW/ro= +github.com/goburrow/modbus v0.1.0/go.mod h1:Kx552D5rLIS8E7TyUwQ/UdHEqvX5T8tyiGBTlzMcZBg= +github.com/goburrow/serial v0.1.0 h1:v2T1SQa/dlUqQiYIT8+Cu7YolfqAi3K96UmhwYyuSrA= +github.com/goburrow/serial v0.1.0/go.mod h1:sAiqG0nRVswsm1C97xsttiYCzSLBmUZ/VSlVLZJ8haA= diff --git a/modbus.go b/modbus.go index 869473c..944c670 100644 --- a/modbus.go +++ b/modbus.go @@ -26,6 +26,14 @@ const ( FuncCodeReadWriteMultipleRegisters = 23 FuncCodeMaskWriteRegister = 22 FuncCodeReadFIFOQueue = 24 + + //Device ID + FuncCodeDevId = 0x2B + MEITypeDevId = 0x0E + BasicDeviceIdentification = 1 + RegularDeviceIdentification = 2 + ExtendedDeviceIdentification = 3 + SingleDeviceIdentification = 4 ) const ( diff --git a/tcpclient.go b/tcpclient.go index 4e53c73..12e462a 100644 --- a/tcpclient.go +++ b/tcpclient.go @@ -15,6 +15,44 @@ import ( "time" ) +const ( + ExceptionCodeWrongTransactionId = 1 + ExceptionCodeWrongProtocolId = 2 + ExceptionCodeWrongUnitId = 3 + ExceptionCodeWrongSize = 4 + ExceptionCodeLengthZero = 5 + ExceptionCodeLengthMaxSize = 6 +) + +// ModbusError implements error interface. +type ModbusTCPError struct { + ExceptionCode byte + Request interface{} + Response interface{} +} + +// Error converts known modbus TCP exception code to error message. +func (e *ModbusTCPError) Error() string { + var name string + switch e.ExceptionCode { + case ExceptionCodeWrongTransactionId: + name = "modbus: response transaction id '%v' does not match request '%v'" + case ExceptionCodeWrongProtocolId: + name = "modbus: response protocol id '%v' does not match request '%v'" + case ExceptionCodeWrongUnitId: + name = "modbus: response unit id '%v' does not match request '%v'" + case ExceptionCodeWrongSize: + name = "modbus: length in response '%v' does not match pdu data length '%v'" + case ExceptionCodeLengthZero: + name = "modbus: length in response header '%v' must not be zero" + case ExceptionCodeLengthMaxSize: + name = "modbus: length in response header '%v' must not greater than '%v'" + default: + name = "unknown" + } + return fmt.Sprintf(name, e.Request, e.Response) +} + const ( tcpProtocolIdentifier uint16 = 0x0000 @@ -88,20 +126,29 @@ func (mb *tcpPackager) Verify(aduRequest []byte, aduResponse []byte) (err error) responseVal := binary.BigEndian.Uint16(aduResponse) requestVal := binary.BigEndian.Uint16(aduRequest) if responseVal != requestVal { - err = fmt.Errorf("modbus: response transaction id '%v' does not match request '%v'", responseVal, requestVal) - return + return &ModbusTCPError{ + ExceptionCode: ExceptionCodeWrongTransactionId, + Request: requestVal, + Response: responseVal, + } } // Protocol id responseVal = binary.BigEndian.Uint16(aduResponse[2:]) requestVal = binary.BigEndian.Uint16(aduRequest[2:]) if responseVal != requestVal { - err = fmt.Errorf("modbus: response protocol id '%v' does not match request '%v'", responseVal, requestVal) - return + return &ModbusTCPError{ + ExceptionCode: ExceptionCodeWrongProtocolId, + Request: requestVal, + Response: responseVal, + } } // Unit id (1 byte) if aduResponse[6] != aduRequest[6] { - err = fmt.Errorf("modbus: response unit id '%v' does not match request '%v'", aduResponse[6], aduRequest[6]) - return + return &ModbusTCPError{ + ExceptionCode: ExceptionCodeWrongUnitId, + Request: aduResponse[6], + Response: aduRequest[6], + } } return } @@ -116,8 +163,11 @@ func (mb *tcpPackager) Decode(adu []byte) (pdu *ProtocolDataUnit, err error) { length := binary.BigEndian.Uint16(adu[4:]) pduLength := len(adu) - tcpHeaderSize if pduLength <= 0 || pduLength != int(length-1) { - err = fmt.Errorf("modbus: length in response '%v' does not match pdu data length '%v'", length-1, pduLength) - return + return pdu, &ModbusTCPError{ + ExceptionCode: ExceptionCodeWrongSize, + Request: length - 1, + Response: pduLength, + } } pdu = &ProtocolDataUnit{} // The first byte after header is function code @@ -178,13 +228,18 @@ func (mb *tcpTransporter) Send(aduRequest []byte) (aduResponse []byte, err error length := int(binary.BigEndian.Uint16(data[4:])) if length <= 0 { mb.flush(data[:]) - err = fmt.Errorf("modbus: length in response header '%v' must not be zero", length) - return + return aduResponse, &ModbusTCPError{ + ExceptionCode: ExceptionCodeLengthZero, + Request: length, + } } if length > (tcpMaxLength - (tcpHeaderSize - 1)) { mb.flush(data[:]) - err = fmt.Errorf("modbus: length in response header '%v' must not greater than '%v'", length, tcpMaxLength-tcpHeaderSize+1) - return + return aduResponse, &ModbusTCPError{ + ExceptionCode: ExceptionCodeLengthMaxSize, + Request: length, + Response: tcpMaxLength - tcpHeaderSize + 1, + } } // Skip unit id length += tcpHeaderSize - 1