274 lines
9.5 KiB
Go
274 lines
9.5 KiB
Go
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
|
||
}
|