From 593d7758bf0deecd77fda6859ff8282326f98a7d Mon Sep 17 00:00:00 2001 From: mapleafgo Date: Wed, 26 Feb 2025 19:45:47 +0800 Subject: [PATCH] =?UTF-8?q?=E6=92=AD=E6=94=BE=E6=B8=B8=E6=88=8F=E6=97=B6?= =?UTF-8?q?=EF=BC=8C=E5=81=9C=E6=AD=A2=E5=BE=85=E6=9C=BA=E4=BB=BB=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.yml | 7 +- config/config.go | 1 + go.mod | 1 + go.sum | 2 + internal/common/pause.go | 29 +++ internal/middleware/pause.go | 70 ++++++ internal/middleware/pause_wait.go | 15 ++ internal/middleware/sound_start.go | 4 +- internal/middleware/timer.go | 2 +- internal/routes/wait.go | 340 ++++++++++++----------------- internal/schema/play.go | 9 +- internal/schema/wait.go | 9 +- internal/server.go | 4 +- json.md | 52 +++++ leaf/context.go | 18 +- pkg/tts/aliyun.go | 4 +- puml/游戏.puml | 48 ++-- readme.md | 43 +++- todo.md | 39 ++-- 19 files changed, 422 insertions(+), 275 deletions(-) create mode 100644 internal/common/pause.go create mode 100644 internal/middleware/pause.go create mode 100644 internal/middleware/pause_wait.go create mode 100644 json.md diff --git a/config.yml b/config.yml index 40ee209..acfd2cf 100755 --- a/config.yml +++ b/config.yml @@ -1,6 +1,6 @@ location: wushan -point: 4 -relay: /dev/ttyUSB1 +point: 2 +relay: maxTimeout: 60 # 单位 s,必须大于 0 log: level: debug @@ -11,13 +11,14 @@ log: maxAge: 30 compress: true mqtt: - url: mqtt://36.138.38.16:1883 + url: mqtt://58.144.199.46:1883 aliyun: accessKeyID: accessKeySecret: appKey: timeout: 10 # 单位 s voice: zhifeng_emo + speechRate: 50 # 语速 game: # addr: /dev/ttyUSB0 # 点位 5 的串口地址 pushGroups: # 点位 4 的发卡器配置 diff --git a/config/config.go b/config/config.go index 3158ea1..5aa16b7 100644 --- a/config/config.go +++ b/config/config.go @@ -14,6 +14,7 @@ type AliyunConfig struct { AppKey string Timeout int Voice string + SpeechRate int } type Logger struct { diff --git a/go.mod b/go.mod index c823bde..6530a9f 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,7 @@ 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/go.sum b/go.sum index 6de3c48..db7d2f6 100644 --- a/go.sum +++ b/go.sum @@ -25,6 +25,8 @@ github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7z github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-pkgz/cronrange v0.2.0 h1:FaJ/TB7Ng3xTCfRgblfLecL07RccXVVB6+/hdFaGbBE= +github.com/go-pkgz/cronrange v0.2.0/go.mod h1:2dPQzEVkSwXsRdcGFXIE6xllAnwELWUusad2MyVskLs= github.com/go-rod/rod v0.116.2 h1:A5t2Ky2A+5eD/ZJQr1EfsQSe5rms5Xof/qj296e+ZqA= github.com/go-rod/rod v0.116.2/go.mod h1:H+CMO9SCNc2TJ2WfrG+pKhITz57uGNYU43qYHh438Mg= github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= diff --git a/internal/common/pause.go b/internal/common/pause.go new file mode 100644 index 0000000..eb6724e --- /dev/null +++ b/internal/common/pause.go @@ -0,0 +1,29 @@ +package common + +type CtrlWait struct { + // 用于暂停的chan + P chan struct{} + // 用于恢复的chan + R chan struct{} +} + +// Pause 暂停 +func (c *CtrlWait) Pause() { + c.P <- struct{}{} +} + +// Resume 恢复 +func (c *CtrlWait) Resume() { + c.R <- struct{}{} +} + +// NewCtrlWait 创建一个控制等待 +func NewCtrlWait() *CtrlWait { + return &CtrlWait{ + P: make(chan struct{}), + R: make(chan struct{}), + } +} + +// PassCtrl 全局控制等待 +var PassCtrl = NewCtrlWait() diff --git a/internal/middleware/pause.go b/internal/middleware/pause.go new file mode 100644 index 0000000..f712f98 --- /dev/null +++ b/internal/middleware/pause.go @@ -0,0 +1,70 @@ +package middleware + +import ( + "context" + "game-driver/internal/common" + "game-driver/leaf" + "go.uber.org/zap" + "sync" +) + +func Pause(ctrl *common.CtrlWait) leaf.HandlerFunc { + return func(c *leaf.Context) { + var cancel context.CancelFunc + + // 获取锚点 + holdPoint := c.Hold() + + // 等待组 + var wait sync.WaitGroup + defer wait.Wait() + + // 结束信号通道 + a := make(chan struct{}) + // 发送结束信号 + defer close(a) + + run := true + + // 保存原始的 Context + originalCtx := c.Context + + wait.Add(1) + go func() { + defer wait.Done() + zap.S().Infoln("待机控制器") + + for { + select { + case <-originalCtx.Done(): + zap.S().Infoln("待机控制器监听结束") + return + case <-ctrl.R: + { + zap.S().Infoln("待机控制器 Resume 触发") + c.Context = originalCtx + run = true + } + case <-ctrl.P: + { + zap.S().Infoln("待机控制器 Pause 触发") + run = false + cancel() + } + } + } + }() + + for { + select { + case <-originalCtx.Done(): + return + default: + if run { + cancel = leaf.WithCancel(c) + c.Resume(holdPoint) + } + } + } + } +} diff --git a/internal/middleware/pause_wait.go b/internal/middleware/pause_wait.go new file mode 100644 index 0000000..bfa819e --- /dev/null +++ b/internal/middleware/pause_wait.go @@ -0,0 +1,15 @@ +package middleware + +import ( + "game-driver/internal/common" + "game-driver/leaf" +) + +func PauseWait(ctrl *common.CtrlWait) leaf.HandlerFunc { + return func(c *leaf.Context) { + ctrl.Pause() + defer ctrl.Resume() + + c.Next() + } +} diff --git a/internal/middleware/sound_start.go b/internal/middleware/sound_start.go index 1300f25..de6e98f 100644 --- a/internal/middleware/sound_start.go +++ b/internal/middleware/sound_start.go @@ -14,8 +14,10 @@ func SoundStart() leaf.HandlerFunc { defer func() { switch leaf.Value[leaf.EndType](c, leaf.EndKey) { - case leaf.EndTimer: + case leaf.End: tts.DefaultTTS.Sound(pm.TTS.End) + case leaf.EndTimeout: + tts.DefaultTTS.Sound(pm.TTS.Timeout) case leaf.EndStop: tts.DefaultTTS.Sound(pm.TTS.Stop) } diff --git a/internal/middleware/timer.go b/internal/middleware/timer.go index bdb56a0..66a3e87 100644 --- a/internal/middleware/timer.go +++ b/internal/middleware/timer.go @@ -46,7 +46,7 @@ func TimeoutOver(maxTimeout int) leaf.HandlerFunc { { zap.S().Infoln("超时 Timer 触发") cancel() - leaf.WithValue[leaf.EndType](c, leaf.EndKey, leaf.EndTimer) + leaf.WithValue[leaf.EndType](c, leaf.EndKey, leaf.EndTimeout) } } }() diff --git a/internal/routes/wait.go b/internal/routes/wait.go index b40a8ea..7813ed0 100644 --- a/internal/routes/wait.go +++ b/internal/routes/wait.go @@ -1,6 +1,7 @@ package routes import ( + "context" "game-driver/internal/middleware" "game-driver/internal/schema" "game-driver/leaf" @@ -10,180 +11,165 @@ import ( "game-driver/pkg/tts" "game-driver/pkg/utils" "game-driver/pkg/video" + "github.com/go-pkgz/cronrange" "github.com/gopxl/beep/v2/speaker" "go.uber.org/zap" "sync" "time" ) -func timerAction(timestamp int64) <-chan struct{} { - a := make(chan struct{}) - - go func() { - if timestamp == 0 { - close(a) - } else { - <-time.After(time.Until(time.Unix(timestamp, 0))) - close(a) - } - }() - - return a -} - -func WaitAction(c *leaf.Context) { - payload := leaf.Value[*schema.WaitModel](c, middleware.PayloadJSONKey) - - if payload.Start != 0 && payload.End != 0 && time.Unix(payload.Start, 0).After(time.Unix(payload.End, 0)) { - zap.S().Infoln("开始时间大于结束时间") +func runAction(c *leaf.Context, item schema.WaitItemModel, rootRules []cronrange.Rule, play func(c context.Context, item schema.WaitItemModel)) { + if item.Cron == "" { + item.Cron = "* * * *" + } + rules, err := cronrange.Parse(item.Cron) + if err != nil { + zap.S().Errorln("解析时间规则异常: ", err) return } - if payload.End != 0 { - cancel := leaf.WithDeadline(c, time.Unix(payload.End, 0)) - defer cancel() - } + // 等待组 + var wait sync.WaitGroup + defer wait.Wait() - select { - case <-c.Done(): - case <-timerAction(payload.Start): - // 等待组 - var wait sync.WaitGroup - defer wait.Wait() - for _, item := range payload.Items { - switch item.Type { - case schema.WaitAudio: - // 执行音乐播放 - wait.Add(1) - go func() { - defer wait.Done() - audioAction(c, item, payload.TimeModel) - }() - case schema.WaitTTS: - // 执行TTS播放 - wait.Add(1) - go func() { - defer wait.Done() - ttsAction(c, item, payload.TimeModel) - }() - case schema.WaitRelay: - // 执行继电器供电 - wait.Add(1) - go func() { - defer wait.Done() - relayAction(c, item, payload.TimeModel) - }() - case schema.WaitVideo: - // 执行视频播放 - wait.Add(1) - go func() { - defer wait.Done() - videoAction(c, item, payload.TimeModel) - }() - case schema.WaitWeb: - // 执行网页打开 - wait.Add(1) - go func() { - defer wait.Done() - webAction(c, item, payload.TimeModel) - }() - default: - zap.S().Infof("不支持的类型: %d\n", item.Type) + ticker := time.NewTicker(time.Second) + defer ticker.Stop() + + ctx, cancel := context.WithCancel(context.TODO()) + + var run bool + + wait.Add(1) + go func() { + defer wait.Done() + for { + select { + case <-c.Done(): + cancel() + return + case <-ticker.C: + if cronrange.Match(rules, time.Now()) && cronrange.Match(rootRules, time.Now()) { + run = true + } else { + run = false + cancel() + } + } + } + }() + + for { + select { + case <-c.Done(): + return + default: + if run { + play(ctx, item) } } } } -func audioAction(c *leaf.Context, item schema.WaitItemModel, root schema.TimeModel) { - if item.Start != 0 && time.Unix(item.Start, 0).Before(time.Unix(root.Start, 0)) { - zap.S().Infoln("开始时间小于根任务开始时间") +func WaitAction(c *leaf.Context) { + payload := leaf.Value[*schema.WaitModel](c, middleware.PayloadJSONKey) + + rules, err := cronrange.Parse(payload.Cron) + if err != nil { + zap.S().Errorln("解析时间规则异常: ", err) return } - if item.End != 0 { - cancel := leaf.WithDeadline(c, time.Unix(item.End, 0)) - defer cancel() + // 等待组 + var wait sync.WaitGroup + defer wait.Wait() + for _, item := range payload.Items { + switch item.Type { + case schema.WaitAudio: + // 执行音乐播放 + wait.Add(1) + go func() { + defer wait.Done() + runAction(c, item, rules, audioAction) + }() + case schema.WaitTTS: + // 执行TTS播放 + wait.Add(1) + go func() { + defer wait.Done() + runAction(c, item, rules, ttsAction) + }() + case schema.WaitRelay: + // 执行继电器供电 + wait.Add(1) + go func() { + defer wait.Done() + runAction(c, item, rules, relayAction) + }() + case schema.WaitVideo: + // 执行视频播放 + wait.Add(1) + go func() { + defer wait.Done() + runAction(c, item, rules, videoAction) + }() + case schema.WaitWeb: + // 执行网页打开 + wait.Add(1) + go func() { + defer wait.Done() + runAction(c, item, rules, webAction) + }() + default: + zap.S().Infof("不支持的类型: %d\n", item.Type) + } } +} +func audioAction(c context.Context, item schema.WaitItemModel) { data, err := utils.LinkAudio(item.Data) if err != nil { zap.S().Errorln("音频数据获取异常: ", err) return } - select { - case <-c.Done(): - case <-timerAction(item.Start): - { - zap.S().Infoln("播放待机音乐") - defer zap.S().Infoln("结束待机音乐") + zap.S().Infoln("播放待机音乐") + defer zap.S().Infoln("结束待机音乐") - ctrl, closer, e := audio.PlayBgmMP3(data) - defer closer() - if e != nil { - zap.S().Errorln("播放待机音乐异常", e) - return - } - - select { - case <-c.Done(): - { - speaker.Lock() - ctrl.Streamer = nil - speaker.Unlock() - } - } - } - } -} - -func ttsAction(c *leaf.Context, item schema.WaitItemModel, root schema.TimeModel) { - if item.Start != 0 && time.Unix(item.Start, 0).Before(time.Unix(root.Start, 0)) { - zap.S().Infoln("开始时间小于根任务开始时间") + ctrl, closer, e := audio.PlayBgmMP3(data) + defer closer() + if e != nil { + zap.S().Errorln("播放待机音乐异常", e) return } - if item.End != 0 { - cancel := leaf.WithDeadline(c, time.Unix(item.End, 0)) - defer cancel() - } + <-c.Done() + speaker.Lock() + ctrl.Streamer = nil + speaker.Unlock() +} + +func ttsAction(c context.Context, item schema.WaitItemModel) { reader, err := tts.DefaultTTS.Get(item.Data) if err != nil { zap.S().Errorln("语音合成异常: ", err) return } - select { - case <-c.Done(): - case <-timerAction(item.Start): - { - zap.S().Infoln("循环播放待机 TTS 语音") - defer zap.S().Infoln("结束待机 TTS 语音") + zap.S().Infoln("循环播放待机 TTS 语音") + defer zap.S().Infoln("结束待机 TTS 语音") - for { - audio.PlayWav(c, reader) - select { - case <-c.Done(): - return - case <-time.After(time.Duration(item.Interval) * time.Second): - } - } + for { + audio.PlayWav(c, reader) + select { + case <-c.Done(): + return + case <-time.After(time.Duration(item.Interval) * time.Second): } } } -func relayAction(c *leaf.Context, item schema.WaitItemModel, root schema.TimeModel) { - if item.Start != 0 && time.Unix(item.Start, 0).Before(time.Unix(root.Start, 0)) { - zap.S().Infoln("开始时间小于根任务开始时间") - return - } - - if item.End != 0 { - cancel := leaf.WithDeadline(c, time.Unix(item.End, 0)) - defer cancel() - } - +func relayAction(c context.Context, item schema.WaitItemModel) { r, err := relay.New(item.Data) if err != nil { zap.S().Errorln("继电器初始化异常: ", err) @@ -191,85 +177,47 @@ func relayAction(c *leaf.Context, item schema.WaitItemModel, root schema.TimeMod } defer r.Close() - select { - case <-c.Done(): - case <-timerAction(item.Start): - { - zap.S().Infoln("待机继电器供电") - defer zap.S().Infoln("待机继电器断电") + zap.S().Infoln("待机继电器供电") + defer zap.S().Infoln("待机继电器断电") - r.On(0) - <-c.Done() - r.Off(0) - } - } + _ = r.On(0) + <-c.Done() + _ = r.Off(0) } -func videoAction(c *leaf.Context, item schema.WaitItemModel, root schema.TimeModel) { - if item.Start != 0 && time.Unix(item.Start, 0).Before(time.Unix(root.Start, 0)) { - zap.S().Infoln("开始时间小于根任务开始时间") - return - } - - if item.End != 0 { - cancel := leaf.WithDeadline(c, time.Unix(item.End, 0)) - defer cancel() - } - +func videoAction(c context.Context, item schema.WaitItemModel) { local, err := utils.LinkVideo(item.Data) if err != nil { zap.S().Errorln("视频文件获取异常: ", err) return } - select { - case <-c.Done(): - case <-timerAction(item.Start): - { - zap.S().Infoln("循环播放待机视频") - defer zap.S().Infoln("结束待机视频") + zap.S().Infoln("循环播放待机视频") + defer zap.S().Infoln("结束待机视频") - utils.BlankOpen() - defer utils.BlankClose() + utils.BlankOpen() + defer utils.BlankClose() - for { - err := video.Play(c, local) - if err != nil { - zap.S().Infof("视频播放异常: %s", err) - return - } - select { - case <-c.Done(): - return - case <-time.After(time.Duration(item.Interval) * time.Second): - } - } + for { + err := video.Play(c, local) + if err != nil { + zap.S().Infof("视频播放异常: %s", err) + return + } + select { + case <-c.Done(): + return + case <-time.After(time.Duration(item.Interval) * time.Second): } } } -func webAction(c *leaf.Context, item schema.WaitItemModel, root schema.TimeModel) { - if item.Start != 0 && time.Unix(item.Start, 0).Before(time.Unix(root.Start, 0)) { - zap.S().Infoln("开始时间小于根任务开始时间") - return - } +func webAction(c context.Context, item schema.WaitItemModel) { + zap.S().Infoln("打开待机网页") - if item.End != 0 { - cancel := leaf.WithDeadline(c, time.Unix(item.End, 0)) - defer cancel() - } + // 控制背光 + utils.BlankOpen() + defer utils.BlankClose() - select { - case <-c.Done(): - case <-timerAction(item.Start): - { - zap.S().Infoln("打开待机网页") - - // 控制背光 - utils.BlankOpen() - defer utils.BlankClose() - - browser.OpenApp(c, item.Data) - } - } + browser.OpenApp(c, item.Data) } diff --git a/internal/schema/play.go b/internal/schema/play.go index 592bb3e..3958adc 100644 --- a/internal/schema/play.go +++ b/internal/schema/play.go @@ -6,10 +6,11 @@ type TTSTimer struct { } type TTSModal struct { - Start string `json:"start"` - End string `json:"end"` - Stop string `json:"stop"` - Timer []TTSTimer `json:"timer"` + Start string `json:"start"` + End string `json:"end"` + Stop string `json:"stop"` + Timeout string `json:"timeout"` + Timer []TTSTimer `json:"timer"` } type PrintModal struct { diff --git a/internal/schema/wait.go b/internal/schema/wait.go index 8f5c648..8ebeb96 100644 --- a/internal/schema/wait.go +++ b/internal/schema/wait.go @@ -10,13 +10,8 @@ const ( WaitWeb ) -type TimeModel struct { - Start int64 `json:"start"` - End int64 `json:"end"` -} - type WaitItemModel struct { - TimeModel + Cron string `json:"cron"` Type WaitType `json:"type"` Data string `json:"data"` Interval int64 `json:"interval"` @@ -24,6 +19,6 @@ type WaitItemModel struct { type WaitModel struct { JsonModel - TimeModel + Cron string `json:"cron"` Items []WaitItemModel `json:"items"` } diff --git a/internal/server.go b/internal/server.go index c36db6e..e654c91 100644 --- a/internal/server.go +++ b/internal/server.go @@ -54,7 +54,7 @@ func buildMqtt(c config.MqttConfig, r *leaf.Engine, subTopics ...string) autopah zap.S().Infof("MQTT 连接异常: %s\n", err) }, ClientConfig: paho.ClientConfig{ - ClientID: "TestSubscriber", + ClientID: fmt.Sprintf("game-driver-%s-%v", config.C.Location, config.C.Point), OnPublishReceived: []func(paho.PublishReceived) (bool, error){ func(pr paho.PublishReceived) (bool, error) { r.Route(pr.Packet.Packet()) @@ -131,6 +131,7 @@ func Run() { middleware.RunLog(), middleware.PayloadJSON[schema.PlayModal](), middleware.DeviceLock(device), + middleware.PauseWait(common.PassCtrl), middleware.EmergencyStop(common.GlobalStopper), middleware.SoundStart(), middleware.RelayMaster(r), @@ -145,6 +146,7 @@ func Run() { middleware.PayloadJSON[schema.WaitModel](), middleware.Unique(common.GlobalBgStopper), middleware.EmergencyStop(common.GlobalBgStopper), + middleware.Pause(common.PassCtrl), routes.WaitAction, ) // 处理指令 diff --git a/json.md b/json.md new file mode 100644 index 0000000..0cea813 --- /dev/null +++ b/json.md @@ -0,0 +1,52 @@ +## 点位2(镇水塔) +### 待机 + +url: `server/wushan/2/wait` + +```json +{ + "cron": "* * * *", + "items": [ + { + "data": "file://./三峡龙脊BGM.mp3" + } + ] +} +``` + +### Game + +url: `server/wushan/2/play` + +```json +{ + "tts": { + "start": "刘佳勇者,恭喜你成功通关!", + "timer": [ + { + "time": 2, + "value": "你的荣耀将获得法阵加持,请迅速移步到法阵位置!" + } + ] + }, + "game": { + "video": "file://./镇水塔法阵.mp4" + } +} +``` + +### STOP 待机 + +url: `server/wushan/2/command` + +```text +stop-bg +``` + +### STOP GAME + +url: `server/wushan/2/command` + +```text +stop +``` \ No newline at end of file diff --git a/leaf/context.go b/leaf/context.go index 8967dd8..66ae35a 100644 --- a/leaf/context.go +++ b/leaf/context.go @@ -17,7 +17,8 @@ const EndKey endKeyType = "end" type EndType int const ( - EndTimer EndType = iota + 1 + End = iota + EndTimeout EndStop ) @@ -75,6 +76,17 @@ func (c *Context) Handler() HandlerFunc { return c.handlers.Last() } +// Hold 在当前中保留一个锚点,以便后续可以从此恢复后续处理程序。 +func (c *Context) Hold() int8 { + return c.index +} + +// Resume 从 Hold 保留的锚点恢复后续处理程序。 +func (c *Context) Resume(index int8) { + c.index = index + c.Next() +} + /************************************/ /*********** FLOW CONTROL ***********/ /************************************/ @@ -106,6 +118,10 @@ func (c *Context) Abort() { c.index = abortIndex } +func (c *Context) Done() <-chan struct{} { + return c.Context.Done() +} + func WithValue[T any](ctx *Context, k any, v T) { ctx.value = &KeyValue{ parent: ctx.value, diff --git a/pkg/tts/aliyun.go b/pkg/tts/aliyun.go index c9ae784..7f5a9d1 100644 --- a/pkg/tts/aliyun.go +++ b/pkg/tts/aliyun.go @@ -62,6 +62,7 @@ func (tts *AliTTS) getToken() error { if err != nil { return err } else if resultMessage.ErrMsg != "" { + zap.S().Errorf("获取Token失败: %s", resultMessage.ErrMsg) return errorsx.ThirdPartyErr } tts.tokenResult = resultMessage.TokenResult @@ -70,8 +71,9 @@ func (tts *AliTTS) getToken() error { func (tts *AliTTS) Get(text string) (io.Reader, error) { param := nls.DefaultSpeechSynthesisParam() - param.Volume = 100 + param.Volume = 200 param.Voice = tts.Voice + param.SpeechRate = tts.SpeechRate err := tts.getToken() if err != nil { diff --git a/puml/游戏.puml b/puml/游戏.puml index 4325618..e5a42cd 100644 --- a/puml/游戏.puml +++ b/puml/游戏.puml @@ -4,40 +4,36 @@ +++ 待机 ++++ 背景音乐 +++ Game -++++ 播放欢迎语音 -++ 一阶段门头(无) -++ 召唤神女(1) +++++ tts播报欢迎词 +++ 击缶台(1) ++++ 待机 +++ Game -++++ 等待结束 -++ 神女低语(无) -+++ 按下按钮 -+++ 播放语音 -+++ 发送结果数据 -++ 镇水神力(2) +++++ bgm +++++ 供电 +++++ tts播报游客名 +++++ tts游戏结束 +++ 镇水塔(2) +++ 待机 ++++ 投影仪待机 +++ Game +++++ bgm ++++ 播放法阵视频 --- 二阶段门头(无) --- 神女除妖(3) ---- Game ----- 控制设备启动 ----- 接收游戏结果 ----- 发送结果数据 --- 流光寻踪(无) --- 神女授书(4) +-- 神女镇邪祟(3) --- 待机 --- Game ----- 控制设备吐卡 ----- 播放取卡提示语音 ----- 异常状态处理 --- 青云龙台(5) +---- tts播报恭喜通关词 +-- 神女影像(4) +--- 待机 +--- Game +---- tts播报恭喜通关词 +-- 青龙云台(5) --- 待机 ---- 网页默认页面 --- Game ----- 等待卡片插入 ----- 播放恭喜语音 ----- 屏幕恭喜页面 ----- 灯光播放 ----- 结束屏幕提示 +---- 供电 +---- 播放语音 +-- 俱乐部(6) +--- 待机 +---- 启动屏幕 +--- Game @endmindmap diff --git a/readme.md b/readme.md index 1b0eaae..d78ef3d 100644 --- a/readme.md +++ b/readme.md @@ -22,13 +22,15 @@ Payload: "default-print": "", // 文本转语音整体控制 "tts": { - // 开始语音 + // 开始播报语音 "start": "", - // 结束语音 + // 超时自动停止时播报语音 + "timeout": "", + // 结束播报语音 "end": "", - // 终止语音 + // 终止播报语音 "stop": "", - // 固定节点语音 + // 固定节点播报语音 "timer": [ { // 时间节点(s) @@ -125,17 +127,13 @@ Payload: ```json lines { - // 开始时间戳(s), default 0, 0表示立即执行 - "start": 1730793361, - // 结束时间戳(s), default 0, 0表示无限执行 - "end": 1730793368, + // 执行的时间区间 + "cron": "17:20-21:35 1-5 * *", // 执行项 "items": [ { - // 开始时间戳(s), 默认根的时间戳, 只有在根执行时间内才会执行 - "start": 1730793361, - // 结束时间戳(s), 默认根的时间戳, 只有在根执行时间内才会执行 - "end": 1730793368, + // 执行的时间区间 + "cron": "17:20-21:35 1-5 * *", // 间隔时间(s), 类型>2时, 该项无效, default 0 "interval": 0, // 事件类型(0: 音频; 1: 视频; 2: TTS; 3: 继电器; 4: 网页), default 0 @@ -149,3 +147,24 @@ Payload: ``` > 同一个类型的待机任务只能有一个,当有新的任务到达时会覆盖之前的任务 + +### Cron Format + +The format consists of four fields separated by whitespace: +``` +time dow dom month +``` + +Where: +- `time`: Time range in 24-hour format (HH:MM[:SS]-HH:MM[:SS]) or * for all day. Seconds are optional. +- `dow`: Day of week (0-6, where 0=Sunday) +- `dom`: Day of month (1-31) +- `month`: Month (1-12) + +Multiple rules can be combined using semicolons (;). + +Each field (except time) supports: +- Single values: "5" +- Lists: "1,3,5" +- Ranges: "1-5" +- Asterisk: "*" for any/all values diff --git a/todo.md b/todo.md index b82770b..809e34f 100644 --- a/todo.md +++ b/todo.md @@ -1,32 +1,27 @@ # 技术点记录 ## linux 下播放音频 - ```bash - sudo apt install libasound2-dev alsa-utils - ``` +```bash +sudo apt install libasound2-dev alsa-utils +``` ## linux 下播放视频 - ```bash - sudo apt install ffmpeg - ``` - 显示安装 - ```bash - sudo apt install libdirectfb-dev # 轻量级显示服务器 - # 或 - # sudo apt install xorg - ``` +```bash +sudo apt install ffmpeg +``` -### 当前用户加入播放音频与视频的组中 - ```bash - sudo usermod -aG audio,video $USER - ``` - -### 关闭屏幕帧缓冲(关闭背光) +显示安装 ```bash -# 关闭帧缓冲设备 -echo 1 | sudo tee /sys/class/graphics/fb0/blank -# 重新打开帧缓冲设备 -echo 0 | sudo tee /sys/class/graphics/fb0/blank +sudo apt install xorg +``` +### 当前用户加入播放音频与视频的组中 +```bash +sudo usermod -aG audio,video $USER +``` + +### 关闭背光 + +```bash # xorg 环境,关闭背光 xset dpms force off # xorg 环境,重新打开背光