├── .gitignore
├── .idea
├── vcs.xml
└── workspace.xml
├── LICENSE
├── README.md
├── api.go
├── asciiclient_test.go
├── buffer.go
├── client.go
├── crc.go
├── crc_test.go
├── dlt645.go
├── dlt645ConfigClient.go
├── dlt645client.go
├── dltcon
├── dlt.go
├── option.go
└── proc.go
├── error.go
├── example
└── example.go
├── go.mod
├── go.sum
├── log.go
├── messageAnalysis.go
├── res
└── DataMarkerConfig.toml
├── revive.toml
└── serial.go
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | ### Go template
3 | # Binaries for programs and plugins
4 | *.exe
5 | *.exe~
6 | *.dll
7 | *.so
8 | *.dylib
9 |
10 | # Test binary, built with `go test -c`
11 | *.test
12 |
13 | # Output of the go coverage tool, specifically when used with LiteIDE
14 | *.out
15 |
16 | # Dependency directories (remove the comment below to include it)
17 | # vendor/
18 |
19 | .idea
20 |
21 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/workspace.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
66 |
67 |
68 |
69 |
70 |
71 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 | true
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 jiang
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # go dlt645(通过串口解析DLT/645协议(目前只支持07规约)可以很方便地与edgex foundry结合起来)
2 |
3 | ### Supported formats
4 |
5 | - dlt645 Serial
6 |
7 | ### Installation
8 |
9 | Use go get.
10 | ```bash
11 | go get github.com/themeyic/go-dlt645
12 | ```
13 | Then import the dlt645 package into your own code.
14 | ```bash
15 | import dlt "github.com/themeyic/go-dlt645"
16 | ```
17 |
18 | ### Example
19 |
20 |
21 | ```
22 | 调用ClientProvider的构造函数,返回结构体指针
23 | p := dlt.NewClientProvider()
24 | //windows 下面就是 com开头的,比如说 com3
25 | //mac OS/linux/unix 下面就是 /dev/下面的,比如说 dev/tty.usbserial-14320
26 | p.Address = "com3"
27 | p.BaudRate = 2400
28 | p.DataBits = 8
29 | p.Parity = "E"
30 | p.StopBits = 1
31 | p.Timeout = 100 * time.Millisecond
32 |
33 | client := dltcon.NewClient(p)
34 | client.LogMode(true)
35 | err := client.Start()
36 | if err != nil {
37 | fmt.Println("start err,", err)
38 | return
39 | }
40 | //MeterNumber是表号 005223440001
41 | //DataMarker是数据标识别 02010300
42 | test := &dlt.Dlt645ConfigClient{"005223440001", "02010300"}
43 | for {
44 | value, err := test.SendMessageToSerial(client)
45 | if err != nil {
46 | fmt.Println("readHoldErr,", err)
47 | } else {
48 | fmt.Printf("%#v\n", value)
49 | }
50 | time.Sleep(time.Second * 3)
51 | }
52 | ```
53 |
54 |
--------------------------------------------------------------------------------
/api.go:
--------------------------------------------------------------------------------
1 | package dlt645
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 | }
57 |
--------------------------------------------------------------------------------
/asciiclient_test.go:
--------------------------------------------------------------------------------
1 | package dlt645
2 |
3 | import (
4 | "reflect"
5 | "testing"
6 | )
7 |
8 | func TestASCIIClientProvider_encodeASCIIFrame(t *testing.T) {
9 | type args struct {
10 | slaveID byte
11 | pdu ProtocolDataUnit
12 | }
13 | tests := []struct {
14 | name string
15 | ascii *protocolFrame
16 | args args
17 | want []byte
18 | wantErr bool
19 | }{
20 | {
21 | "ASCII encode right 1",
22 | &protocolFrame{adu: make([]byte, 0, asciiCharacterMaxSize)},
23 | args{8, ProtocolDataUnit{1, []byte{2, 66, 1, 5}}},
24 | []byte(":080102420105AD\r\n"),
25 | false,
26 | },
27 | {
28 | "ASCII encode right 2",
29 | &protocolFrame{adu: make([]byte, 0, asciiCharacterMaxSize)},
30 | args{1, ProtocolDataUnit{3, []byte{8, 100, 10, 13}}},
31 | []byte(":010308640A0D79\r\n"),
32 | false,
33 | },
34 | {
35 | "ASCII encode error",
36 | &protocolFrame{adu: make([]byte, 0, asciiCharacterMaxSize)},
37 | args{1, ProtocolDataUnit{3, make([]byte, 254)}},
38 | nil,
39 | true,
40 | },
41 | }
42 | for _, tt := range tests {
43 | t.Run(tt.name, func(t *testing.T) {
44 | got, err := tt.ascii.encodeASCIIFrame(tt.args.slaveID, tt.args.pdu)
45 | if (err != nil) != tt.wantErr {
46 | t.Errorf("ASCIIClientProvider.encode() error = %v, wantErr %v", err, tt.wantErr)
47 | return
48 | }
49 | if !reflect.DeepEqual(got, tt.want) {
50 | t.Errorf("ASCIIClientProvider.encode() = %s, want %s", got, tt.want)
51 | }
52 | })
53 | }
54 | }
55 |
56 | func TestASCIIClientProvider_decodeASCIIFrame(t *testing.T) {
57 | type args struct {
58 | adu []byte
59 | }
60 | tests := []struct {
61 | name string
62 | args args
63 | slaveID uint8
64 | pdu []byte
65 | wantErr bool
66 | }{
67 | {
68 | "ASCII decode 1",
69 | args{[]byte(":080102420105AD\r\n")},
70 | 8,
71 | []byte{1, 2, 66, 1, 5},
72 | false,
73 | },
74 | {
75 | "ASCII decode 2",
76 | args{[]byte(":010308640A0D79\r\n")},
77 | 1,
78 | []byte{3, 8, 100, 10, 13},
79 | false,
80 | },
81 | }
82 | for _, tt := range tests {
83 | t.Run(tt.name, func(t *testing.T) {
84 | gotslaveID, gotpdu, err := decodeASCIIFrame(tt.args.adu)
85 | if (err != nil) != tt.wantErr {
86 | t.Errorf("ASCIIClientProvider.decode() error = %v, wantErr %v", err, tt.wantErr)
87 | return
88 | }
89 | if gotslaveID != tt.slaveID {
90 | t.Errorf("ASCIIClientProvider.decode() gotslaveID = %v, want %v", gotslaveID, tt.slaveID)
91 | }
92 | if !reflect.DeepEqual(gotpdu, tt.pdu) {
93 | t.Errorf("ASCIIClientProvider.decode() gotpdu = %v, want %v", gotpdu, tt.pdu)
94 | }
95 | })
96 | }
97 | }
98 |
99 | func BenchmarkASCIIClientProvider_encodeASCIIFrame(b *testing.B) {
100 | p := protocolFrame{adu: make([]byte, 0, asciiCharacterMaxSize)}
101 | pdu := ProtocolDataUnit{
102 | 1,
103 | []byte{2, 3, 4, 5, 6, 7, 8, 9},
104 | }
105 | for i := 0; i < b.N; i++ {
106 | _, err := p.encodeASCIIFrame(10, pdu)
107 | if err != nil {
108 | b.Fatal(err)
109 | }
110 | }
111 | }
112 |
113 | func BenchmarkASCIIClientProvider_decodeASCIIFrame(b *testing.B) {
114 | adu := []byte(":010308640A0D79\r\n")
115 | for i := 0; i < b.N; i++ {
116 | _, _, err := decodeASCIIFrame(adu)
117 | if err != nil {
118 | b.Fatal(err)
119 | }
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/buffer.go:
--------------------------------------------------------------------------------
1 | package dlt645
2 |
3 | import (
4 | "sync"
5 | )
6 |
7 | type pool struct {
8 | pl *sync.Pool
9 | }
10 |
11 | func newPool(size int) *pool {
12 | return &pool{
13 | &sync.Pool{
14 | New: func() interface{} {
15 | return &protocolFrame{make([]byte, 0, size)}
16 | },
17 | },
18 | }
19 | }
20 |
21 | func (sf *pool) get() *protocolFrame {
22 | v := sf.pl.Get().(*protocolFrame)
23 | v.adu = v.adu[:0]
24 | return v
25 | }
26 |
27 | func (sf *pool) put(buffer *protocolFrame) {
28 | sf.pl.Put(buffer)
29 | }
30 |
--------------------------------------------------------------------------------
/client.go:
--------------------------------------------------------------------------------
1 | package dlt645
2 |
3 | import (
4 | "encoding/binary"
5 | "fmt"
6 | )
7 |
8 | // check implements Client interface
9 | var _ Client = (*client)(nil)
10 |
11 | // client implements Client interface
12 | type client struct {
13 | ClientProvider
14 | }
15 |
16 | // NewClient creates a new dltcon client with given backend handler.
17 | func NewClient(p ClientProvider) Client {
18 | return &client{p}
19 | }
20 |
21 | // Request:
22 | // Slave Id : 1 byte
23 | // Function code : 1 byte (0x01)
24 | // Starting address : 2 bytes
25 | // Quantity of coils : 2 bytes
26 | // Response:
27 | // Function code : 1 byte (0x01)
28 | // Byte count : 1 byte
29 | // Coil status : N* bytes (=N or N+1)
30 | // return coils status
31 | func (sf *client) ReadCoils(slaveID byte, address, quantity uint16) ([]byte, error) {
32 | if slaveID < AddressMin || slaveID > AddressMax {
33 | return nil, fmt.Errorf("dltcon: slaveID '%v' must be between '%v' and '%v'",
34 | slaveID, AddressMin, AddressMax)
35 | }
36 | if quantity < ReadBitsQuantityMin || quantity > ReadBitsQuantityMax {
37 | return nil, fmt.Errorf("dltcon: quantity '%v' must be between '%v' and '%v'",
38 | quantity, ReadBitsQuantityMin, ReadBitsQuantityMax)
39 |
40 | }
41 |
42 | response, err := sf.Send(slaveID, ProtocolDataUnit{
43 | FuncCodeReadCoils,
44 | pduDataBlock(address, quantity),
45 | })
46 |
47 | switch {
48 | case err != nil:
49 | return nil, err
50 | case len(response.Data)-1 != int(response.Data[0]):
51 | return nil, fmt.Errorf("dltcon: response byte size '%v' does not match count '%v'",
52 | len(response.Data)-1, int(response.Data[0]))
53 | case uint16(response.Data[0]) != (quantity+7)/8:
54 | return nil, fmt.Errorf("dltcon: response byte size '%v' does not match quantity to bytes '%v'",
55 | response.Data[0], (quantity+7)/8)
56 | }
57 | return response.Data[1:], nil
58 | }
59 |
60 | // Request:
61 | // Slave Id : 1 byte
62 | // Function code : 1 byte (0x02)
63 | // Starting address : 2 bytes
64 | // Quantity of inputs : 2 bytes
65 | // Response:
66 | // Function code : 1 byte (0x02)
67 | // Byte count : 1 byte
68 | // Input status : N* bytes (=N or N+1)
69 | // return result data
70 | func (sf *client) ReadDiscreteInputs(slaveID byte, address, quantity uint16) ([]byte, error) {
71 | if slaveID < AddressMin || slaveID > AddressMax {
72 | return nil, fmt.Errorf("dltcon: slaveID '%v' must be between '%v' and '%v'",
73 | slaveID, AddressMin, AddressMax)
74 | }
75 | if quantity < ReadBitsQuantityMin || quantity > ReadBitsQuantityMax {
76 | return nil, fmt.Errorf("dltcon: quantity '%v' must be between '%v' and '%v'",
77 | quantity, ReadBitsQuantityMin, ReadBitsQuantityMax)
78 | }
79 | response, err := sf.Send(slaveID, ProtocolDataUnit{
80 | FuncCode: FuncCodeReadDiscreteInputs,
81 | Data: pduDataBlock(address, quantity),
82 | })
83 |
84 | switch {
85 | case err != nil:
86 | return nil, err
87 | case len(response.Data)-1 != int(response.Data[0]):
88 | return nil, fmt.Errorf("dltcon: response byte size '%v' does not match count '%v'",
89 | len(response.Data)-1, response.Data[0])
90 | case uint16(response.Data[0]) != (quantity+7)/8:
91 | return nil, fmt.Errorf("dltcon: response byte size '%v' does not match quantity to bytes '%v'",
92 | response.Data[0], (quantity+7)/8)
93 | }
94 | return response.Data[1:], nil
95 | }
96 |
97 | // Request:
98 | // Slave Id : 1 byte
99 | // Function code : 1 byte (0x03)
100 | // Starting address : 2 bytes
101 | // Quantity of registers : 2 bytes
102 | // Response:
103 | // Function code : 1 byte (0x03)
104 | // Byte count : 1 byte
105 | // Register value : Nx2 bytes
106 | func (sf *client) ReadHoldingRegistersBytes(slaveID byte, address, quantity uint16) ([]byte, error) {
107 | if slaveID < AddressMin || slaveID > AddressMax {
108 | return nil, fmt.Errorf("dltcon: slaveID '%v' must be between '%v' and '%v'",
109 | slaveID, AddressMin, AddressMax)
110 | }
111 | if quantity < ReadRegQuantityMin || quantity > ReadRegQuantityMax {
112 | return nil, fmt.Errorf("dltcon: quantity '%v' must be between '%v' and '%v'",
113 | quantity, ReadRegQuantityMin, ReadRegQuantityMax)
114 | }
115 | response, err := sf.Send(slaveID, ProtocolDataUnit{
116 | FuncCode: FuncCodeReadHoldingRegisters,
117 | Data: pduDataBlock(address, quantity),
118 | })
119 |
120 | switch {
121 | case err != nil:
122 | return nil, err
123 | case len(response.Data)-1 != int(response.Data[0]):
124 | return nil, fmt.Errorf("dltcon: response data size '%v' does not match count '%v'",
125 | len(response.Data)-1, response.Data[0])
126 | case uint16(response.Data[0]) != quantity*2:
127 | return nil, fmt.Errorf("dltcon: response data size '%v' does not match quantity to bytes '%v'",
128 | response.Data[0], quantity*2)
129 | }
130 | return response.Data[1:], nil
131 | }
132 |
133 | // Request:
134 | // Slave Id : 1 byte
135 | // Function code : 1 byte (0x03)
136 | // Starting address : 2 bytes
137 | // Quantity of registers : 2 bytes
138 | // Response:
139 | // Function code : 1 byte (0x03)
140 | // Byte count : 1 byte
141 | // Register value : N 2-bytes
142 | func (sf *client) ReadHoldingRegisters(slaveID byte, address, quantity uint16) ([]uint16, error) {
143 | b, err := sf.ReadHoldingRegistersBytes(slaveID, address, quantity)
144 | if err != nil {
145 | return nil, err
146 | }
147 | return bytes2Uint16(b), nil
148 | }
149 |
150 | // Request:
151 | // Slave Id : 1 byte
152 | // Function code : 1 byte (0x04)
153 | // Starting address : 2 bytes
154 | // Quantity of registers : 2 bytes
155 | // Response:
156 | // Function code : 1 byte (0x04)
157 | // Byte count : 1 byte
158 | // Input registers : Nx2 bytes
159 | func (sf *client) ReadInputRegistersBytes(slaveID byte, address, quantity uint16) ([]byte, error) {
160 | if slaveID < AddressMin || slaveID > AddressMax {
161 | return nil, fmt.Errorf("dltcon: slaveID '%v' must be between '%v' and '%v'",
162 | slaveID, AddressMin, AddressMax)
163 | }
164 | if quantity < ReadRegQuantityMin || quantity > ReadRegQuantityMax {
165 | return nil, fmt.Errorf("dltcon: quantity '%v' must be between '%v' and '%v'",
166 | quantity, ReadRegQuantityMin, ReadRegQuantityMax)
167 |
168 | }
169 | response, err := sf.Send(slaveID, ProtocolDataUnit{
170 | FuncCode: FuncCodeReadInputRegisters,
171 | Data: pduDataBlock(address, quantity),
172 | })
173 |
174 | switch {
175 | case err != nil:
176 | return nil, err
177 | }
178 |
179 | if len(response.Data)-1 != int(response.Data[0]) {
180 | return nil, fmt.Errorf("dltcon: response data size '%v' does not match count '%v'",
181 | len(response.Data)-1, response.Data[0])
182 | }
183 | if uint16(response.Data[0]) != quantity*2 {
184 | return nil, fmt.Errorf("dltcon: response data size '%v' does not match quantity to bytes '%v'",
185 | response.Data[0], quantity*2)
186 | }
187 | return response.Data[1:], nil
188 | }
189 |
190 | // Request:
191 | // Slave Id : 1 byte
192 | // Function code : 1 byte (0x04)
193 | // Starting address : 2 bytes
194 | // Quantity of registers : 2 bytes
195 | // Response:
196 | // Function code : 1 byte (0x04)
197 | // Byte count : 1 byte
198 | // Input registers : N 2-bytes
199 | func (sf *client) ReadInputRegisters(slaveID byte, address, quantity uint16) ([]uint16, error) {
200 | b, err := sf.ReadInputRegistersBytes(slaveID, address, quantity)
201 | if err != nil {
202 | return nil, err
203 | }
204 | return bytes2Uint16(b), nil
205 | }
206 |
207 | // Request:
208 | // Slave Id : 1 byte
209 | // Function code : 1 byte (0x05)
210 | // Output address : 2 bytes
211 | // Output value : 2 bytes
212 | // Response:
213 | // Function code : 1 byte (0x05)
214 | // Output address : 2 bytes
215 | // Output value : 2 bytes
216 | func (sf *client) WriteSingleCoil(slaveID byte, address uint16, isOn bool) error {
217 | if slaveID > AddressMax {
218 | return fmt.Errorf("dltcon: slaveID '%v' must be between '%v' and '%v'",
219 | slaveID, AddressBroadCast, AddressMax)
220 | }
221 | var value uint16
222 | if isOn { // The requested ON/OFF state can only be 0xFF00 and 0x0000
223 | value = 0xFF00
224 | }
225 | response, err := sf.Send(slaveID, ProtocolDataUnit{
226 | FuncCode: FuncCodeWriteSingleCoil,
227 | Data: pduDataBlock(address, value),
228 | })
229 |
230 | switch {
231 | case err != nil:
232 | return err
233 | case len(response.Data) != 4:
234 | // Fixed response length
235 | return fmt.Errorf("dltcon: response data size '%v' does not match expected '%v'",
236 | len(response.Data), 4)
237 | case binary.BigEndian.Uint16(response.Data) != address:
238 | // check address
239 | return fmt.Errorf("dltcon: response address '%v' does not match request '%v'",
240 | binary.BigEndian.Uint16(response.Data), address)
241 | case binary.BigEndian.Uint16(response.Data[2:]) != value:
242 | // check value
243 | return fmt.Errorf("dltcon: response value '%v' does not match request '%v'",
244 | binary.BigEndian.Uint16(response.Data[2:]), value)
245 | }
246 | return nil
247 | }
248 |
249 | // Request:
250 | // Slave Id : 1 byte
251 | // Function code : 1 byte (0x06)
252 | // Register address : 2 bytes
253 | // Register value : 2 bytes
254 | // Response:
255 | // Function code : 1 byte (0x06)
256 | // Register address : 2 bytes
257 | // Register value : 2 bytes
258 | func (sf *client) WriteSingleRegister(slaveID byte, address, value uint16) error {
259 | if slaveID > AddressMax {
260 | return fmt.Errorf("dltcon: slaveID '%v' must be between '%v' and '%v'",
261 | slaveID, AddressBroadCast, AddressMax)
262 | }
263 | response, err := sf.Send(slaveID, ProtocolDataUnit{
264 | FuncCode: FuncCodeWriteSingleRegister,
265 | Data: pduDataBlock(address, value),
266 | })
267 |
268 | switch {
269 | case err != nil:
270 | return err
271 | case len(response.Data) != 4:
272 | // Fixed response length
273 | return fmt.Errorf("dltcon: response data size '%v' does not match expected '%v'",
274 | len(response.Data), 4)
275 | case binary.BigEndian.Uint16(response.Data) != address:
276 | return fmt.Errorf("dltcon: response address '%v' does not match request '%v'",
277 | binary.BigEndian.Uint16(response.Data), address)
278 | case binary.BigEndian.Uint16(response.Data[2:]) != value:
279 | return fmt.Errorf("dltcon: response value '%v' does not match request '%v'",
280 | binary.BigEndian.Uint16(response.Data[2:]), value)
281 | }
282 | return nil
283 | }
284 |
285 | // Request:
286 | // Slave Id : 1 byte
287 | // Function code : 1 byte (0x0F)
288 | // Starting address : 2 bytes
289 | // Quantity of outputs : 2 bytes
290 | // Byte count : 1 byte
291 | // Outputs value : N* bytes
292 | // Response:
293 | // Function code : 1 byte (0x0F)
294 | // Starting address : 2 bytes
295 | // Quantity of outputs : 2 bytes
296 | func (sf *client) WriteMultipleCoils(slaveID byte, address, quantity uint16, value []byte) error {
297 | if slaveID > AddressMax {
298 | return fmt.Errorf("dltcon: slaveID '%v' must be between '%v' and '%v'",
299 | slaveID, AddressBroadCast, AddressMax)
300 | }
301 | if quantity < WriteBitsQuantityMin || quantity > WriteBitsQuantityMax {
302 | return fmt.Errorf("dltcon: quantity '%v' must be between '%v' and '%v'",
303 | quantity, WriteBitsQuantityMin, WriteBitsQuantityMax)
304 | }
305 | response, err := sf.Send(slaveID, ProtocolDataUnit{
306 | FuncCode: FuncCodeWriteMultipleCoils,
307 | Data: pduDataBlockSuffix(value, address, quantity),
308 | })
309 |
310 | switch {
311 | case err != nil:
312 | return err
313 | case len(response.Data) != 4:
314 | // Fixed response length
315 | return fmt.Errorf("dltcon: response data size '%v' does not match expected '%v'",
316 | len(response.Data), 4)
317 | case binary.BigEndian.Uint16(response.Data) != address:
318 | return fmt.Errorf("dltcon: response address '%v' does not match request '%v'",
319 | binary.BigEndian.Uint16(response.Data), address)
320 | case binary.BigEndian.Uint16(response.Data[2:]) != quantity:
321 | return fmt.Errorf("dltcon: response quantity '%v' does not match request '%v'",
322 | binary.BigEndian.Uint16(response.Data[2:]), quantity)
323 | }
324 | return nil
325 | }
326 |
327 | // Request:
328 | // Slave Id : 1 byte
329 | // Function code : 1 byte (0x10)
330 | // Starting address : 2 bytes
331 | // Quantity of outputs : 2 bytes
332 | // Byte count : 1 byte
333 | // Registers value : N* bytes
334 | // Response:
335 | // Function code : 1 byte (0x10)
336 | // Starting address : 2 bytes
337 | // Quantity of registers : 2 bytes
338 | func (sf *client) WriteMultipleRegisters(slaveID byte, address, quantity uint16, value []byte) error {
339 | if slaveID > AddressMax {
340 | return fmt.Errorf("dltcon: slaveID '%v' must be between '%v' and '%v'",
341 | slaveID, AddressBroadCast, AddressMax)
342 | }
343 | if quantity < WriteRegQuantityMin || quantity > WriteRegQuantityMax {
344 | return fmt.Errorf("dltcon: quantity '%v' must be between '%v' and '%v'",
345 | quantity, WriteRegQuantityMin, WriteRegQuantityMax)
346 | }
347 |
348 | response, err := sf.Send(slaveID, ProtocolDataUnit{
349 | FuncCode: FuncCodeWriteMultipleRegisters,
350 | Data: pduDataBlockSuffix(value, address, quantity),
351 | })
352 |
353 | switch {
354 | case err != nil:
355 | return err
356 | case len(response.Data) != 4:
357 | // Fixed response length
358 | return fmt.Errorf("dltcon: response data size '%v' does not match expected '%v'",
359 | len(response.Data), 4)
360 | case binary.BigEndian.Uint16(response.Data) != address:
361 | return fmt.Errorf("dltcon: response address '%v' does not match request '%v'",
362 | binary.BigEndian.Uint16(response.Data), address)
363 | case binary.BigEndian.Uint16(response.Data[2:]) != quantity:
364 | return fmt.Errorf("dltcon: response quantity '%v' does not match request '%v'",
365 | binary.BigEndian.Uint16(response.Data[2:]), quantity)
366 | }
367 | return nil
368 | }
369 |
370 | // Request:
371 | // Slave Id : 1 byte
372 | // Function code : 1 byte (0x16)
373 | // Reference address : 2 bytes
374 | // AND-mask : 2 bytes
375 | // OR-mask : 2 bytes
376 | // Response:
377 | // Function code : 1 byte (0x16)
378 | // Reference address : 2 bytes
379 | // AND-mask : 2 bytes
380 | // OR-mask : 2 bytes
381 | func (sf *client) MaskWriteRegister(slaveID byte, address, andMask, orMask uint16) error {
382 | if slaveID > AddressMax {
383 | return fmt.Errorf("dltcon: slaveID '%v' must be between '%v' and '%v'",
384 | slaveID, AddressBroadCast, AddressMax)
385 | }
386 | response, err := sf.Send(slaveID, ProtocolDataUnit{
387 | FuncCode: FuncCodeMaskWriteRegister,
388 | Data: pduDataBlock(address, andMask, orMask),
389 | })
390 |
391 | switch {
392 | case err != nil:
393 | return err
394 | case len(response.Data) != 6:
395 | // Fixed response length
396 | return fmt.Errorf("dltcon: response data size '%v' does not match expected '%v'",
397 | len(response.Data), 6)
398 | case binary.BigEndian.Uint16(response.Data) != address:
399 | return fmt.Errorf("dltcon: response address '%v' does not match request '%v'",
400 | binary.BigEndian.Uint16(response.Data), address)
401 | case binary.BigEndian.Uint16(response.Data[2:]) != andMask:
402 | return fmt.Errorf("dltcon: response AND-mask '%v' does not match request '%v'",
403 | binary.BigEndian.Uint16(response.Data[2:]), andMask)
404 | case binary.BigEndian.Uint16(response.Data[4:]) != orMask:
405 | return fmt.Errorf("dltcon: response OR-mask '%v' does not match request '%v'",
406 | binary.BigEndian.Uint16(response.Data[4:]), orMask)
407 | }
408 | return nil
409 | }
410 |
411 | // Request:
412 | // Slave Id : 1 byte
413 | // Function code : 1 byte (0x17)
414 | // Read starting address : 2 bytes
415 | // Quantity to read : 2 bytes
416 | // Write starting address: 2 bytes
417 | // Quantity to write : 2 bytes
418 | // Write byte count : 1 byte
419 | // Write registers value : N* bytes
420 | // Response:
421 | // Function code : 1 byte (0x17)
422 | // Byte count : 1 byte
423 | // Read registers value : Nx2 bytes
424 | func (sf *client) ReadWriteMultipleRegistersBytes(slaveID byte, readAddress, readQuantity,
425 | writeAddress, writeQuantity uint16, value []byte) ([]byte, error) {
426 | if slaveID < AddressMin || slaveID > AddressMax {
427 | return nil, fmt.Errorf("dltcon: slaveID '%v' must be between '%v' and '%v'",
428 | slaveID, AddressMin, AddressMax)
429 | }
430 | if readQuantity < ReadWriteOnReadRegQuantityMin || readQuantity > ReadWriteOnReadRegQuantityMax {
431 | return nil, fmt.Errorf("dltcon: quantity to read '%v' must be between '%v' and '%v'",
432 | readQuantity, ReadWriteOnReadRegQuantityMin, ReadWriteOnReadRegQuantityMax)
433 | }
434 | if writeQuantity < ReadWriteOnWriteRegQuantityMin || writeQuantity > ReadWriteOnWriteRegQuantityMax {
435 | return nil, fmt.Errorf("dltcon: quantity to write '%v' must be between '%v' and '%v'",
436 | writeQuantity, ReadWriteOnWriteRegQuantityMin, ReadWriteOnWriteRegQuantityMax)
437 | }
438 |
439 | response, err := sf.Send(slaveID, ProtocolDataUnit{
440 | FuncCode: FuncCodeReadWriteMultipleRegisters,
441 | Data: pduDataBlockSuffix(value, readAddress, readQuantity, writeAddress, writeQuantity),
442 | })
443 | if err != nil {
444 | return nil, err
445 | }
446 | if int(response.Data[0]) != (len(response.Data) - 1) {
447 | return nil, fmt.Errorf("dltcon: response data size '%v' does not match count '%v'",
448 | len(response.Data)-1, response.Data[0])
449 | }
450 | return response.Data[1:], nil
451 | }
452 |
453 | // Request:
454 | // Slave Id : 1 byte
455 | // Function code : 1 byte (0x17)
456 | // Read starting address quantity: 2 bytes
457 | // Quantity to read : 2 bytes
458 | // Write starting address: 2 bytes
459 | // Quantity to write : 2 bytes
460 | // Write byte count : 1 byte
461 | // Write registers value : N* bytes
462 | // Response:
463 | // Function code : 1 byte (0x17)
464 | // Byte count : 1 byte
465 | // Read registers value : N 2-bytes
466 | func (sf *client) ReadWriteMultipleRegisters(slaveID byte, readAddress, readQuantity,
467 | writeAddress, writeQuantity uint16, value []byte) ([]uint16, error) {
468 | b, err := sf.ReadWriteMultipleRegistersBytes(slaveID, readAddress, readQuantity,
469 | writeAddress, writeQuantity, value)
470 | if err != nil {
471 | return nil, err
472 | }
473 | return bytes2Uint16(b), nil
474 | }
475 |
476 | // Request:
477 | // Slave Id : 1 byte
478 | // Function code : 1 byte (0x18)
479 | // FIFO pointer address : 2 bytes
480 | // Response:
481 | // Function code : 1 byte (0x18)
482 | // Byte count : 2 bytes only include follow
483 | // FIFO count : 2 bytes (<=31)
484 | // FIFO value register : Nx2 bytes
485 | func (sf *client) ReadFIFOQueue(slaveID byte, address uint16) ([]byte, error) {
486 | if slaveID < AddressMin || slaveID > AddressMax {
487 | return nil, fmt.Errorf("dltcon: slaveID '%v' must be between '%v' and '%v'",
488 | slaveID, AddressMin, AddressMax)
489 | }
490 | response, err := sf.Send(slaveID, ProtocolDataUnit{
491 | FuncCode: FuncCodeReadFIFOQueue,
492 | Data: pduDataBlock(address),
493 | })
494 | switch {
495 | case err != nil:
496 | return nil, err
497 | case len(response.Data) < 4:
498 | return nil, fmt.Errorf("dltcon: response data size '%v' is less than expected '%v'",
499 | len(response.Data), 4)
500 | case len(response.Data)-2 != int(binary.BigEndian.Uint16(response.Data)):
501 | return nil, fmt.Errorf("dltcon: response data size '%v' does not match count '%v'",
502 | len(response.Data)-2, binary.BigEndian.Uint16(response.Data))
503 | case int(binary.BigEndian.Uint16(response.Data[2:])) > 31:
504 | return nil, fmt.Errorf("dltcon: fifo count '%v' is greater than expected '%v'",
505 | binary.BigEndian.Uint16(response.Data[2:]), 31)
506 | }
507 | return response.Data[4:], nil
508 | }
509 |
510 | // pduDataBlock creates a sequence of uint16 data.
511 | func pduDataBlock(value ...uint16) []byte {
512 | data := make([]byte, 2*len(value))
513 | for i, v := range value {
514 | binary.BigEndian.PutUint16(data[i*2:], v)
515 | }
516 | return data
517 | }
518 |
519 | // pduDataBlockSuffix creates a sequence of uint16 data and append the suffix plus its length.
520 | func pduDataBlockSuffix(suffix []byte, value ...uint16) []byte {
521 | length := 2 * len(value)
522 | data := make([]byte, length+1+len(suffix))
523 | for i, v := range value {
524 | binary.BigEndian.PutUint16(data[i*2:], v)
525 | }
526 | data[length] = uint8(len(suffix))
527 | copy(data[length+1:], suffix)
528 | return data
529 | }
530 |
531 | // responseError response error
532 | func responseError(response ProtocolDataUnit) error {
533 | mbError := &ExceptionError{}
534 | if response.Data != nil && len(response.Data) > 0 {
535 | mbError.ExceptionCode = response.Data[0]
536 | }
537 | return mbError
538 | }
539 |
540 | // bytes2Uint16 bytes conver to uint16 for register
541 | func bytes2Uint16(buf []byte) []uint16 {
542 | result := make([]uint16, len(buf)/2)
543 | for i := 0; i < len(buf)/2; i++ {
544 | result[i] = binary.BigEndian.Uint16(buf[i*2:])
545 | }
546 | return result
547 | }
548 |
--------------------------------------------------------------------------------
/crc.go:
--------------------------------------------------------------------------------
1 | package dlt645
2 |
3 | import (
4 | "sync"
5 | )
6 |
7 | // Cyclical Redundancy Checking
8 | type crc struct {
9 | once sync.Once
10 | table []uint16
11 | }
12 |
13 | var crcTb crc
14 |
15 | func crc16(bs []byte) uint16 {
16 | crcTb.once.Do(crcTb.initTable)
17 |
18 | val := uint16(0xFFFF)
19 | for _, v := range bs {
20 | val = (val >> 8) ^ crcTb.table[(val^uint16(v))&0x00FF]
21 | }
22 | return val
23 | }
24 |
25 | // initTable 初始化表
26 | func (c *crc) initTable() {
27 | crcPoly16 := uint16(0xa001)
28 | c.table = make([]uint16, 256)
29 |
30 | for i := uint16(0); i < 256; i++ {
31 | crc := uint16(0)
32 | b := i
33 |
34 | for j := uint16(0); j < 8; j++ {
35 | if ((crc ^ b) & 0x0001) > 0 {
36 | crc = (crc >> 1) ^ crcPoly16
37 | } else {
38 | crc = crc >> 1
39 | }
40 | b = b >> 1
41 | }
42 | c.table[i] = crc
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/crc_test.go:
--------------------------------------------------------------------------------
1 | package dlt645
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func Test_crc16(t *testing.T) {
8 | type args struct {
9 | bs []byte
10 | }
11 | tests := []struct {
12 | name string
13 | args args
14 | want uint16
15 | }{
16 | {"crc16 ", args{[]byte{0x01, 0x02, 0x03, 0x04, 0x05}}, 0xbb2a},
17 | }
18 | for _, tt := range tests {
19 | t.Run(tt.name, func(t *testing.T) {
20 | if got := crc16(tt.args.bs); got != tt.want {
21 | t.Errorf("crc16() = %v, want %v", got, tt.want)
22 | }
23 | })
24 | }
25 | }
26 |
27 | func Benchmark_crc16(b *testing.B) {
28 | for i := 0; i < b.N; i++ {
29 | _ = crc16([]byte{0x01, 0x02, 0x03, 0x04, 0x05})
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/dlt645.go:
--------------------------------------------------------------------------------
1 | package dlt645
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | // proto address limit
8 | const (
9 | AddressBroadCast = 0
10 | AddressMin = 1
11 | addressMax = 247
12 | )
13 |
14 | // AddressMax proto address max limit
15 | // you can change with SetSpecialAddressMax,
16 | // when your device have address upon addressMax
17 | var AddressMax byte = addressMax
18 |
19 | const (
20 | pduMinSize = 1 // funcCode(1)
21 | pduMaxSize = 253 // funcCode(1) + data(252)
22 |
23 | rtuAduMinSize = 4 // address(1) + funcCode(1) + crc(2)
24 | rtuAduMaxSize = 256 // address(1) + PDU(253) + crc(2)
25 |
26 | asciiAduMinSize = 3
27 | asciiAduMaxSize = 256
28 | asciiCharacterMaxSize = 513
29 |
30 | tcpProtocolIdentifier = 0x0000
31 |
32 | tcpHeaderMbapSize = 7 // MBAP header
33 | tcpAduMinSize = 8 // MBAP + funcCode
34 | tcpAduMaxSize = 260
35 | )
36 |
37 | // proto register limit
38 | const (
39 | // Bits
40 | ReadBitsQuantityMin = 1 // 0x0001
41 | ReadBitsQuantityMax = 2000 // 0x07d0
42 | WriteBitsQuantityMin = 1 // 1
43 | WriteBitsQuantityMax = 1968 // 0x07b0
44 | // 16 Bits
45 | ReadRegQuantityMin = 1 // 1
46 | ReadRegQuantityMax = 125 // 0x007d
47 | WriteRegQuantityMin = 1 // 1
48 | WriteRegQuantityMax = 123 // 0x007b
49 | ReadWriteOnReadRegQuantityMin = 1 // 1
50 | ReadWriteOnReadRegQuantityMax = 125 // 0x007d
51 | ReadWriteOnWriteRegQuantityMin = 1 // 1
52 | ReadWriteOnWriteRegQuantityMax = 121 // 0x0079
53 | )
54 |
55 | // Function Code
56 | const (
57 | // Bit access
58 | FuncCodeReadDiscreteInputs = 2
59 | FuncCodeReadCoils = 1
60 | FuncCodeWriteSingleCoil = 5
61 | FuncCodeWriteMultipleCoils = 15
62 |
63 | // 16-bit access
64 | FuncCodeReadInputRegisters = 4
65 | FuncCodeReadHoldingRegisters = 3
66 | FuncCodeWriteSingleRegister = 6
67 | FuncCodeWriteMultipleRegisters = 16
68 | FuncCodeReadWriteMultipleRegisters = 23
69 | FuncCodeMaskWriteRegister = 22
70 | FuncCodeReadFIFOQueue = 24
71 | FuncCodeOtherReportSlaveID = 17
72 | // FuncCodeDiagReadException = 7
73 | // FuncCodeDiagDiagnostic = 8
74 | // FuncCodeDiagGetComEventCnt = 11
75 | // FuncCodeDiagGetComEventLog = 12
76 | )
77 |
78 | // Exception Code
79 | const (
80 | ExceptionCodeIllegalFunction = 1
81 | ExceptionCodeIllegalDataAddress = 2
82 | ExceptionCodeIllegalDataValue = 3
83 | ExceptionCodeServerDeviceFailure = 4
84 | ExceptionCodeAcknowledge = 5
85 | ExceptionCodeServerDeviceBusy = 6
86 | ExceptionCodeNegativeAcknowledge = 7
87 | ExceptionCodeMemoryParityError = 8
88 | ExceptionCodeGatewayPathUnavailable = 10
89 | ExceptionCodeGatewayTargetDeviceFailedToRespond = 11
90 | )
91 |
92 | // ExceptionError implements error interface.
93 | type ExceptionError struct {
94 | ExceptionCode byte
95 | }
96 |
97 | func (e *ExceptionError) Error() string {
98 | var name string
99 | switch e.ExceptionCode {
100 | case ExceptionCodeIllegalFunction:
101 | name = "illegal function"
102 | case ExceptionCodeIllegalDataAddress:
103 | name = "illegal data address"
104 | case ExceptionCodeIllegalDataValue:
105 | name = "illegal data value"
106 | case ExceptionCodeServerDeviceFailure:
107 | name = "server device failure"
108 | case ExceptionCodeAcknowledge:
109 | name = "acknowledge"
110 | case ExceptionCodeServerDeviceBusy:
111 | name = "server device busy"
112 | case ExceptionCodeNegativeAcknowledge:
113 | name = "Negative Acknowledge"
114 | case ExceptionCodeMemoryParityError:
115 | name = "memory parity error"
116 | case ExceptionCodeGatewayPathUnavailable:
117 | name = "gateway path unavailable"
118 | case ExceptionCodeGatewayTargetDeviceFailedToRespond:
119 | name = "gateway target device failed to respond"
120 | default:
121 | name = "unknown"
122 | }
123 | return fmt.Sprintf("dltcon: exception '%v' (%s)", e.ExceptionCode, name)
124 | }
125 |
126 | // ProtocolDataUnit (PDU) is independent of underlying communication layers.
127 | type ProtocolDataUnit struct {
128 | FuncCode byte
129 | Data []byte
130 | }
131 |
132 | // protocolFrame 帧结构用于底层对象缓冲池
133 | type protocolFrame struct {
134 | adu []byte
135 | }
136 |
137 | // ClientProvider is the interface implements underlying methods.
138 | type ClientProvider interface {
139 | // Connect try to connect the remote server
140 | Connect() error
141 | // IsConnected returns a bool signifying whether
142 | // the client is connected or not.
143 | IsConnected() bool
144 | // SetAutoReconnect set auto reconnect count
145 | // if cnt == 0, disable auto reconnect
146 | // if cnt > 0 ,enable auto reconnect,but max 6
147 | SetAutoReconnect(cnt byte)
148 | // LogMode set enable or diable log output when you has set logger
149 | LogMode(enable bool)
150 | // SetLogProvider set logger provider
151 | SetLogProvider(p LogProvider)
152 | // Close disconnect the remote server
153 | Close() error
154 | // Send request to the remote server,it implements on SendRawFrame
155 | Send(slaveID byte, request ProtocolDataUnit) (ProtocolDataUnit, error)
156 | // SendPdu send pdu request to the remote server
157 | SendPdu(slaveID byte, pduRequest []byte) (pduResponse []byte, err error)
158 | // SendRawFrame send raw frame to the remote server
159 | SendRawFrame(request string) (response int16, err error)
160 | }
161 |
162 | // LogProvider RFC5424 log message levels only Debug and Error
163 | type LogProvider interface {
164 | Error(format string, v ...interface{})
165 | Debug(format string, v ...interface{})
166 | }
167 |
--------------------------------------------------------------------------------
/dlt645ConfigClient.go:
--------------------------------------------------------------------------------
1 | package dlt645
2 |
3 | import (
4 | "fmt"
5 | "strconv"
6 | "strings"
7 | )
8 |
9 | type Dlt645ConfigClient struct {
10 | MeterNumber string
11 | DataMarker string
12 | }
13 |
14 | func (dltconfig *Dlt645ConfigClient) SendMessageToSerial(dlt Client) (response int16, err error) {
15 | //表号
16 | meterNumberHandle := HexStringToBytes(dltconfig.MeterNumber)
17 | meterNumberHandleX := fmt.Sprintf("% x", meterNumberHandle)
18 | meterNumberHandleReverse := strings.Split(meterNumberHandleX, " ")
19 | for i := 0; i < len(meterNumberHandleReverse)/2; i++ {
20 | mid := meterNumberHandleReverse[i]
21 | meterNumberHandleReverse[i] = meterNumberHandleReverse[len(meterNumberHandleReverse)-1-i]
22 | meterNumberHandleReverse[len(meterNumberHandleReverse)-1-i] = mid
23 | }
24 | midMeterNumberHandle := fmt.Sprintf("% s", meterNumberHandleReverse)
25 | meterNumberHandleReverseFinished := strings.Replace(midMeterNumberHandle, "[", "", -1)
26 | meterNumberHandleReverseFinished = strings.Replace(meterNumberHandleReverseFinished, "]", "", -1)
27 | //数据标识
28 | DataMarkerHandle := HexStringToBytes(dltconfig.DataMarker)
29 | DataMarkerHandleX := fmt.Sprintf("% x", DataMarkerHandle)
30 | DataMarkerHandleReverse := strings.Split(DataMarkerHandleX, " ")
31 | for i := 0; i < len(DataMarkerHandleReverse)/2; i++ {
32 | mid := DataMarkerHandleReverse[i]
33 | DataMarkerForEnd, _ := strconv.Atoi(DataMarkerHandleReverse[len(DataMarkerHandleReverse)-1-i])
34 | DataMarkerHandleReverse[i] = strconv.Itoa(DataMarkerForEnd + 33)
35 | DataMarkerForStrt, _ := strconv.Atoi(mid)
36 | DataMarkerHandleReverse[len(DataMarkerHandleReverse)-1-i] = strconv.Itoa(DataMarkerForStrt + 33)
37 | }
38 | midDataMarkerHandle := fmt.Sprintf("% s", DataMarkerHandleReverse)
39 | DataMarkerHandleReverseFinished := strings.Replace(midDataMarkerHandle, "[", "", -1)
40 | DataMarkerHandleReverseFinished = strings.Replace(DataMarkerHandleReverseFinished, "]", "", -1)
41 |
42 | messageFinshed := "68 " + meterNumberHandleReverseFinished + " 68" + " 11 " + "04 " + DataMarkerHandleReverseFinished
43 | value, err := dlt.SendRawFrame(CheckCode(messageFinshed))
44 | return value, err
45 | }
46 |
47 | //计算出校验码
48 | func CheckCode(data string) string {
49 | midData := data
50 | data = strings.ReplaceAll(data, " ", "")
51 | total := 0
52 | length := len(data)
53 | num := 0
54 | for num < length {
55 | s := data[num : num+2]
56 | //16进制转换成10进制
57 | totalMid, _ := strconv.ParseUint(s, 16, 32)
58 | total += int(totalMid)
59 | num = num + 2
60 | }
61 | //将校验码前面的所有数通过16进制加起来转换成10进制,然后除256区余数,然后余数转换成16进制,得到的就是校验码
62 | mod := total % 256
63 | hex, _ := DecConvertToX(mod, 16)
64 | len := len(hex)
65 | //如果校验位长度不够,就补0,因为校验位必须是要2位
66 | if len < 2 {
67 | hex = "0" + hex
68 | }
69 | return midData + " " + strings.ToUpper(hex) + " 16"
70 | }
71 |
--------------------------------------------------------------------------------
/dlt645client.go:
--------------------------------------------------------------------------------
1 | package dlt645
2 |
3 | import (
4 | "encoding/binary"
5 | "fmt"
6 | "io"
7 | "strings"
8 | "time"
9 | )
10 |
11 | const (
12 | rtuExceptionSize = 5
13 | )
14 |
15 | // RTUClientProvider implements ClientProvider interface.
16 | type Dlt645ClientProvider struct {
17 | serialPort
18 | logger
19 | *pool // 请求池,所有RTU客户端共用一个请求池
20 | }
21 |
22 | // check RTUClientProvider implements underlying method
23 | var _ ClientProvider = (*Dlt645ClientProvider)(nil)
24 |
25 | // 请求池,所有RTU客户端共用一个请求池
26 | var rtuPool = newPool(rtuAduMaxSize)
27 |
28 | // NewRTUClientProvider allocates and initializes a RTUClientProvider.
29 | // it will use default /dev/ttyS0 19200 8 1 N and timeout 1000
30 | func NewClientProvider() *Dlt645ClientProvider {
31 | p := &Dlt645ClientProvider{
32 | logger: newLogger("dlt645OutputLog =>"),
33 | pool: rtuPool,
34 | }
35 | p.Timeout = SerialDefaultTimeout
36 | p.autoReconnect = SerialDefaultAutoReconnect
37 | return p
38 | }
39 |
40 | func (sf *protocolFrame) encodeRTUFrame(slaveID byte, pdu ProtocolDataUnit) ([]byte, error) {
41 | length := len(pdu.Data) + 4
42 | if length > rtuAduMaxSize {
43 | return nil, fmt.Errorf("dltcon: length of data '%v' must not be bigger than '%v'", length, rtuAduMaxSize)
44 | }
45 | requestAdu := sf.adu[:0:length]
46 | requestAdu = append(requestAdu, slaveID, pdu.FuncCode)
47 | requestAdu = append(requestAdu, pdu.Data...)
48 | checksum := crc16(requestAdu)
49 | requestAdu = append(requestAdu, byte(checksum), byte(checksum>>8))
50 | return requestAdu, nil
51 | }
52 |
53 | // decode extracts slaveID and PDU from RTU frame and verify CRC.
54 | //解码从RTU帧中提取slaveID和PDU,并验证CRC。
55 | func decodeRTUFrame(adu []byte) (uint8, []byte, error) {
56 | if len(adu) < rtuAduMinSize { // Minimum size (including address, funcCode and CRC)
57 | return 0, nil, fmt.Errorf("dltcon: response length '%v' does not meet minimum '%v'", len(adu), rtuAduMinSize)
58 | }
59 | // Calculate checksum
60 | crc := crc16(adu[0 : len(adu)-2])
61 | expect := binary.LittleEndian.Uint16(adu[len(adu)-2:])
62 | if crc != expect {
63 | return 0, nil, fmt.Errorf("dltcon: response crc '%x' does not match expected '%x'", expect, crc)
64 | }
65 | // slaveID & PDU but pass crc
66 | return adu[0], adu[1 : len(adu)-2], nil
67 | }
68 |
69 | // Send request to the remote server,it implements on SendRawFrame
70 | func (sf *Dlt645ClientProvider) Send(slaveID byte, request ProtocolDataUnit) (ProtocolDataUnit, error) {
71 | return ProtocolDataUnit{}, nil
72 |
73 | }
74 |
75 | // SendPdu send pdu request to the remote server
76 | func (sf *Dlt645ClientProvider) SendPdu(slaveID byte, pduRequest []byte) ([]byte, error) {
77 |
78 | return nil, nil
79 | }
80 |
81 | // SendRawFrame send Adu frame
82 | // SendRawFrame发送Adu帧。
83 | func (dlt *Dlt645ClientProvider) SendRawFrame(request string) (response int16, err error) {
84 | request = strings.Replace(request, " ", "", -1)
85 | dlt.mu.Lock()
86 | defer dlt.mu.Unlock()
87 |
88 | // check port is connected
89 | if !dlt.isConnected() {
90 | return 0, ErrClosedConnection
91 | }
92 |
93 | // Send the request
94 | //yoyo := []byte{104,01,00,68,35,82,00,104,17,04,51,51,52,51,108,22}
95 | serialMessage := HexStringToBytes(request)
96 | dlt.Debug("sending [% x]", serialMessage)
97 |
98 | //68 01 00 44 23 52 00 68 91 18 33 32 34 33 BC 36 33 33 33 33 33 33 33 33 33 33 BC 36 33 33 33 33 33 33 13 16
99 | //68 01 00 44 23 52 00 68 91 06 33 36 34 35 95 55 dd 16
100 |
101 | //test := fmt.Sprintf("wtf?sending [% x]", aduRequest)
102 | //fmt.Println(test)
103 |
104 | var tryCnt byte
105 | for {
106 | _, err = dlt.port.Write(serialMessage)
107 | if err == nil { // success
108 | break
109 | }
110 | if dlt.autoReconnect == 0 {
111 | return
112 | }
113 | for {
114 | err = dlt.connect()
115 | if err == nil {
116 | break
117 | }
118 | if tryCnt++; tryCnt >= dlt.autoReconnect {
119 | return
120 | }
121 | }
122 | }
123 |
124 | var data [rtuAduMaxSize]byte
125 |
126 | //bytesToRead := calculateResponseLength(HexStringToBytes(request))
127 | //time.Sleep(dlt.calculateDelay(len(HexStringToBytes(request)) + bytesToRead))
128 |
129 | sum, _ := io.ReadFull(dlt.port, data[:])
130 | backData := fmt.Sprintf("[% x]", data[0:sum])
131 |
132 | return analysis(dlt, backData), nil
133 | }
134 |
135 | //把字符串转换成字节数组
136 | func HexStringToBytes(data string) []byte {
137 | if "" == data {
138 | return nil
139 | }
140 | data = strings.ToUpper(data)
141 | length := len(data) / 2
142 | dataChars := []byte(data)
143 | var byteData []byte = make([]byte, length)
144 | for i := 0; i < length; i++ {
145 | pos := i * 2
146 | byteData[i] = byte(charToByte(dataChars[pos])<<4 | charToByte(dataChars[pos+1]))
147 | }
148 | return byteData
149 |
150 | }
151 |
152 | func charToByte(c byte) byte {
153 | return (byte)(strings.Index("0123456789ABCDEF", string(c)))
154 | }
155 |
156 | // calculateDelay roughly calculates time needed for the next frame.
157 | // See dltcon over Serial Line - Specification and Implementation Guide (page 13).
158 | func (sf *Dlt645ClientProvider) calculateDelay(chars int) time.Duration {
159 | var characterDelay, frameDelay int // us
160 |
161 | if sf.BaudRate <= 0 || sf.BaudRate > 19200 {
162 | characterDelay = 750
163 | frameDelay = 1750
164 | } else {
165 | characterDelay = 15000000 / sf.BaudRate
166 | frameDelay = 35000000 / sf.BaudRate
167 | }
168 | return time.Duration(characterDelay*chars+frameDelay) * time.Microsecond
169 | }
170 |
171 | func calculateResponseLength(adu []byte) int {
172 | length := rtuAduMinSize
173 | switch adu[1] {
174 | case FuncCodeReadDiscreteInputs,
175 | FuncCodeReadCoils:
176 | count := int(binary.BigEndian.Uint16(adu[4:]))
177 | length += 1 + count/8
178 | if count%8 != 0 {
179 | length++
180 | }
181 | case FuncCodeReadInputRegisters,
182 | FuncCodeReadHoldingRegisters,
183 | FuncCodeReadWriteMultipleRegisters:
184 | count := int(binary.BigEndian.Uint16(adu[4:]))
185 | length += 1 + count*2
186 | case FuncCodeWriteSingleCoil,
187 | FuncCodeWriteMultipleCoils,
188 | FuncCodeWriteSingleRegister,
189 | FuncCodeWriteMultipleRegisters:
190 | length += 4
191 | case FuncCodeMaskWriteRegister:
192 | length += 6
193 | case FuncCodeReadFIFOQueue:
194 | // undetermined
195 | default:
196 | }
197 | return length
198 | }
199 |
--------------------------------------------------------------------------------
/dltcon/dlt.go:
--------------------------------------------------------------------------------
1 | package dltcon
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "fmt"
7 | "github.com/themeyic/go-dlt645"
8 | "math/rand"
9 | "time"
10 |
11 | "github.com/themeyic/timing"
12 | )
13 |
14 | const (
15 | // DefaultRandValue 单位ms
16 | // 默认随机值上限,它影响当超时请求入ready队列时,
17 | // 当队列满,会启动一个随机时间rand.Intn(v)*1ms 延迟入队
18 | // 用于需要重试的延迟重试时间
19 | DefaultRandValue = 50
20 | // DefaultReadyQueuesLength 默认就绪列表长度
21 | DefaultReadyQueuesLength = 128
22 | )
23 |
24 | // Client 客户端
25 | type Client struct {
26 | dlt645.Client
27 | randValue int
28 | readyQueueSize int
29 | ready chan *Request
30 | handler Handler
31 | panicHandle func(err interface{})
32 | ctx context.Context
33 | cancel context.CancelFunc
34 | }
35 |
36 | // Result 某个请求的结果与参数
37 | type Result struct {
38 | SlaveID byte // 从机地址
39 | FuncCode byte // 功能码
40 | Address uint16 // 请求数据用实际地址
41 | Quantity uint16 // 请求数量
42 | ScanRate time.Duration // 扫描速率scan rate
43 | TxCnt uint64 // 发送计数
44 | ErrCnt uint64 // 发送错误计数
45 | }
46 |
47 | // Request 请求
48 | type Request struct {
49 | SlaveID byte // 从机地址
50 | FuncCode byte // 功能码
51 | Address uint16 // 请求数据用实际地址
52 | Quantity uint16 // 请求数量
53 | ScanRate time.Duration // 扫描速率scan rate
54 | Retry byte // 失败重试次数
55 | retryCnt byte // 重试计数
56 | txCnt uint64 // 发送计数
57 | errCnt uint64 // 发送错误计数
58 | tm *timing.Entry // 时间句柄
59 | }
60 |
61 | // NewClient 创建新的client
62 | func NewClient(p dlt645.ClientProvider, opts ...Option) *Client {
63 | ctx, cancel := context.WithCancel(context.Background())
64 | c := &Client{
65 | Client: dlt645.NewClient(p),
66 | randValue: DefaultRandValue,
67 | readyQueueSize: DefaultReadyQueuesLength,
68 | handler: &NopProc{},
69 | panicHandle: func(interface{}) {},
70 | ctx: ctx,
71 | cancel: cancel,
72 | }
73 |
74 | for _, f := range opts {
75 | f(c)
76 | }
77 | c.ready = make(chan *Request, c.readyQueueSize)
78 | return c
79 | }
80 |
81 | // Start 启动
82 | func (sf *Client) Start() error {
83 | if err := sf.Connect(); err != nil {
84 | return err
85 | }
86 | go sf.readPoll()
87 | return nil
88 | }
89 |
90 | // Close 关闭
91 | func (sf *Client) Close() error {
92 | sf.cancel()
93 | return sf.Client.Close()
94 | }
95 |
96 | // AddGatherJob 增加采集任务
97 | func (sf *Client) AddGatherJob(r Request) error {
98 | var quantityMax int
99 |
100 | if err := sf.ctx.Err(); err != nil {
101 | return err
102 | }
103 |
104 | if r.SlaveID < dlt645.AddressMin || r.SlaveID > dlt645.AddressMax {
105 | return fmt.Errorf("dltcon: slaveID '%v' must be between '%v' and '%v'",
106 | r.SlaveID, dlt645.AddressMin, dlt645.AddressMax)
107 | }
108 |
109 | switch r.FuncCode {
110 | case dlt645.FuncCodeReadCoils, dlt645.FuncCodeReadDiscreteInputs:
111 | quantityMax = dlt645.ReadBitsQuantityMax
112 | case dlt645.FuncCodeReadInputRegisters, dlt645.FuncCodeReadHoldingRegisters:
113 | quantityMax = dlt645.ReadRegQuantityMax
114 | default:
115 | return errors.New("invalid function code")
116 | }
117 |
118 | address := r.Address
119 | remain := int(r.Quantity)
120 | for remain > 0 {
121 | count := remain
122 | if count > quantityMax {
123 | count = quantityMax
124 | }
125 |
126 | req := &Request{
127 | SlaveID: r.SlaveID,
128 | FuncCode: r.FuncCode,
129 | Address: address,
130 | Quantity: uint16(count),
131 | ScanRate: r.ScanRate,
132 | }
133 |
134 | req.tm = timing.NewOneShotFuncEntry(func() {
135 | select {
136 | case <-sf.ctx.Done():
137 | return
138 | case sf.ready <- req:
139 | default:
140 | timing.Start(req.tm, time.Duration(rand.Intn(sf.randValue))*time.Millisecond)
141 | }
142 | }, req.ScanRate)
143 | timing.Start(req.tm)
144 |
145 | address += uint16(count)
146 | remain -= count
147 | }
148 | return nil
149 | }
150 |
151 | // 读协程
152 | func (sf *Client) readPoll() {
153 | var req *Request
154 |
155 | for {
156 | select {
157 | case <-sf.ctx.Done():
158 | return
159 | case req = <-sf.ready: // 查看是否有准备好的请求
160 | sf.procRequest(req)
161 | }
162 | }
163 | }
164 |
165 | func (sf *Client) procRequest(req *Request) {
166 | var err error
167 | var result []byte
168 |
169 | defer func() {
170 | if err := recover(); err != nil {
171 | sf.panicHandle(err)
172 | }
173 | }()
174 |
175 | req.txCnt++
176 | switch req.FuncCode {
177 | // Bit access read
178 | case dlt645.FuncCodeReadCoils:
179 | result, err = sf.ReadCoils(req.SlaveID, req.Address, req.Quantity)
180 | if err != nil {
181 | req.errCnt++
182 | } else {
183 | sf.handler.ProcReadCoils(req.SlaveID, req.Address, req.Quantity, result)
184 | }
185 | case dlt645.FuncCodeReadDiscreteInputs:
186 | result, err = sf.ReadDiscreteInputs(req.SlaveID, req.Address, req.Quantity)
187 | if err != nil {
188 | req.errCnt++
189 | } else {
190 | sf.handler.ProcReadDiscretes(req.SlaveID, req.Address, req.Quantity, result)
191 | }
192 |
193 | // 16-bit access read
194 | case dlt645.FuncCodeReadHoldingRegisters:
195 | result, err = sf.ReadHoldingRegistersBytes(req.SlaveID, req.Address, req.Quantity)
196 | if err != nil {
197 | req.errCnt++
198 | } else {
199 | sf.handler.ProcReadHoldingRegisters(req.SlaveID, req.Address, req.Quantity, result)
200 | }
201 |
202 | case dlt645.FuncCodeReadInputRegisters:
203 | result, err = sf.ReadInputRegistersBytes(req.SlaveID, req.Address, req.Quantity)
204 | if err != nil {
205 | req.errCnt++
206 | } else {
207 | sf.handler.ProcReadInputRegisters(req.SlaveID, req.Address, req.Quantity, result)
208 | }
209 |
210 | // FIFO read
211 | //case dltcon.FuncCodeReadFIFOQueue:
212 | // _, err = sf.ReadFIFOQueue(req.SlaveID, req.Address)
213 | // if err != nil {
214 | // req.errCnt++
215 | // }
216 | }
217 | if err != nil && req.Retry > 0 {
218 | if req.retryCnt++; req.retryCnt < req.Retry {
219 | timing.Start(req.tm, time.Duration(rand.Intn(sf.randValue))*time.Millisecond)
220 | } else if req.ScanRate > 0 {
221 | timing.Start(req.tm)
222 | }
223 | } else if req.ScanRate > 0 {
224 | timing.Start(req.tm)
225 | }
226 |
227 | sf.handler.ProcResult(err, &Result{
228 | req.SlaveID,
229 | req.FuncCode,
230 | req.Address,
231 | req.Quantity,
232 | req.ScanRate,
233 | req.txCnt,
234 | req.errCnt,
235 | })
236 | }
237 |
--------------------------------------------------------------------------------
/dltcon/option.go:
--------------------------------------------------------------------------------
1 | package dltcon
2 |
3 | // Option 可选项
4 | type Option func(client *Client)
5 |
6 | // WithReadyQueueSize 就绪队列长度
7 | func WithReadyQueueSize(size int) Option {
8 | return func(client *Client) {
9 | client.readyQueueSize = size
10 | }
11 | }
12 |
13 | // WitchHandler 配置handler
14 | func WitchHandler(h Handler) Option {
15 | return func(client *Client) {
16 | if h != nil {
17 | client.handler = h
18 | }
19 | }
20 | }
21 |
22 | // WitchRetryRandValue 单位ms
23 | // 默认随机值上限,它影响当超时请求入ready队列时,
24 | // 当队列满,会启动一个随机时间rand.Intn(v)*1ms 延迟入队
25 | // 用于需要重试的延迟重试时间
26 | func WitchRetryRandValue(v int) Option {
27 | return func(client *Client) {
28 | if v > 0 {
29 | client.randValue = v
30 | }
31 | }
32 | }
33 |
34 | // WitchPanicHandle 发生panic回调,主要用于调试
35 | func WitchPanicHandle(f func(interface{})) Option {
36 | return func(client *Client) {
37 | if f != nil {
38 | client.panicHandle = f
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/dltcon/proc.go:
--------------------------------------------------------------------------------
1 | package dltcon
2 |
3 | // Handler 处理函数
4 | type Handler interface {
5 | ProcReadCoils(slaveID byte, address, quality uint16, valBuf []byte)
6 | ProcReadDiscretes(slaveID byte, address, quality uint16, valBuf []byte)
7 | ProcReadHoldingRegisters(slaveID byte, address, quality uint16, valBuf []byte)
8 | ProcReadInputRegisters(slaveID byte, address, quality uint16, valBuf []byte)
9 | ProcResult(err error, result *Result)
10 | }
11 |
12 | type NopProc struct{}
13 |
14 | func (NopProc) ProcReadCoils(byte, uint16, uint16, []byte) {}
15 | func (NopProc) ProcReadDiscretes(byte, uint16, uint16, []byte) {}
16 | func (NopProc) ProcReadHoldingRegisters(byte, uint16, uint16, []byte) {}
17 | func (NopProc) ProcReadInputRegisters(byte, uint16, uint16, []byte) {}
18 | func (NopProc) ProcResult(_ error, result *Result) {
19 | //log.Printf("Tx=%d,Err=%d,SlaveID=%d,FC=%d,Address=%d,Quantity=%d,SR=%dms",
20 | // result.TxCnt, result.ErrCnt, result.SlaveID, result.FuncCode,
21 | // result.Address, result.Quantity, result.ScanRate/time.Millisecond)
22 | }
23 |
--------------------------------------------------------------------------------
/error.go:
--------------------------------------------------------------------------------
1 | package dlt645
2 |
3 | import (
4 | "errors"
5 | )
6 |
7 | // ErrClosedConnection 连接已关闭
8 | var ErrClosedConnection = errors.New("use of closed connection")
9 |
--------------------------------------------------------------------------------
/example/example.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | dlt "github.com/themeyic/go-dlt645"
6 | "github.com/themeyic/go-dlt645/dltcon"
7 | "time"
8 | )
9 |
10 | func main() {
11 | //调用ClientProvider的构造函数,返回结构体指针
12 | p := dlt.NewClientProvider()
13 | //windows 下面就是 com开头的,比如说 com3
14 | //mac OS 下面就是 /dev/下面的,比如说 dev/tty.usbserial-14320
15 | p.Address = "com3"
16 | p.BaudRate = 2400
17 | p.DataBits = 8
18 | p.Parity = "E"
19 | p.StopBits = 1
20 | p.Timeout = 100 * time.Millisecond
21 |
22 | client := dltcon.NewClient(p)
23 | client.LogMode(true)
24 | err := client.Start()
25 | if err != nil {
26 | fmt.Println("start err,", err)
27 | return
28 | }
29 | //MeterNumber是表号 005223440001
30 | //DataMarker是数据标识别 02010300
31 | test := &dlt.Dlt645ConfigClient{"005223440001", "02010300"}
32 | for {
33 | value, err := test.SendMessageToSerial(client)
34 | if err != nil {
35 | fmt.Println("readHoldErr,", err)
36 | } else {
37 | fmt.Printf("%#v\n", value)
38 | }
39 | time.Sleep(time.Second * 3)
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/themeyic/go-dlt645
2 |
3 | go 1.12
4 |
5 | require (
6 | github.com/goburrow/serial v0.1.0
7 | github.com/themeyic/timing v1.1.2
8 | github.com/BurntSushi/toml v0.3.1
9 | github.com/shopspring/decimal v1.2.0
10 | )
11 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
3 | github.com/goburrow/serial v0.1.0 h1:v2T1SQa/dlUqQiYIT8+Cu7YolfqAi3K96UmhwYyuSrA=
4 | github.com/goburrow/serial v0.1.0/go.mod h1:sAiqG0nRVswsm1C97xsttiYCzSLBmUZ/VSlVLZJ8haA=
5 | github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
6 | github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
7 | github.com/themeyic/timing v1.1.2 h1:QxJlh+KaL0OW+LXXij2Q3udCeVVERtP2y9M+5RoCuTc=
8 | github.com/themeyic/timing v1.1.2/go.mod h1:YspyGgaGbFOSpkLO0K48oDmYujwSLUoazYB3n2zn294=
9 | github.com/thinkgos/timing v1.1.2 h1:Gkpht/YViWFl/ADWy85VAQygaKcn8K8q/WLWcE5Rfag=
10 | github.com/thinkgos/timing v1.1.2/go.mod h1:zS1qzRn2ISTjI1xJc6AHNfWAvCtc7FylwWjt32L8qvo=
11 |
--------------------------------------------------------------------------------
/log.go:
--------------------------------------------------------------------------------
1 | package dlt645
2 |
3 | import (
4 | "os"
5 | "sync/atomic"
6 |
7 | "log"
8 | )
9 |
10 | // 内部调试实现
11 | type logger struct {
12 | provider LogProvider
13 | // has log output enabled,
14 | // 1: enable
15 | // 0: disable
16 | has uint32
17 | }
18 |
19 | // newLogger new logger with prefix
20 | func newLogger(prefix string) logger {
21 | return logger{
22 | provider: defaultLogger{log.New(os.Stdout, prefix, log.LstdFlags)},
23 | has: 0,
24 | }
25 | }
26 |
27 | // LogMode set enable or disable log output when you has set logger
28 | func (sf *logger) LogMode(enable bool) {
29 | if enable {
30 | atomic.StoreUint32(&sf.has, 1)
31 | } else {
32 | atomic.StoreUint32(&sf.has, 0)
33 | }
34 | }
35 |
36 | // SetLogProvider overwrite log provider
37 | func (sf *logger) SetLogProvider(p LogProvider) {
38 | if p != nil {
39 | sf.provider = p
40 | }
41 | }
42 |
43 | // Error Log ERROR level message.
44 | func (sf logger) Error(format string, v ...interface{}) {
45 | if atomic.LoadUint32(&sf.has) == 1 {
46 | sf.provider.Error(format, v...)
47 | }
48 | }
49 |
50 | // Debug Log DEBUG level message.
51 | func (sf logger) Debug(format string, v ...interface{}) {
52 | if atomic.LoadUint32(&sf.has) == 1 {
53 | sf.provider.Debug(format, v...)
54 | }
55 | }
56 |
57 | // default log
58 | type defaultLogger struct {
59 | *log.Logger
60 | }
61 |
62 | // check implement LogProvider interface
63 | var _ LogProvider = (*defaultLogger)(nil)
64 |
65 | // Error Log ERROR level message.
66 | func (sf defaultLogger) Error(format string, v ...interface{}) {
67 | sf.Printf("[E]: "+format, v...)
68 | }
69 |
70 | // Debug Log DEBUG level message.
71 | func (sf defaultLogger) Debug(format string, v ...interface{}) {
72 | sf.Printf("[D]: "+format, v...)
73 | }
74 |
--------------------------------------------------------------------------------
/messageAnalysis.go:
--------------------------------------------------------------------------------
1 | package dlt645
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "github.com/BurntSushi/toml"
7 | "github.com/shopspring/decimal"
8 | "io/ioutil"
9 | "strconv"
10 | "strings"
11 | )
12 |
13 | func DecConvertToX(n, num int) (string, error) {
14 | if n < 0 {
15 | return strconv.Itoa(n), errors.New("只支持正整数")
16 | }
17 | if num != 2 && num != 8 && num != 16 {
18 | return strconv.Itoa(n), errors.New("只支持二、八、十六进制的转换")
19 | }
20 | result := ""
21 | h := map[int]string{
22 | 0: "0",
23 | 1: "1",
24 | 2: "2",
25 | 3: "3",
26 | 4: "4",
27 | 5: "5",
28 | 6: "6",
29 | 7: "7",
30 | 8: "8",
31 | 9: "9",
32 | 10: "A",
33 | 11: "B",
34 | 12: "C",
35 | 13: "D",
36 | 14: "E",
37 | 15: "F",
38 | }
39 | for ; n > 0; n /= num {
40 | lsb := h[n%num]
41 | result = lsb + result
42 | }
43 | return result, nil
44 | }
45 |
46 | func analysis(dlt *Dlt645ClientProvider, command string) int16 {
47 | command = strings.Replace(command, "[", "", -1)
48 | command = strings.Replace(command, "]", "", -1)
49 | newCommands := strings.Split(command, " ")
50 | start, _ := strconv.Atoi(newCommands[0])
51 | end, _ := strconv.Atoi(newCommands[len(newCommands)-1])
52 | if len(newCommands) < 16 || len(newCommands) > 26 || start != 68 || end != 16 {
53 | panic("非法帧,无法解析!")
54 | } else {
55 | dlt.Debug("报文源码:%s", command)
56 | dlt.Debug("帧起始符:%s", newCommands[0])
57 | meter_serial := newCommands[6] + newCommands[5] + newCommands[4] + newCommands[3] + newCommands[2] + newCommands[1]
58 | dlt.Debug("电表地址:%s", meter_serial)
59 | dlt.Debug("控制域:%s", newCommands[8])
60 | dlt.Debug("数据域长度:%s", newCommands[9])
61 | dlt.Debug("校验码:%s", newCommands[len(newCommands)-2])
62 | dlt.Debug("停止位:%s", newCommands[len(newCommands)-1])
63 |
64 | dltData, _ := strconv.ParseUint(newCommands[13], 16, 32)
65 | dltData = dltData - 51
66 | var dltDataFinished string
67 | if len(strconv.FormatInt(int64(dltData), 10)) == 1 {
68 | dltDataFinished = "0" + strconv.Itoa(int(dltData))
69 | }
70 |
71 | dltData1, _ := strconv.ParseUint(newCommands[12], 16, 32)
72 | dltData1 = dltData1 - 51
73 | var dltDataFinished1 string
74 | if len(strconv.FormatInt(int64(dltData1), 10)) == 1 {
75 | dltDataFinished1 = "0" + strconv.Itoa(int(dltData1))
76 | }
77 |
78 | dltData2, _ := strconv.ParseUint(newCommands[11], 16, 32)
79 | dltData2 = dltData2 - 51
80 | var dltDataFinished2 string
81 |
82 | if len(strconv.FormatInt(int64(dltData2), 10)) == 1 {
83 | dltDataFinished2 = "0" + strconv.Itoa(int(dltData2))
84 | }
85 |
86 | dltData3, _ := strconv.ParseUint(newCommands[10], 16, 32)
87 | dltData3 = dltData3 - 51
88 |
89 | var dltDataFinished3 string
90 | if len(strconv.FormatInt(int64(dltData3), 10)) == 1 {
91 | dltDataFinished3 = "0" + strconv.Itoa(int(dltData3))
92 | }
93 |
94 | makers := dltDataFinished + dltDataFinished1 + dltDataFinished2 + dltDataFinished3
95 | dlt.Debug("数据标识:%s", makers)
96 | dataUnits := len(newCommands) - 2
97 | var data string
98 | for i := dataUnits; i > 14; i-- {
99 | v1, _ := strconv.ParseUint(newCommands[i-1], 16, 32)
100 | v2, _ := strconv.ParseUint("33", 16, 32)
101 | midData, _ := DecConvertToX(int(v1-v2), 16)
102 | if len(midData) == 1 {
103 | midData = "0" + midData
104 | }
105 | data += fmt.Sprintf("%s", midData)
106 | }
107 |
108 | contents, err1 := ioutil.ReadFile("C:\\Users\\yic\\sdk\\go1.14.3\\src\\go-dltcon\\res\\DataMarkerConfig.toml")
109 | if err1 != nil {
110 | dlt.Debug("找不到路径:%s", err1)
111 | }
112 |
113 | dataMarker := make(map[string]string)
114 | err := toml.Unmarshal([]byte(string(contents)), &dataMarker)
115 | if err != nil {
116 | panic(err)
117 | }
118 |
119 | markerValue := dataMarker[makers]
120 |
121 | if dltDataFinished == "02" && dltDataFinished1 == "01" {
122 | n1, _ := decimal.NewFromString(data)
123 | n2, _ := decimal.NewFromString("0.1")
124 | over := n1.Mul(n2)
125 | //dlt.Debug(markerValue+":%s", over.String()+"v")
126 | value, _ := strconv.ParseFloat(data, 32)
127 | return int16(value)
128 | } else if dltDataFinished == "02" && dltDataFinished1 == "02" {
129 | n1, _ := decimal.NewFromString(data)
130 | n2, _ := decimal.NewFromString("0.01")
131 | over := n1.Mul(n2)
132 | //dlt.Debug(markerValue+"%s", over.String()+"A")
133 | value, _ := strconv.ParseInt(data, 10, 16)
134 | return int16(value)
135 | } else if (dltDataFinished == "02" && dltDataFinished1 == "03") || (dltDataFinished == "02" && dltDataFinished1 == "04") || (dltDataFinished == "02" && dltDataFinished1 == "05") {
136 | n1, _ := decimal.NewFromString(data)
137 | n2, _ := decimal.NewFromString("0.0001")
138 | over := n1.Mul(n2)
139 | //dlt.Debug(markerValue+"%s", over)
140 | value, _ := strconv.ParseInt(data, 10, 16)
141 | return int16(value)
142 | } else if dltDataFinished == "02" && dltDataFinished1 == "06" {
143 | n1, _ := decimal.NewFromString(data)
144 | n2, _ := decimal.NewFromString("0.001")
145 | over := n1.Mul(n2)
146 | //dlt.Debug(markerValue+"%s", over)
147 | value, _ := strconv.ParseInt(data, 10, 16)
148 | return int16(value)
149 | } else if dltDataFinished == "00" && dltDataFinished1 == "01" {
150 | n1, _ := decimal.NewFromString(data)
151 | n2, _ := decimal.NewFromString("0.01")
152 | over := n1.Mul(n2)
153 | //dlt.Debug(markerValue+"%s", over)
154 | value, _ := strconv.ParseInt(data, 10, 16)
155 | return int16(value)
156 | } else if dltDataFinished == "00" && dltDataFinished1 == "02" {
157 | n1, _ := decimal.NewFromString(data)
158 | n2, _ := decimal.NewFromString("0.01")
159 | over := n1.Mul(n2)
160 | //dlt.Debug(markerValue+"%s", over)
161 | value, _ := strconv.ParseInt(data, 10, 16)
162 | return int16(value)
163 | } else if dltDataFinished == "02" && dltDataFinished3 == "02" {
164 | n1, _ := decimal.NewFromString(data)
165 | n2, _ := decimal.NewFromString("0.01")
166 | over := n1.Mul(n2)
167 | //dlt.Debug(markerValue+"%s", over)
168 | value, _ := strconv.ParseInt(data, 10, 16)
169 | return int16(value)
170 | } else if dltDataFinished == "02" && dltDataFinished1 == "01" {
171 | n1, _ := decimal.NewFromString(data)
172 | n2, _ := decimal.NewFromString("0.1")
173 | over := n1.Mul(n2)
174 | //dlt.Debug(markerValue+"%s", over)
175 | value, _ := strconv.ParseInt(data, 10, 16)
176 | return int16(value)
177 | } else if dltDataFinished == "02" && dltDataFinished1 == "02" {
178 | n1, _ := decimal.NewFromString(data)
179 | n2, _ := decimal.NewFromString("0.001")
180 | over := n1.Mul(n2)
181 | //dlt.Debug(markerValue+"%s", over)
182 | value, _ := strconv.ParseInt(data, 10, 16)
183 | return int16(value)
184 | }
185 |
186 | }
187 | return 0
188 | }
189 |
--------------------------------------------------------------------------------
/res/DataMarkerConfig.toml:
--------------------------------------------------------------------------------
1 | 00000000='当前组合有功总电能'
2 | 00010000='当前正向有功总电能'
3 | 00020000='当前反向有功总电能'
4 | 00030000='当前组合无功1总电能'
5 | 00040000='当前组合无功2总电能'
6 | 00050000='当前第一象限无功总电能'
7 | 00060000='当前第二象限无功总电能'
8 | 00070000='当前第三象限无功总电能'
9 | 00080000='当前第四象限无功总电能'
10 | 00000001='上1结算日组合有功总电能'
11 | 00010001='上1结算日正向有功总电能'
12 | 00020001='上1结算日反向有功总电能'
13 | 00030001='上1结算日组合无功1总电能'
14 | 00040001='上1结算日组合无功2总电能'
15 | 00050001='上1结算日第一象限无功总电能'
16 | 00060001='上1结算日第二象限无功总电能'
17 | 00070001='上1结算日第三象限无功总电能'
18 | 00080001='上1结算日第四象限无功总电能'
19 |
20 | 02010100='A相电压'
21 | 02010200='B相电压'
22 | 02010300='C相电压'
23 | 0201FF00='电压数据块'
24 | 02020100='A相电流'
25 | 02020200='B相电流'
26 | 02020300='C相电流'
27 | 0202FF00='电流数据块'
28 | 02030000='瞬时总有功功率'
29 | 02030100='瞬时A相有功功率'
30 | 02030200='瞬时B相有功功率'
31 | 02030300='瞬时C相有功功率'
32 | 02040000='瞬时总无功功率'
33 | 02040100='瞬时A相无功功率'
34 | 02040200='瞬时B相无功功率'
35 | 02040300='瞬时C相无功功率'
36 | 02060000='总功率因数'
37 | 02060100='A相功率因数'
38 | 02060200='B相功率因数'
39 | 02060300='C相功率因数'
40 | 02800001='零线电流'
41 | 02800002='电网频率'
42 | 02800003='一分钟有功总平均功率'
43 | 02800004='当前有功需量'
44 | 08000005='当前无功需量'
45 | 02050000='瞬时总视在功率'
46 | 02050100='瞬时A相视在功率'
47 | 02050200='瞬时B相视在功率'
48 | 02050300='瞬时C相视在功率'
49 |
50 | 04000101='日期及星期(年月日星期)'
51 | 04000102='时间(时分秒)'
52 | 04000303='显示电能小数位数(位)'
53 | 04000404='额定电压(ASCII码)'
54 | 04800001='厂家软件版本号(ASCII码)'
55 | 04800002='厂家硬件版本号(ASCII码)'
56 | 04800003='厂家编号(ASCII码)'
57 |
--------------------------------------------------------------------------------
/revive.toml:
--------------------------------------------------------------------------------
1 | ignoreGeneratedHeader = false
2 | severity = "error"
3 | confidence = 0.8
4 | errorCode = 0
5 | warningCode = 0
6 |
7 | [rule.blank-imports]
8 | [rule.context-as-argument]
9 | [rule.context-keys-type]
10 | [rule.dot-imports]
11 | [rule.error-return]
12 | [rule.error-strings]
13 | [rule.error-naming]
14 | [rule.exported]
15 | [rule.if-return]
16 | [rule.increment-decrement]
17 | [rule.var-naming]
18 | [rule.var-declaration]
19 | [rule.package-comments]
20 | [rule.range]
21 | [rule.time-naming]
22 | [rule.unexported-return]
23 | [rule.indent-error-flow]
24 | [rule.errorf]
25 | [rule.empty-block]
26 | [rule.superfluous-else]
27 | [rule.unused-parameter]
28 | [rule.unreachable-code]
29 | [rule.redefines-builtin-id]
30 |
31 | # Currently this makes too much noise, but should add it in
32 | # and perhaps ignore it in a few files
33 | #[rule.confusing-naming]
34 | # severity = "warning"
35 | #[rule.confusing-results]
36 | # severity = "warning"
37 | #[rule.unused-parameter]
38 | # severity = "warning"
39 | #[rule.deep-exit]
40 | # severity = "warning"
41 | #[rule.flag-parameter]
42 | # severity = "warning"
43 |
44 | # 接收者名字的检测
45 | [rule.receiver-naming]
--------------------------------------------------------------------------------
/serial.go:
--------------------------------------------------------------------------------
1 | package dlt645
2 |
3 | import (
4 | "io"
5 | "sync"
6 | "time"
7 |
8 | "github.com/goburrow/serial"
9 | )
10 |
11 | const (
12 | // SerialDefaultTimeout Serial Default timeout
13 | SerialDefaultTimeout = 1 * time.Second
14 | // SerialDefaultAutoReconnect Serial Default auto reconnect count
15 | SerialDefaultAutoReconnect = 0
16 | )
17 |
18 | // serialPort has configuration and I/O controller.
19 | type serialPort struct {
20 | // Serial port configuration.
21 | serial.Config
22 | mu sync.Mutex
23 | port io.ReadWriteCloser
24 | // if > 0, when disconnect,it will try to reconnect the remote
25 | // but if we active close self,it will not to reconncet
26 | // if == 0 auto reconnect not active
27 | autoReconnect byte
28 | }
29 |
30 | // Connect try to connect the remote server
31 | func (sf *serialPort) Connect() error {
32 | sf.mu.Lock()
33 | err := sf.connect()
34 | sf.mu.Unlock()
35 | return err
36 | }
37 |
38 | // Caller must hold the mutex before calling this method.
39 | func (sf *serialPort) connect() error {
40 | port, err := serial.Open(&sf.Config)
41 | if err != nil {
42 | return err
43 | }
44 | sf.port = port
45 | return nil
46 | }
47 |
48 | // IsConnected returns a bool signifying whether the client is connected or not.
49 | func (sf *serialPort) IsConnected() bool {
50 | sf.mu.Lock()
51 | b := sf.isConnected()
52 | sf.mu.Unlock()
53 | return b
54 | }
55 |
56 | // Caller must hold the mutex before calling this method.
57 | func (sf *serialPort) isConnected() bool {
58 | return sf.port != nil
59 | }
60 |
61 | // SetAutoReconnect set auto reconnect count
62 | // if cnt == 0, disable auto reconnect
63 | // if cnt > 0 ,enable auto reconnect,but max 6
64 | func (sf *serialPort) SetAutoReconnect(cnt byte) {
65 | sf.mu.Lock()
66 | sf.autoReconnect = cnt
67 | if sf.autoReconnect > 6 {
68 | sf.autoReconnect = 6
69 | }
70 | sf.mu.Unlock()
71 | }
72 |
73 | // Close close current connection.
74 | func (sf *serialPort) Close() error {
75 | var err error
76 | sf.mu.Lock()
77 | if sf.port != nil {
78 | err = sf.port.Close()
79 | sf.port = nil
80 | }
81 | sf.mu.Unlock()
82 | return err
83 | }
84 |
--------------------------------------------------------------------------------