| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352 |
- package lc
- import (
- "bytes"
- "encoding/binary"
- "encoding/hex"
- "errors"
- "fmt"
- "io"
- "sync"
- "time"
- "github.com/tarm/serial"
- "golang.org/x/text/encoding/simplifiedchinese"
- "golang.org/x/text/transform"
- )
- // Loudspeaker 扬声器接口
- type Loudspeaker interface {
- Speak(txt string)
- }
- // IpCast 串口语音播报实现【最终极简版】
- // 核心准则:✅ 仅雷达触发Speak播放 ✅ 播放中丢弃重复雷达信号 ✅ 无轮询 ✅ 无等待 ✅ 无超时 ✅ 无通道
- type IpCast struct {
- Port string // 串口端口(外部传入)
- Baud int // 波特率(固定为115200)
- serialPort *serial.Port // 串口连接实例
- mu sync.Mutex // 全局互斥锁(并发安全修改状态+串口读写)
- isClosed bool // 串口是否已关闭
- isPlaying bool // 唯一播放状态:true=播放中(拒收雷达) false=空闲(可播放)
- }
- // NewIpCast 初始化串口语音播报实例
- func NewIpCast(prot string) *IpCast {
- s := &IpCast{
- Port: prot,
- Baud: 115200,
- isClosed: false,
- isPlaying: false,
- }
- if err := s.Reconnect(); err != nil {
- fmt.Printf("串口初始化失败: %v\n", err)
- return s
- }
- go s.listenSerialResponse()
- return s
- }
- // Reconnect 重连串口(加锁保护+标记串口状态)
- func (ip *IpCast) Reconnect() error {
- ip.mu.Lock()
- defer ip.mu.Unlock()
- ip.isClosed = true
- if ip.serialPort != nil {
- _ = ip.serialPort.Close()
- ip.serialPort = nil
- }
- cfg := &serial.Config{
- Name: ip.Port,
- Baud: ip.Baud,
- Size: 8,
- Parity: serial.ParityNone,
- StopBits: 1,
- ReadTimeout: time.Millisecond * 200,
- }
- port, err := serial.OpenPort(cfg)
- if err != nil {
- return fmt.Errorf("打开串口失败: %w", err)
- }
- ip.serialPort = port
- ip.isClosed = false
- fmt.Println("串口重连成功")
- return nil
- }
- // listenSerialResponse 后台监听串口返回【核心不变,极简】
- // 唯一逻辑:收到0x41=日志打印;收到0x4F=加锁改isPlaying=false(释放播放权限)
- func (ip *IpCast) listenSerialResponse() {
- buf := make([]byte, 64)
- for {
- ip.mu.Lock()
- isClosed := ip.isClosed
- serialPort := ip.serialPort
- ip.mu.Unlock()
- if isClosed || serialPort == nil {
- time.Sleep(time.Second * 1)
- continue
- }
- ip.mu.Lock()
- n, err := serialPort.Read(buf)
- ip.mu.Unlock()
- if err != nil {
- switch {
- case err == io.EOF, err.Error() == "serial: read timed out":
- continue
- default:
- fmt.Printf("串口读取异常: %v\n", err)
- ip.mu.Lock()
- ip.isClosed = true
- ip.mu.Unlock()
- }
- continue
- }
- if n == 0 {
- continue
- }
- // 解析返回字节
- recvBytes := buf[:n]
- for _, b := range recvBytes {
- switch b {
- case 0x41:
- fmt.Println("<---- 41 指令接收成功,芯片开始播放")
- case 0x4F:
- fmt.Println("<---- 4F 语音播放完成 ✅,释放播放权限")
- // 唯一操作:播放完成,解锁状态,让下次雷达信号可以触发
- ip.mu.Lock()
- ip.isPlaying = false
- ip.mu.Unlock()
- }
- }
- }
- }
- // Speak 发送语音指令 【✅ 核心改版,彻底解决你的问题,极简逻辑】
- // 唯一触发源:雷达信号调用 → 播放;无雷达调用 → 绝对不播放
- // 播放中重复触发:直接return丢弃,无任何处理
- // 发送指令后:立刻退出函数,无等待、无轮询、无超时,芯片独立播放
- func (ip *IpCast) Speak(txt string) {
- // ========== 第一步:核心判断,播放中直接丢弃雷达信号【保留刚需】 ==========
- ip.mu.Lock()
- if ip.isPlaying {
- fmt.Printf("[丢弃雷达信号] 语音播放中,拒绝执行:%s\n", txt)
- ip.mu.Unlock()
- return
- }
- // 空闲状态,原子标记为播放中,并发安全,杜绝竞态
- ip.isPlaying = true
- ip.mu.Unlock()
- // ========== 异常兜底:任何执行失败,都重置播放状态 ==========
- defer func() {
- ip.mu.Lock()
- if ip.isClosed {
- ip.isPlaying = false
- fmt.Println("[异常] 串口关闭,重置播放状态为空闲")
- }
- ip.mu.Unlock()
- }()
- // ========== 校验串口状态 ==========
- ip.mu.Lock()
- isClosed := ip.isClosed
- serialPort := ip.serialPort
- ip.mu.Unlock()
- if isClosed || serialPort == nil {
- fmt.Println("串口未连接,尝试重连...")
- if err := ip.Reconnect(); err != nil {
- fmt.Printf("串口重连失败,无法发送指令: %v\n", err)
- ip.mu.Lock()
- ip.isPlaying = false
- ip.mu.Unlock()
- return
- }
- ip.mu.Lock()
- serialPort = ip.serialPort
- ip.mu.Unlock()
- }
- // ========== 文本转GBK编码 ==========
- GBKBytes, err := convertToGBK(txt)
- if err != nil {
- fmt.Printf("文本转GBK失败: %v\n", err)
- ip.mu.Lock()
- ip.isPlaying = false
- ip.mu.Unlock()
- return
- }
- // ========== 构造语音帧数据 ==========
- dataAreaLen := uint16(1 + 1 + len(GBKBytes))
- frameBuf := bytes.NewBuffer([]byte{0xFD})
- _ = binary.Write(frameBuf, binary.BigEndian, dataAreaLen)
- frameBuf.Write([]byte{0x01, 0x01})
- frameBuf.Write(GBKBytes)
- // 保留你需要的500ms延迟
- time.Sleep(500 * time.Millisecond)
- // ========== 发送语音指令给芯片 ==========
- ip.mu.Lock()
- _, err = serialPort.Write(frameBuf.Bytes())
- ip.mu.Unlock()
- if err != nil {
- fmt.Printf("串口发送失败: %v\n", err)
- ip.mu.Lock()
- ip.isClosed = true
- ip.isPlaying = false
- ip.mu.Unlock()
- _ = ip.Reconnect()
- return
- }
- // ========== ✅ 核心修改:发送成功后,立刻打印日志,直接退出函数 ==========
- // 无等待、无轮询、无超时,芯片收到指令后独立播放,和程序解耦
- // 雷达信号停止 → 不会再调用这里,自然不会有新的播放指令
- fmt.Printf("[雷达触发成功] 语音指令已发送 → %s,芯片独立播放中\n", txt)
- }
- // CorrectTime 保留原有方法(空实现)
- func (ip IpCast) CorrectTime() {}
- // convertToGBK 文本转GBK编码 【已修复语法错误】
- func convertToGBK(s string) ([]byte, error) {
- if s == "" {
- return nil, errors.New("文本不能为空")
- }
- encoder := simplifiedchinese.GBK.NewEncoder()
- reader := transform.NewReader(bytes.NewReader([]byte(s)), encoder)
- result, err := io.ReadAll(reader)
- if err != nil {
- return nil, fmt.Errorf("编码转换失败: %w", err)
- }
- fmt.Printf("文本「%s」的GBK编码: %s\n", s, hex.EncodeToString(result))
- if len(result) == 0 {
- return nil, errors.New("GBK转换结果为空")
- }
- return result, nil
- }
- // Close 手动关闭串口(清理资源)
- func (ip *IpCast) Close() {
- ip.mu.Lock()
- defer ip.mu.Unlock()
- ip.isClosed = true
- ip.isPlaying = false
- if ip.serialPort != nil {
- _ = ip.serialPort.Close()
- ip.serialPort = nil
- }
- fmt.Println("串口已关闭,播放状态已重置")
- }
- // VoiceParams 语音参数结构体
- type VoiceParams struct {
- Speaker int
- Volume int
- Tone int
- Speed int
- }
- // DefaultVoiceParams 默认语音参数
- var DefaultVoiceParams = VoiceParams{
- Speaker: 3,
- Volume: 10,
- Tone: 10,
- Speed: 30,
- }
- // SetVoiceParams 独立设置语音参数 + 播放中禁止修改
- func (ip *IpCast) SetVoiceParams(params VoiceParams) error {
- ip.mu.Lock()
- if ip.isPlaying {
- ip.mu.Unlock()
- return errors.New("语音播放中,禁止修改参数")
- }
- ip.mu.Unlock()
- if err := ip.validateVoiceParams(params); err != nil {
- return fmt.Errorf("参数校验失败: %w", err)
- }
- ip.mu.Lock()
- defer ip.mu.Unlock()
- if ip.isClosed || ip.serialPort == nil {
- fmt.Println("串口未连接,尝试重连...")
- if err := ip.Reconnect(); err != nil {
- return fmt.Errorf("串口重连失败: %w", err)
- }
- }
- paramStr := fmt.Sprintf("[m%d][v%d][t%d][s%d]", params.Speaker, params.Volume, params.Tone, params.Speed)
- fmt.Printf("设置语音参数:%s\n", paramStr)
- gbkBytes, err := convertToGBK(paramStr)
- if err != nil {
- return fmt.Errorf("参数字符串转GBK失败: %w", err)
- }
- dataLen := uint16(1 + 1 + len(gbkBytes))
- frameBuf := bytes.NewBuffer([]byte{0xFD})
- _ = binary.Write(frameBuf, binary.BigEndian, dataLen)
- frameBuf.Write([]byte{0x06, 0x01})
- frameBuf.Write(gbkBytes)
- if _, err := ip.serialPort.Write(frameBuf.Bytes()); err != nil {
- ip.isClosed = true
- _ = ip.Reconnect()
- return fmt.Errorf("发送参数失败: %w", err)
- }
- respBuf := make([]byte, 16)
- timeout := time.After(time.Second * 3)
- for {
- select {
- case <-timeout:
- return errors.New("参数设置超时")
- default:
- n, err := ip.serialPort.Read(respBuf)
- if err != nil && err.Error() != "serial: read timed out" {
- return fmt.Errorf("读取参数响应失败: %w", err)
- }
- if bytes.Contains(respBuf[:n], []byte{0x41}) {
- fmt.Printf("参数设置成功: %+v\n", params)
- return nil
- }
- }
- }
- }
- // validateVoiceParams 参数校验
- func (ip *IpCast) validateVoiceParams(params VoiceParams) error {
- validSpeakers := map[int]bool{3: true, 51: true, 52: true, 53: true, 54: true, 55: true, 56: true, 57: true}
- if !validSpeakers[params.Speaker] {
- return fmt.Errorf("发音人无效: %d", params.Speaker)
- }
- if params.Volume < 0 || params.Volume > 10 {
- return fmt.Errorf("音量无效: %d", params.Volume)
- }
- if params.Tone < 0 || params.Tone > 10 {
- return fmt.Errorf("语调无效: %d", params.Tone)
- }
- if params.Speed < 0 || params.Speed > 30 {
- return fmt.Errorf("语速无效: %d", params.Speed)
- }
- return nil
- }
|