modbus.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. package modbus
  2. import (
  3. "encoding/binary"
  4. "fmt"
  5. "time"
  6. )
  7. const (
  8. // Bit access
  9. FuncCodeReadDiscreteInputs = 2
  10. FuncCodeReadCoils = 1
  11. FuncCodeWriteSingleCoil = 5
  12. FuncCodeWriteMultipleCoils = 15
  13. // 16-bit access
  14. FuncCodeReadInputRegisters = 4
  15. FuncCodeReadHoldingRegisters = 3
  16. FuncCodeWriteSingleRegister = 6
  17. FuncCodeWriteMultipleRegisters = 16
  18. FuncCodeReadWriteMultipleRegisters = 23
  19. FuncCodeMaskWriteRegister = 22
  20. FuncCodeReadFIFOQueue = 24
  21. )
  22. const (
  23. ExceptionCodeIllegalFunction = 1
  24. ExceptionCodeIllegalDataAddress = 2
  25. ExceptionCodeIllegalDataValue = 3
  26. ExceptionCodeServerDeviceFailure = 4
  27. ExceptionCodeAcknowledge = 5
  28. ExceptionCodeServerDeviceBusy = 6
  29. ExceptionCodeMemoryParityError = 8
  30. ExceptionCodeGatewayPathUnavailable = 10
  31. ExceptionCodeGatewayTargetDeviceFailedToRespond = 11
  32. )
  33. const (
  34. rtuMinSize = 4
  35. rtuMaxSize = 256
  36. rtuExceptionSize = 5
  37. tcpProtocolIdentifier uint16 = 0x0000
  38. // Modbus Application Protocol
  39. tcpHeaderSize = 7
  40. tcpMaxLength = 260
  41. // Default TCP timeout is not set
  42. tcpTimeout = 10 * time.Second
  43. tcpIdleTimeout = 60 * time.Second
  44. )
  45. // ModbusError implements error interface.
  46. type ModbusError struct {
  47. FunctionCode byte
  48. ExceptionCode byte
  49. }
  50. // Error converts known modbus exception code to error message.
  51. func (e *ModbusError) Error() string {
  52. var name string
  53. switch e.ExceptionCode {
  54. case ExceptionCodeIllegalFunction:
  55. name = "illegal function"
  56. case ExceptionCodeIllegalDataAddress:
  57. name = "illegal data address"
  58. case ExceptionCodeIllegalDataValue:
  59. name = "illegal data value"
  60. case ExceptionCodeServerDeviceFailure:
  61. name = "server device failure"
  62. case ExceptionCodeAcknowledge:
  63. name = "acknowledge"
  64. case ExceptionCodeServerDeviceBusy:
  65. name = "server device busy"
  66. case ExceptionCodeMemoryParityError:
  67. name = "memory parity error"
  68. case ExceptionCodeGatewayPathUnavailable:
  69. name = "gateway path unavailable"
  70. case ExceptionCodeGatewayTargetDeviceFailedToRespond:
  71. name = "gateway target device failed to respond"
  72. default:
  73. name = "unknown"
  74. }
  75. return fmt.Sprintf("modbus: exception '%v' (%s), function '%v'", e.ExceptionCode, name, e.FunctionCode)
  76. }
  77. // ProtocolDataUnit (PDU) is independent of underlying communication layers.
  78. type ProtocolDataUnit struct {
  79. FunctionCode byte
  80. Data []byte
  81. }
  82. // Transporter specifies the transport layer.
  83. type Transporter interface {
  84. Send(aduRequest []byte) (aduResponse []byte, err error)
  85. }
  86. // dataBlock creates a sequence of uint16 data.
  87. func dataBlock(value ...uint16) []byte {
  88. data := make([]byte, 2*len(value))
  89. for i, v := range value {
  90. binary.BigEndian.PutUint16(data[i*2:], v)
  91. }
  92. return data
  93. }
  94. // dataBlockSuffix creates a sequence of uint16 data and append the suffix plus its length.
  95. func dataBlockSuffix(suffix []byte, value ...uint16) []byte {
  96. length := 2 * len(value)
  97. data := make([]byte, length+1+len(suffix))
  98. for i, v := range value {
  99. binary.BigEndian.PutUint16(data[i*2:], v)
  100. }
  101. data[length] = uint8(len(suffix))
  102. copy(data[length+1:], suffix)
  103. return data
  104. }
  105. func ReadHoldingRegisters(address, quantity uint16) (results []byte, err error) {
  106. if quantity < 1 || quantity > 125 {
  107. err = fmt.Errorf("modbus: quantity '%v' must be between '%v' and '%v',", quantity, 1, 125)
  108. return
  109. }
  110. request := ProtocolDataUnit{
  111. FunctionCode: FuncCodeReadHoldingRegisters,
  112. Data: dataBlock(address, quantity),
  113. }
  114. aduRequest, err := Encode(&request)
  115. if err != nil {
  116. return
  117. }
  118. return aduRequest, nil
  119. }
  120. // WriteMultipleRegisters
  121. // Request:
  122. //
  123. // Function code : 1 byte (0x10)
  124. // Starting address : 2 bytes
  125. // Quantity of outputs : 2 bytes
  126. // Byte count : 1 byte
  127. // Registers value : N* bytes
  128. //
  129. // Response:
  130. //
  131. // Function code : 1 byte (0x10)
  132. // Starting address : 2 bytes
  133. // Quantity of registers : 2 bytes
  134. func WriteMultipleRegisters(address, quantity uint16, value []byte) (results []byte, err error) {
  135. if quantity < 1 || quantity > 123 {
  136. err = fmt.Errorf("modbus: quantity '%v' must be between '%v' and '%v',", quantity, 1, 123)
  137. return
  138. }
  139. request := ProtocolDataUnit{
  140. FunctionCode: FuncCodeWriteMultipleRegisters,
  141. Data: dataBlockSuffix(value, address, quantity),
  142. }
  143. aduRequest, err := Encode(&request)
  144. if err != nil {
  145. return
  146. }
  147. return aduRequest, nil
  148. }
  149. func Encode(pdu *ProtocolDataUnit) (adu []byte, err error) {
  150. length := len(pdu.Data) + 4
  151. if length > rtuMaxSize {
  152. err = fmt.Errorf("modbus: length of data '%v' must not be bigger than '%v'", length, rtuMaxSize)
  153. return
  154. }
  155. adu = make([]byte, length)
  156. adu[0] = 0x02
  157. adu[1] = pdu.FunctionCode
  158. copy(adu[2:], pdu.Data)
  159. // Append Crc
  160. var crc Crc
  161. crc.Reset().PushBytes(adu[0 : length-2])
  162. checksum := crc.Value()
  163. adu[length-1] = byte(checksum >> 8)
  164. adu[length-2] = byte(checksum)
  165. return
  166. }