speaker.go 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. package lc
  2. import (
  3. "bytes"
  4. "encoding/binary"
  5. "encoding/hex"
  6. "errors"
  7. "fmt"
  8. "io"
  9. "sync"
  10. "time"
  11. "github.com/tarm/serial"
  12. "golang.org/x/text/encoding/simplifiedchinese"
  13. "golang.org/x/text/transform"
  14. )
  15. // Loudspeaker 扬声器接口
  16. type Loudspeaker interface {
  17. Speak(txt string)
  18. }
  19. // IpCast 串口语音播报实现(核心:播放中丢弃请求+仅空闲时接收+无缓存)
  20. type IpCast struct {
  21. Port string // 串口端口(外部传入)
  22. Baud int // 波特率(固定为115200)
  23. serialPort *serial.Port // 串口连接实例
  24. mu sync.Mutex // 全局互斥锁(保护isPlaying/串口操作)
  25. idleChan chan struct{} // 芯片空闲通知通道
  26. isClosed bool // 串口是否已关闭
  27. isPlaying bool // 标记是否正在播放语音(核心控制字段)
  28. }
  29. // NewIpCast 初始化串口语音播报实例
  30. // prot: 串口端口(如"/dev/ttyUSB0")
  31. func NewIpCast(prot string) *IpCast {
  32. s := &IpCast{
  33. Port: prot,
  34. Baud: 115200,
  35. idleChan: make(chan struct{}, 1), // 缓冲通道避免阻塞
  36. isClosed: false,
  37. isPlaying: false, // 初始为未播放状态
  38. }
  39. // 初始化串口
  40. if err := s.Reconnect(); err != nil {
  41. fmt.Printf("串口初始化失败: %v\n", err)
  42. }
  43. // 启动后台监听协程
  44. go s.listenSerialResponse()
  45. return s
  46. }
  47. // Reconnect 重连串口(加锁保护+标记串口状态)
  48. func (ip *IpCast) Reconnect() error {
  49. ip.mu.Lock()
  50. defer ip.mu.Unlock()
  51. // 标记串口为关闭状态
  52. ip.isClosed = true
  53. if ip.serialPort != nil {
  54. _ = ip.serialPort.Close()
  55. ip.serialPort = nil
  56. }
  57. // 配置串口参数
  58. cfg := &serial.Config{
  59. Name: ip.Port,
  60. Baud: ip.Baud,
  61. Size: 8,
  62. Parity: serial.ParityNone,
  63. StopBits: 1,
  64. ReadTimeout: time.Millisecond * 200, // 减少EOF报错频率
  65. }
  66. port, err := serial.OpenPort(cfg)
  67. if err != nil {
  68. return fmt.Errorf("打开串口失败: %w", err)
  69. }
  70. ip.serialPort = port
  71. ip.isClosed = false // 标记串口可用
  72. fmt.Println("串口重连成功")
  73. return nil
  74. }
  75. // clearIdleChan 清空空闲通道所有残留信号(避免旧信号干扰)
  76. func (ip *IpCast) clearIdleChan() {
  77. for {
  78. select {
  79. case <-ip.idleChan:
  80. default:
  81. return
  82. }
  83. }
  84. }
  85. // listenSerialResponse 后台监听串口返回(仅处理0x41/0x4F,清空残留信号)
  86. func (ip *IpCast) listenSerialResponse() {
  87. buf := make([]byte, 64)
  88. for {
  89. // 串口未就绪时低频轮询
  90. if ip.isClosed || ip.serialPort == nil {
  91. time.Sleep(time.Second * 1)
  92. continue
  93. }
  94. n, err := ip.serialPort.Read(buf)
  95. // 忽略EOF和读取超时(正常无数据场景)
  96. if err != nil {
  97. switch {
  98. case err == io.EOF:
  99. continue
  100. case err.Error() == "serial: read timed out":
  101. continue
  102. default:
  103. fmt.Printf("串口读取异常: %v\n", err)
  104. ip.mu.Lock()
  105. ip.isClosed = true
  106. ip.mu.Unlock()
  107. }
  108. continue
  109. }
  110. if n == 0 {
  111. continue
  112. }
  113. // 解析返回字节
  114. recvBytes := buf[:n]
  115. fmt.Printf("串口返回原始字节(十六进制): %s\n", hex.EncodeToString(recvBytes))
  116. for _, b := range recvBytes {
  117. switch b {
  118. case 0x41:
  119. fmt.Println("<---- 41 接收成功")
  120. case 0x4F:
  121. fmt.Println("<---- 4F 芯片空闲")
  122. // 清空残留信号后发送新的空闲标记
  123. ip.clearIdleChan()
  124. ip.idleChan <- struct{}{}
  125. }
  126. }
  127. }
  128. }
  129. // Speak 发送语音指令(核心逻辑:播放中丢弃请求+仅空闲时接收+无缓存)
  130. func (ip *IpCast) Speak(txt string) {
  131. // 1. 加锁检查播放状态,核心控制逻辑
  132. ip.mu.Lock()
  133. // 若正在播放,直接丢弃当前雷达触发请求
  134. if ip.isPlaying {
  135. fmt.Printf("语音正在播放中,丢弃雷达触发请求:%s\n", txt)
  136. ip.mu.Unlock()
  137. return
  138. }
  139. // 标记为正在播放(后续只有收到0x4F才会重置)
  140. ip.isPlaying = true
  141. ip.mu.Unlock()
  142. // 2. 函数退出时保证重置播放状态(无论成功/失败/超时)
  143. defer func() {
  144. ip.mu.Lock()
  145. ip.isPlaying = false
  146. ip.mu.Unlock()
  147. fmt.Println("语音播放流程结束,恢复接收雷达信号")
  148. }()
  149. // 3. 校验串口状态
  150. ip.mu.Lock()
  151. isClosed := ip.isClosed
  152. serialPort := ip.serialPort
  153. ip.mu.Unlock()
  154. if isClosed || serialPort == nil {
  155. fmt.Println("串口未连接,尝试重连...")
  156. if err := ip.Reconnect(); err != nil {
  157. fmt.Printf("串口重连失败,无法发送指令: %v\n", err)
  158. return
  159. }
  160. // 重连后重新获取串口实例
  161. ip.mu.Lock()
  162. serialPort = ip.serialPort
  163. ip.mu.Unlock()
  164. }
  165. // 4. 清空空闲通道残留信号(避免旧信号干扰)
  166. ip.clearIdleChan()
  167. // 5. 文本转GBK编码
  168. GBKBytes, err := convertToGBK(txt)
  169. if err != nil {
  170. fmt.Printf("文本转GBK失败: %v\n", err)
  171. return
  172. }
  173. // 6. 构造语音帧数据(匹配官方格式)
  174. dataAreaLen := uint16(1 + 1 + len(GBKBytes)) // 命令字+编码格式+文本长度
  175. frameBuf := bytes.NewBuffer([]byte{0xFD}) // 帧头FD
  176. _ = binary.Write(frameBuf, binary.BigEndian, dataAreaLen)
  177. frameBuf.Write([]byte{0x01, 0x01}) // 命令字01 + 编码格式01(GBK)
  178. frameBuf.Write(GBKBytes)
  179. // 调试打印帧数据
  180. //frameHex := hex.EncodeToString(frameBuf.Bytes())
  181. // 7. 发送帧数据到串口
  182. _, err = serialPort.Write(frameBuf.Bytes())
  183. if err != nil {
  184. fmt.Printf("串口发送失败: %v\n", err)
  185. ip.mu.Lock()
  186. ip.isClosed = true
  187. ip.mu.Unlock()
  188. _ = ip.Reconnect()
  189. return
  190. }
  191. // 8. 等待芯片空闲(0x4F),仅收到空闲信号才允许下一次播放
  192. select {
  193. case <-ip.idleChan:
  194. case <-time.After(time.Second * 10): // 超时延长至10秒,适配长语音
  195. }
  196. }
  197. // CorrectTime 保留原有方法(空实现)
  198. func (ip IpCast) CorrectTime() {}
  199. // convertToGBK 文本转GBK编码(保留原有逻辑)
  200. func convertToGBK(s string) ([]byte, error) {
  201. if s == "" {
  202. return nil, errors.New("文本不能为空")
  203. }
  204. encoder := simplifiedchinese.GBK.NewEncoder()
  205. reader := transform.NewReader(bytes.NewReader([]byte(s)), encoder)
  206. result, err := io.ReadAll(reader)
  207. if err != nil {
  208. return nil, fmt.Errorf("编码转换失败: %w", err)
  209. }
  210. fmt.Printf("文本「%s」的GBK编码(十六进制): %s\n", s, hex.EncodeToString(result))
  211. if len(result) == 0 {
  212. return nil, errors.New("GBK转换结果为空")
  213. }
  214. return result, nil
  215. }
  216. // Close 手动关闭串口(清理资源)
  217. func (ip *IpCast) Close() {
  218. ip.mu.Lock()
  219. defer ip.mu.Unlock()
  220. ip.isClosed = true
  221. ip.isPlaying = false // 强制重置播放状态
  222. ip.clearIdleChan() // 清空通道
  223. if ip.serialPort != nil {
  224. _ = ip.serialPort.Close()
  225. ip.serialPort = nil
  226. }
  227. fmt.Println("串口已手动关闭")
  228. }