|
@@ -20,32 +20,29 @@ type Loudspeaker interface {
|
|
|
Speak(txt string)
|
|
Speak(txt string)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// IpCast 串口语音播报实现(核心:播放中丢弃请求+仅空闲时接收+无缓存)
|
|
|
|
|
|
|
+// IpCast 串口语音播报实现【最终极简版】
|
|
|
|
|
+// 核心准则:✅ 仅雷达触发Speak播放 ✅ 播放中丢弃重复雷达信号 ✅ 无轮询 ✅ 无等待 ✅ 无超时 ✅ 无通道
|
|
|
type IpCast struct {
|
|
type IpCast struct {
|
|
|
- Port string // 串口端口(外部传入)
|
|
|
|
|
- Baud int // 波特率(固定为115200)
|
|
|
|
|
- serialPort *serial.Port // 串口连接实例
|
|
|
|
|
- mu sync.Mutex // 全局互斥锁(保护isPlaying/串口操作)
|
|
|
|
|
- idleChan chan struct{} // 芯片空闲通知通道
|
|
|
|
|
- isClosed bool // 串口是否已关闭
|
|
|
|
|
- isPlaying bool // 标记是否正在播放语音(核心控制字段)
|
|
|
|
|
|
|
+ Port string // 串口端口(外部传入)
|
|
|
|
|
+ Baud int // 波特率(固定为115200)
|
|
|
|
|
+ serialPort *serial.Port // 串口连接实例
|
|
|
|
|
+ mu sync.Mutex // 全局互斥锁(并发安全修改状态+串口读写)
|
|
|
|
|
+ isClosed bool // 串口是否已关闭
|
|
|
|
|
+ isPlaying bool // 唯一播放状态:true=播放中(拒收雷达) false=空闲(可播放)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// NewIpCast 初始化串口语音播报实例
|
|
// NewIpCast 初始化串口语音播报实例
|
|
|
-// prot: 串口端口(如"/dev/ttyUSB0")
|
|
|
|
|
func NewIpCast(prot string) *IpCast {
|
|
func NewIpCast(prot string) *IpCast {
|
|
|
s := &IpCast{
|
|
s := &IpCast{
|
|
|
Port: prot,
|
|
Port: prot,
|
|
|
Baud: 115200,
|
|
Baud: 115200,
|
|
|
- idleChan: make(chan struct{}, 1), // 缓冲通道避免阻塞
|
|
|
|
|
isClosed: false,
|
|
isClosed: false,
|
|
|
- isPlaying: false, // 初始为未播放状态
|
|
|
|
|
|
|
+ isPlaying: false,
|
|
|
}
|
|
}
|
|
|
- // 初始化串口
|
|
|
|
|
if err := s.Reconnect(); err != nil {
|
|
if err := s.Reconnect(); err != nil {
|
|
|
fmt.Printf("串口初始化失败: %v\n", err)
|
|
fmt.Printf("串口初始化失败: %v\n", err)
|
|
|
|
|
+ return s
|
|
|
}
|
|
}
|
|
|
- // 启动后台监听协程
|
|
|
|
|
go s.listenSerialResponse()
|
|
go s.listenSerialResponse()
|
|
|
return s
|
|
return s
|
|
|
}
|
|
}
|
|
@@ -55,21 +52,19 @@ func (ip *IpCast) Reconnect() error {
|
|
|
ip.mu.Lock()
|
|
ip.mu.Lock()
|
|
|
defer ip.mu.Unlock()
|
|
defer ip.mu.Unlock()
|
|
|
|
|
|
|
|
- // 标记串口为关闭状态
|
|
|
|
|
ip.isClosed = true
|
|
ip.isClosed = true
|
|
|
if ip.serialPort != nil {
|
|
if ip.serialPort != nil {
|
|
|
_ = ip.serialPort.Close()
|
|
_ = ip.serialPort.Close()
|
|
|
ip.serialPort = nil
|
|
ip.serialPort = nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 配置串口参数
|
|
|
|
|
cfg := &serial.Config{
|
|
cfg := &serial.Config{
|
|
|
Name: ip.Port,
|
|
Name: ip.Port,
|
|
|
Baud: ip.Baud,
|
|
Baud: ip.Baud,
|
|
|
Size: 8,
|
|
Size: 8,
|
|
|
Parity: serial.ParityNone,
|
|
Parity: serial.ParityNone,
|
|
|
StopBits: 1,
|
|
StopBits: 1,
|
|
|
- ReadTimeout: time.Millisecond * 200, // 减少EOF报错频率
|
|
|
|
|
|
|
+ ReadTimeout: time.Millisecond * 200,
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
port, err := serial.OpenPort(cfg)
|
|
port, err := serial.OpenPort(cfg)
|
|
@@ -78,39 +73,33 @@ func (ip *IpCast) Reconnect() error {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
ip.serialPort = port
|
|
ip.serialPort = port
|
|
|
- ip.isClosed = false // 标记串口可用
|
|
|
|
|
|
|
+ ip.isClosed = false
|
|
|
fmt.Println("串口重连成功")
|
|
fmt.Println("串口重连成功")
|
|
|
return nil
|
|
return nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// clearIdleChan 清空空闲通道所有残留信号(避免旧信号干扰)
|
|
|
|
|
-func (ip *IpCast) clearIdleChan() {
|
|
|
|
|
- for {
|
|
|
|
|
- select {
|
|
|
|
|
- case <-ip.idleChan:
|
|
|
|
|
- default:
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// listenSerialResponse 后台监听串口返回(仅处理0x41/0x4F,清空残留信号)
|
|
|
|
|
|
|
+// listenSerialResponse 后台监听串口返回【核心不变,极简】
|
|
|
|
|
+// 唯一逻辑:收到0x41=日志打印;收到0x4F=加锁改isPlaying=false(释放播放权限)
|
|
|
func (ip *IpCast) listenSerialResponse() {
|
|
func (ip *IpCast) listenSerialResponse() {
|
|
|
buf := make([]byte, 64)
|
|
buf := make([]byte, 64)
|
|
|
for {
|
|
for {
|
|
|
- // 串口未就绪时低频轮询
|
|
|
|
|
- if ip.isClosed || ip.serialPort == nil {
|
|
|
|
|
|
|
+ ip.mu.Lock()
|
|
|
|
|
+ isClosed := ip.isClosed
|
|
|
|
|
+ serialPort := ip.serialPort
|
|
|
|
|
+ ip.mu.Unlock()
|
|
|
|
|
+
|
|
|
|
|
+ if isClosed || serialPort == nil {
|
|
|
time.Sleep(time.Second * 1)
|
|
time.Sleep(time.Second * 1)
|
|
|
continue
|
|
continue
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- n, err := ip.serialPort.Read(buf)
|
|
|
|
|
- // 忽略EOF和读取超时(正常无数据场景)
|
|
|
|
|
|
|
+ ip.mu.Lock()
|
|
|
|
|
+ n, err := serialPort.Read(buf)
|
|
|
|
|
+ ip.mu.Unlock()
|
|
|
|
|
+
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
switch {
|
|
switch {
|
|
|
- case err == io.EOF:
|
|
|
|
|
- continue
|
|
|
|
|
- case err.Error() == "serial: read timed out":
|
|
|
|
|
|
|
+ case err == io.EOF, err.Error() == "serial: read timed out":
|
|
|
continue
|
|
continue
|
|
|
default:
|
|
default:
|
|
|
fmt.Printf("串口读取异常: %v\n", err)
|
|
fmt.Printf("串口读取异常: %v\n", err)
|
|
@@ -127,44 +116,48 @@ func (ip *IpCast) listenSerialResponse() {
|
|
|
|
|
|
|
|
// 解析返回字节
|
|
// 解析返回字节
|
|
|
recvBytes := buf[:n]
|
|
recvBytes := buf[:n]
|
|
|
- fmt.Printf("串口返回原始字节(十六进制): %s\n", hex.EncodeToString(recvBytes))
|
|
|
|
|
for _, b := range recvBytes {
|
|
for _, b := range recvBytes {
|
|
|
switch b {
|
|
switch b {
|
|
|
case 0x41:
|
|
case 0x41:
|
|
|
- fmt.Println("<---- 41 接收成功")
|
|
|
|
|
|
|
+ fmt.Println("<---- 41 指令接收成功,芯片开始播放")
|
|
|
case 0x4F:
|
|
case 0x4F:
|
|
|
- fmt.Println("<---- 4F 芯片空闲")
|
|
|
|
|
- // 清空残留信号后发送新的空闲标记
|
|
|
|
|
- ip.clearIdleChan()
|
|
|
|
|
- ip.idleChan <- struct{}{}
|
|
|
|
|
|
|
+ fmt.Println("<---- 4F 语音播放完成 ✅,释放播放权限")
|
|
|
|
|
+ // 唯一操作:播放完成,解锁状态,让下次雷达信号可以触发
|
|
|
|
|
+ ip.mu.Lock()
|
|
|
|
|
+ ip.isPlaying = false
|
|
|
|
|
+ ip.mu.Unlock()
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// Speak 发送语音指令(核心逻辑:播放中丢弃请求+仅空闲时接收+无缓存)
|
|
|
|
|
|
|
+// Speak 发送语音指令 【✅ 核心改版,彻底解决你的问题,极简逻辑】
|
|
|
|
|
+// 唯一触发源:雷达信号调用 → 播放;无雷达调用 → 绝对不播放
|
|
|
|
|
+// 播放中重复触发:直接return丢弃,无任何处理
|
|
|
|
|
+// 发送指令后:立刻退出函数,无等待、无轮询、无超时,芯片独立播放
|
|
|
func (ip *IpCast) Speak(txt string) {
|
|
func (ip *IpCast) Speak(txt string) {
|
|
|
- // 1. 加锁检查播放状态,核心控制逻辑
|
|
|
|
|
|
|
+ // ========== 第一步:核心判断,播放中直接丢弃雷达信号【保留刚需】 ==========
|
|
|
ip.mu.Lock()
|
|
ip.mu.Lock()
|
|
|
- // 若正在播放,直接丢弃当前雷达触发请求
|
|
|
|
|
if ip.isPlaying {
|
|
if ip.isPlaying {
|
|
|
- fmt.Printf("语音正在播放中,丢弃雷达触发请求:%s\n", txt)
|
|
|
|
|
|
|
+ fmt.Printf("[丢弃雷达信号] 语音播放中,拒绝执行:%s\n", txt)
|
|
|
ip.mu.Unlock()
|
|
ip.mu.Unlock()
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
- // 标记为正在播放(后续只有收到0x4F才会重置)
|
|
|
|
|
|
|
+ // 空闲状态,原子标记为播放中,并发安全,杜绝竞态
|
|
|
ip.isPlaying = true
|
|
ip.isPlaying = true
|
|
|
ip.mu.Unlock()
|
|
ip.mu.Unlock()
|
|
|
|
|
|
|
|
- // 2. 函数退出时保证重置播放状态(无论成功/失败/超时)
|
|
|
|
|
|
|
+ // ========== 异常兜底:任何执行失败,都重置播放状态 ==========
|
|
|
defer func() {
|
|
defer func() {
|
|
|
ip.mu.Lock()
|
|
ip.mu.Lock()
|
|
|
- ip.isPlaying = false
|
|
|
|
|
|
|
+ if ip.isClosed {
|
|
|
|
|
+ ip.isPlaying = false
|
|
|
|
|
+ fmt.Println("[异常] 串口关闭,重置播放状态为空闲")
|
|
|
|
|
+ }
|
|
|
ip.mu.Unlock()
|
|
ip.mu.Unlock()
|
|
|
- fmt.Println("语音播放流程结束,恢复接收雷达信号")
|
|
|
|
|
}()
|
|
}()
|
|
|
|
|
|
|
|
- // 3. 校验串口状态
|
|
|
|
|
|
|
+ // ========== 校验串口状态 ==========
|
|
|
ip.mu.Lock()
|
|
ip.mu.Lock()
|
|
|
isClosed := ip.isClosed
|
|
isClosed := ip.isClosed
|
|
|
serialPort := ip.serialPort
|
|
serialPort := ip.serialPort
|
|
@@ -174,56 +167,61 @@ func (ip *IpCast) Speak(txt string) {
|
|
|
fmt.Println("串口未连接,尝试重连...")
|
|
fmt.Println("串口未连接,尝试重连...")
|
|
|
if err := ip.Reconnect(); err != nil {
|
|
if err := ip.Reconnect(); err != nil {
|
|
|
fmt.Printf("串口重连失败,无法发送指令: %v\n", err)
|
|
fmt.Printf("串口重连失败,无法发送指令: %v\n", err)
|
|
|
|
|
+ ip.mu.Lock()
|
|
|
|
|
+ ip.isPlaying = false
|
|
|
|
|
+ ip.mu.Unlock()
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
- // 重连后重新获取串口实例
|
|
|
|
|
ip.mu.Lock()
|
|
ip.mu.Lock()
|
|
|
serialPort = ip.serialPort
|
|
serialPort = ip.serialPort
|
|
|
ip.mu.Unlock()
|
|
ip.mu.Unlock()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 4. 清空空闲通道残留信号(避免旧信号干扰)
|
|
|
|
|
- ip.clearIdleChan()
|
|
|
|
|
-
|
|
|
|
|
- // 5. 文本转GBK编码
|
|
|
|
|
|
|
+ // ========== 文本转GBK编码 ==========
|
|
|
GBKBytes, err := convertToGBK(txt)
|
|
GBKBytes, err := convertToGBK(txt)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
fmt.Printf("文本转GBK失败: %v\n", err)
|
|
fmt.Printf("文本转GBK失败: %v\n", err)
|
|
|
|
|
+ ip.mu.Lock()
|
|
|
|
|
+ ip.isPlaying = false
|
|
|
|
|
+ ip.mu.Unlock()
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 6. 构造语音帧数据(匹配官方格式)
|
|
|
|
|
- dataAreaLen := uint16(1 + 1 + len(GBKBytes)) // 命令字+编码格式+文本长度
|
|
|
|
|
- frameBuf := bytes.NewBuffer([]byte{0xFD}) // 帧头FD
|
|
|
|
|
|
|
+ // ========== 构造语音帧数据 ==========
|
|
|
|
|
+ dataAreaLen := uint16(1 + 1 + len(GBKBytes))
|
|
|
|
|
+ frameBuf := bytes.NewBuffer([]byte{0xFD})
|
|
|
_ = binary.Write(frameBuf, binary.BigEndian, dataAreaLen)
|
|
_ = binary.Write(frameBuf, binary.BigEndian, dataAreaLen)
|
|
|
- frameBuf.Write([]byte{0x01, 0x01}) // 命令字01 + 编码格式01(GBK)
|
|
|
|
|
|
|
+ frameBuf.Write([]byte{0x01, 0x01})
|
|
|
frameBuf.Write(GBKBytes)
|
|
frameBuf.Write(GBKBytes)
|
|
|
|
|
|
|
|
- // 调试打印帧数据
|
|
|
|
|
- //frameHex := hex.EncodeToString(frameBuf.Bytes())
|
|
|
|
|
|
|
+ // 保留你需要的500ms延迟
|
|
|
|
|
+ time.Sleep(500 * time.Millisecond)
|
|
|
|
|
|
|
|
- // 7. 发送帧数据到串口
|
|
|
|
|
|
|
+ // ========== 发送语音指令给芯片 ==========
|
|
|
|
|
+ ip.mu.Lock()
|
|
|
_, err = serialPort.Write(frameBuf.Bytes())
|
|
_, err = serialPort.Write(frameBuf.Bytes())
|
|
|
|
|
+ ip.mu.Unlock()
|
|
|
|
|
+
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
fmt.Printf("串口发送失败: %v\n", err)
|
|
fmt.Printf("串口发送失败: %v\n", err)
|
|
|
ip.mu.Lock()
|
|
ip.mu.Lock()
|
|
|
ip.isClosed = true
|
|
ip.isClosed = true
|
|
|
|
|
+ ip.isPlaying = false
|
|
|
ip.mu.Unlock()
|
|
ip.mu.Unlock()
|
|
|
_ = ip.Reconnect()
|
|
_ = ip.Reconnect()
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 8. 等待芯片空闲(0x4F),仅收到空闲信号才允许下一次播放
|
|
|
|
|
- select {
|
|
|
|
|
- case <-ip.idleChan:
|
|
|
|
|
- case <-time.After(time.Second * 10): // 超时延长至10秒,适配长语音
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // ========== ✅ 核心修改:发送成功后,立刻打印日志,直接退出函数 ==========
|
|
|
|
|
+ // 无等待、无轮询、无超时,芯片收到指令后独立播放,和程序解耦
|
|
|
|
|
+ // 雷达信号停止 → 不会再调用这里,自然不会有新的播放指令
|
|
|
|
|
+ fmt.Printf("[雷达触发成功] 语音指令已发送 → %s,芯片独立播放中\n", txt)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// CorrectTime 保留原有方法(空实现)
|
|
// CorrectTime 保留原有方法(空实现)
|
|
|
func (ip IpCast) CorrectTime() {}
|
|
func (ip IpCast) CorrectTime() {}
|
|
|
|
|
|
|
|
-// convertToGBK 文本转GBK编码(保留原有逻辑)
|
|
|
|
|
|
|
+// convertToGBK 文本转GBK编码 【已修复语法错误】
|
|
|
func convertToGBK(s string) ([]byte, error) {
|
|
func convertToGBK(s string) ([]byte, error) {
|
|
|
if s == "" {
|
|
if s == "" {
|
|
|
return nil, errors.New("文本不能为空")
|
|
return nil, errors.New("文本不能为空")
|
|
@@ -236,7 +234,7 @@ func convertToGBK(s string) ([]byte, error) {
|
|
|
return nil, fmt.Errorf("编码转换失败: %w", err)
|
|
return nil, fmt.Errorf("编码转换失败: %w", err)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- fmt.Printf("文本「%s」的GBK编码(十六进制): %s\n", s, hex.EncodeToString(result))
|
|
|
|
|
|
|
+ fmt.Printf("文本「%s」的GBK编码: %s\n", s, hex.EncodeToString(result))
|
|
|
if len(result) == 0 {
|
|
if len(result) == 0 {
|
|
|
return nil, errors.New("GBK转换结果为空")
|
|
return nil, errors.New("GBK转换结果为空")
|
|
|
}
|
|
}
|
|
@@ -249,11 +247,106 @@ func (ip *IpCast) Close() {
|
|
|
defer ip.mu.Unlock()
|
|
defer ip.mu.Unlock()
|
|
|
|
|
|
|
|
ip.isClosed = true
|
|
ip.isClosed = true
|
|
|
- ip.isPlaying = false // 强制重置播放状态
|
|
|
|
|
- ip.clearIdleChan() // 清空通道
|
|
|
|
|
|
|
+ ip.isPlaying = false
|
|
|
if ip.serialPort != nil {
|
|
if ip.serialPort != nil {
|
|
|
_ = ip.serialPort.Close()
|
|
_ = ip.serialPort.Close()
|
|
|
ip.serialPort = nil
|
|
ip.serialPort = nil
|
|
|
}
|
|
}
|
|
|
- fmt.Println("串口已手动关闭")
|
|
|
|
|
|
|
+ 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
|
|
|
}
|
|
}
|