Skip to content

Commit ddc1ca7

Browse files
author
hezhuozhuo
committed
Initial commit: SL427-2021 protocol implementation
0 parents  commit ddc1ca7

File tree

27 files changed

+3409
-0
lines changed

27 files changed

+3409
-0
lines changed

README.md

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
# go-sl427
2+
3+
[![Go Reference](https://pkg.go.dev/badge/github.com/ThingsPanel/go-sl427.svg)](https://pkg.go.dev/github.com/ThingsPanel/go-sl427)
4+
[![Go Report Card](https://goreportcard.com/badge/github.com/ThingsPanel/go-sl427)](https://goreportcard.com/report/github.com/ThingsPanel/go-sl427)
5+
6+
go-sl427是一个用Go语言实现的SL427-2021水资源监测数据传输规约库。该库提供了完整的协议实现,支持监测站和数据中心服务器的开发。
7+
8+
## 特性
9+
10+
- 完整实现SL427-2021协议规范
11+
- 支持监测站和服务器端开发
12+
- 提供灵活的配置选项
13+
- 内置监控指标收集
14+
- 支持自定义日志接口
15+
- 线程安全设计
16+
- 详细的错误处理
17+
18+
## 安装
19+
20+
```bash
21+
go get github.com/ThingsPanel/go-sl427
22+
```
23+
24+
## 快速开始
25+
26+
### 监测站示例
27+
28+
```go
29+
package main
30+
31+
import (
32+
"log"
33+
"time"
34+
35+
"github.com/ThingsPanel/go-sl427/pkg/sl427/station"
36+
)
37+
38+
func main() {
39+
// 创建监测站实例
40+
config := station.Config{
41+
Address: 0x01, // 站点地址
42+
Server: "localhost:8080", // 服务器地址
43+
Interval: time.Second * 30, // 数据上报间隔
44+
}
45+
46+
s := station.NewStation(config)
47+
48+
// 启动监测站
49+
if err := s.Start(config); err != nil {
50+
log.Fatal(err)
51+
}
52+
defer s.Stop()
53+
54+
// 保持运行
55+
select {}
56+
}
57+
```
58+
59+
### 服务器示例
60+
61+
```go
62+
package main
63+
64+
import (
65+
"context"
66+
"log"
67+
"os"
68+
"os/signal"
69+
"syscall"
70+
71+
"github.com/ThingsPanel/go-sl427/pkg/sl427/transport"
72+
)
73+
74+
func main() {
75+
// 服务器配置
76+
config := transport.Config{
77+
ListenAddr: ":8080",
78+
ReadTimeout: 30,
79+
WriteTimeout: 30,
80+
MaxConns: 1000,
81+
MaxPacketSize: 1024,
82+
}
83+
84+
// 创建服务器
85+
server := transport.NewServer(config)
86+
87+
// 创建context用于优雅关闭
88+
ctx, cancel := context.WithCancel(context.Background())
89+
defer cancel()
90+
91+
// 启动服务器
92+
if err := server.Start(ctx); err != nil {
93+
log.Fatal(err)
94+
}
95+
96+
// 等待中断信号
97+
sigChan := make(chan os.Signal, 1)
98+
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
99+
<-sigChan
100+
101+
// 优雅关闭
102+
server.Stop()
103+
}
104+
```
105+
106+
## 核心组件
107+
108+
- **station**: 监测站实现
109+
- **transport**: 网络传输层
110+
- **protocol**: 协议解析与封装
111+
- **codec**: 数据编解码
112+
- **packet**: 数据包定义
113+
- **types**: 基础类型定义
114+
- **metrics**: 监控指标收集
115+
116+
## 数据项定义
117+
118+
库提供了内置的数据项注册表,支持自定义数据项定义:
119+
120+
```go
121+
// 注册数据项
122+
types.DefaultRegistry.RegisterBatch([]types.DataItemDef{
123+
{
124+
ID: 1001,
125+
Name: "水位",
126+
Type: types.TypeInt32,
127+
Unit: "m",
128+
Scale: -3,
129+
Description: "站点水位",
130+
},
131+
// ... 更多数据项定义
132+
})
133+
```
134+
135+
## 监控指标
136+
137+
内置的监控指标包括:
138+
139+
- 接收的数据包数量
140+
- 发送的数据包数量
141+
- 丢弃的数据包数量
142+
- 最后接收时间
143+
- 最后发送时间
144+
- 处理延迟
145+
146+
## 错误处理
147+
148+
库提供了统一的错误处理机制:
149+
150+
```go
151+
if sl427.IsErrorCode(err, sl427.ErrCodeInvalidData) {
152+
// 处理无效数据错误
153+
}
154+
```
155+
156+
## 配置选项
157+
158+
both站点和服务器支持多种配置选项:
159+
160+
```go
161+
// 站点配置
162+
station.Config{
163+
Address: 0x01,
164+
Server: "localhost:8080",
165+
Interval: time.Second * 30,
166+
}
167+
168+
// 服务器配置
169+
transport.Config{
170+
ListenAddr: ":8080",
171+
ReadTimeout: 30,
172+
WriteTimeout: 30,
173+
MaxConns: 1000,
174+
MaxPacketSize: 1024,
175+
}
176+
```
177+
178+
## 日志接口
179+
180+
支持自定义日志实现:
181+
182+
```go
183+
type CustomLogger struct {
184+
// 自定义日志实现
185+
}
186+
187+
func (l *CustomLogger) Printf(format string, v ...interface{}) {
188+
// 实现日志记录
189+
}
190+
191+
// 设置日志接口
192+
types.SetLogger(&CustomLogger{})
193+
```
194+
195+
## 示例程序
196+
197+
`cmd/examples` 目录下提供了完整的示例程序:
198+
199+
- `basic`: 基础使用示例
200+
- `server`: 完整的服务器示例
201+
- `station`: 监测站示例
202+
203+
## 文档
204+
205+
详细的文档和API参考请访问:[pkg.go.dev](https://pkg.go.dev/github.com/ThingsPanel/go-sl427)
206+
207+
## 贡献指南
208+
209+
1. Fork 项目
210+
2. 创建特性分支 (`git checkout -b feature/AmazingFeature`)
211+
3. 提交更改 (`git commit -m 'Add some AmazingFeature'`)
212+
4. 推送到分支 (`git push origin feature/AmazingFeature`)
213+
5. 打开Pull Request
214+
215+
## 许可证
216+
217+
采用 MIT 许可证。详见 [LICENSE](LICENSE) 文件。
218+
219+
## 支持
220+
221+
如有问题或建议,请提交 [Issue](https://github.com/ThingsPanel/go-sl427/issues)
222+
223+
## 致谢
224+
225+
感谢所有贡献者对项目的支持。

cmd/examples/basic/main.go

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
// cmd/examples/basic/main.go
2+
package main
3+
4+
import (
5+
"encoding/binary"
6+
"log"
7+
"time"
8+
9+
"github.com/ThingsPanel/go-sl427/pkg/sl427/codec"
10+
"github.com/ThingsPanel/go-sl427/pkg/sl427/packet"
11+
"github.com/ThingsPanel/go-sl427/pkg/sl427/types"
12+
)
13+
14+
func main() {
15+
// 运行所有示例
16+
sendDataExample()
17+
receiveDataExample()
18+
handleHeartbeatExample()
19+
}
20+
21+
// 发送数据示例
22+
func sendDataExample() {
23+
log.Println("运行发送数据示例...")
24+
25+
// 构造测量数据包
26+
var payload []byte
27+
28+
// 1. 添加时间戳
29+
timestamp := types.NewTimeStamp(time.Now())
30+
payload = append(payload, timestamp.Bytes()...)
31+
32+
// 2. 添加一个Int16类型的测量值
33+
payload = append(payload, types.TypeInt16) // 数据类型标识
34+
value := uint16(1234) // 测量值
35+
valueBuf := make([]byte, 2)
36+
binary.BigEndian.PutUint16(valueBuf, value)
37+
payload = append(payload, valueBuf...)
38+
39+
// 3. 创建数据包
40+
p, err := packet.NewPacket(0x12345678, types.CmdUpload, payload)
41+
if err != nil {
42+
log.Printf("创建数据包失败: %v", err)
43+
return
44+
}
45+
46+
// 4. 编码数据包
47+
codec := codec.NewPacketCodec()
48+
encoded, err := codec.EncodePacket(p)
49+
if err != nil {
50+
log.Printf("编码失败: %v", err)
51+
return
52+
}
53+
54+
log.Printf("数据包已编码: %X", encoded)
55+
56+
// 5. 解码验证
57+
decoded, err := codec.DecodePacket(encoded)
58+
if err != nil {
59+
log.Printf("解码失败: %v", err)
60+
return
61+
}
62+
63+
// 6. 解析数据内容
64+
data := decoded.Data
65+
if len(data) >= types.TimestampLen {
66+
ts, err := types.ParseTimeStamp(data[:types.TimestampLen])
67+
if err != nil {
68+
log.Printf("解析时间戳失败: %v", err)
69+
return
70+
}
71+
log.Printf("时间戳: %v", ts.Time)
72+
73+
// 解析测量值
74+
if len(data) >= types.TimestampLen+3 { // timestampLen + type(1) + value(2)
75+
dataType := data[types.TimestampLen]
76+
if dataType == types.TypeInt16 {
77+
measureValue := binary.BigEndian.Uint16(data[types.TimestampLen+1:])
78+
log.Printf("测量值: %d", measureValue)
79+
}
80+
}
81+
}
82+
}
83+
84+
// 接收数据示例
85+
func receiveDataExample() {
86+
log.Println("运行接收数据示例...")
87+
88+
// 创建一个模拟的数据包
89+
timestamp := types.NewTimeStamp(time.Now())
90+
mockPayload := append(timestamp.Bytes(), types.TypeInt16)
91+
mockValue := uint16(1234)
92+
valueBuf := make([]byte, 2)
93+
binary.BigEndian.PutUint16(valueBuf, mockValue)
94+
mockPayload = append(mockPayload, valueBuf...)
95+
96+
p, _ := packet.NewPacket(0x12345678, types.CmdUpload, mockPayload)
97+
codec := codec.NewPacketCodec()
98+
mockData, _ := codec.EncodePacket(p)
99+
100+
// 解码数据包
101+
decoded, err := codec.DecodePacket(mockData)
102+
if err != nil {
103+
log.Printf("解码失败: %v", err)
104+
return
105+
}
106+
107+
// 解析数据内容
108+
data := decoded.Data
109+
if len(data) >= types.TimestampLen {
110+
ts, _ := types.ParseTimeStamp(data[:types.TimestampLen])
111+
log.Printf("接收到数据 - 时间戳: %v", ts.Time)
112+
113+
if len(data) >= types.TimestampLen+3 {
114+
dataType := data[types.TimestampLen]
115+
if dataType == types.TypeInt16 {
116+
value := binary.BigEndian.Uint16(data[types.TimestampLen+1:])
117+
log.Printf("接收到数据 - 测量值: %d", value)
118+
}
119+
}
120+
}
121+
}
122+
123+
// 心跳包处理示例
124+
func handleHeartbeatExample() {
125+
log.Println("运行心跳包示例...")
126+
127+
// 创建心跳包,心跳包payload中只包含时间戳
128+
timestamp := types.NewTimeStamp(time.Now())
129+
p, _ := packet.NewPacket(0x12345678, types.CmdHeartbeat, timestamp.Bytes())
130+
131+
// 编码发送
132+
codec := codec.NewPacketCodec()
133+
encoded, _ := codec.EncodePacket(p)
134+
135+
log.Printf("发送心跳包: %X", encoded)
136+
137+
// 模拟接收并处理
138+
received, err := codec.DecodePacket(encoded)
139+
if err != nil {
140+
log.Printf("解码心跳包失败: %v", err)
141+
return
142+
}
143+
144+
if received.Header.Command == types.CmdHeartbeat {
145+
ts, _ := types.ParseTimeStamp(received.Data)
146+
log.Printf("收到心跳包 - 地址: %X, 时间: %v", received.Header.Address, ts.Time)
147+
}
148+
}

0 commit comments

Comments
 (0)