
本文介绍如何使用纯 go 语言解析 gsm 短信的 pdu(protocol data unit)编码,包括从 at 命令获取的十六进制字符串到可读短信内容的完整解码流程,并提供开箱即用的第三方库方案与实操示例。
本文介绍如何使用纯 go 语言解析 gsm 短信的 pdu(protocol data unit)编码,包括从 at 命令获取的十六进制字符串到可读短信内容的完整解码流程,并提供开箱即用的第三方库方案与实操示例。
在嵌入式通信、iot 设备或 Modem 管理场景中,常需通过串口发送 AT 命令(如 AT+CMGL=4)读取短信,返回结果为十六进制格式的 PDU 字符串(例如 0791361907002039040C9136198748701300005150713220052308C8303A8C0EA3C3)。这类数据遵循 GSM 03.40 标准,结构紧凑但解析复杂——涉及地址域编码(BCD/TON/NPI)、时间戳(7-bit 时间压缩)、UDH(用户数据头)、7-bit/8-bit/UCS2 编码切换等细节。
幸运的是,Go 生态中已有成熟、轻量且符合标准的 PDU 解析库:github.com/xlab/at/sms。它完全基于 Go 原生实现,不依赖 CGO 或外部工具,支持接收、发送及完整字段解析(发件人、接收人、时间、短信正文、状态报告等),并严格遵循 ETSI TS 123 040 和 3GPP 23.040 规范。
以下是一个最小可行示例,演示如何将原始 PDU 十六进制字符串解码为结构化短信对象:
package main import ( "encoding/hex" "fmt" "log" "github.com/xlab/at/sms" ) func main() { // 来自 AT+CMGL=4 的原始 PDU 十六进制字符串 pduHex := "0791361907002039040C9136198748701300005150713220052308C8303A8C0EA3C3" // 解码为字节切片 pduBytes, err := hex.DecodeString(pduHex) if err != nil { log.Fatal("PDU hex decode failed:", err) } // 初始化 SMS 消息结构体 msg := new(sms.Message) if _, err := msg.ReadFrom(pduBytes); err != nil { log.Fatal("PDU parsing failed:", err) } // 输出结构化信息 fmt.Printf("Sender: %sn", msg.Sender) fmt.Printf("Recipient: %sn", msg.Recipient) fmt.Printf("Timestamp: %vn", msg.Timestamp) fmt.Printf("Text: %sn", msg.Text) fmt.Printf("Status: %v (is status report: %t)n", msg.Status, msg.IsStatusReport) }
运行后输出类似:
Sender: +639170000293 Recipient: +639178840731 Timestamp: 2023-05-23 08:30:32 +0000 UTC Text: Hahahaha Status: 0 (is status report: false)
⚠️ 关键注意事项:
- sms.Message.ReadFrom() 会按标准自动识别 PDU 类型(SMS-DELIVER、SMS-SUBMIT、SMS-STATUS-REPORT),无需手动判断;
- 时间戳字段(TP-SC-TS)默认以 UTC 解析;若需本地时区,请在解析后手动转换;
- 中文短信若采用 UCS2 编码(常见于含中文的 AT+CMGF=0 模式),该库已内置 UTF-16BE 解码逻辑,无需额外处理;
- 若需发送短信,可调用 msg.WriteTo() 生成合法 PDU 字节流,再通过串口发送至 Modem;
- 该库不包含串口通信能力,建议搭配 github.com/tarm/serial 或 machine(TinyGo)完成 AT 命令交互闭环。
总结:Go 完全胜任 SMS PDU 解析任务。借助 github.com/xlab/at/sms,开发者可跳过底层位操作与编码转换的复杂性,专注业务逻辑。对于高可靠性要求的工业级 Modem 管理系统,建议结合单元测试覆盖各类 PDU 变体(长短信、带 UDH、状态报告等),并校验 msg.Error 字段确保解析完整性。