123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184 |
- 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
- }
|