diff --git a/config.yml b/config.yml index acfd2cf..8f014ea 100755 --- a/config.yml +++ b/config.yml @@ -1,5 +1,5 @@ -location: wushan -point: 2 +location: wushan # 项目名称 +point: 2 # 点位 relay: maxTimeout: 60 # 单位 s,必须大于 0 log: @@ -17,26 +17,27 @@ aliyun: accessKeySecret: appKey: timeout: 10 # 单位 s - voice: zhifeng_emo + volume: 200 # 音量 + voice: zhifeng_emo # 发音人 speechRate: 50 # 语速 -game: - # addr: /dev/ttyUSB0 # 点位 5 的串口地址 - pushGroups: # 点位 4 的发卡器配置 - - name: gpiochip0 - read: /dev/ttyUSB0 - outOK: 21 - lower: 20 - error: 16 - empty: 12 - push: 13 - reset: 19 - pull: 26 - - name: gpiochip0 - read: /dev/ttyUSB0 - outOK: 7 - lower: 8 - error: 25 - empty: 24 - push: 10 - reset: 9 - pull: 11 \ No newline at end of file +#game: +# addr: /dev/ttyUSB0 # 点位 11 的串口地址 +# pushGroups: # 点位 10 的发卡器配置 +# - name: gpiochip0 +# read: /dev/ttyUSB0 +# outOK: 21 +# lower: 20 +# error: 16 +# empty: 12 +# push: 13 +# reset: 19 +# pull: 26 +# - name: gpiochip0 +# read: /dev/ttyUSB0 +# outOK: 7 +# lower: 8 +# error: 25 +# empty: 24 +# push: 10 +# reset: 9 +# pull: 11 \ No newline at end of file diff --git a/config/config.go b/config/config.go index 5aa16b7..387cc8a 100644 --- a/config/config.go +++ b/config/config.go @@ -13,6 +13,7 @@ type AliyunConfig struct { AccessKeySecret string AppKey string Timeout int + Volume int Voice string SpeechRate int } diff --git a/config/game/game.go b/config/game/game.go index e549618..9861178 100644 --- a/config/game/game.go +++ b/config/game/game.go @@ -4,9 +4,9 @@ type Config any func NewConfig(point int) Config { switch point { - case 4: + case 10: return ConfigPush{} - case 5: + case 11: return ConfigWait{} default: return nil diff --git a/go.mod b/go.mod index 6530a9f..6053126 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.23.2 require ( github.com/aliyun/alibabacloud-nls-go-sdk v1.1.1 github.com/eclipse/paho.golang v0.22.0 + github.com/go-pkgz/cronrange v0.2.0 github.com/go-rod/rod v0.116.2 github.com/gopxl/beep/v2 v2.1.0 github.com/grid-x/modbus v0.0.0-20241004123532-f6c6fb5201b3 @@ -20,7 +21,6 @@ require ( github.com/ebitengine/oto/v3 v3.3.2 // indirect github.com/ebitengine/purego v0.8.1 // indirect github.com/fsnotify/fsnotify v1.8.0 // indirect - github.com/go-pkgz/cronrange v0.2.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect github.com/grid-x/serial v0.0.0-20211107191517-583c7356b3aa // indirect github.com/hajimehoshi/go-mp3 v0.3.4 // indirect diff --git a/internal/middleware/stop.go b/internal/middleware/stop.go index c2b37f5..cc1c3a3 100644 --- a/internal/middleware/stop.go +++ b/internal/middleware/stop.go @@ -4,6 +4,7 @@ import ( "game-driver/internal/common" "game-driver/leaf" "go.uber.org/zap" + "sync" ) // EmergencyStop 紧急停止中间件 @@ -12,13 +13,19 @@ func EmergencyStop(stopper common.Stopper) leaf.HandlerFunc { cancel := leaf.WithCancel(c) defer stopper.Reset() + // 等待组 + var wait sync.WaitGroup + defer wait.Wait() + // 结束信号通道 a := make(chan struct{}) // 发送结束信号 defer close(a) zap.S().Infoln("监听停止信号") + wait.Add(1) go func() { + defer wait.Done() defer zap.S().Infoln("结束停止信号监听") select { diff --git a/internal/middleware/ticker.go b/internal/middleware/ticker.go index 3fbbe97..83c5168 100644 --- a/internal/middleware/ticker.go +++ b/internal/middleware/ticker.go @@ -9,6 +9,7 @@ import ( "time" ) +// TickerAction 定时器动作,用于在指定时间点执行打印和语音播报 func TickerAction() leaf.HandlerFunc { return func(c *leaf.Context) { pm := leaf.Value[*schema.PlayModal](c, PayloadJSONKey) diff --git a/internal/middleware/timer.go b/internal/middleware/timer.go index 66a3e87..0dcecd9 100644 --- a/internal/middleware/timer.go +++ b/internal/middleware/timer.go @@ -8,7 +8,7 @@ import ( "time" ) -// TimeoutOver 定时器中间件,用于定时触发屏幕打印和语音播报。 t 是语音播报实例 +// TimeoutOver 超时停止 func TimeoutOver(maxTimeout int) leaf.HandlerFunc { return func(c *leaf.Context) { pm := leaf.Value[*schema.PlayModal](c, PayloadJSONKey) diff --git a/internal/routes/play.go b/internal/routes/play.go index 3f3f11d..288cf33 100644 --- a/internal/routes/play.go +++ b/internal/routes/play.go @@ -19,11 +19,11 @@ func switchPoint(ctx context.Context, point int) leaf.HandlerFunc { switch point { case 2: // 镇水塔点位 return play.OnlyVideo - case 4: - // 4号点位(发卡机) + case 10: + // 10号点位(发卡机) return play.PushCard(ctx) - case 5: - // 5号点位(等待插卡) + case 11: + // 11号点位(等待插卡) return play.WaitCard(ctx) default: return play.Default diff --git a/internal/routes/wait.go b/internal/routes/wait.go index 7813ed0..2201633 100644 --- a/internal/routes/wait.go +++ b/internal/routes/wait.go @@ -2,6 +2,7 @@ package routes import ( "context" + "fmt" "game-driver/internal/middleware" "game-driver/internal/schema" "game-driver/leaf" @@ -18,10 +19,12 @@ import ( "time" ) -func runAction(c *leaf.Context, item schema.WaitItemModel, rootRules []cronrange.Rule, play func(c context.Context, item schema.WaitItemModel)) { +func runAction(c *leaf.Context, item schema.WaitItemModel, rootRules []cronrange.Rule, play func(c context.Context, item schema.WaitItemModel) error) { + // 设定默认时间规则 if item.Cron == "" { item.Cron = "* * * *" } + rules, err := cronrange.Parse(item.Cron) if err != nil { zap.S().Errorln("解析时间规则异常: ", err) @@ -64,7 +67,17 @@ func runAction(c *leaf.Context, item schema.WaitItemModel, rootRules []cronrange return default: if run { - play(ctx, item) + err := play(ctx, item) + if err != nil { + zap.S().Errorln("执行动作异常: ", err) + <-time.After(time.Minute) + } + } else { + select { + case <-c.Done(): + return + case <-time.After(time.Second): + } } } } @@ -73,6 +86,11 @@ func runAction(c *leaf.Context, item schema.WaitItemModel, rootRules []cronrange func WaitAction(c *leaf.Context) { payload := leaf.Value[*schema.WaitModel](c, middleware.PayloadJSONKey) + // 设定默认时间规则 + if payload.Cron == "" { + payload.Cron = "* * * *" + } + rules, err := cronrange.Parse(payload.Cron) if err != nil { zap.S().Errorln("解析时间规则异常: ", err) @@ -125,11 +143,13 @@ func WaitAction(c *leaf.Context) { } } -func audioAction(c context.Context, item schema.WaitItemModel) { +func audioAction(c context.Context, item schema.WaitItemModel) error { data, err := utils.LinkAudio(item.Data) if err != nil { - zap.S().Errorln("音频数据获取异常: ", err) - return + return fmt.Errorf("音频数据获取异常: %w", err) + } + if data == nil { + return fmt.Errorf("音频数据获取为空") } zap.S().Infoln("播放待机音乐") @@ -138,8 +158,7 @@ func audioAction(c context.Context, item schema.WaitItemModel) { ctrl, closer, e := audio.PlayBgmMP3(data) defer closer() if e != nil { - zap.S().Errorln("播放待机音乐异常", e) - return + return fmt.Errorf("播放待机音乐异常: %w", e) } <-c.Done() @@ -147,13 +166,14 @@ func audioAction(c context.Context, item schema.WaitItemModel) { speaker.Lock() ctrl.Streamer = nil speaker.Unlock() + + return nil } -func ttsAction(c context.Context, item schema.WaitItemModel) { +func ttsAction(c context.Context, item schema.WaitItemModel) error { reader, err := tts.DefaultTTS.Get(item.Data) if err != nil { - zap.S().Errorln("语音合成异常: ", err) - return + return fmt.Errorf("语音合成异常: %w", err) } zap.S().Infoln("循环播放待机 TTS 语音") @@ -163,17 +183,16 @@ func ttsAction(c context.Context, item schema.WaitItemModel) { audio.PlayWav(c, reader) select { case <-c.Done(): - return + return nil case <-time.After(time.Duration(item.Interval) * time.Second): } } } -func relayAction(c context.Context, item schema.WaitItemModel) { +func relayAction(c context.Context, item schema.WaitItemModel) error { r, err := relay.New(item.Data) if err != nil { - zap.S().Errorln("继电器初始化异常: ", err) - return + return fmt.Errorf("继电器初始化异常: %w", err) } defer r.Close() @@ -183,13 +202,14 @@ func relayAction(c context.Context, item schema.WaitItemModel) { _ = r.On(0) <-c.Done() _ = r.Off(0) + + return nil } -func videoAction(c context.Context, item schema.WaitItemModel) { +func videoAction(c context.Context, item schema.WaitItemModel) error { local, err := utils.LinkVideo(item.Data) if err != nil { - zap.S().Errorln("视频文件获取异常: ", err) - return + return fmt.Errorf("视频文件获取异常: %w", err) } zap.S().Infoln("循环播放待机视频") @@ -201,18 +221,17 @@ func videoAction(c context.Context, item schema.WaitItemModel) { for { err := video.Play(c, local) if err != nil { - zap.S().Infof("视频播放异常: %s", err) - return + return fmt.Errorf("视频播放异常: %w", err) } select { case <-c.Done(): - return + return nil case <-time.After(time.Duration(item.Interval) * time.Second): } } } -func webAction(c context.Context, item schema.WaitItemModel) { +func webAction(c context.Context, item schema.WaitItemModel) error { zap.S().Infoln("打开待机网页") // 控制背光 @@ -220,4 +239,5 @@ func webAction(c context.Context, item schema.WaitItemModel) { defer utils.BlankClose() browser.OpenApp(c, item.Data) + return nil } diff --git a/json.md b/json.md index 0cea813..05af5e1 100644 --- a/json.md +++ b/json.md @@ -8,7 +8,7 @@ url: `server/wushan/2/wait` "cron": "* * * *", "items": [ { - "data": "file://./三峡龙脊BGM.mp3" + "data": "file:///opt/game/三峡龙脊BGM.mp3" } ] } @@ -30,7 +30,7 @@ url: `server/wushan/2/play` ] }, "game": { - "video": "file://./镇水塔法阵.mp4" + "video": "file:///opt/game/镇水塔法阵.mp4" } } ``` diff --git a/pkg/tts/aliyun.go b/pkg/tts/aliyun.go index 7f5a9d1..4e9ae61 100644 --- a/pkg/tts/aliyun.go +++ b/pkg/tts/aliyun.go @@ -71,7 +71,7 @@ func (tts *AliTTS) getToken() error { func (tts *AliTTS) Get(text string) (io.Reader, error) { param := nls.DefaultSpeechSynthesisParam() - param.Volume = 200 + param.Volume = tts.Volume param.Voice = tts.Voice param.SpeechRate = tts.SpeechRate diff --git a/pkg/utils/link_audio.go b/pkg/utils/link_audio.go index 0618318..36a4f65 100644 --- a/pkg/utils/link_audio.go +++ b/pkg/utils/link_audio.go @@ -64,7 +64,9 @@ func LinkAudio(link string) (bgm io.ReadCloser, err error) { } else { err = fmt.Errorf("不支持的链接协议: %v", u.String()) } - bgm = toSeeker(bgm) + if bgm != nil { + bgm = toSeeker(bgm) + } } return } diff --git a/pkg/video/paly.go b/pkg/video/paly.go index 2b09f1f..f8adbda 100644 --- a/pkg/video/paly.go +++ b/pkg/video/paly.go @@ -5,6 +5,7 @@ import ( "context" "go.uber.org/zap" "io" + "os" "os/exec" "sync" ) @@ -14,6 +15,11 @@ func Play(ctx context.Context, file string) error { zap.S().Infoln("video file is empty") return nil } + // 判断文件是否存在 + if _, err := os.Stat(file); err != nil { + zap.S().Errorf("视频文件不存在: %v", err) + return err + } cmd := exec.CommandContext(ctx, "ffplay", "-autoexit", "-fs", file) pipe, err := cmd.StderrPipe() if err != nil { diff --git a/puml/游戏.puml b/puml/游戏.puml index e5a42cd..78da3bb 100644 --- a/puml/游戏.puml +++ b/puml/游戏.puml @@ -28,12 +28,11 @@ ---- tts播报恭喜通关词 -- 青龙云台(5) --- 待机 ----- 网页默认页面 --- Game ---- 供电 ---- 播放语音 -- 俱乐部(6) --- 待机 ---- 启动屏幕 ---- Game +---- 网页默认页面 @endmindmap