├── .github └── workflows │ └── go.yml ├── .gitignore ├── 645.go ├── LICENSE ├── Makefile ├── api.go ├── client.go ├── client_option.go ├── conf └── config.go ├── control.go ├── decode.go ├── docs ├── .nojekyll ├── README.md ├── _coverpage.md └── index.html ├── example ├── bf │ └── main.go └── gaea │ └── main.go ├── exception.go ├── go.mod ├── go.sum ├── log.go ├── prefix.go ├── protocol.go ├── protocol_test.go ├── read.go ├── readme.md ├── rtuClient.go ├── serial.go └── value.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - name: Set up Go 17 | uses: actions/setup-go@v2 18 | with: 19 | go-version: 1.16 20 | 21 | - name: Test 22 | run: go test -v -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### JetBrains template 2 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 3 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 4 | 5 | # User-specific stuff 6 | .idea/**/workspace.xml 7 | .idea/**/tasks.xml 8 | .idea/**/usage.statistics.xml 9 | .idea/**/dictionaries 10 | .idea/**/shelf 11 | 12 | # Generated files 13 | .idea/**/contentModel.xml 14 | 15 | # Sensitive or high-churn files 16 | .idea/**/dataSources/ 17 | .idea/**/dataSources.ids 18 | .idea/**/dataSources.local.xml 19 | .idea/**/sqlDataSources.xml 20 | .idea/**/dynamic.xml 21 | .idea/**/uiDesigner.xml 22 | .idea/**/dbnavigator.xml 23 | 24 | # Gradle 25 | .idea/**/gradle.xml 26 | .idea/**/libraries 27 | 28 | .idea 29 | 30 | # Gradle and Maven with auto-import 31 | # When using Gradle or Maven with auto-import, you should exclude module files, 32 | # since they will be recreated, and may cause churn. Uncomment if using 33 | # auto-import. 34 | # .idea/artifacts 35 | # .idea/compiler.xml 36 | # .idea/jarRepositories.xml 37 | # .idea/modules.xml 38 | # .idea/*.iml 39 | # .idea/modules 40 | # *.iml 41 | # *.ipr 42 | 43 | # CMake 44 | cmake-build-*/ 45 | 46 | # Mongo Explorer plugin 47 | .idea/**/mongoSettings.xml 48 | 49 | # File-based project format 50 | *.iws 51 | 52 | # IntelliJ 53 | out/ 54 | 55 | # mpeltonen/sbt-idea plugin 56 | .idea_modules/ 57 | 58 | # JIRA plugin 59 | atlassian-ide-plugin.xml 60 | 61 | # Cursive Clojure plugin 62 | .idea/replstate.xml 63 | 64 | # Crashlytics plugin (for Android Studio and IntelliJ) 65 | com_crashlytics_export_strings.xml 66 | crashlytics.properties 67 | crashlytics-build.properties 68 | fabric.properties 69 | 70 | # Editor-based Rest Client 71 | .idea/httpRequests 72 | 73 | # Android studio 3.1+ serialized cache file 74 | .idea/caches/build_file_checksums.ser 75 | 76 | ### Go template 77 | # Binaries for programs and plugins 78 | *.exe 79 | *.exe~ 80 | *.dll 81 | *.so 82 | *.dylib 83 | 84 | # Test binary, built with `go test -c` 85 | *.test 86 | 87 | # Output of the go coverage tool, specifically when used with LiteIDE 88 | *.out 89 | 90 | # Dependency directories (remove the comment below to include it) 91 | # vendor/ 92 | 93 | -------------------------------------------------------------------------------- /645.go: -------------------------------------------------------------------------------- 1 | package go645 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/tarm/serial" 7 | ) 8 | 9 | type ClientProvider interface { 10 | // Connect try to connect the remote server 11 | Connect() error 12 | // IsConnected returns a bool signifying whether 13 | // the client is connected or not. 14 | IsConnected() bool 15 | LogMode(enable bool) 16 | // Close disconnect the remote server 17 | Close() error 18 | setSerialConfig(config serial.Config) 19 | setPrefixHandler(handler PrefixHandler) 20 | // setTCPTimeout set tcp connect & read timeout 21 | setTCPTimeout(t time.Duration) 22 | setLogProvider(p LogProvider) 23 | SendAndRead(*Protocol) (aduResponse []byte, err error) 24 | SendRawFrameAndRead(aduRequest []byte) (aduResponse []byte, err error) 25 | SendRawFrame(aduRequest []byte) (err error) 26 | ReadRawFrame() (aduResponse []byte, err error) 27 | Send(*Protocol) (err error) 28 | } 29 | 30 | // LogProvider log message levels only Debug and Error 31 | type LogProvider interface { 32 | Errorf(format string, v ...interface{}) 33 | Debugf(format string, v ...interface{}) 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 短狐 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BUILD_TIME=`date +%FT%T%z` 2 | GITTAG=1.0 3 | OUTPUT=./out/zoom 4 | 5 | ARM=arm 6 | MAC= mac 7 | WIN =win 8 | OUTPUT=./g645 9 | all: ${ARM} ${MAC} ${WIN} 10 | 11 | ${ARM}: 12 | @echo "============ [${ARM}] 开始编译 ==========" 13 | GOOS=linux GOARCH=arm go build -o ${OUTPUT}_${ARM} ./example/gaea/main.go 14 | @echo "============[${ARM}] 编译结束============" 15 | ${MAC}: 16 | @echo "============ [${MAC}] 开始编译 ============" 17 | GOOS=darwin GOARCH=amd64 go build -o ${OUTPUT}_${MAC}_amd64 ./main.go 18 | @echo "============ [${MAC}] 编译结束============" 19 | ${WIN}: 20 | @echo "============ [${WIN}] 开始编译 ============" 21 | GOOS=windows GOARCH=amd64 go build -o ${OUTPUT}_${WIN}_amd64 ./main.go 22 | @echo "============ [${WIN}] 编译结束 ============" -------------------------------------------------------------------------------- /api.go: -------------------------------------------------------------------------------- 1 | package go645 2 | 3 | type Client interface { 4 | ClientProvider 5 | //Read 发送读请求 6 | Read(address Address, itemCode int32) (*ReadData, bool, error) 7 | //ReadWithBlock 读请求使能块 8 | ReadWithBlock(address Address, data ReadRequestData) (*Protocol, error) 9 | //Broadcast 开始广播 10 | Broadcast(p InformationElement, control Control) error 11 | } 12 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package go645 2 | 3 | import ( 4 | "bytes" 5 | "sync" 6 | ) 7 | 8 | var ( 9 | // check implements Client interface. 10 | _ Client = (*client)(nil) 11 | ) 12 | 13 | type client struct { 14 | ClientProvider 15 | mu sync.Mutex 16 | } 17 | 18 | func (c *client) Read(address Address, itemCode int32) (*ReadData, bool, error) { 19 | resp, err := c.ClientProvider.SendAndRead(ReadRequest(address, itemCode)) 20 | if err != nil { 21 | return nil, false, err 22 | } 23 | decode, err := Decode(bytes.NewBuffer(resp)) 24 | if err != nil { 25 | return nil, false, err 26 | } 27 | return decode.Data.(*ReadData), decode.Control.IsState(HasNext), err 28 | } 29 | 30 | //Broadcast 设备广播 31 | func (c *client) Broadcast(p InformationElement, control Control) error { 32 | var err error 33 | bf := bytes.NewBuffer(make([]byte, 0)) 34 | err = p.Encode(bf) 35 | if err != nil { 36 | return err 37 | } 38 | return c.Send(NewProtocol(NewAddress(BroadcastAddress, LittleEndian), p, &control)) 39 | } 40 | 41 | func (c *client) ReadWithBlock(address Address, data ReadRequestData) (*Protocol, error) { 42 | resp, err := c.ClientProvider.SendAndRead(ReadRequestWithBlock(address, data)) 43 | if err != nil { 44 | return nil, err 45 | } 46 | return Decode(bytes.NewBuffer(resp)) 47 | } 48 | 49 | // Option custom option 50 | type Option func(c *client) 51 | 52 | func NewClient(p ClientProvider, opts ...Option) Client { 53 | c := &client{ClientProvider: p} 54 | for _, opt := range opts { 55 | opt(c) 56 | } 57 | return c 58 | } 59 | -------------------------------------------------------------------------------- /client_option.go: -------------------------------------------------------------------------------- 1 | package go645 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/tarm/serial" 7 | ) 8 | 9 | // ClientProviderOption client provider option for user. 10 | type ClientProviderOption func(ClientProvider) 11 | 12 | // WithLogProvider set logger provider. 13 | func WithLogProvider(provider LogProvider) ClientProviderOption { 14 | return func(p ClientProvider) { 15 | p.setLogProvider(provider) 16 | } 17 | } 18 | 19 | // WithEnableLogger enable log output when you has set logger. 20 | func WithEnableLogger() ClientProviderOption { 21 | return func(p ClientProvider) { 22 | p.LogMode(true) 23 | } 24 | } 25 | 26 | // WithSerialConfig set serial config, only valid on serial. 27 | func WithSerialConfig(config serial.Config) ClientProviderOption { 28 | return func(p ClientProvider) { 29 | p.setSerialConfig(config) 30 | } 31 | } 32 | 33 | func WithPrefixHandler(prefixHandler PrefixHandler) ClientProviderOption { 34 | return func(p ClientProvider) { 35 | p.setPrefixHandler(prefixHandler) 36 | } 37 | } 38 | 39 | // WithTCPTimeout set tcp Connect & Read timeout, only valid on TCP. 40 | func WithTCPTimeout(t time.Duration) ClientProviderOption { 41 | return func(p ClientProvider) { 42 | p.setTCPTimeout(t) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /conf/config.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/tarm/serial" 7 | ) 8 | 9 | type Config struct { 10 | Retry int 11 | Com []Com 12 | Mqtt Mqtt 13 | } 14 | type Com struct { 15 | Config SerialConfig 16 | Size int 17 | Type uint8 18 | Start int 19 | } 20 | 21 | type SerialConfig struct { 22 | Address string 23 | BaudRate int 24 | Timeout time.Duration 25 | Size byte 26 | Parity string 27 | StopBits byte 28 | } 29 | type Mqtt struct { 30 | Clinet string 31 | Broker string 32 | Username string 33 | Password string 34 | InfoTopic string 35 | AvgTopic string 36 | 37 | ReceiveTopic string 38 | TimeOut uint8 39 | SendTime uint8 40 | ControlTopic string 41 | ControlCallBackTopic string 42 | } 43 | 44 | func (s SerialConfig) NewSerialConfig() serial.Config { 45 | var p serial.Parity 46 | switch s.Parity { 47 | case "N": 48 | p = serial.ParityNone 49 | case "E": 50 | p = serial.ParityEven 51 | case "O": 52 | p = serial.ParityOdd 53 | } 54 | 55 | return serial.Config{ 56 | Parity: p, 57 | Name: s.Address, 58 | Baud: s.BaudRate, 59 | ReadTimeout: s.Timeout, 60 | StopBits: serial.StopBits(s.StopBits), 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /control.go: -------------------------------------------------------------------------------- 1 | package go645 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "errors" 7 | "fmt" 8 | ) 9 | 10 | type ControlType byte 11 | 12 | //01010 13 | const ( 14 | IsSlave ControlType = 1 << 7 15 | SlaveErr ControlType = 1 << 6 16 | HasNext ControlType = 1 << 5 17 | //Retain 保留 18 | Retain ControlType = 0b00000 19 | //Broadcast 广播校时 20 | Broadcast ControlType = 0b01000 21 | // ReadNext 读后续10001 22 | ReadNext ControlType = 0b10010 23 | //ReadAddress 读通讯地址 24 | ReadAddress ControlType = 0b10011 25 | //Write 写数据 26 | Write ControlType = 0b10100 27 | //WriteAddress 读通讯地址 28 | WriteAddress ControlType = 0b10101 29 | //ToChangeCommunicationRate 更改通讯速率 30 | ToChangeCommunicationRate ControlType = 0b10111 31 | Freeze ControlType = 0b10110 32 | //PassWord 修改密码 33 | PassWord ControlType = 0b11000 34 | ResetMaxDemand ControlType = 0b11001 35 | //ResetEM 电表清零 36 | ResetEM ControlType = 0b11010 37 | ResetEvent ControlType = 0b11011 38 | //Read 读 39 | Read ControlType = 0b10001 40 | ) 41 | 42 | type Control struct { 43 | Data ControlType 44 | } 45 | 46 | func DecodeControl(buffer *bytes.Buffer) (*Control, error) { 47 | c := new(Control) 48 | if err := binary.Read(buffer, binary.LittleEndian, &c.Data); err != nil { 49 | return nil, err 50 | } 51 | return c, nil 52 | } 53 | func NewControl() *Control { 54 | return &Control{Data: 0} 55 | } 56 | func NewControlValue(data ControlType) *Control { 57 | return &Control{Data: data} 58 | } 59 | 60 | func (c *Control) SetState(state ControlType) { 61 | c.Data = c.Data | state 62 | } 63 | 64 | //SetStates 批量设置状态 65 | func (c *Control) SetStates(state ...ControlType) { 66 | for _, s := range state { 67 | c.Data = c.Data | s 68 | } 69 | } 70 | func (c *Control) IsState(state ControlType) bool { 71 | return (c.Data & state) == state 72 | } 73 | 74 | //IsStates 判断控制域 75 | func (c *Control) IsStates(state ...ControlType) bool { 76 | for _, s := range state { 77 | if !c.IsState(s) { 78 | return false 79 | } 80 | } 81 | return true 82 | } 83 | 84 | func (c *Control) Reset() { 85 | c.Data = 0 86 | } 87 | func (c *Control) getLen() uint16 { 88 | return 1 89 | } 90 | 91 | func (c *Control) Encode(buffer *bytes.Buffer) error { 92 | if err := binary.Write(buffer, binary.BigEndian, c.Data); err != nil { 93 | s := fmt.Sprintf("Control , %v", err) 94 | return errors.New(s) 95 | } 96 | return nil 97 | } 98 | -------------------------------------------------------------------------------- /decode.go: -------------------------------------------------------------------------------- 1 | package go645 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "errors" 7 | "log" 8 | ) 9 | 10 | type Decoder func(buffer *bytes.Buffer) (*InformationElement, error) 11 | 12 | func Handler(control *Control, buffer *bytes.Buffer, size byte) (InformationElement, error) { 13 | //从站响应异常响应 14 | if control == nil { 15 | return nil, errors.New("未知错误") 16 | } 17 | if control.IsState(SlaveErr) { 18 | return nil, DecodeException(buffer) 19 | } 20 | //从站读正确响应 21 | if control.IsState(Read) { 22 | return DecodeRead(buffer, int(size)), nil 23 | } 24 | //佳和强制联机 25 | if control.Data == 0x8a { 26 | return DecodeNullData(buffer), nil 27 | } 28 | return nil, errors.New("未定义的数据类型") 29 | } 30 | func Decode(buffer *bytes.Buffer) (*Protocol, error) { 31 | var err error 32 | read := func(data interface{}) { 33 | if err != nil { 34 | return 35 | } 36 | err = binary.Read(buffer, binary.LittleEndian, data) 37 | } 38 | p := new(Protocol) 39 | read(&p.Start) 40 | p.Address, err = DecodeAddress(buffer, 6) 41 | read(&p.Start2) 42 | p.Control, err = DecodeControl(buffer) 43 | read(&p.DataLength) 44 | p.Data, err = Handler(p.Control, buffer, p.DataLength) 45 | read(&p.CS) 46 | read(&p.End) 47 | if err != nil { 48 | log.Print(err.Error()) 49 | } 50 | return p, err 51 | } 52 | func DecodeAddress(buffer *bytes.Buffer, size int) (Address, error) { 53 | var a Address 54 | 55 | value := make([]byte, size) 56 | if err := binary.Read(buffer, binary.LittleEndian, &value); err != nil { 57 | return a, err 58 | } 59 | { 60 | a.value = value 61 | a.strValue = Bcd2Number(a.value) 62 | } 63 | return a, nil 64 | } 65 | func DecodeData(buffer *bytes.Buffer, size byte) (*ReadData, error) { 66 | var err error 67 | read := func(data interface{}) { 68 | if err != nil { 69 | return 70 | } 71 | err = binary.Read(buffer, binary.LittleEndian, data) 72 | } 73 | data := new(ReadData) 74 | var dataType []byte 75 | dataValue := make([]byte, size-4) 76 | read(&dataType) 77 | //for i, j := 0, len(dataType)-1; i < j; i, j = i+1, j-1 { 78 | // dataType[j], dataType[i] = dataType[i], dataType[j] 79 | //} 80 | for index, item := range dataType { 81 | dataType[index] = item - 0x33 82 | } 83 | read(&dataValue) 84 | for index, item := range dataValue { 85 | dataValue[index] = item - 0x33 86 | } 87 | for i, j := 0, len(dataValue)-1; i < j; i, j = i+1, j-1 { 88 | dataValue[i], dataValue[j] = dataValue[j], dataValue[i] 89 | } 90 | 91 | //瞬时功率及当前需量最高位表示方向,0正,1负,三相三线B相为0。取值范围:0.0000~79.9999。 92 | //表内温度 最高位0表示零上,1表示零下。取值范围:0.0~799.9 93 | //电流最高位表示方向,0正,1负,取值范围:0.000~799.999。功率因数最高位表示方向,0正,1负,取值范围 94 | data.bcdValue = Bcd2Number(dataValue) 95 | data.rawValue = dataValue 96 | data.dataType = dataType 97 | return data, nil 98 | } 99 | func DecoderData(buffer *bytes.Buffer, size int) (*bytes.Buffer, error) { 100 | var err error 101 | read := func(data interface{}) { 102 | if err != nil { 103 | return 104 | } 105 | err = binary.Read(buffer, binary.LittleEndian, data) 106 | } 107 | var value = make([]byte, size) 108 | read(value) 109 | for i, j := 0, len(value)-1; i <= j; i, j = i+1, j-1 { 110 | value[i], value[j] = value[j]-0x33, value[i]-0x33 111 | } 112 | 113 | return bytes.NewBuffer(value), nil 114 | } 115 | func DecodeRead(buffer *bytes.Buffer, size int) InformationElement { 116 | df, _ := DecoderData(buffer, size) 117 | var err error 118 | read := func(data interface{}) { 119 | if err != nil { 120 | return 121 | } 122 | err = binary.Read(df, binary.LittleEndian, data) 123 | } 124 | data := new(ReadData) 125 | var dataType = make([]byte, 4) 126 | dataValue := make([]byte, size-4) 127 | read(&dataValue) 128 | read(&dataType) 129 | for i, j := 0, len(dataType)-1; i < j; i, j = i+1, j-1 { 130 | dataType[j], dataType[i] = dataType[i], dataType[j] 131 | } 132 | //瞬时功率及当前需量最高位表示方向,0正,1负,三相三线B相为0。取值范围:0.0000~79.9999。 133 | //表内温度 最高位0表示零上,1表示零下。取值范围:0.0~799.9 134 | //电流最高位表示方向,0正,1负,取值范围:0.000~799.999。功率因数最高位表示方向,0正,1负,取值范围 135 | //判断最高位是否为0 136 | if dataType[3] == 0x02 && dataType[2] >= 0x3 && dataType[2] <= 0x6 { 137 | if IsStateUin8(dataValue[0], 7) { 138 | data.Negative = true 139 | dataValue[0] = dataValue[0] << 1 >> 1 140 | } 141 | } 142 | if dataType[3] == 02 && dataType[2] == 0x80 && dataType[0] >= 0x4 && dataType[0] <= 0x7 { 143 | if IsStateUin8(dataValue[0], 7) { 144 | data.Negative = true 145 | dataValue[0] = dataValue[0] << 1 >> 1 146 | } 147 | } 148 | 149 | data.bcdValue = Bcd2Number(dataValue) 150 | data.dataType = dataType 151 | return data 152 | } 153 | func DecodeException(buffer *bytes.Buffer) error { 154 | var data uint16 155 | err := binary.Read(buffer, binary.LittleEndian, &data) 156 | if err != nil { 157 | log.Print(err) 158 | return nil 159 | } 160 | return &Exception{data} 161 | } 162 | func DecodeNullData(*bytes.Buffer) InformationElement { 163 | return NullData{} 164 | } 165 | func IsStateUin8(des uint8, i int) bool { 166 | return (des & (0x01 << i)) != 0 167 | } 168 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zcx1218029121/go645/af08785ca9dceaeb8d6d9b229c2295f49bc97aa3/docs/.nojekyll -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # 快速开始 2 | 3 | ### 安装 4 | 5 | ```shell 6 | go get github.com/zcx1218029121/go645 7 | ``` 8 | 9 | ### 创建串口客户端 10 | 11 | ```go 12 | p := go645.NewRTUClientProvider( 13 | go645.WithSerialConfig(serial.Config{ 14 | Address: "COM2", 15 | BaudRate: b, 16 | DataBits: 8, 17 | StopBits: 1, 18 | Parity: "E", 19 | Timeout: time.Second * 30})) 20 | ``` 21 | 22 | ### 创建串口客户端并打印日志 23 | 24 | ```go 25 | p := go645.NewRTUClientProvider( 26 | go645.WithSerialConfig(serial.Config{ 27 | Address: "COM2", 28 | BaudRate: b, 29 | DataBits: 8, 30 | StopBits: 1, 31 | Parity: "E", 32 | Timeout: time.Second * 30}), 33 | go645.WithEnableLogger()) 34 | ``` 35 | 36 | ### 连接串口 37 | 38 | ```go 39 | err := c.Connect() 40 | if err != nil { 41 | panic(err) 42 | } 43 | ``` 44 | 45 | ### 自定义引导词 46 | 47 | ```go 48 | var _ go645.PrefixHandler = (*Handler)(nil) 49 | 50 | type Handler struct { 51 | } 52 | 53 | func (h Handler) EncodePrefix(buffer *bytes.Buffer) error { 54 | // 写入引导词 55 | buffer.Write([]byte{0xfe, 0xfe, 0xfe, 0xfe}) 56 | return nil 57 | } 58 | // 写入引导解码 一般来说 在这里的引导词都会被丢弃 59 | func (h Handler) DecodePrefix(reader io.Reader) ([]byte, error) { 60 | fe := make([]byte, 4) 61 | _, err := io.ReadAtLeast(reader, fe, 4) 62 | if err != nil { 63 | return nil, err 64 | } 65 | return fe, err 66 | } 67 | p := go645.NewRTUClientProvider( 68 | go645.WithSerialConfig(serial.Config{ 69 | Address: "COM2", 70 | BaudRate: b, 71 | DataBits: 8, 72 | StopBits: 1, 73 | Parity: "E", 74 | Timeout: time.Second * 30}), 75 | go645.WithEnableLogger(),go645.WithPrefixHandler(&Handler{})) 76 | ``` 77 | ### 发送读请求 78 | 1. hasNext 为是否有后续的Frame 标识 79 | 2. read 为读请求响应 80 | ```go 81 | read, hasNext, err := c.Read(go645.NewAddress("000703200136", go645.LittleEndian), 0x00_01_00_00) 82 | if err != nil{ 83 | log.Printf("rec %s \n", read.GetValue()) 84 | if hasNext{ 85 | log.Printf("有后续Frame \n", read.GetValue()) 86 | } 87 | 88 | } 89 | 90 | ``` 91 | 92 | ### 发送广播请求 93 | > 标准的广播请求不会有后续响应如果为特殊响应请手动调用 frame, err := c.ReadRawFrame() 94 | #### 正常广播(无需返回值) 95 | ```go 96 | err:=c.Broadcast(go645.NullData{}, *go645.NewControlValue(0x0a)) 97 | if err != nil{ 98 | log.Printf("广播发送失败") 99 | } 100 | ``` 101 | #### 特殊广播(需返回值) 102 | ```go 103 | err:=c.Broadcast(go645.NullData{}, *go645.NewControlValue(0x0a)) 104 | if err != nil{ 105 | log.Printf("广播发送失败") 106 | } 107 | frame, err := c.ReadRawFrame() 108 | if err != nil { 109 | log.Printf(err.Error()) 110 | return 111 | } 112 | ``` 113 | -------------------------------------------------------------------------------- /docs/_coverpage.md: -------------------------------------------------------------------------------- 1 | ![logo](https://docsify.js.org/_media/icon.svg) 2 | 3 | # G645 4 | 5 | > 一个纯Go实现的645-2007 协议解析 6 | 7 | [GitHub](https://github.com/zcx1218029121/go645) 8 | [Get Started](#quick-start) 9 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Document 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 19 | 20 | 21 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /example/bf/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "flag" 6 | "github.com/goburrow/serial" 7 | "github.com/zcx1218029121/go645" 8 | "io" 9 | "log" 10 | "time" 11 | ) 12 | 13 | var _ go645.PrefixHandler = (*Handler)(nil) 14 | 15 | type Handler struct { 16 | } 17 | 18 | func (h Handler) EncodePrefix(buffer *bytes.Buffer) error { 19 | buffer.Write([]byte{0xfe, 0xfe, 0xfe, 0xfe}) 20 | return nil 21 | } 22 | 23 | func (h Handler) DecodePrefix(reader io.Reader) ([]byte, error) { 24 | fe := make([]byte, 4) 25 | _, err := io.ReadAtLeast(reader, fe, 4) 26 | if err != nil { 27 | return nil, err 28 | } 29 | return fe, err 30 | } 31 | 32 | //百富电表 33 | func main() { 34 | var b int 35 | var code int 36 | flag.IntVar(&b, "b", 2400, "波特率") 37 | flag.IntVar(&code, "c", 0x00_03_00_00, "波特率") 38 | flag.Parse() 39 | p := go645.NewRTUClientProvider(go645.WithSerialConfig(serial.Config{Address: "COM2", BaudRate: b, DataBits: 8, StopBits: 1, Parity: "E", Timeout: time.Second * 30}), go645.WithEnableLogger(), go645.WithPrefixHandler(&Handler{})) 40 | c := go645.NewClient(p) 41 | err := c.Connect() 42 | if err != nil { 43 | panic(err) 44 | } 45 | defer c.Close() 46 | for { 47 | time.Sleep(50 * time.Millisecond) 48 | read, _, err := c.Read(go645.NewAddress("000703200136", go645.LittleEndian), 0x00_01_00_00) 49 | if err == nil { 50 | log.Printf("rec %s", read.GetValue()) 51 | } 52 | 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /example/gaea/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "flag" 6 | "github.com/goburrow/serial" 7 | "github.com/zcx1218029121/go645" 8 | "log" 9 | "sync" 10 | "time" 11 | ) 12 | 13 | var device sync.Map 14 | var mu sync.Mutex 15 | 16 | //特殊电表解析 同步 17 | func main() { 18 | var b int 19 | var code int 20 | //1200、2400、4800、9600 21 | flag.IntVar(&b, "b", 19200, "波特率") 22 | flag.IntVar(&code, "c", 0x02_04_00_00, "特征码") 23 | flag.Parse() 24 | p := go645.NewRTUClientProvider( 25 | go645.WithSerialConfig(serial.Config{ 26 | Address: "/dev/ttyUSB3", 27 | BaudRate: b, 28 | DataBits: 8, 29 | StopBits: 1, 30 | Parity: "E", 31 | Timeout: time.Second * 30}), 32 | go645.WithEnableLogger()) 33 | c := go645.NewClient(p) 34 | err := c.Connect() 35 | if err != nil { 36 | panic(err) 37 | } 38 | if err != nil { 39 | return 40 | } 41 | defer c.Close() 42 | 43 | forceOnline(c) 44 | 45 | go func() { 46 | time.Sleep(1 * time.Minute) 47 | forceOnline(c) 48 | }() 49 | 50 | for { 51 | //如果对扫描速度不是要求很高 需要重新扫描 52 | time.Sleep(50 * time.Millisecond) 53 | 54 | device.Range(func(key, value interface{}) bool { 55 | func() { 56 | mu.Lock() 57 | defer mu.Unlock() 58 | read, _, err := c.Read(go645.NewAddress(key.(string), go645.LittleEndian), int32(code)) 59 | if err == nil { 60 | log.Printf("address %s :rec %f", key.(string), read.GetFloat64Value()) 61 | } 62 | }() 63 | return false 64 | }) 65 | } 66 | 67 | } 68 | func forceOnline(c go645.Client) { 69 | c.Broadcast(go645.NullData{}, *go645.NewControlValue(0x0a)) 70 | mu.Lock() 71 | defer mu.Unlock() 72 | for { 73 | 74 | frame, err := c.ReadRawFrame() 75 | if err != nil { 76 | log.Printf(err.Error()) 77 | return 78 | } 79 | if frame != nil && len(frame) > 10 { 80 | p, err := go645.Decode(bytes.NewBuffer(frame)) 81 | if err != nil { 82 | log.Printf(err.Error()) 83 | return 84 | } 85 | device.Store(p.Address.GetStrAddress(go645.LittleEndian), nil) 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /exception.go: -------------------------------------------------------------------------------- 1 | package go645 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | ) 8 | 9 | //Exception 错误 10 | type Exception struct { 11 | Err uint16 12 | } 13 | 14 | func (e *Exception) Error() string { 15 | 16 | return fmt.Sprintf("645 err %d", e.Err) 17 | } 18 | 19 | func (e Exception) Encode(buffer *bytes.Buffer) error { 20 | return binary.Write(buffer, binary.LittleEndian, e.Err+0x33) 21 | } 22 | 23 | //GetLen 错误响应报文长度2 24 | func (e Exception) GetLen() byte { 25 | return 2 26 | } 27 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/zcx1218029121/go645 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/goburrow/serial v0.1.0 7 | github.com/jonboulle/clockwork v0.2.2 // indirect 8 | github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible 9 | github.com/lestrrat-go/strftime v1.0.5 // indirect 10 | go.uber.org/atomic v1.9.0 11 | go.uber.org/zap v1.20.0 12 | ) 13 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= 2 | github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 5 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/goburrow/serial v0.1.0 h1:v2T1SQa/dlUqQiYIT8+Cu7YolfqAi3K96UmhwYyuSrA= 7 | github.com/goburrow/serial v0.1.0/go.mod h1:sAiqG0nRVswsm1C97xsttiYCzSLBmUZ/VSlVLZJ8haA= 8 | github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= 9 | github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= 10 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 11 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 12 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 13 | github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc h1:RKf14vYWi2ttpEmkA4aQ3j4u9dStX2t4M8UM6qqNsG8= 14 | github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc/go.mod h1:kopuH9ugFRkIXf3YoqHKyrJ9YfUFsckUU9S7B+XP+is= 15 | github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible h1:Y6sqxHMyB1D2YSzWkLibYKgg+SwmyFU9dF2hn6MdTj4= 16 | github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible/go.mod h1:ZQnN8lSECaebrkQytbHj4xNgtg8CR7RYXnPok8e0EHA= 17 | github.com/lestrrat-go/strftime v1.0.5 h1:A7H3tT8DhTz8u65w+JRpiBxM4dINQhUXAZnhBa2xeOE= 18 | github.com/lestrrat-go/strftime v1.0.5/go.mod h1:E1nN3pCbtMSu1yjSVeyuRFVm/U0xoR76fd03sz+Qz4g= 19 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 20 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 21 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 22 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 23 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 24 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 25 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 26 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 27 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 28 | go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 29 | go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= 30 | go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 31 | go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= 32 | go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= 33 | go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= 34 | go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= 35 | go.uber.org/zap v1.20.0 h1:N4oPlghZwYG55MlU6LXk/Zp00FVNE9X9wrYO8CEs4lc= 36 | go.uber.org/zap v1.20.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= 37 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 38 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 39 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 40 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 41 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 42 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 43 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 44 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 45 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 46 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 47 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 48 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 49 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 50 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 51 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 52 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 53 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 54 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 55 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 56 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 57 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 58 | golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 59 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 60 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 61 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 62 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 63 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 64 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= 65 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 66 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 67 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= 68 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 69 | -------------------------------------------------------------------------------- /log.go: -------------------------------------------------------------------------------- 1 | package go645 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "sync/atomic" 7 | 8 | "go.uber.org/zap" 9 | ) 10 | 11 | // 内部调试实现. 12 | type logger struct { 13 | provider LogProvider 14 | // has log output enabled, 15 | // 1: enable 16 | // 0: disable 17 | has uint32 18 | } 19 | 20 | // newLogger new logger with prefix. 21 | func newLogger(prefix string) logger { 22 | return logger{ 23 | provider: defaultLogger{log.New(os.Stdout, prefix, log.LstdFlags)}, 24 | has: 0, 25 | } 26 | } 27 | 28 | // LogMode set enable or disable log output when you has set logger. 29 | func (sf *logger) LogMode(enable bool) { 30 | if enable { 31 | atomic.StoreUint32(&sf.has, 1) 32 | } else { 33 | atomic.StoreUint32(&sf.has, 0) 34 | } 35 | } 36 | 37 | // setLogProvider overwrite log provider. 38 | func (sf *logger) setLogProvider(p LogProvider) { 39 | if p != nil { 40 | sf.provider = p 41 | } 42 | } 43 | 44 | // Error Log ERROR level message. 45 | func (sf logger) Errorf(format string, v ...interface{}) { 46 | if atomic.LoadUint32(&sf.has) == 1 { 47 | sf.provider.Errorf(format, v...) 48 | } 49 | } 50 | 51 | // Debug Log DEBUG level message. 52 | func (sf logger) Debugf(format string, v ...interface{}) { 53 | if atomic.LoadUint32(&sf.has) == 1 { 54 | sf.provider.Debugf(format, v...) 55 | } 56 | } 57 | 58 | // default log. 59 | type defaultLogger struct { 60 | *log.Logger 61 | } 62 | 63 | // check implement LogProvider interface. 64 | var _ LogProvider = (*defaultLogger)(nil) 65 | 66 | // Error Log ERROR level message. 67 | func (sf defaultLogger) Errorf(format string, v ...interface{}) { 68 | sf.Printf("[E]: "+format, v...) 69 | } 70 | 71 | // Debug Log DEBUG level message. 72 | func (sf defaultLogger) Debugf(format string, v ...interface{}) { 73 | sf.Printf("[D]: "+format, v...) 74 | } 75 | 76 | // default log. 77 | type ZapLogger struct { 78 | *zap.Logger 79 | } 80 | 81 | // check implement LogProvider interface. 82 | var _ LogProvider = (*ZapLogger)(nil) 83 | 84 | // Error Log ERROR level message. 85 | func (sf ZapLogger) Errorf(format string, v ...interface{}) { 86 | sf.Logger.Sugar().Errorf("[E]: "+format, v...) 87 | } 88 | 89 | // Debug Log DEBUG level message. 90 | func (sf ZapLogger) Debugf(format string, v ...interface{}) { 91 | sf.Logger.Sugar().Debugf("[D]: "+format, v...) 92 | } 93 | -------------------------------------------------------------------------------- /prefix.go: -------------------------------------------------------------------------------- 1 | package go645 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | ) 7 | 8 | var _ PrefixHandler = (*DefaultPrefix)(nil) 9 | 10 | type PrefixHandler interface { 11 | EncodePrefix(buffer *bytes.Buffer) error 12 | DecodePrefix(reader io.Reader) ([]byte, error) 13 | } 14 | 15 | type DefaultPrefix struct { 16 | } 17 | 18 | func (d DefaultPrefix) EncodePrefix(buffer *bytes.Buffer) error { 19 | 20 | return nil 21 | } 22 | 23 | func (d DefaultPrefix) DecodePrefix(reader io.Reader) ([]byte, error) { 24 | return nil, nil 25 | } 26 | -------------------------------------------------------------------------------- /protocol.go: -------------------------------------------------------------------------------- 1 | package go645 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "encoding/hex" 7 | "fmt" 8 | "strconv" 9 | "strings" 10 | "time" 11 | ) 12 | 13 | const ( 14 | LittleEndian Order = false 15 | BigEndian Order = true 16 | Start = 0x68 17 | End = 0x16 18 | HeadLen = 1 + 6 + 1 19 | BroadcastAddress = "999999999999" 20 | ) 21 | 22 | var ( 23 | _ InformationElement = (*Address)(nil) 24 | 25 | _ InformationElement = (*Protocol)(nil) 26 | 27 | _ InformationElement = (*ReadData)(nil) 28 | 29 | _ InformationElement = (*ReadRequestData)(nil) 30 | 31 | _ InformationElement = (*Exception)(nil) 32 | _ InformationElement = (*YearDateTimeS)(nil) 33 | _ InformationElement = (*DateTime)(nil) 34 | _ InformationElement = (*NullData)(nil) 35 | ) 36 | 37 | type ( 38 | //Order 地址大小端 39 | Order bool 40 | InformationElement interface { 41 | Encode(buffer *bytes.Buffer) error 42 | GetLen() byte 43 | } 44 | NullData struct { 45 | } 46 | 47 | YearDateTimeS struct { 48 | ss byte 49 | mm byte 50 | hh byte 51 | day byte 52 | month byte 53 | year byte 54 | } 55 | 56 | DateTime struct { 57 | mm byte 58 | hh byte 59 | day byte 60 | month byte 61 | } 62 | 63 | //Address 表计地址 64 | Address struct { 65 | value []byte 66 | strValue string 67 | } 68 | 69 | //Protocol 协议 70 | Protocol struct { 71 | //Start 645协议起始符号 72 | Start byte 73 | //设备地址 6个字节的BCD 74 | Address Address 75 | //Start 645协议起始符号 标志报文头结束 76 | Start2 byte 77 | //Control 控制域 78 | Control *Control 79 | //Control 数据长度 80 | DataLength byte 81 | //Control 数据抽象 82 | Data InformationElement 83 | //CS 校验和 84 | CS byte 85 | //End 0x16 86 | End byte 87 | } 88 | ) 89 | 90 | func (n NullData) Encode(buffer *bytes.Buffer) error { 91 | return nil 92 | } 93 | 94 | func (n NullData) GetLen() byte { 95 | return 0 96 | } 97 | 98 | func (t DateTime) Encode(buffer *bytes.Buffer) error { 99 | var err error 100 | err = binary.Write(buffer, binary.BigEndian, t.mm) 101 | if err != nil { 102 | return err 103 | } 104 | err = binary.Write(buffer, binary.BigEndian, t.hh) 105 | if err != nil { 106 | return err 107 | } 108 | err = binary.Write(buffer, binary.BigEndian, t.day) 109 | if err != nil { 110 | return err 111 | } 112 | err = binary.Write(buffer, binary.BigEndian, t.month) 113 | if err != nil { 114 | return err 115 | } 116 | return err 117 | } 118 | 119 | func (t DateTime) GetLen() byte { 120 | panic("implement me") 121 | } 122 | 123 | func NewTimeS() *YearDateTimeS { 124 | t := time.Now() 125 | return &YearDateTimeS{ss: byte(t.Second()), mm: byte(t.Minute()), hh: byte(t.Hour()), day: byte(t.Day()), year: byte(t.Year()), month: byte(t.Month())} 126 | } 127 | func (t YearDateTimeS) Encode(buffer *bytes.Buffer) error { 128 | var err error 129 | err = binary.Write(buffer, binary.BigEndian, t.ss) 130 | if err != nil { 131 | return err 132 | } 133 | err = binary.Write(buffer, binary.BigEndian, t.mm) 134 | if err != nil { 135 | return err 136 | } 137 | err = binary.Write(buffer, binary.BigEndian, t.hh) 138 | if err != nil { 139 | return err 140 | } 141 | err = binary.Write(buffer, binary.BigEndian, t.day) 142 | if err != nil { 143 | return err 144 | } 145 | err = binary.Write(buffer, binary.BigEndian, t.month) 146 | if err != nil { 147 | return err 148 | } 149 | err = binary.Write(buffer, binary.BigEndian, t.year) 150 | if err != nil { 151 | return err 152 | } 153 | return err 154 | 155 | } 156 | 157 | func (t YearDateTimeS) GetLen() byte { 158 | return 6 159 | } 160 | 161 | // NewAddress ,构建设备地址 162 | // 参数: 163 | // address : 设备地址 164 | // order : 大小端表示 165 | // 返回值: 166 | // *Address 设备地址 167 | func NewAddress(address string, order Order) Address { 168 | value := Number2bcd(address) 169 | if !order { 170 | for i, j := 0, len(value)-1; i < j; i, j = i+1, j-1 { 171 | value[i], value[j] = value[j], value[i] 172 | } 173 | } 174 | 175 | return Address{value: value, strValue: address} 176 | } 177 | 178 | func NewReadData(dataType int32, value string) ReadData { 179 | return ReadData{dataType: Int2bytes(dataType), bcdValue: value} 180 | } 181 | 182 | func NewProtocol(address Address, data InformationElement, control *Control) *Protocol { 183 | return &Protocol{ 184 | Start: Start, 185 | Start2: Start, 186 | End: End, 187 | Address: address, 188 | Control: control, 189 | DataLength: data.GetLen(), 190 | Data: data, 191 | } 192 | } 193 | 194 | // Encode ,协议解码 195 | // 参数: 196 | // buffer : 字节码缓冲 197 | // 返回值: 198 | // error 解码异常 199 | func (a Address) Encode(buffer *bytes.Buffer) error { 200 | return binary.Write(buffer, binary.BigEndian, a.value) 201 | } 202 | 203 | func (a Address) GetStrAddress(order Order) string { 204 | if !order { 205 | temp := make([]byte, len(a.value)) 206 | for i, j := 0, len(a.value)-1; i < j; i, j = i+1, j-1 { 207 | temp[i], temp[j] = a.value[j], a.value[i] 208 | } 209 | return Bcd2Number(temp) 210 | } 211 | return a.strValue 212 | } 213 | 214 | func (a Address) GetLen() byte { 215 | return 6 216 | } 217 | 218 | //GetHex 返回16进制string 219 | func GetHex(protocol *Protocol) (string, error) { 220 | bf := bytes.NewBuffer(make([]byte, 0)) 221 | if err := protocol.Encode(bf); err != nil { 222 | return "", err 223 | } 224 | return hex.EncodeToString(bf.Bytes()), nil 225 | } 226 | 227 | func (p Protocol) Encode(buffer *bytes.Buffer) error { 228 | //计算cs 需要重写开辟字节码缓冲区 229 | tmp := make([]byte, 0) 230 | bf := bytes.NewBuffer(tmp) 231 | var err error 232 | write := func(data interface{}) { 233 | if err != nil { 234 | return 235 | } 236 | err = binary.Write(bf, binary.LittleEndian, data) 237 | } 238 | write(&p.Start) 239 | err = p.Address.Encode(bf) 240 | write(&p.Start2) 241 | err = p.Control.Encode(bf) 242 | write(&p.DataLength) 243 | err = p.Data.Encode(bf) 244 | var cs = 0 245 | for _, b := range bf.Bytes() { 246 | cs += int(b) 247 | } 248 | p.CS = byte(cs) 249 | write(p.CS) 250 | write(p.End) 251 | err = binary.Write(buffer, binary.LittleEndian, bf.Bytes()) 252 | return err 253 | 254 | } 255 | 256 | func (p Protocol) GetLen() byte { 257 | if p.DataLength != 0 { 258 | return p.DataLength 259 | } 260 | return HeadLen + 4 + p.Data.GetLen() 261 | } 262 | 263 | func Bcd2Number(bcd []byte) string { 264 | var number string 265 | for _, i := range bcd { 266 | number += fmt.Sprintf("%02X", i) 267 | } 268 | pos := strings.LastIndex(number, "F") 269 | if pos == 8 { 270 | return "0" 271 | } 272 | return number[pos+1:] 273 | } 274 | 275 | func Number2bcd(number string) []byte { 276 | var rNumber = number 277 | for i := 0; i < 8-len(number); i++ { 278 | rNumber = "f" + rNumber 279 | } 280 | bcd := Hex2Byte(rNumber) 281 | return bcd 282 | } 283 | 284 | func Hex2Byte(str string) []byte { 285 | slen := len(str) 286 | bHex := make([]byte, len(str)/2) 287 | ii := 0 288 | for i := 0; i < len(str); i = i + 2 { 289 | if slen != 1 { 290 | ss := string(str[i]) + string(str[i+1]) 291 | bt, _ := strconv.ParseInt(ss, 16, 32) 292 | bHex[ii] = byte(bt) 293 | ii = ii + 1 294 | slen = slen - 2 295 | } 296 | } 297 | return bHex 298 | } 299 | 300 | func Int2bytes(data int32) []byte { 301 | var b3 = make([]byte, 4) 302 | b3[0] = uint8(data) 303 | b3[1] = uint8(data >> 8) 304 | b3[2] = uint8(data >> 16) 305 | b3[3] = uint8(data >> 24) 306 | return b3 307 | } 308 | 309 | func WriteWithOffSet(buffer *bytes.Buffer, data byte) error { 310 | return binary.Write(buffer, binary.LittleEndian, data+0x33) 311 | } 312 | -------------------------------------------------------------------------------- /protocol_test.go: -------------------------------------------------------------------------------- 1 | package go645 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "encoding/hex" 7 | "errors" 8 | "fmt" 9 | "testing" 10 | ) 11 | 12 | //TestRead 测试上报请求 13 | func TestDecode(t *testing.T) { 14 | str := "681401003182216891083333343339333333f116" 15 | decodeString, err := hex.DecodeString(str) 16 | if err != nil { 17 | return 18 | } 19 | p, _ := Decode(bytes.NewBuffer(decodeString)) 20 | if p.Address.strValue != "140100318221" { 21 | t.Errorf("地址解析错误") 22 | } 23 | if p.Address.GetLen() != 6 { 24 | t.Errorf("长度错误") 25 | } 26 | if !p.Control.IsState(IsSlave) || !p.Control.IsState(Read) { 27 | t.Errorf("状态解析错误") 28 | } 29 | fmt.Printf("%f \n", p.Data.(*ReadData).GetFloat64Value()) 30 | if p.GetLen() != 0x08 { 31 | t.Errorf("长度错误") 32 | } 33 | 34 | } 35 | 36 | //TestRead 测试解析读请求 37 | func TestRead(t *testing.T) { 38 | str := "6881040007213a68910733353535333333ba16" 39 | data := make([]byte, 0) 40 | c := NewControl() 41 | c.SetState(Read) 42 | r := ReadRequest(NewAddress("610100000000", BigEndian), 0x00_01_00_00) 43 | bf := bytes.NewBuffer(data) 44 | _ = r.Encode(bf) 45 | decodeString, _ := hex.DecodeString(str) 46 | p2, _ := Decode(bytes.NewBuffer(decodeString)) 47 | print(p2.Data.(*ReadData).GetValue() + "\n") 48 | print(p2.Data.(*ReadData).GetDataTypeStr() + "\n") 49 | fmt.Printf("%f", p2.Data.(*ReadData).GetFloat64Value()) 50 | } 51 | 52 | //TestSend 测试发送 53 | func TestSend(t *testing.T) { 54 | str := "68610100000000681104333334331416" 55 | data := make([]byte, 0) 56 | c := NewControl() 57 | c.SetState(Read) 58 | r := ReadRequest(NewAddress("610100000000", BigEndian), 0x00_01_00_00) 59 | bf := bytes.NewBuffer(data) 60 | _ = r.Encode(bf) 61 | p, _ := Decode(bf) 62 | decodeString, _ := hex.DecodeString(str) 63 | p2, _ := Decode(bytes.NewBuffer(decodeString)) 64 | print(p.Data.(*ReadData).GetValue()) 65 | AssertEquest("地址错误", p2.Address.strValue, p.Address.strValue, t) 66 | AssertEquest("校验码错误", p.CS, p2.CS, t) 67 | 68 | } 69 | func TestLEnd(t *testing.T) { 70 | str := "68610100000000681104333334331416" 71 | data := make([]byte, 0) 72 | c := NewControl() 73 | c.SetState(Read) 74 | r := ReadRequest(NewAddress("610100000000", LittleEndian), 0x00_01_00_00) 75 | bf := bytes.NewBuffer(data) 76 | _ = r.Encode(bf) 77 | p, _ := Decode(bf) 78 | decodeString, _ := hex.DecodeString(str) 79 | p2, _ := Decode(bytes.NewBuffer(decodeString)) 80 | AssertEquest("地址错误", p2.Address.GetStrAddress(LittleEndian), "000000000161", t) 81 | AssertEquest("地址错误", p2.Address.GetStrAddress(BigEndian), "610100000000", t) 82 | AssertEquest("校验码错误", p.CS, p2.CS, t) 83 | } 84 | func Assert(msg string, assert func() bool, t *testing.T) { 85 | if !assert() { 86 | t.Errorf(msg) 87 | } 88 | } 89 | func AssertEquest(msg string, exp interface{}, act interface{}, t *testing.T) { 90 | Assert(msg, func() bool { return exp == act }, t) 91 | } 92 | 93 | func AssertState(assert func() bool, t *testing.T) { 94 | Assert("状态解析错误", assert, t) 95 | } 96 | func TestControl_IsState(t *testing.T) { 97 | d := NewControlValue(0x0A) 98 | println(d.IsStates(IsSlave)) 99 | println(d.IsStates(Broadcast)) 100 | println(d.IsStates(Read)) 101 | 102 | } 103 | func TestControl(t *testing.T) { 104 | c := &Control{} 105 | if c.getLen() != 1 { 106 | t.Errorf("长度错误") 107 | } 108 | c.SetState(SlaveErr) 109 | if !c.IsState(SlaveErr) { 110 | t.Errorf("设置错误") 111 | } 112 | c.Reset() 113 | if c.IsState(SlaveErr) { 114 | t.Errorf("复归错误") 115 | } 116 | c.SetStates(SlaveErr, IsSlave, HasNext, Retain, Broadcast, ReadNext, ReadAddress) 117 | if !c.IsStates(SlaveErr, IsSlave) { 118 | t.Errorf("设置错误") 119 | } 120 | } 121 | func (c *Control) TestErr(buffer *bytes.Buffer) error { 122 | 123 | var bf *bytes.Buffer 124 | r := ReadRequest(NewAddress("610100000000", LittleEndian), 0x00_01_00_00) 125 | _ = r.Encode(bf) 126 | 127 | if err := binary.Write(buffer, binary.BigEndian, c.Data); err != nil { 128 | s := fmt.Sprintf("Control , %v", err) 129 | return errors.New(s) 130 | } 131 | return nil 132 | } 133 | func TestReadResponse(t *testing.T) { 134 | rp := ReadResponse(NewAddress("610100000000", LittleEndian), 0x00_01_00_00, NewControl(), "200") 135 | 136 | if rp.Address.GetStrAddress(LittleEndian) != "610100000000" { 137 | t.Errorf("地址错误") 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /read.go: -------------------------------------------------------------------------------- 1 | package go645 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "encoding/hex" 7 | "strconv" 8 | "time" 9 | ) 10 | 11 | type ( 12 | ReadDataWithTime struct { 13 | ReadData 14 | value float64 15 | time time.Time 16 | } 17 | //ReadData 数据域 18 | ReadData struct { 19 | //数据标识 4 个字节 20 | dataType []byte 21 | //bcd 码的数据 22 | bcdValue string 23 | rawValue []byte 24 | Negative bool 25 | } 26 | //WriteData 写数据 27 | WriteData struct { 28 | dataType []byte 29 | permissions byte 30 | passWord []byte 31 | optCode []byte 32 | } 33 | ReadRequestData struct { 34 | dataType []byte 35 | recordNum byte 36 | min byte 37 | hours byte 38 | day byte 39 | month byte 40 | year byte 41 | withTime bool 42 | } 43 | ) 44 | 45 | func (d ReadData) GetDataType() []byte { 46 | return d.dataType 47 | } 48 | func (d ReadData) GetDataTypeStr() string { 49 | return hex.EncodeToString(d.dataType) 50 | } 51 | func (d *ReadData) GetFloat64ValueWithTime() *ReadDataWithTime { 52 | if d.dataType[3] == 0x01 { 53 | _, _ = strconv.Atoi(d.bcdValue[:6]) 54 | } 55 | return nil 56 | } 57 | func (d *ReadData) GetIntValue() (int, error) { 58 | value, err := strconv.Atoi(d.bcdValue) 59 | if err != nil { 60 | return 0, err 61 | } 62 | return value,nil 63 | } 64 | func (d *ReadData) GetFloat64Value() float64 { 65 | var data float64 66 | if d.dataType[3] == 0x00 { 67 | value, _ := strconv.Atoi(d.bcdValue) 68 | data = float64(value) * 0.01 69 | } 70 | if d.dataType[3] == 0x02 && d.dataType[2] == 0x01 && d.dataType[0] == 0 { 71 | value, _ := strconv.Atoi(d.bcdValue) 72 | data = float64(value) / 10 73 | } 74 | if d.dataType[3] == 0x02 && d.dataType[2] == 0x02 && d.dataType[0] == 0 { 75 | value, _ := strconv.Atoi(d.bcdValue) 76 | data = float64(value) * 0.001 77 | } 78 | if d.dataType[3] == 0x02 && d.dataType[2] == 0x03 && d.dataType[0] == 0 { 79 | value, _ := strconv.Atoi(d.bcdValue) 80 | data = float64(value) * 0.0001 81 | } 82 | if d.dataType[3] == 0x02 && d.dataType[2] == 0x04 { 83 | value, _ := strconv.Atoi(d.bcdValue) 84 | data = float64(value) * 0.0001 85 | 86 | } 87 | if d.dataType[3] == 0x02 && d.dataType[2] == 0x05 && d.dataType[0] == 0 { 88 | value, _ := strconv.Atoi(d.bcdValue) 89 | data = float64(value) * 0.0001 90 | } 91 | if d.dataType[3] == 0x02 && d.dataType[2] == 0x06 && d.dataType[0] == 0 { 92 | value, _ := strconv.Atoi(d.bcdValue) 93 | data = float64(value) * 0.001 94 | } 95 | if d.dataType[3] == 0x02 && d.dataType[2] == 0x07 && d.dataType[0] == 0 { 96 | value, _ := strconv.Atoi(d.bcdValue) 97 | data = float64(value) * 0.01 98 | } 99 | if d.dataType[3] == 0x02 && d.dataType[2] == 0x08 && d.dataType[0] == 0 { 100 | value, _ := strconv.Atoi(d.bcdValue) 101 | data = float64(value) * 0.01 102 | } 103 | if d.dataType[3] == 0x02 && d.dataType[2] == 0x09 && d.dataType[0] == 0 { 104 | value, _ := strconv.Atoi(d.bcdValue) 105 | data = float64(value) * 0.01 106 | } 107 | if d.dataType[3] == 0x02 && d.dataType[2] == 0x0A && d.dataType[1] == 0x01 { 108 | value, _ := strconv.Atoi(d.bcdValue) 109 | data = float64(value) * 0.01 110 | } 111 | if d.dataType[3] == 0x02 && d.dataType[2] == 0x0A && d.dataType[1] == 0x02 { 112 | value, _ := strconv.Atoi(d.bcdValue) 113 | data = float64(value) * 0.01 114 | } 115 | if d.dataType[3] == 0x02 && d.dataType[2] == 0x0A && d.dataType[1] == 0x03 { 116 | value, _ := strconv.Atoi(d.bcdValue) 117 | data = float64(value) * 0.01 118 | } 119 | if d.dataType[3] == 0x02 && d.dataType[2] == 0x0B && d.dataType[1] == 0x01 { 120 | value, _ := strconv.Atoi(d.bcdValue) 121 | data = float64(value) * 0.01 122 | } 123 | if d.dataType[3] == 0x02 && d.dataType[2] == 0x0B && d.dataType[1] == 0x02 { 124 | value, _ := strconv.Atoi(d.bcdValue) 125 | data = float64(value) * 0.01 126 | } 127 | if d.dataType[3] == 0x02 && d.dataType[2] == 0x0B && d.dataType[1] == 0x03 { 128 | value, _ := strconv.Atoi(d.bcdValue) 129 | data = float64(value) * 0.01 130 | } 131 | if d.dataType[3] == 0x02 && d.dataType[2] == 0x80 && d.dataType[0] == 0x01 { 132 | value, _ := strconv.Atoi(d.bcdValue) 133 | data = float64(value) / 1000 134 | } 135 | if d.dataType[3] == 0x02 && d.dataType[2] == 0x80 && d.dataType[0] == 0x02 { 136 | value, _ := strconv.Atoi(d.bcdValue) 137 | data = float64(value) / 100 138 | } 139 | if d.dataType[3] == 0x02 && d.dataType[2] == 0x80 && d.dataType[0] == 0x03 { 140 | value, _ := strconv.Atoi(d.bcdValue) 141 | data = float64(value) / 10000 142 | } 143 | if d.dataType[3] == 0x02 && d.dataType[2] == 0x80 && d.dataType[0] == 0x04 { 144 | value, _ := strconv.Atoi(d.bcdValue) 145 | data = float64(value) / 10000 146 | } 147 | if d.dataType[3] == 0x02 && d.dataType[2] == 0x80 && d.dataType[0] == 0x05 { 148 | value, _ := strconv.Atoi(d.bcdValue) 149 | data = float64(value) / 10000 150 | } 151 | if d.dataType[3] == 0x02 && d.dataType[2] == 0x80 && d.dataType[0] == 0x06 { 152 | value, _ := strconv.Atoi(d.bcdValue) 153 | data = float64(value) / 10000 154 | } 155 | if d.dataType[3] == 0x02 && d.dataType[2] == 0x80 && d.dataType[0] == 0x07 { 156 | value, _ := strconv.Atoi(d.bcdValue) 157 | data = float64(value) / 10 158 | } 159 | if d.dataType[3] == 0x02 && d.dataType[2] == 0x80 && d.dataType[0] == 0x08 { 160 | value, _ := strconv.Atoi(d.bcdValue) 161 | data = float64(value) / 100 162 | } 163 | if d.dataType[3] == 0x02 && d.dataType[2] == 0x80 && d.dataType[0] == 0x09 { 164 | value, _ := strconv.Atoi(d.bcdValue) 165 | data = float64(value) / 100 166 | } 167 | if d.dataType[3] == 0x02 && d.dataType[2] == 0x80 && d.dataType[0] == 0x0A { 168 | value, _ := strconv.Atoi(d.bcdValue) 169 | data = float64(value) 170 | } 171 | if d.dataType[3] == 0x02 && d.dataType[2] == 0x80 && d.dataType[0] == 0x0B { 172 | value, _ := strconv.Atoi(d.bcdValue) 173 | data = float64(value) / 10000 174 | } 175 | if d.Negative { 176 | return data * -1 177 | } 178 | return data 179 | } 180 | func (d *ReadData) GetFloat64ValueUnsigned() float64 { 181 | for i, j := 0, len(d.dataType)-1; i < j; i, j = i+1, j-1 { 182 | d.dataType[j], d.dataType[i] = d.dataType[i], d.dataType[j] 183 | } 184 | if d.dataType[3] == 0x00 { 185 | value, _ := strconv.Atoi(d.bcdValue) 186 | return float64(value) * 0.01 187 | } 188 | if d.dataType[3] == 0x02 && d.dataType[2] == 0x03 && d.dataType[0] == 0 { 189 | value, _ := strconv.Atoi(d.bcdValue) 190 | return float64(value) * 0.0001 191 | 192 | } 193 | if d.dataType[3] == 0x02 && d.dataType[2] == 0x04 { 194 | value, _ := strconv.Atoi(d.bcdValue) 195 | data := float64(value) * 0.0001 196 | if data > 80 { 197 | return (data - 80) * -1 198 | } else { 199 | return data 200 | } 201 | } 202 | if d.dataType[3] == 0x02 && d.dataType[2] == 0x05 && d.dataType[0] == 0 { 203 | value, _ := strconv.Atoi(d.bcdValue) 204 | return float64(value) * 0.0001 205 | } 206 | if d.dataType[3] == 0x02 && d.dataType[2] == 0x06 && d.dataType[0] == 0 { 207 | value, _ := strconv.Atoi(d.bcdValue) 208 | return float64(value) * 0.001 209 | } 210 | if d.dataType[3] == 0x02 && d.dataType[2] == 0x05 && d.dataType[0] == 0 { 211 | value, _ := strconv.Atoi(d.bcdValue) 212 | return float64(value) * 0.0001 213 | } 214 | if d.dataType[3] == 0x02 && d.dataType[2] == 0x06 && d.dataType[0] == 0 { 215 | value, _ := strconv.Atoi(d.bcdValue) 216 | return float64(value) * 0.001 217 | } 218 | if d.dataType[3] == 0x02 && d.dataType[2] == 0x07 && d.dataType[0] == 0 { 219 | value, _ := strconv.Atoi(d.bcdValue) 220 | return float64(value) * 0.1 221 | } 222 | if d.dataType[3] == 0x02 && d.dataType[2] == 0x08 && d.dataType[0] == 0 { 223 | value, _ := strconv.Atoi(d.bcdValue) 224 | return float64(value) * 0.01 225 | } 226 | if d.dataType[3] == 0x02 && d.dataType[2] == 0x09 && d.dataType[0] == 0 { 227 | value, _ := strconv.Atoi(d.bcdValue) 228 | return float64(value) * 0.01 229 | } 230 | if d.dataType[3] == 0x02 && d.dataType[2] == 0x0A && d.dataType[1] == 0x01 { 231 | value, _ := strconv.Atoi(d.bcdValue) 232 | return float64(value) * 0.01 233 | } 234 | if d.dataType[3] == 0x02 && d.dataType[2] == 0x0A && d.dataType[1] == 0x02 { 235 | value, _ := strconv.Atoi(d.bcdValue) 236 | return float64(value) * 0.01 237 | } 238 | if d.dataType[3] == 0x02 && d.dataType[2] == 0x0A && d.dataType[1] == 0x03 { 239 | value, _ := strconv.Atoi(d.bcdValue) 240 | return float64(value) * 0.01 241 | } 242 | if d.dataType[3] == 0x02 && d.dataType[2] == 0x0B && d.dataType[1] == 0x01 { 243 | value, _ := strconv.Atoi(d.bcdValue) 244 | return float64(value) * 0.01 245 | } 246 | if d.dataType[3] == 0x02 && d.dataType[2] == 0x0B && d.dataType[1] == 0x02 { 247 | value, _ := strconv.Atoi(d.bcdValue) 248 | return float64(value) * 0.01 249 | } 250 | if d.dataType[3] == 0x02 && d.dataType[2] == 0x0B && d.dataType[1] == 0x03 { 251 | value, _ := strconv.Atoi(d.bcdValue) 252 | return float64(value) * 0.01 253 | } 254 | if d.dataType[3] == 0x02 && d.dataType[2] == 0x80 && d.dataType[1] == 0x01 { 255 | value, _ := strconv.Atoi(d.bcdValue) 256 | return float64(value) * 0.001 257 | } 258 | if d.dataType[3] == 0x02 && d.dataType[2] == 0x80 && d.dataType[1] == 0x02 { 259 | value, _ := strconv.Atoi(d.bcdValue) 260 | return float64(value) * 0.01 261 | } 262 | if d.dataType[3] == 0x02 && d.dataType[2] == 0x80 && d.dataType[1] == 0x03 { 263 | value, _ := strconv.Atoi(d.bcdValue) 264 | return float64(value) * 0.001 265 | } 266 | if d.dataType[3] == 0x02 && d.dataType[2] == 0x80 && d.dataType[1] == 0x04 { 267 | value, _ := strconv.Atoi(d.bcdValue) 268 | return float64(value) * 0.001 269 | } 270 | if d.dataType[3] == 0x02 && d.dataType[2] == 0x80 && d.dataType[1] == 0x05 { 271 | value, _ := strconv.Atoi(d.bcdValue) 272 | return float64(value) * 0.001 273 | } 274 | if d.dataType[3] == 0x02 && d.dataType[2] == 0x80 && d.dataType[1] == 0x06 { 275 | value, _ := strconv.Atoi(d.bcdValue) 276 | return float64(value) * 0.001 277 | } 278 | if d.dataType[3] == 0x02 && d.dataType[2] == 0x80 && d.dataType[1] == 0x07 { 279 | value, _ := strconv.Atoi(d.bcdValue) 280 | return float64(value) * 0.1 281 | } 282 | if d.dataType[3] == 0x02 && d.dataType[2] == 0x80 && d.dataType[1] == 0x08 { 283 | value, _ := strconv.Atoi(d.bcdValue) 284 | return float64(value) * 0.01 285 | } 286 | if d.dataType[3] == 0x02 && d.dataType[2] == 0x80 && d.dataType[1] == 0x09 { 287 | value, _ := strconv.Atoi(d.bcdValue) 288 | return float64(value) * 0.01 289 | } 290 | if d.dataType[3] == 0x02 && d.dataType[2] == 0x80 && d.dataType[1] == 0x09 { 291 | value, _ := strconv.Atoi(d.bcdValue) 292 | return float64(value) * 0.01 293 | } 294 | if d.dataType[3] == 0x02 && d.dataType[2] == 0x80 && d.dataType[1] == 0x0A { 295 | value, _ := strconv.Atoi(d.bcdValue) 296 | return float64(value) 297 | } 298 | if d.dataType[3] == 0x02 && d.dataType[2] == 0x80 && d.dataType[1] == 0x0B { 299 | value, _ := strconv.Atoi(d.bcdValue) 300 | return float64(value) * 0.0001 301 | } 302 | return 0 303 | } 304 | 305 | func (d ReadData) GetValue() string { 306 | return d.bcdValue 307 | } 308 | 309 | func (d ReadData) Encode(buffer *bytes.Buffer) error { 310 | //写入数据域 已经反转过了 311 | for index, b := range d.dataType { 312 | d.dataType[index] = b + 0x33 313 | } 314 | if err := binary.Write(buffer, binary.LittleEndian, d.dataType); err != nil { 315 | return err 316 | } 317 | if d.bcdValue != "" { 318 | //写入数据 319 | bcd := Number2bcd(d.bcdValue) 320 | //翻转 321 | for i, j := 0, len(bcd)-1; i < j; i, j = i+1, j-1 { 322 | bcd[i], bcd[j] = bcd[j], bcd[i] 323 | } 324 | if err := binary.Write(buffer, binary.LittleEndian, &bcd); err != nil { 325 | return err 326 | } 327 | } 328 | return nil 329 | } 330 | 331 | func (d ReadData) GetLen() byte { 332 | if d.bcdValue == "" { 333 | return 4 334 | } 335 | return 4 + byte(len(Number2bcd(d.bcdValue))) 336 | } 337 | 338 | //ReadRequest 读数据 339 | func ReadRequest(address Address, itemCode int32) *Protocol { 340 | c := NewControl() 341 | c.SetState(Read) 342 | d := NewReadData(itemCode, "") 343 | return NewProtocol(address, d, c) 344 | 345 | } 346 | 347 | //ReadRequestWithBlock 读数据 348 | func ReadRequestWithBlock(address Address, data ReadRequestData) *Protocol { 349 | c := NewControl() 350 | c.SetState(Read) 351 | return NewProtocol(address, data, c) 352 | 353 | } 354 | 355 | //ReadResponse 创建读响应 356 | func ReadResponse(address Address, itemCode int32, control *Control, rawValue string) *Protocol { 357 | return &Protocol{ 358 | Start: Start, 359 | Start2: Start, 360 | End: End, 361 | Address: address, 362 | Control: control, 363 | DataLength: 0x04, 364 | Data: NewReadData(itemCode, rawValue), 365 | } 366 | 367 | } 368 | 369 | func (r ReadRequestData) Encode(buffer *bytes.Buffer) error { 370 | //写入数据域 已经反转过了 371 | var err error 372 | for _, b := range r.dataType { 373 | err = WriteWithOffSet(buffer, b) 374 | } 375 | if r.recordNum != 0 { 376 | err = WriteWithOffSet(buffer, r.recordNum) 377 | 378 | } 379 | if r.withTime { 380 | err = WriteWithOffSet(buffer, r.min) 381 | err = WriteWithOffSet(buffer, r.hours) 382 | err = WriteWithOffSet(buffer, r.day) 383 | err = WriteWithOffSet(buffer, r.month) 384 | err = WriteWithOffSet(buffer, r.year) 385 | } 386 | return err 387 | } 388 | 389 | func (r ReadRequestData) GetLen() byte { 390 | var dataLen byte = 4 391 | if r.withTime { 392 | dataLen += 5 393 | } 394 | if r.recordNum != 0 { 395 | dataLen += 1 396 | } 397 | return dataLen 398 | } 399 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # go dlt645-2007 2 | 3 | 4 | 5 | 6 | 7 | 8 | 用go语言实现的dlt645解析 9 | ```shell 10 | go get github.com/zcx1218029121/go645 11 | ``` 12 | 13 | 1. 读请求 14 | ```go 15 | c := go645.NewClient(go645.NewRTUClientProvider(go645.WithEnableLogger(), go645.WithSerialConfig(serial.Config{ 16 | Address: "/dev/ttyUSB3", 17 | BaudRate: 19200, 18 | DataBits: 8, 19 | StopBits: 1, 20 | Parity: "E", 21 | Timeout: time.Second * 8, 22 | }))) 23 | 24 | for { 25 | time.Sleep(time.Second) 26 | pr, err := c.Read(go645.NewAddress("3a2107000481", go645.LittleEndian), 0x00_01_00_00) 27 | if err != nil { 28 | log.Print(err.Error()) 29 | } else { 30 | println(pr.GetValue()) 31 | } 32 | 33 | } 34 | ``` 35 | -------------------------------------------------------------------------------- /rtuClient.go: -------------------------------------------------------------------------------- 1 | package go645 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "io" 7 | "log" 8 | "time" 9 | ) 10 | 11 | var _ ClientProvider = (*RTUClientProvider)(nil) 12 | 13 | type RTUClientProvider struct { 14 | serialPort 15 | logger 16 | PrefixHandler 17 | } 18 | 19 | func (sf *RTUClientProvider) setPrefixHandler(handler PrefixHandler) { 20 | sf.PrefixHandler = handler 21 | } 22 | 23 | //SendAndRead 发送数据并读取返回值 24 | func (sf *RTUClientProvider) SendAndRead(p *Protocol) (aduResponse []byte, err error) { 25 | bf := bytes.NewBuffer(make([]byte, 0)) 26 | err = sf.EncodePrefix(bf) 27 | if err != nil { 28 | return nil, err 29 | } 30 | 31 | err = p.Encode(bf) 32 | if err != nil { 33 | return nil, err 34 | } 35 | return sf.SendRawFrameAndRead(bf.Bytes()) 36 | } 37 | func (sf *RTUClientProvider) Send(p *Protocol) (err error) { 38 | bf := bytes.NewBuffer(make([]byte, 0)) 39 | err = sf.EncodePrefix(bf) 40 | if err != nil { 41 | return err 42 | } 43 | err = p.Encode(bf) 44 | if err != nil { 45 | return err 46 | } 47 | return sf.SendRawFrame(bf.Bytes()) 48 | } 49 | 50 | //ReadRawFrame 读取返回数据 51 | func (sf *RTUClientProvider) ReadRawFrame() (aduResponse []byte, err error) { 52 | fe, err := sf.DecodePrefix(sf.port) 53 | if err != nil { 54 | log.Printf(err.Error()) 55 | return nil, err 56 | } 57 | head := make([]byte, 10) 58 | size, err := io.ReadAtLeast(sf.port, head, 10) 59 | if err != nil || size != 10 { 60 | return nil, err 61 | } 62 | //数据域+2 63 | expLen := head[9] + 2 64 | playLoad := make([]byte, expLen) 65 | if _, err := io.ReadAtLeast(sf.port, playLoad, int(expLen)); err != nil { 66 | return nil, err 67 | } 68 | //拆包器重新实现 69 | content := append(head, playLoad...) 70 | 71 | sf.Debugf("rec <==[% x]", append(fe, content...)) 72 | var cs uint8 73 | for _, v := range content[:len(content)-2] { 74 | cs += v 75 | } 76 | if cs != content[len(content)-2] { 77 | return content, errors.New("cs errors") 78 | } 79 | return content, nil 80 | } 81 | func (sf *RTUClientProvider) SendRawFrameAndRead(aduRequest []byte) (aduResponse []byte, err error) { 82 | sf.mu.Lock() 83 | defer sf.mu.Unlock() 84 | err = sf.serialPort.port.Flush() 85 | if err != nil { 86 | return nil, err 87 | } 88 | if err = sf.connect(); err != nil { 89 | return 90 | } 91 | err = sf.SendRawFrame(aduRequest) 92 | if err != nil { 93 | log.Printf(err.Error()) 94 | _ = sf.close() 95 | return 96 | } 97 | return sf.ReadRawFrame() 98 | } 99 | func (sf *RTUClientProvider) SendRawFrame(aduRequest []byte) (err error) { 100 | if err = sf.connect(); err != nil { 101 | return 102 | } 103 | // Send the request 104 | sf.Debugf("sending ==> [% x]", aduRequest) 105 | //发送数据 106 | _, err = sf.port.Write(aduRequest) 107 | return err 108 | } 109 | 110 | // NewRTUClientProvider allocates and initializes a RTUClientProvider. 111 | // it will use default /dev/ttyS0 19200 8 1 N and timeout 1000 112 | func NewRTUClientProvider(opts ...ClientProviderOption) *RTUClientProvider { 113 | p := &RTUClientProvider{ 114 | logger: newLogger("645RTUMaster => "), 115 | PrefixHandler: &DefaultPrefix{}, 116 | } 117 | for _, opt := range opts { 118 | opt(p) 119 | } 120 | return p 121 | } 122 | 123 | // calculateDelay roughly calculates time needed for the next frame. 124 | // See MODBUS over Serial Line - Specification and Implementation Guide (page 13). 125 | func (sf *RTUClientProvider) calculateDelay(chars int) time.Duration { 126 | var characterDelay, frameDelay int // us 127 | 128 | if sf.Baud <= 0 || sf.Baud > 19200 { 129 | characterDelay = 750 130 | frameDelay = 1750 131 | } else { 132 | characterDelay = 15000000 / sf.Baud 133 | frameDelay = 35000000 / sf.Baud 134 | } 135 | return time.Duration(characterDelay*chars+frameDelay) * time.Microsecond 136 | } 137 | -------------------------------------------------------------------------------- /serial.go: -------------------------------------------------------------------------------- 1 | package go645 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | 7 | "github.com/tarm/serial" 8 | ) 9 | 10 | // SerialDefaultTimeout Serial Default timeout 11 | const SerialDefaultTimeout = 2 * time.Second 12 | 13 | // serialPort has configuration and I/O controller. 14 | type serialPort struct { 15 | // Serial port configuration. 16 | serial.Config 17 | mu sync.Mutex 18 | port *serial.Port 19 | } 20 | 21 | // Connect try to connect the remote server 22 | func (sf *serialPort) Connect() (err error) { 23 | sf.mu.Lock() 24 | err = sf.connect() 25 | sf.mu.Unlock() 26 | return 27 | } 28 | 29 | // Caller must hold the mutex before calling this method. 30 | func (sf *serialPort) connect() error { 31 | if sf.port == nil { 32 | port, err := serial.OpenPort(&sf.Config) 33 | if err != nil { 34 | return err 35 | } 36 | sf.port = port 37 | } 38 | return nil 39 | } 40 | 41 | // IsConnected returns a bool signifying whether the client is connected or not. 42 | func (sf *serialPort) IsConnected() (b bool) { 43 | sf.mu.Lock() 44 | b = sf.port != nil 45 | sf.mu.Unlock() 46 | return b 47 | } 48 | 49 | // setSerialConfig set serial config 50 | func (sf *serialPort) setSerialConfig(config serial.Config) { 51 | sf.Config = config 52 | } 53 | 54 | func (sf *serialPort) setTCPTimeout(time.Duration) {} 55 | 56 | func (sf *serialPort) close() (err error) { 57 | if sf.port != nil { 58 | err = sf.port.Close() 59 | sf.port = nil 60 | } 61 | return err 62 | } 63 | 64 | // Close close current connection. 65 | func (sf *serialPort) Close() (err error) { 66 | sf.mu.Lock() 67 | err = sf.close() 68 | sf.mu.Unlock() 69 | return 70 | } 71 | -------------------------------------------------------------------------------- /value.go: -------------------------------------------------------------------------------- 1 | package go645 2 | 3 | type Value struct { 4 | value float64 5 | } 6 | --------------------------------------------------------------------------------