speaker.go 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  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. // 核心准则:✅ 仅雷达触发Speak播放 ✅ 播放中丢弃重复雷达信号 ✅ 无轮询 ✅ 无等待 ✅ 无超时 ✅ 无通道
  21. type IpCast struct {
  22. Port string // 串口端口(外部传入)
  23. Baud int // 波特率(固定为115200)
  24. serialPort *serial.Port // 串口连接实例
  25. mu sync.Mutex // 全局互斥锁(并发安全修改状态+串口读写)
  26. isClosed bool // 串口是否已关闭
  27. isPlaying bool // 唯一播放状态:true=播放中(拒收雷达) false=空闲(可播放)
  28. }
  29. // NewIpCast 初始化串口语音播报实例
  30. func NewIpCast(prot string) *IpCast {
  31. s := &IpCast{
  32. Port: prot,
  33. Baud: 115200,
  34. isClosed: false,
  35. isPlaying: false,
  36. }
  37. if err := s.Reconnect(); err != nil {
  38. fmt.Printf("串口初始化失败: %v\n", err)
  39. return s
  40. }
  41. go s.listenSerialResponse()
  42. return s
  43. }
  44. // Reconnect 重连串口(加锁保护+标记串口状态)
  45. func (ip *IpCast) Reconnect() error {
  46. ip.mu.Lock()
  47. defer ip.mu.Unlock()
  48. ip.isClosed = true
  49. if ip.serialPort != nil {
  50. _ = ip.serialPort.Close()
  51. ip.serialPort = nil
  52. }
  53. cfg := &serial.Config{
  54. Name: ip.Port,
  55. Baud: ip.Baud,
  56. Size: 8,
  57. Parity: serial.ParityNone,
  58. StopBits: 1,
  59. ReadTimeout: time.Millisecond * 200,
  60. }
  61. port, err := serial.OpenPort(cfg)
  62. if err != nil {
  63. return fmt.Errorf("打开串口失败: %w", err)
  64. }
  65. ip.serialPort = port
  66. ip.isClosed = false
  67. fmt.Println("串口重连成功")
  68. return nil
  69. }
  70. // listenSerialResponse 后台监听串口返回【核心不变,极简】
  71. // 唯一逻辑:收到0x41=日志打印;收到0x4F=加锁改isPlaying=false(释放播放权限)
  72. func (ip *IpCast) listenSerialResponse() {
  73. buf := make([]byte, 64)
  74. for {
  75. ip.mu.Lock()
  76. isClosed := ip.isClosed
  77. serialPort := ip.serialPort
  78. ip.mu.Unlock()
  79. if isClosed || serialPort == nil {
  80. time.Sleep(time.Second * 1)
  81. continue
  82. }
  83. ip.mu.Lock()
  84. n, err := serialPort.Read(buf)
  85. ip.mu.Unlock()
  86. if err != nil {
  87. switch {
  88. case err == io.EOF, err.Error() == "serial: read timed out":
  89. continue
  90. default:
  91. fmt.Printf("串口读取异常: %v\n", err)
  92. ip.mu.Lock()
  93. ip.isClosed = true
  94. ip.mu.Unlock()
  95. }
  96. continue
  97. }
  98. if n == 0 {
  99. continue
  100. }
  101. // 解析返回字节
  102. recvBytes := buf[:n]
  103. for _, b := range recvBytes {
  104. switch b {
  105. case 0x41:
  106. fmt.Println("<---- 41 指令接收成功,芯片开始播放")
  107. case 0x4F:
  108. fmt.Println("<---- 4F 语音播放完成 ✅,释放播放权限")
  109. // 唯一操作:播放完成,解锁状态,让下次雷达信号可以触发
  110. ip.mu.Lock()
  111. ip.isPlaying = false
  112. ip.mu.Unlock()
  113. }
  114. }
  115. }
  116. }
  117. // Speak 发送语音指令 【✅ 核心改版,彻底解决你的问题,极简逻辑】
  118. // 唯一触发源:雷达信号调用 → 播放;无雷达调用 → 绝对不播放
  119. // 播放中重复触发:直接return丢弃,无任何处理
  120. // 发送指令后:立刻退出函数,无等待、无轮询、无超时,芯片独立播放
  121. func (ip *IpCast) Speak(txt string) {
  122. // ========== 第一步:核心判断,播放中直接丢弃雷达信号【保留刚需】 ==========
  123. ip.mu.Lock()
  124. if ip.isPlaying {
  125. fmt.Printf("[丢弃雷达信号] 语音播放中,拒绝执行:%s\n", txt)
  126. ip.mu.Unlock()
  127. return
  128. }
  129. // 空闲状态,原子标记为播放中,并发安全,杜绝竞态
  130. ip.isPlaying = true
  131. ip.mu.Unlock()
  132. // ========== 异常兜底:任何执行失败,都重置播放状态 ==========
  133. defer func() {
  134. ip.mu.Lock()
  135. if ip.isClosed {
  136. ip.isPlaying = false
  137. fmt.Println("[异常] 串口关闭,重置播放状态为空闲")
  138. }
  139. ip.mu.Unlock()
  140. }()
  141. // ========== 校验串口状态 ==========
  142. ip.mu.Lock()
  143. isClosed := ip.isClosed
  144. serialPort := ip.serialPort
  145. ip.mu.Unlock()
  146. if isClosed || serialPort == nil {
  147. fmt.Println("串口未连接,尝试重连...")
  148. if err := ip.Reconnect(); err != nil {
  149. fmt.Printf("串口重连失败,无法发送指令: %v\n", err)
  150. ip.mu.Lock()
  151. ip.isPlaying = false
  152. ip.mu.Unlock()
  153. return
  154. }
  155. ip.mu.Lock()
  156. serialPort = ip.serialPort
  157. ip.mu.Unlock()
  158. }
  159. // ========== 文本转GBK编码 ==========
  160. GBKBytes, err := convertToGBK(txt)
  161. if err != nil {
  162. fmt.Printf("文本转GBK失败: %v\n", err)
  163. ip.mu.Lock()
  164. ip.isPlaying = false
  165. ip.mu.Unlock()
  166. return
  167. }
  168. // ========== 构造语音帧数据 ==========
  169. dataAreaLen := uint16(1 + 1 + len(GBKBytes))
  170. frameBuf := bytes.NewBuffer([]byte{0xFD})
  171. _ = binary.Write(frameBuf, binary.BigEndian, dataAreaLen)
  172. frameBuf.Write([]byte{0x01, 0x01})
  173. frameBuf.Write(GBKBytes)
  174. // 保留你需要的500ms延迟
  175. time.Sleep(500 * time.Millisecond)
  176. // ========== 发送语音指令给芯片 ==========
  177. ip.mu.Lock()
  178. _, err = serialPort.Write(frameBuf.Bytes())
  179. ip.mu.Unlock()
  180. if err != nil {
  181. fmt.Printf("串口发送失败: %v\n", err)
  182. ip.mu.Lock()
  183. ip.isClosed = true
  184. ip.isPlaying = false
  185. ip.mu.Unlock()
  186. _ = ip.Reconnect()
  187. return
  188. }
  189. // ========== ✅ 核心修改:发送成功后,立刻打印日志,直接退出函数 ==========
  190. // 无等待、无轮询、无超时,芯片收到指令后独立播放,和程序解耦
  191. // 雷达信号停止 → 不会再调用这里,自然不会有新的播放指令
  192. fmt.Printf("[雷达触发成功] 语音指令已发送 → %s,芯片独立播放中\n", txt)
  193. }
  194. // CorrectTime 保留原有方法(空实现)
  195. func (ip IpCast) CorrectTime() {}
  196. // convertToGBK 文本转GBK编码 【已修复语法错误】
  197. func convertToGBK(s string) ([]byte, error) {
  198. if s == "" {
  199. return nil, errors.New("文本不能为空")
  200. }
  201. encoder := simplifiedchinese.GBK.NewEncoder()
  202. reader := transform.NewReader(bytes.NewReader([]byte(s)), encoder)
  203. result, err := io.ReadAll(reader)
  204. if err != nil {
  205. return nil, fmt.Errorf("编码转换失败: %w", err)
  206. }
  207. fmt.Printf("文本「%s」的GBK编码: %s\n", s, hex.EncodeToString(result))
  208. if len(result) == 0 {
  209. return nil, errors.New("GBK转换结果为空")
  210. }
  211. return result, nil
  212. }
  213. // Close 手动关闭串口(清理资源)
  214. func (ip *IpCast) Close() {
  215. ip.mu.Lock()
  216. defer ip.mu.Unlock()
  217. ip.isClosed = true
  218. ip.isPlaying = false
  219. if ip.serialPort != nil {
  220. _ = ip.serialPort.Close()
  221. ip.serialPort = nil
  222. }
  223. fmt.Println("串口已关闭,播放状态已重置")
  224. }
  225. // VoiceParams 语音参数结构体
  226. type VoiceParams struct {
  227. Speaker int
  228. Volume int
  229. Tone int
  230. Speed int
  231. }
  232. // DefaultVoiceParams 默认语音参数
  233. var DefaultVoiceParams = VoiceParams{
  234. Speaker: 3,
  235. Volume: 10,
  236. Tone: 10,
  237. Speed: 30,
  238. }
  239. // SetVoiceParams 独立设置语音参数 + 播放中禁止修改
  240. func (ip *IpCast) SetVoiceParams(params VoiceParams) error {
  241. ip.mu.Lock()
  242. if ip.isPlaying {
  243. ip.mu.Unlock()
  244. return errors.New("语音播放中,禁止修改参数")
  245. }
  246. ip.mu.Unlock()
  247. if err := ip.validateVoiceParams(params); err != nil {
  248. return fmt.Errorf("参数校验失败: %w", err)
  249. }
  250. ip.mu.Lock()
  251. defer ip.mu.Unlock()
  252. if ip.isClosed || ip.serialPort == nil {
  253. fmt.Println("串口未连接,尝试重连...")
  254. if err := ip.Reconnect(); err != nil {
  255. return fmt.Errorf("串口重连失败: %w", err)
  256. }
  257. }
  258. paramStr := fmt.Sprintf("[m%d][v%d][t%d][s%d]", params.Speaker, params.Volume, params.Tone, params.Speed)
  259. fmt.Printf("设置语音参数:%s\n", paramStr)
  260. gbkBytes, err := convertToGBK(paramStr)
  261. if err != nil {
  262. return fmt.Errorf("参数字符串转GBK失败: %w", err)
  263. }
  264. dataLen := uint16(1 + 1 + len(gbkBytes))
  265. frameBuf := bytes.NewBuffer([]byte{0xFD})
  266. _ = binary.Write(frameBuf, binary.BigEndian, dataLen)
  267. frameBuf.Write([]byte{0x06, 0x01})
  268. frameBuf.Write(gbkBytes)
  269. if _, err := ip.serialPort.Write(frameBuf.Bytes()); err != nil {
  270. ip.isClosed = true
  271. _ = ip.Reconnect()
  272. return fmt.Errorf("发送参数失败: %w", err)
  273. }
  274. respBuf := make([]byte, 16)
  275. timeout := time.After(time.Second * 3)
  276. for {
  277. select {
  278. case <-timeout:
  279. return errors.New("参数设置超时")
  280. default:
  281. n, err := ip.serialPort.Read(respBuf)
  282. if err != nil && err.Error() != "serial: read timed out" {
  283. return fmt.Errorf("读取参数响应失败: %w", err)
  284. }
  285. if bytes.Contains(respBuf[:n], []byte{0x41}) {
  286. fmt.Printf("参数设置成功: %+v\n", params)
  287. return nil
  288. }
  289. }
  290. }
  291. }
  292. // validateVoiceParams 参数校验
  293. func (ip *IpCast) validateVoiceParams(params VoiceParams) error {
  294. validSpeakers := map[int]bool{3: true, 51: true, 52: true, 53: true, 54: true, 55: true, 56: true, 57: true}
  295. if !validSpeakers[params.Speaker] {
  296. return fmt.Errorf("发音人无效: %d", params.Speaker)
  297. }
  298. if params.Volume < 0 || params.Volume > 10 {
  299. return fmt.Errorf("音量无效: %d", params.Volume)
  300. }
  301. if params.Tone < 0 || params.Tone > 10 {
  302. return fmt.Errorf("语调无效: %d", params.Tone)
  303. }
  304. if params.Speed < 0 || params.Speed > 30 {
  305. return fmt.Errorf("语速无效: %d", params.Speed)
  306. }
  307. return nil
  308. }