package modbus import ( "encoding/binary" "fmt" "time" ) const ( // Bit access FuncCodeReadDiscreteInputs = 2 FuncCodeReadCoils = 1 FuncCodeWriteSingleCoil = 5 FuncCodeWriteMultipleCoils = 15 // 16-bit access FuncCodeReadInputRegisters = 4 FuncCodeReadHoldingRegisters = 3 FuncCodeWriteSingleRegister = 6 FuncCodeWriteMultipleRegisters = 16 FuncCodeReadWriteMultipleRegisters = 23 FuncCodeMaskWriteRegister = 22 FuncCodeReadFIFOQueue = 24 ) const ( ExceptionCodeIllegalFunction = 1 ExceptionCodeIllegalDataAddress = 2 ExceptionCodeIllegalDataValue = 3 ExceptionCodeServerDeviceFailure = 4 ExceptionCodeAcknowledge = 5 ExceptionCodeServerDeviceBusy = 6 ExceptionCodeMemoryParityError = 8 ExceptionCodeGatewayPathUnavailable = 10 ExceptionCodeGatewayTargetDeviceFailedToRespond = 11 ) const ( rtuMinSize = 4 rtuMaxSize = 256 rtuExceptionSize = 5 tcpProtocolIdentifier uint16 = 0x0000 // Modbus Application Protocol tcpHeaderSize = 7 tcpMaxLength = 260 // Default TCP timeout is not set tcpTimeout = 10 * time.Second tcpIdleTimeout = 60 * time.Second ) // ModbusError implements error interface. type ModbusError struct { FunctionCode byte ExceptionCode byte } // Error converts known modbus exception code to error message. func (e *ModbusError) Error() string { var name string switch e.ExceptionCode { case ExceptionCodeIllegalFunction: name = "illegal function" case ExceptionCodeIllegalDataAddress: name = "illegal data address" case ExceptionCodeIllegalDataValue: name = "illegal data value" case ExceptionCodeServerDeviceFailure: name = "server device failure" case ExceptionCodeAcknowledge: name = "acknowledge" case ExceptionCodeServerDeviceBusy: name = "server device busy" case ExceptionCodeMemoryParityError: name = "memory parity error" case ExceptionCodeGatewayPathUnavailable: name = "gateway path unavailable" case ExceptionCodeGatewayTargetDeviceFailedToRespond: name = "gateway target device failed to respond" default: name = "unknown" } return fmt.Sprintf("modbus: exception '%v' (%s), function '%v'", e.ExceptionCode, name, e.FunctionCode) } // ProtocolDataUnit (PDU) is independent of underlying communication layers. type ProtocolDataUnit struct { FunctionCode byte Data []byte } // Transporter specifies the transport layer. type Transporter interface { Send(aduRequest []byte) (aduResponse []byte, err error) } // dataBlock creates a sequence of uint16 data. func dataBlock(value ...uint16) []byte { data := make([]byte, 2*len(value)) for i, v := range value { binary.BigEndian.PutUint16(data[i*2:], v) } return data } // dataBlockSuffix creates a sequence of uint16 data and append the suffix plus its length. func dataBlockSuffix(suffix []byte, value ...uint16) []byte { length := 2 * len(value) data := make([]byte, length+1+len(suffix)) for i, v := range value { binary.BigEndian.PutUint16(data[i*2:], v) } data[length] = uint8(len(suffix)) copy(data[length+1:], suffix) return data } func 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 } request := ProtocolDataUnit{ FunctionCode: FuncCodeReadHoldingRegisters, Data: dataBlock(address, quantity), } aduRequest, err := Encode(&request) if err != nil { return } return aduRequest, nil } // WriteMultipleRegisters // Request: // // 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 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 } request := ProtocolDataUnit{ FunctionCode: FuncCodeWriteMultipleRegisters, Data: dataBlockSuffix(value, address, quantity), } aduRequest, err := Encode(&request) if err != nil { return } return aduRequest, nil } func 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] = 0x02 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 }