|
| 1 | +package modbus |
| 2 | + |
| 3 | +import ( |
| 4 | + "encoding/hex" |
| 5 | + "fmt" |
| 6 | + "sync" |
| 7 | +) |
| 8 | + |
| 9 | +const ( |
| 10 | + asciiStart = ":" |
| 11 | + asciiEnd = "\r\n" |
| 12 | + |
| 13 | + hexTable = "0123456789ABCDEF" |
| 14 | +) |
| 15 | + |
| 16 | +// ASCIIClientProvider implements ClientProvider interface. |
| 17 | +type ASCIIClientProvider struct { |
| 18 | + serialPort |
| 19 | + logs |
| 20 | + // 请求池,所有ascii客户端共用一个请求池 |
| 21 | + pool *sync.Pool |
| 22 | +} |
| 23 | + |
| 24 | +// check ASCIIClientProvider implements underlying method |
| 25 | +var _ ClientProvider = (*ASCIIClientProvider)(nil) |
| 26 | + |
| 27 | +// 请求池,所有ascii客户端共用一个请求池 |
| 28 | +var asciiPool = &sync.Pool{New: func() interface{} { return &protocolASCIIFrame{} }} |
| 29 | + |
| 30 | +// NewASCIIClientProvider allocates and initializes a ASCIIClientProvider. |
| 31 | +func NewASCIIClientProvider(address string) *ASCIIClientProvider { |
| 32 | + p := &ASCIIClientProvider{pool: asciiPool} |
| 33 | + p.Address = address |
| 34 | + p.Timeout = SerialDefaultTimeout |
| 35 | + p.autoReconnect = SerialDefaultAutoReconnect |
| 36 | + return p |
| 37 | +} |
| 38 | + |
| 39 | +// encode slaveID & PDU to a ASCII frame,return adu |
| 40 | +// Start : 1 char |
| 41 | +// slaveID : 2 chars |
| 42 | +// ---- data Unit ---- |
| 43 | +// Function : 2 chars |
| 44 | +// Data : 0 up to 2x252 chars |
| 45 | +// ---- checksun ---- |
| 46 | +// LRC : 2 chars |
| 47 | +// End : 2 chars |
| 48 | +func (this *protocolASCIIFrame) encode(slaveID byte, pdu *ProtocolDataUnit) ([]byte, error) { |
| 49 | + length := len(pdu.Data) + 3 |
| 50 | + if length > asciiAduMaxSize { |
| 51 | + return nil, fmt.Errorf("modbus: length of data '%v' must not be bigger than '%v'", length, asciiAduMaxSize) |
| 52 | + } |
| 53 | + // save |
| 54 | + this.slaveID = slaveID |
| 55 | + this.pdu.FuncCode = pdu.FuncCode |
| 56 | + |
| 57 | + // Exclude the beginning colon and terminating CRLF pair characters |
| 58 | + var lrc lrc |
| 59 | + lrc.reset().push(this.slaveID) |
| 60 | + lrc.push(pdu.FuncCode).push(pdu.Data...) |
| 61 | + lrcVal := lrc.value() |
| 62 | + |
| 63 | + // real ascii frame to send, |
| 64 | + // includeing asciiStart + ( slaveID + funciton + data + lrc ) + CRLF |
| 65 | + frame := this.adu[: 0 : (len(pdu.Data)+3)*2+3] |
| 66 | + frame = append(frame, []byte(asciiStart)...) // the beginning colon characters |
| 67 | + // the real adu |
| 68 | + frame = append(frame, hexTable[this.slaveID>>4], hexTable[this.slaveID&0x0F]) // slave ID |
| 69 | + frame = append(frame, hexTable[pdu.FuncCode>>4], hexTable[pdu.FuncCode&0x0F]) // pdu funcCode |
| 70 | + for _, v := range pdu.Data { |
| 71 | + frame = append(frame, hexTable[v>>4], hexTable[v&0x0F]) // pdu data |
| 72 | + } |
| 73 | + frame = append(frame, hexTable[lrcVal>>4], hexTable[lrcVal&0x0F]) // lrc |
| 74 | + // terminating CRLF characters |
| 75 | + return append(frame, []byte(asciiEnd)...), nil |
| 76 | +} |
| 77 | + |
| 78 | +// decode extracts slaveID & PDU from ASCII frame and verify LRC. |
| 79 | +func (this *protocolASCIIFrame) decode(adu []byte) (uint8, *ProtocolDataUnit, []byte, error) { |
| 80 | + if len(adu) < asciiAduMinSize+6 { // Minimum size (including address, function and LRC) |
| 81 | + return 0, nil, nil, fmt.Errorf("modbus: response length '%v' does not meet minimum '%v'", len(adu), 9) |
| 82 | + } |
| 83 | + switch { |
| 84 | + case len(adu)%2 != 1: |
| 85 | + // Length excluding colon must be an even number |
| 86 | + return 0, nil, nil, fmt.Errorf("modbus: response length '%v' is not an even number", len(adu)-1) |
| 87 | + case string(adu[0:len(asciiStart)]) != asciiStart: |
| 88 | + // First char must be a colons |
| 89 | + return 0, nil, nil, fmt.Errorf("modbus: response frame '%v'... is not started with '%v'", |
| 90 | + string(adu[0:len(asciiStart)]), asciiStart) |
| 91 | + case string(adu[len(adu)-len(asciiEnd):]) != asciiEnd: |
| 92 | + // 2 last chars must be \r\n |
| 93 | + return 0, nil, nil, fmt.Errorf("modbus: response frame ...'%v' is not ended with '%v'", |
| 94 | + string(adu[len(adu)-len(asciiEnd):]), asciiEnd) |
| 95 | + } |
| 96 | + |
| 97 | + // real adu pass Start and CRLF |
| 98 | + dat := adu[1 : len(adu)-2] |
| 99 | + buf := make([]byte, hex.DecodedLen(len(dat))) |
| 100 | + length, err := hex.Decode(buf, dat) |
| 101 | + if err != nil { |
| 102 | + return 0, nil, nil, err |
| 103 | + } |
| 104 | + // Calculate checksum |
| 105 | + var lrc lrc |
| 106 | + sum := lrc.reset().push(buf[:length-1]...).value() |
| 107 | + if buf[length-1] != sum { // LRC |
| 108 | + return 0, nil, nil, fmt.Errorf("modbus: response lrc '%v' does not match expected '%v'", buf[length-1], sum) |
| 109 | + } |
| 110 | + return buf[0], &ProtocolDataUnit{buf[1], buf[2 : length-1]}, buf[1 : length-1], nil |
| 111 | +} |
| 112 | + |
| 113 | +// verify confirms vaild data |
| 114 | +func (this *protocolASCIIFrame) verify(reqSlaveID, rspSlaveID uint8, reqPDU, rspPDU *ProtocolDataUnit) error { |
| 115 | + return verify(reqSlaveID, rspSlaveID, reqPDU, rspPDU) |
| 116 | +} |
| 117 | + |
| 118 | +// Send request to the remote server,it implements on SendRawFrame |
| 119 | +func (this *ASCIIClientProvider) Send(slaveID byte, request *ProtocolDataUnit) (*ProtocolDataUnit, error) { |
| 120 | + frame := this.pool.Get().(*protocolASCIIFrame) |
| 121 | + defer this.pool.Put(frame) |
| 122 | + aduRequest, err := frame.encode(slaveID, request) |
| 123 | + if err != nil { |
| 124 | + return nil, err |
| 125 | + } |
| 126 | + aduResponse, err := this.SendRawFrame(aduRequest) |
| 127 | + if err != nil { |
| 128 | + return nil, err |
| 129 | + } |
| 130 | + rspSlaveID, response, _, err := frame.decode(aduResponse) |
| 131 | + if err != nil { |
| 132 | + return nil, err |
| 133 | + } |
| 134 | + if err = frame.verify(slaveID, rspSlaveID, request, response); err != nil { |
| 135 | + return nil, err |
| 136 | + } |
| 137 | + return response, nil |
| 138 | +} |
| 139 | + |
| 140 | +// SendPdu send pdu request to the remote server |
| 141 | +func (this *ASCIIClientProvider) SendPdu(slaveID byte, pduRequest []byte) (pduResponse []byte, err error) { |
| 142 | + if len(pduRequest) < pduMinSize || len(pduRequest) > pduMaxSize { |
| 143 | + return nil, fmt.Errorf("modbus: pdus size '%v' must not be between '%v' and '%v'", |
| 144 | + len(pduRequest), pduMinSize, pduMaxSize) |
| 145 | + } |
| 146 | + |
| 147 | + frame := this.pool.Get().(*protocolASCIIFrame) |
| 148 | + defer this.pool.Put(frame) |
| 149 | + request := &ProtocolDataUnit{pduRequest[0], pduRequest[1:]} |
| 150 | + aduRequest, err := frame.encode(slaveID, request) |
| 151 | + if err != nil { |
| 152 | + return nil, err |
| 153 | + } |
| 154 | + aduResponse, err := this.SendRawFrame(aduRequest) |
| 155 | + if err != nil { |
| 156 | + return nil, err |
| 157 | + } |
| 158 | + rspSlaveID, response, pdu, err := frame.decode(aduResponse) |
| 159 | + if err != nil { |
| 160 | + return nil, err |
| 161 | + } |
| 162 | + if err = frame.verify(slaveID, rspSlaveID, request, response); err != nil { |
| 163 | + return nil, err |
| 164 | + } |
| 165 | + return pdu, nil |
| 166 | +} |
| 167 | + |
| 168 | +// SendRawFrame send Adu frame |
| 169 | +func (this *ASCIIClientProvider) SendRawFrame(aduRequest []byte) (aduResponse []byte, err error) { |
| 170 | + this.mu.Lock() |
| 171 | + defer this.mu.Unlock() |
| 172 | + |
| 173 | + // check port is connected |
| 174 | + if !this.isConnected() { |
| 175 | + return nil, fmt.Errorf("modbus: Client is not connected") |
| 176 | + } |
| 177 | + |
| 178 | + // Send the request |
| 179 | + this.logf("modbus: sending %#v\n", aduRequest) |
| 180 | + var tryCnt byte |
| 181 | + for { |
| 182 | + _, err = this.port.Write(aduRequest) |
| 183 | + if err == nil { // success |
| 184 | + break |
| 185 | + } |
| 186 | + if this.autoReconnect == 0 { |
| 187 | + return |
| 188 | + } |
| 189 | + for { |
| 190 | + err = this.connect() |
| 191 | + if err == nil { |
| 192 | + break |
| 193 | + } |
| 194 | + if tryCnt++; tryCnt >= this.autoReconnect { |
| 195 | + return |
| 196 | + } |
| 197 | + } |
| 198 | + } |
| 199 | + |
| 200 | + // Get the response |
| 201 | + var n int |
| 202 | + var data [asciiCharacterMaxSize]byte |
| 203 | + length := 0 |
| 204 | + for { |
| 205 | + if n, err = this.port.Read(data[length:]); err != nil { |
| 206 | + return |
| 207 | + } |
| 208 | + length += n |
| 209 | + if length >= asciiCharacterMaxSize || n == 0 { |
| 210 | + break |
| 211 | + } |
| 212 | + // Expect end of frame in the data received |
| 213 | + if length > asciiAduMinSize { |
| 214 | + if string(data[length-len(asciiEnd):length]) == asciiEnd { |
| 215 | + break |
| 216 | + } |
| 217 | + } |
| 218 | + } |
| 219 | + aduResponse = data[:length] |
| 220 | + this.logf("modbus: received %#v\n", aduResponse) |
| 221 | + return |
| 222 | +} |
0 commit comments