EdgexAgent/device-ble-go/pkg/uart/serial_queue.go
2025-07-10 20:40:32 +08:00

274 lines
9.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package uart
import (
"device-ble/internal/interfaces"
"fmt"
"io"
"strings"
"time"
"github.com/edgexfoundry/go-mod-core-contracts/v4/clients/logger"
)
// SerialQueue 串口命令队列管理器,用于管理串口命令的发送和响应处理。
type SerialQueue struct {
serialPort interfaces.SerialPortInterface // 串口操作接口
requestCh chan interfaces.SerialRequest // 命令请求队列通道
pendingRequests []interfaces.SerialRequest // 待处理请求,按顺序存储
commandCallback func(string) // 异步命令消息回调函数
upAgentCallback func(string) // 异步透明代理回调函数
stopCh chan struct{} // 停止信号通道
logger logger.LoggingClient // 日志记录器
readerCh chan string // 串口读取数据的通用管道
}
// NewSerialQueue 创建新的串口队列管理器并启动后台处理协程。
func NewSerialQueue(port interfaces.SerialPortInterface, logger logger.LoggingClient, ccb, uacb func(string), queueSize int) *SerialQueue {
if queueSize <= 0 {
queueSize = 10 // 默认容量 10
}
q := &SerialQueue{
serialPort: port,
requestCh: make(chan interfaces.SerialRequest, queueSize),
pendingRequests: make([]interfaces.SerialRequest, 0),
commandCallback: ccb,
upAgentCallback: uacb,
stopCh: make(chan struct{}),
logger: logger,
}
go q.processRequests()
go q.startReaderLoop()
q.logger.Infof("串口队列管理器已启动,请求队列容量: %d", queueSize)
return q
}
// SendCommand 发送串口命令并等待响应。
// 参数:
// - command: 要发送到串口设备的命令字节数组,不能为空。
// - timeout: 等待设备响应的最大时间,单位为纳秒。
// - readDelay: 发送命令后等待设备处理的延迟时间,单位为纳秒。
// - queueTimeout: 尝试将请求放入队列的最大等待时间,单位为纳秒。
//
// 返回值:
// - string: 设备返回的响应数据(例如 "OK" 或 "ERROR")。
// - error: 如果发生错误(如命令为空、队列满、响应超时),返回非 nil 错误。
//
// 错误:
// - "命令不能为空": 如果 command 参数为空。
// - "请求队列已满": 如果在 queueTimeout 时间内无法将请求放入队列(最多重试 3 次)。
// - "等待响应超时": 如果在 readDelay + timeout 时间内未收到设备响应。
func (q *SerialQueue) SendCommand(command []byte, timeout, readDelay, queueTimeout time.Duration) (string, error) {
if len(command) == 0 {
return "", fmt.Errorf("命令不能为空")
}
responseCh := make(chan interfaces.SerialResponse, 1) // 容量为 1确保单一响应
req := interfaces.SerialRequest{
Command: command,
Timeout: timeout,
DelayBeforeRead: readDelay,
ResponseCh: responseCh,
Timestamp: time.Now(), // 用于超时清理
}
for retries := 0; retries < 3; retries++ {
select {
case q.requestCh <- req:
q.logger.Debugf("请求发送成功,命令: %s, 重试次数: %d", string(command), retries)
goto WaitResponse
case <-time.After(queueTimeout):
q.logger.Warnf("请求队列已满,当前长度: %d/%d, 重试次数: %d", len(q.requestCh), cap(q.requestCh), retries)
if retries == 2 {
return "", fmt.Errorf("请求队列已满,当前长度: %d/%d, 重试 3 次失败", len(q.requestCh), cap(q.requestCh))
}
}
}
WaitResponse:
select {
case resp := <-responseCh:
return resp.Data, resp.Error
case <-time.After(readDelay + timeout):
return "", fmt.Errorf("等待响应超时,命令: %s", string(command))
}
}
// GetResponse 读取串口中的一条响应数据,不发送任何命令。
// 参数:
// - timeout: 最长等待数据的时间(纳秒)
//
// 返回:
// - string: 串口读取到的数据(通常是一整行)
// - error: 超时或读取错误
func (q *SerialQueue) GetResponse(timeout time.Duration) (string, error) {
if q.readerCh == nil {
return "", fmt.Errorf("读取通道未初始化")
}
select {
case line := <-q.readerCh:
q.logger.Debugf("收到串口数据: %s", line)
return line, nil
case <-time.After(timeout):
return "", fmt.Errorf("串口读取超时(%s 内无数据)", timeout)
}
}
// processRequests 后台协程,串行处理所有命令请求。
// 从 requestCh 读取请求,写入串口命令,并将成功写入的请求加入 pendingRequests 等待响应。
func (q *SerialQueue) processRequests() {
for {
select {
case <-q.stopCh:
q.logger.Debugf("停止处理请求协程")
return
case req := <-q.requestCh:
if err := q.writeCommand(req.Command); err != nil {
// 写入失败,直接发送错误响应,不加入 pendingRequests
resp := interfaces.SerialResponse{Data: "", Error: fmt.Errorf("写入命令失败: %v", err)}
select {
case req.ResponseCh <- resp:
q.logger.Debugf("响应发送成功(写入错误),命令: %s", string(req.Command))
default:
q.logger.Warnf("响应通道已满,命令: %s", string(req.Command))
}
continue // 处理下一个请求
}
// 写入成功,加入 pendingRequests
q.pendingRequests = append(q.pendingRequests, req)
q.logger.Debugf("请求加入待处理列表,命令: %s, 当前待处理数: %d", string(req.Command), len(q.pendingRequests))
}
}
}
// writeCommand 实际写入命令到串口。
func (q *SerialQueue) writeCommand(cmd []byte) error {
_, err := q.serialPort.Write(cmd)
if err != nil {
q.logger.Errorf("串口写入失败: %v", err)
}
q.logger.Debugf("已发送命令: %s", string(cmd))
return err
}
// startReaderLoop 启动串口读取循环。
// 持续读取串口数据,处理终止响应并匹配到 pendingRequests 的最早请求。
func (q *SerialQueue) startReaderLoop() {
q.readerCh = make(chan string, 100)
go func() {
for {
select {
case <-q.stopCh:
q.logger.Debugf("停止读取协程")
return
default:
// 清理超时的 pendingRequests
now := time.Now()
for len(q.pendingRequests) > 0 && now.Sub(q.pendingRequests[0].Timestamp) > q.pendingRequests[0].Timeout+q.pendingRequests[0].DelayBeforeRead {
req := q.pendingRequests[0]
resp := interfaces.SerialResponse{Data: "", Error: fmt.Errorf("请求超时,未收到响应,命令: %s", string(req.Command))}
select {
case req.ResponseCh <- resp:
q.logger.Warnf("请求超时,命令: %s, 已移除", string(req.Command))
default:
q.logger.Warnf("响应通道已满,超时请求,命令: %s", string(req.Command))
}
q.pendingRequests = q.pendingRequests[1:]
}
line, err := q.serialPort.ReadLine()
if err != nil {
if err == io.EOF {
time.Sleep(1 * time.Millisecond)
continue
}
q.logger.Errorf("串口读取错误: %v", err)
continue
}
line = strings.Trim(line, "\r\n")
if line == "" || strings.Contains(line, "freqchip") { //跳过蓝牙回显非法的字符
continue
}
q.logger.Debugf("收到串口数据: %s", line)
if strings.Contains(line, "+COMMAND:") { //处理终端运维命令控制回调
lines := strings.Split(line, "+COMMAND:")
for _, part := range lines {
if part == "" {
continue
}
if q.commandCallback != nil {
go q.commandCallback(part)
}
}
continue
}
if q.isTerminal(line) {
if len(q.pendingRequests) > 0 {
req := q.pendingRequests[0]
// 应用 DelayBeforeRead
if req.DelayBeforeRead > 0 {
time.Sleep(req.DelayBeforeRead)
q.logger.Debugf("应用读取延迟: %v, 命令: %s", req.DelayBeforeRead, string(req.Command))
}
resp := interfaces.SerialResponse{Data: line}
if strings.Contains(line, "ERROR") {
resp.Error = fmt.Errorf("命令执行失败: %s", line)
}
select {
case req.ResponseCh <- resp:
q.logger.Debugf("响应发送成功,命令: %s, 数据: %s", string(req.Command), line)
default:
q.logger.Warnf("响应通道已满,命令: %s", string(req.Command))
}
// 无论是否发送成功,都移除请求
q.pendingRequests = q.pendingRequests[1:]
} else {
// 收到终止响应但没有挂起请求
q.logger.Warnf("收到意外终止响应: %s但无挂起请求", line)
}
} else {
if q.upAgentCallback != nil {
go q.upAgentCallback(line)
}
}
}
}
}()
}
// isTerminal 用于判断是不是请求的响应
func (q *SerialQueue) isTerminal(line string) bool {
return strings.Contains(line, "OK") ||
strings.Contains(line, "ERROR") ||
strings.Contains(line, "+QVERSION") ||
strings.Contains(line, "+QBLEADDR")
}
// Close 关闭串口队列管理器,停止后台协程并清理资源。
func (q *SerialQueue) Close() error {
q.logger.Infof("关闭串口队列管理器")
close(q.stopCh)
if err := q.serialPort.Close(); err != nil {
q.logger.Errorf("关闭串口失败: %v", err)
return fmt.Errorf("关闭串口失败: %v", err)
}
// 清空待处理请求
for _, req := range q.pendingRequests {
select {
case req.ResponseCh <- interfaces.SerialResponse{Data: "", Error: fmt.Errorf("串口已关闭")}:
q.logger.Debugf("通知请求关闭,命令: %s", string(req.Command))
default:
q.logger.Warnf("响应通道已满或关闭,命令: %s", string(req.Command))
}
}
q.pendingRequests = nil
close(q.readerCh)
q.logger.Infof("串口队列管理器已关闭")
return nil
}
func (q *SerialQueue) GetPort() interfaces.SerialPortInterface {
return q.serialPort
}