asciiclient.go 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. // Copyright 2014 Quoc-Viet Nguyen. All rights reserved.
  2. // This software may be modified and distributed under the terms
  3. // of the BSD license. See the LICENSE file for details.
  4. package modbus
  5. import (
  6. "bytes"
  7. "encoding/hex"
  8. "fmt"
  9. "time"
  10. )
  11. const (
  12. asciiStart = ":"
  13. asciiEnd = "\r\n"
  14. asciiMinSize = 3
  15. asciiMaxSize = 513
  16. hexTable = "0123456789ABCDEF"
  17. )
  18. // ASCIIClientHandler implements Packager and Transporter interface.
  19. type ASCIIClientHandler struct {
  20. asciiPackager
  21. asciiSerialTransporter
  22. }
  23. // NewASCIIClientHandler allocates and initializes a ASCIIClientHandler.
  24. func NewASCIIClientHandler(address string) *ASCIIClientHandler {
  25. handler := &ASCIIClientHandler{}
  26. handler.Address = address
  27. handler.Timeout = serialTimeout
  28. handler.IdleTimeout = serialIdleTimeout
  29. return handler
  30. }
  31. // ASCIIClient creates ASCII client with default handler and given connect string.
  32. func ASCIIClient(address string) Client {
  33. handler := NewASCIIClientHandler(address)
  34. return NewClient(handler)
  35. }
  36. // asciiPackager implements Packager interface.
  37. type asciiPackager struct {
  38. SlaveId byte
  39. }
  40. // Encode encodes PDU in a ASCII frame:
  41. //
  42. // Start : 1 char
  43. // Address : 2 chars
  44. // Function : 2 chars
  45. // Data : 0 up to 2x252 chars
  46. // LRC : 2 chars
  47. // End : 2 chars
  48. func (mb *asciiPackager) Encode(pdu *ProtocolDataUnit) (adu []byte, err error) {
  49. var buf bytes.Buffer
  50. if _, err = buf.WriteString(asciiStart); err != nil {
  51. return
  52. }
  53. if err = writeHex(&buf, []byte{mb.SlaveId, pdu.FunctionCode}); err != nil {
  54. return
  55. }
  56. if err = writeHex(&buf, pdu.Data); err != nil {
  57. return
  58. }
  59. // Exclude the beginning colon and terminating CRLF pair characters
  60. var lrc lrc
  61. lrc.reset()
  62. lrc.pushByte(mb.SlaveId).pushByte(pdu.FunctionCode).pushBytes(pdu.Data)
  63. if err = writeHex(&buf, []byte{lrc.value()}); err != nil {
  64. return
  65. }
  66. if _, err = buf.WriteString(asciiEnd); err != nil {
  67. return
  68. }
  69. adu = buf.Bytes()
  70. return
  71. }
  72. // Verify verifies response length, frame boundary and slave id.
  73. func (mb *asciiPackager) Verify(aduRequest []byte, aduResponse []byte) (err error) {
  74. length := len(aduResponse)
  75. // Minimum size (including address, function and LRC)
  76. if length < asciiMinSize+6 {
  77. err = fmt.Errorf("modbus: response length '%v' does not meet minimum '%v'", length, 9)
  78. return
  79. }
  80. // Length excluding colon must be an even number
  81. if length%2 != 1 {
  82. err = fmt.Errorf("modbus: response length '%v' is not an even number", length-1)
  83. return
  84. }
  85. // First char must be a colon
  86. str := string(aduResponse[0:len(asciiStart)])
  87. if str != asciiStart {
  88. err = fmt.Errorf("modbus: response frame '%v'... is not started with '%v'", str, asciiStart)
  89. return
  90. }
  91. // 2 last chars must be \r\n
  92. str = string(aduResponse[len(aduResponse)-len(asciiEnd):])
  93. if str != asciiEnd {
  94. err = fmt.Errorf("modbus: response frame ...'%v' is not ended with '%v'", str, asciiEnd)
  95. return
  96. }
  97. // Slave id
  98. responseVal, err := readHex(aduResponse[1:])
  99. if err != nil {
  100. return
  101. }
  102. requestVal, err := readHex(aduRequest[1:])
  103. if err != nil {
  104. return
  105. }
  106. if responseVal != requestVal {
  107. err = fmt.Errorf("modbus: response slave id '%v' does not match request '%v'", responseVal, requestVal)
  108. return
  109. }
  110. return
  111. }
  112. // Decode extracts PDU from ASCII frame and verify LRC.
  113. func (mb *asciiPackager) Decode(adu []byte) (pdu *ProtocolDataUnit, err error) {
  114. pdu = &ProtocolDataUnit{}
  115. // Slave address
  116. address, err := readHex(adu[1:])
  117. if err != nil {
  118. return
  119. }
  120. // Function code
  121. if pdu.FunctionCode, err = readHex(adu[3:]); err != nil {
  122. return
  123. }
  124. // Data
  125. dataEnd := len(adu) - 4
  126. data := adu[5:dataEnd]
  127. pdu.Data = make([]byte, hex.DecodedLen(len(data)))
  128. if _, err = hex.Decode(pdu.Data, data); err != nil {
  129. return
  130. }
  131. // LRC
  132. lrcVal, err := readHex(adu[dataEnd:])
  133. if err != nil {
  134. return
  135. }
  136. // Calculate checksum
  137. var lrc lrc
  138. lrc.reset()
  139. lrc.pushByte(address).pushByte(pdu.FunctionCode).pushBytes(pdu.Data)
  140. if lrcVal != lrc.value() {
  141. err = fmt.Errorf("modbus: response lrc '%v' does not match expected '%v'", lrcVal, lrc.value())
  142. return
  143. }
  144. return
  145. }
  146. // asciiSerialTransporter implements Transporter interface.
  147. type asciiSerialTransporter struct {
  148. serialPort
  149. }
  150. func (mb *asciiSerialTransporter) Send(aduRequest []byte) (aduResponse []byte, err error) {
  151. mb.serialPort.mu.Lock()
  152. defer mb.serialPort.mu.Unlock()
  153. // Make sure port is connected
  154. if err = mb.serialPort.connect(); err != nil {
  155. return
  156. }
  157. // Start the timer to close when idle
  158. mb.serialPort.lastActivity = time.Now()
  159. mb.serialPort.startCloseTimer()
  160. // Send the request
  161. mb.serialPort.logf("modbus: sending %q\n", aduRequest)
  162. if _, err = mb.port.Write(aduRequest); err != nil {
  163. return
  164. }
  165. // Get the response
  166. var n int
  167. var data [asciiMaxSize]byte
  168. length := 0
  169. for {
  170. if n, err = mb.port.Read(data[length:]); err != nil {
  171. return
  172. }
  173. length += n
  174. if length >= asciiMaxSize || n == 0 {
  175. break
  176. }
  177. // Expect end of frame in the data received
  178. if length > asciiMinSize {
  179. if string(data[length-len(asciiEnd):length]) == asciiEnd {
  180. break
  181. }
  182. }
  183. }
  184. aduResponse = data[:length]
  185. mb.serialPort.logf("modbus: received %q\n", aduResponse)
  186. return
  187. }
  188. // writeHex encodes byte to string in hexadecimal, e.g. 0xA5 => "A5"
  189. // (encoding/hex only supports lowercase string).
  190. func writeHex(buf *bytes.Buffer, value []byte) (err error) {
  191. var str [2]byte
  192. for _, v := range value {
  193. str[0] = hexTable[v>>4]
  194. str[1] = hexTable[v&0x0F]
  195. if _, err = buf.Write(str[:]); err != nil {
  196. return
  197. }
  198. }
  199. return
  200. }
  201. // readHex decodes hexa string to byte, e.g. "8C" => 0x8C.
  202. func readHex(data []byte) (value byte, err error) {
  203. var dst [1]byte
  204. if _, err = hex.Decode(dst[:], data[0:2]); err != nil {
  205. return
  206. }
  207. value = dst[0]
  208. return
  209. }