go实现实现 SECS/GEM 协议
在 Go 语言中实现 SECS/GEM 协议,通常有“从零手写底层”和“使用网关透传(推荐)”两种主流方式。
由于 SECS 协议对二进制编码(如 List 结构、ASCII/Binary 转换)要求严格,且涉及复杂的 HSMS 状态机(连接建立、超时处理),直接手写底层代码工作量较大。
以下我为你提供两种不同深度的实现方案:
🛠️ 方案一:轻量级网关模式(推荐,适合快速开发)
这是目前工业现场非常流行的做法。通过一个中间件(如 skullone 或类似的 SECS-Gateway),将底层的 SECS 报文转换为 JSON 格式,你的 Go 程序只需要像写 Web 服务一样处理 JSON 即可。
架构思路:
设备 (SECS) <--> [SECS Gateway] <--> WebSocket/MQTT/HTTP <--> 你的 Go 程序
Go 代码示例(模拟接收 S6F11 事件报告):
假设网关已经将设备的报警信息转换为 JSON 并通过 WebSocket 推送给你的 Go 服务。
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"github.com/gorilla/websocket" // 需安装: go get github.com/gorilla/websocket
)
// 定义接收到的 SECS 消息结构 (由网关转换后的 JSON)
type SecsMessage struct {
Stream int `json:"stream"` // 流号,例如 6
Function int `json:"function"` // 功能号,例如 11
MessageID string `json:"messageId"` // 消息唯一标识
Data struct {
EventID int `json:"ceid"` // 事件 ID
AlarmID string `json:"alid"` // 报警 ID
Message string `json:"msg"` // 报警内容
} `json:"data"`
}
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool { return true }, // 允许跨域测试
}
func handleSecsConnection(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Fatal(err)
}
defer conn.Close()
fmt.Println("SECS Gateway 已连接...")
for {
_, message, err := conn.ReadMessage()
if err != nil {
log.Println("读取消息错误:", err)
break
}
var msg SecsMessage
if err := json.Unmarshal(message, &msg); err != nil {
log.Printf("JSON解析失败: %v", err)
continue
}
// --- 业务逻辑处理 ---
// 这里处理具体的 SECS 业务,例如 S6F11 (事件报告)
if msg.Stream == 6 && msg.Function == 11 {
fmt.Printf("收到设备报警 -> 事件ID: %d, 内容: %s\n", msg.Data.EventID, msg.Data.Message)
// 实际场景中,你需要构造回复给网关的 JSON,网关会将其转为 S6F12 发给设备
sendReply(conn, msg.MessageID)
}
}
}
// 模拟回复确认
func sendReply(conn *websocket.Conn, originalMsgID string) {
reply := map[string]interface{}{
"type": "reply",
"messageId": originalMsgID,
"status": "success", // 告诉网关发送成功
}
data, _ := json.Marshal(reply)
conn.WriteMessage(websocket.TextMessage, data)
}
func main() {
http.HandleFunc("/ws", handleSecsConnection)
fmt.Println("Go SECS 服务启动在 :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
💻 方案二:原生 TCP 实现(硬核,适合深入理解)
如果你需要直接通过网线与设备通信,不使用中间件,你需要利用 Go 的 net 包处理 TCP 连接,并手动处理 HSMS 握手和 SECS-II 编码。
核心难点:
- HSMS 握手:需要处理 Select Request/Response 来建立会话。
- 编码解码:SECS-II 使用特殊的 TLV(Tag-Length-Value)格式,例如 List 类型是
0x00+ 3字节长度 + 元素个数。
Go 代码骨架(TCP 服务端模拟):
package main
import (
"encoding/binary"
"fmt"
"io"
"net"
"time"
)
// HSMS 消息头结构 (10字节)
type HsmsHeader struct {
Length uint32 // 消息体长度
DeviceID uint16 // 设备 ID (通常高位为 SystemByte)
PType byte // 协议类型 (0=数据, 1=Select请求等)
SModifier byte // 系统字节高位
RModifier byte // 系统字节低位
WBit byte // W位 (是否需要回复)
Stream byte
Function byte
}
func main() {
// 监听端口 (HSMS 默认通常是 5000 或 4000)
listener, err := net.Listen("tcp", ":5000")
if err != nil {
fmt.Println("监听失败:", err)
return
}
fmt.Println("等待设备连接 (HSMS Server Mode)...")
conn, err := listener.Accept()
if err != nil {
fmt.Println("连接失败:", err)
return
}
defer conn.Close()
fmt.Println("设备已连接!")
// 简单的消息循环
buffer := make([]byte, 1024)
for {
// 1. 读取消息头 (前4个字节是长度)
lengthBuf := make([]byte, 4)
if _, err := io.ReadFull(conn, lengthBuf); err != nil {
break
}
msgLen := binary.BigEndian.Uint32(lengthBuf)
// 2. 读取剩余的消息头和消息体
remainingLen := int(msgLen) + 6 // 长度字段本身不算在msgLen里,但要读剩下的6字节头+数据
if remainingLen > len(buffer) {
remainingLen = len(buffer)
}
n, err := conn.Read(buffer[:remainingLen])
if err != nil {
fmt.Println("读取异常:", err)
break
}
// 3. 解析 SECS 消息 (简化版解析)
handleSecsMessage(buffer[:n], conn)
}
}
func handleSecsMessage(data []byte, conn net.Conn) {
// 这里需要引入 SECS-II 解码库来解析 data[6:] (跳过HSMS头)
// 简单演示:假设收到了 S1F13 (Establish Communication Request)
// 真实开发中,请使用开源库如 github.com/djherbis/secs 或类似库进行 Unmarshal
fmt.Printf("收到原始数据: % x...\n", data)
// 模拟回复 S1F14 (Establish Communication Response)
// 注意:实际代码需要构建正确的 SECS-II List 结构
replyData := buildS1F14Response()
conn.Write(replyData)
}
// 构建 S1F14 响应 (伪代码,需补充具体二进制构建逻辑)
func buildS1F14Response() []byte {
// 这是一个简化的占位符,实际需要严格按照 SECS-II 标准构建字节数组
// 包括 MDLN (设备型号), SOFTREV (软件版本) 等信息
return []byte{0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x0E, 0x00}
}
📚 关键建议
- 不要重复造轮子:SECS-II 的数据类型(List, Array, Binary, ASCII)编码比较繁琐。如果是原生开发,强烈建议在 GitHub 上寻找 Go 语言的 SECS 库(搜索
go secs-gem或go hsms),或者使用 C# 的成熟库(如Secs4Net)作为参考逻辑。 - 关注流和功能:
- S1 (Equipment Status): 重点关注
S1F13(上线握手),这是通信的第一步。 - S6 (Event Reports): 重点关注
S6F11(设备主动上报数据),这是获取生产数据的核心。 - S2 (Control): 重点关注
S2F41(主机命令),用于远程控制设备启停。
- S1 (Equipment Status): 重点关注
- 调试工具:使用 Wireshark 抓包分析 TCP 流量,或者使用专门的 SECS Simulator(模拟器)来测试你的 Go 程序是否能正确响应。

浙公网安备 33010602011771号