Skip to content
This repository was archived by the owner on Mar 4, 2023. It is now read-only.

Commit f35d29f

Browse files
committed
init
0 parents  commit f35d29f

28 files changed

+3852
-0
lines changed

Diff for: README.md

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
go modbus Supported formats
2+
- modbus TCP Client
3+
- modbus Serial(RTU,ASCII) Client
4+
- modbus TCP Server
5+
6+
大量参考了![goburrow](https://github.com/goburrow/modbus)
7+
8+
Supported functions
9+
-------------------
10+
Bit access:
11+
* Read Discrete Inputs
12+
* Read Coils
13+
* Write Single Coil
14+
* Write Multiple Coils
15+
16+
16-bit access:
17+
* Read Input Registers
18+
* Read Holding Registers
19+
* Write Single Register
20+
* Write Multiple Registers
21+
* Read/Write Multiple Registers
22+
* Mask Write Register
23+
* Read FIFO Queue
24+
25+
References
26+
----------
27+
- [Modbus Specifications and Implementation Guides](http://www.modbus.org/specs.php)

Diff for: api.go

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package modbus
2+
3+
// Client interface
4+
type Client interface {
5+
ClientProvider
6+
// Bits
7+
8+
// ReadCoils reads from 1 to 2000 contiguous status of coils in a
9+
// remote device and returns coil status.
10+
ReadCoils(slaveID byte, address, quantity uint16) (results []byte, err error)
11+
// ReadDiscreteInputs reads from 1 to 2000 contiguous status of
12+
// discrete inputs in a remote device and returns input status.
13+
ReadDiscreteInputs(slaveID byte, address, quantity uint16) (results []byte, err error)
14+
// WriteSingleCoil write a single output to either ON or OFF in a
15+
// remote device and returns success or failed.
16+
WriteSingleCoil(slaveID byte, address uint16, isOn bool) error
17+
// WriteMultipleCoils forces each coil in a sequence of coils to either
18+
// ON or OFF in a remote device and returns success or failed.
19+
WriteMultipleCoils(slaveID byte, address, quantity uint16, value []byte) error
20+
21+
// 16-bits
22+
23+
// ReadInputRegisters reads from 1 to 125 contiguous input registers in
24+
// a remote device and returns input registers.
25+
ReadInputRegistersBytes(slaveID byte, address, quantity uint16) (results []byte, err error)
26+
// ReadInputRegisters reads from 1 to 125 contiguous input registers in
27+
// a remote device and returns input registers.
28+
ReadInputRegisters(slaveID byte, address, quantity uint16) (results []uint16, err error)
29+
// ReadHoldingRegistersBytes reads the contents of a contiguous block of
30+
// holding registers in a remote device and returns register value.
31+
ReadHoldingRegistersBytes(slaveID byte, address, quantity uint16) (results []byte, err error)
32+
// ReadHoldingRegisters reads the contents of a contiguous block of
33+
// holding registers in a remote device and returns register value.
34+
ReadHoldingRegisters(slaveID byte, address, quantity uint16) (results []uint16, err error)
35+
// WriteSingleRegister writes a single holding register in a remote
36+
// device and returns success or failed.
37+
WriteSingleRegister(slaveID byte, address, value uint16) error
38+
// WriteMultipleRegisters writes a block of contiguous registers
39+
// (1 to 123 registers) in a remote device and returns success or failed.
40+
WriteMultipleRegisters(slaveID byte, address, quantity uint16, value []byte) error
41+
// ReadWriteMultipleRegisters performs a combination of one read
42+
// operation and one write operation. It returns read registers value.
43+
ReadWriteMultipleRegistersBytes(slaveID byte, readAddress, readQuantity,
44+
writeAddress, writeQuantity uint16, value []byte) (results []byte, err error)
45+
// ReadWriteMultipleRegisters performs a combination of one read
46+
// operation and one write operation. It returns read registers value.
47+
ReadWriteMultipleRegisters(slaveID byte, readAddress, readQuantity,
48+
writeAddress, writeQuantity uint16, value []byte) (results []uint16, err error)
49+
// MaskWriteRegister modify the contents of a specified holding
50+
// register using a combination of an AND mask, an OR mask, and the
51+
// register's current contents. The function returns success or failed.
52+
MaskWriteRegister(slaveID byte, address, andMask, orMask uint16) error
53+
//ReadFIFOQueue reads the contents of a First-In-First-Out (FIFO) queue
54+
// of register in a remote device and returns FIFO value register.
55+
ReadFIFOQueue(slaveID byte, address uint16) (results []byte, err error)
56+
}

Diff for: asciiclient.go

+222
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
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

Comments
 (0)