diff --git a/internal/common/device.go b/internal/common/device.go index a090cb3..980c405 100644 --- a/internal/common/device.go +++ b/internal/common/device.go @@ -3,6 +3,7 @@ package common import ( "context" "fmt" + "game-driver/pkg/logger" "github.com/eclipse/paho.golang/autopaho" "github.com/eclipse/paho.golang/paho" "sync" @@ -29,12 +30,14 @@ func (d *Device) Lock() { defer d.OnChange() d.mu.Lock() d.status.Store(1) + logger.Infoln("设备上锁") } func (d *Device) Unlock() { defer d.OnChange() d.status.Store(0) d.mu.Unlock() + logger.Infoln("设备解锁") } func (d *Device) Status() int { diff --git a/internal/middleware/bgm.go b/internal/middleware/bgm.go index ba0d02d..4d4a049 100644 --- a/internal/middleware/bgm.go +++ b/internal/middleware/bgm.go @@ -10,12 +10,17 @@ import ( "sync" ) +// PlayBgm 播放背景音乐 func PlayBgm() leaf.HandlerFunc { return func(c *leaf.Context) { pm := leaf.Value[*schema.PlayModal](c, PayloadJSONKey) - bgm := utils.LinkAudio(pm.BGM) + bgm, err := utils.LinkAudio(pm.BGM) + if err != nil { + logger.Errorln("背景音乐解析异常", err) + } if bgm != nil { + logger.Infoln("背景音乐解析成功") // 等待组 var wait sync.WaitGroup defer wait.Wait() @@ -25,15 +30,18 @@ func PlayBgm() leaf.HandlerFunc { // 发送结束信号 defer close(a) + wait.Add(1) go func() { // 等待结束 - wait.Add(1) defer wait.Done() - ctrl, closer := audio.PlayBgmMP3(bgm) + logger.Infoln("开始播放背景音乐") + defer logger.Infoln("结束背景音乐播放") + + ctrl, closer, e := audio.PlayBgmMP3(bgm) defer closer() - if ctrl == nil { - logger.Infoln("播放背景音乐失败") + if e != nil { + logger.Errorln("播放背景音乐失败", e) return } @@ -47,6 +55,8 @@ func PlayBgm() leaf.HandlerFunc { } } }() + } else { + logger.Errorln("背景音乐解析为空") } c.Next() diff --git a/internal/middleware/json.go b/internal/middleware/json.go index 611491e..978097d 100644 --- a/internal/middleware/json.go +++ b/internal/middleware/json.go @@ -17,7 +17,7 @@ func PayloadJSON[T schema.JsonModel]() leaf.HandlerFunc { pm := new(T) err := json.Unmarshal(c.Payload, pm) if err != nil { - logger.Panicf("报文解析错误: %v\n", err) + logger.Panicln("报文解析错误", err) } leaf.WithValue[*T](c, PayloadJSONKey, pm) c.Next() diff --git a/internal/middleware/log.go b/internal/middleware/log.go index 158fbec..18f1c8e 100644 --- a/internal/middleware/log.go +++ b/internal/middleware/log.go @@ -8,7 +8,7 @@ import ( func RunLog() leaf.HandlerFunc { return func(c *leaf.Context) { logger.Infof("收到消息, topic: %s, payload: %s", c.Topic, c.Payload) - defer logger.Infof("执行结束") + defer logger.Infof("处理结束") c.Next() } diff --git a/internal/middleware/relay.go b/internal/middleware/relay.go index 64cdca1..ed56aa8 100644 --- a/internal/middleware/relay.go +++ b/internal/middleware/relay.go @@ -3,6 +3,7 @@ package middleware import ( "game-driver/internal/schema" "game-driver/leaf" + "game-driver/pkg/logger" "game-driver/pkg/relay" ) @@ -13,6 +14,11 @@ func RelayMaster(r *relay.Device) leaf.HandlerFunc { if r != nil && pm.Power { r.On(1) defer r.Off(1) + + logger.Infoln("开启电源") + defer logger.Infoln("关闭电源") + } else { + logger.Infoln("继电器未开启/不需要电源控制") } c.Next() } diff --git a/internal/middleware/stop.go b/internal/middleware/stop.go index 84f37bb..eec5995 100644 --- a/internal/middleware/stop.go +++ b/internal/middleware/stop.go @@ -3,6 +3,7 @@ package middleware import ( "game-driver/internal/common" "game-driver/leaf" + "game-driver/pkg/logger" ) // EmergencyStop 紧急停止中间件 @@ -17,10 +18,13 @@ func EmergencyStop(stopper common.Stopper) leaf.HandlerFunc { defer close(a) go func() { + defer logger.Infoln("结束停止信号监控") + select { case <-a: case <-stopper.Done(): { + logger.Infoln("紧急停止信号触发") cancel() leaf.WithValue[leaf.EndType](c, leaf.EndKey, leaf.EndStop) } diff --git a/internal/middleware/ticker.go b/internal/middleware/ticker.go index e08c436..e69caad 100644 --- a/internal/middleware/ticker.go +++ b/internal/middleware/ticker.go @@ -3,6 +3,7 @@ package middleware import ( "game-driver/internal/schema" "game-driver/leaf" + "game-driver/pkg/logger" "game-driver/pkg/tts" "sync" "time" @@ -31,14 +32,18 @@ func TickerAction() leaf.HandlerFunc { // 发送结束信号 defer close(a) + wait.Add(1) go func() { start := time.Now() // 等待结束 - wait.Add(1) defer wait.Done() // 定时器 ticker := time.NewTicker(time.Second) defer ticker.Stop() + + logger.Infoln("开始 Ticker 计时") + defer logger.Infoln("结束 Ticker 计时") + for over := false; !over; { select { case <-a: diff --git a/internal/middleware/timer.go b/internal/middleware/timer.go index 341ef42..98ce1ff 100644 --- a/internal/middleware/timer.go +++ b/internal/middleware/timer.go @@ -3,6 +3,7 @@ package middleware import ( "game-driver/internal/schema" "game-driver/leaf" + "game-driver/pkg/logger" "sync" "time" ) @@ -31,15 +32,20 @@ func TimeoutOver(maxTimeout int) leaf.HandlerFunc { defer close(a) cancel := leaf.WithCancel(c) + wait.Add(1) go func() { // 等待结束 - wait.Add(1) defer wait.Done() + + logger.Infoln("超时 Timer 监控开始") + defer logger.Infoln("超时 Timer 监控结束") + // 结束标志 select { case <-a: case <-timer.C: // 定时器结束 { + logger.Infoln("超时 Timer 触发") cancel() leaf.WithValue[leaf.EndType](c, leaf.EndKey, leaf.EndTimer) } diff --git a/internal/middleware/unique.go b/internal/middleware/unique.go new file mode 100644 index 0000000..8f9af5b --- /dev/null +++ b/internal/middleware/unique.go @@ -0,0 +1,28 @@ +package middleware + +import ( + "game-driver/internal/common" + "game-driver/leaf" + "game-driver/pkg/logger" + "sync" +) + +// Unique 唯一任务中间件,会停止之前的任务,让新任务执行。同一时间只能有一个任务在执行 +func Unique(stopper common.Stopper) leaf.HandlerFunc { + var lock sync.Mutex + return func(c *leaf.Context) { + if !lock.TryLock() { + logger.Infoln("尝试加锁失败,执行停止任务") + stopper.Stop() + lock.Lock() + } + logger.Infoln("加锁完成") + + defer func() { + lock.Unlock() + logger.Infoln("解锁完成") + }() + + c.Next() + } +} diff --git a/internal/routes/play/only_video.go b/internal/routes/play/only_video.go index 9adad93..0d9ca5a 100644 --- a/internal/routes/play/only_video.go +++ b/internal/routes/play/only_video.go @@ -4,6 +4,7 @@ import ( "game-driver/internal/middleware" "game-driver/internal/schema" "game-driver/leaf" + "game-driver/pkg/logger" "game-driver/pkg/utils" "game-driver/pkg/video" ) @@ -15,6 +16,11 @@ func OnlyVideo(c *leaf.Context) { defer utils.BlankClose() if url, ok := payload.Game["video"]; ok { - _ = video.Play(c, utils.LinkVideo(url.(string))) + local, err := utils.LinkVideo(url.(string)) + if err != nil { + logger.Errorln("视频文件获取异常: ", err) + return + } + _ = video.Play(c, local) } } diff --git a/internal/routes/wait.go b/internal/routes/wait.go index d33f4af..824c44d 100644 --- a/internal/routes/wait.go +++ b/internal/routes/wait.go @@ -98,17 +98,23 @@ func audioAction(c *leaf.Context, item schema.WaitItemModel, root schema.TimeMod defer cancel() } + data, err := utils.LinkAudio(item.Data) + if err != nil { + logger.Errorln("音频文件获取异常: ", err) + return + } + select { case <-c.Done(): case <-timerAction(item.Start): { - logger.Infoln("开始执行后台任务") - data := utils.LinkAudio(item.Data) + logger.Infoln("播放待机音乐") + defer logger.Infoln("结束待机音乐") - ctrl, closer := audio.PlayBgmMP3(data) + ctrl, closer, e := audio.PlayBgmMP3(data) defer closer() - if ctrl == nil { - logger.Infoln("播放背景音乐失败") + if e != nil { + logger.Errorln("播放待机音乐异常", e) return } @@ -137,7 +143,7 @@ func ttsAction(c *leaf.Context, item schema.WaitItemModel, root schema.TimeModel reader, err := tts.DefaultTTS.Get(item.Data) if err != nil { - logger.Infoln("语音合成异常: ", err) + logger.Errorln("语音合成异常: ", err) return } @@ -145,6 +151,9 @@ func ttsAction(c *leaf.Context, item schema.WaitItemModel, root schema.TimeModel case <-c.Done(): case <-timerAction(item.Start): { + logger.Infoln("循环播放待机 TTS 语音") + defer logger.Infoln("结束待机 TTS 语音") + for { audio.PlayWav(c, reader) select { @@ -170,7 +179,7 @@ func relayAction(c *leaf.Context, item schema.WaitItemModel, root schema.TimeMod device, err := relay.New(item.Data, nil) if err != nil { - logger.Infoln("继电器初始化异常: ", err) + logger.Errorln("继电器初始化异常: ", err) return } defer device.Close() @@ -179,6 +188,9 @@ func relayAction(c *leaf.Context, item schema.WaitItemModel, root schema.TimeMod case <-c.Done(): case <-timerAction(item.Start): { + logger.Infoln("待机继电器供电") + defer logger.Infoln("待机继电器断电") + device.On(1) <-c.Done() device.Off(1) @@ -197,17 +209,27 @@ func videoAction(c *leaf.Context, item schema.WaitItemModel, root schema.TimeMod defer cancel() } + local, err := utils.LinkVideo(item.Data) + if err != nil { + logger.Errorln("视频文件获取异常: ", err) + return + } + select { case <-c.Done(): case <-timerAction(item.Start): { + logger.Infoln("循环播放待机视频") + defer logger.Infoln("结束待机视频") + utils.BlankOpen() defer utils.BlankClose() for { - err := video.Play(c, utils.LinkVideo(item.Data)) + err := video.Play(c, local) if err != nil { - logger.Panicln("视频播放异常: ", err) + logger.Errorln("视频播放异常: ", err) + return } select { case <-c.Done(): diff --git a/internal/server.go b/internal/server.go index ecc9399..3894925 100644 --- a/internal/server.go +++ b/internal/server.go @@ -10,6 +10,7 @@ import ( "game-driver/internal/schema" "game-driver/leaf" "game-driver/pkg/logger" + "game-driver/pkg/relay" "game-driver/pkg/tts" "game-driver/pkg/utils" "github.com/eclipse/paho.golang/autopaho" @@ -44,7 +45,7 @@ func buildMqtt(c config.MqttConfig, r *leaf.Engine, subTopics ...string) autopah if _, err := cm.Subscribe(context.Background(), &paho.Subscribe{ Subscriptions: subscriptions, }); err != nil { - logger.Infof("failed to subscribe (%s). This is likely to mean no messages will be received.", err) + logger.Errorf("failed to subscribe (%s). This is likely to mean no messages will be received.", err) return } logger.Infoln("订阅完成") @@ -60,12 +61,12 @@ func buildMqtt(c config.MqttConfig, r *leaf.Engine, subTopics ...string) autopah return true, nil }, }, - OnClientError: func(err error) { fmt.Printf("client error: %s\n", err) }, + OnClientError: func(err error) { logger.Errorf("client error: %s\n", err) }, OnServerDisconnect: func(d *paho.Disconnect) { if d.Properties != nil { - fmt.Printf("server requested disconnect: %s\n", d.Properties.ReasonString) + logger.Warnf("server requested disconnect: %s\n", d.Properties.ReasonString) } else { - fmt.Printf("server requested disconnect; reason code: %d\n", d.ReasonCode) + logger.Warnf("server requested disconnect; reason code: %d\n", d.ReasonCode) } }, }, @@ -82,6 +83,8 @@ func Run() { defer cancel() router := leaf.Default(ctx) + // 应用推出时刷新所有缓冲日志 + defer logger.DefaultLogger.Sync() log, _ := zap.NewStdLogAt(logger.DefaultLogger.ZapLogger(), zap.DebugLevel) router.SetDebugLogger(log) @@ -103,13 +106,13 @@ func Run() { tts.DefaultTTS = tts.New(ctx, config.C.Aliyun) // 构建继电器对象 - //r, err := relay.New(config.C.Relay, func(msg string) { - // logger.Infoln("串口返回: ", msg) - //}) - //if err != nil { - // logger.Panicln("串口连接异常: ", err) - //} - //defer r.Close() + r, err := relay.New(config.C.Relay, func(msg string) { + logger.Infoln("串口返回: ", msg) + }) + if err != nil { + logger.Panicln("串口连接异常: ", err) + } + defer r.Close() // 构建全局设备变量 device := common.DefaultDevice(ctx, cm, publishTopic) @@ -133,6 +136,7 @@ func Run() { router.RegisterHandler(topicPrefix+"wait", middleware.RunLog(), middleware.PayloadJSON[schema.WaitModel](), + middleware.Unique(common.GlobalBgStopper), middleware.EmergencyStop(common.GlobalBgStopper), routes.WaitAction, ) @@ -151,13 +155,13 @@ func Run() { signal.Notify(sig, os.Interrupt, syscall.SIGTERM) <-sig - fmt.Println("接收到关闭命令 - 正在关闭程序") + logger.Infoln("接收到关闭命令 - 正在关闭程序") ctx, cancel = context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if e := cm.Disconnect(ctx); e != nil { - fmt.Printf("断开连接异常: %s\n", e) + logger.Errorln("断开连接异常", e) } - fmt.Println("关闭完成") + logger.Infoln("关闭完成") } diff --git a/pkg/audio/bgm.go b/pkg/audio/bgm.go index cfe4e41..07d438e 100644 --- a/pkg/audio/bgm.go +++ b/pkg/audio/bgm.go @@ -8,16 +8,16 @@ import ( "io" ) -func PlayBgmMP3(r io.ReadCloser, opts ...beep.LoopOption) (*beep.Ctrl, func() error) { +func PlayBgmMP3(r io.ReadCloser, opts ...beep.LoopOption) (*beep.Ctrl, func() error, error) { streamer, format, err := mp3.Decode(r) if err != nil { - return nil, func() error { return nil } + return nil, func() error { return nil }, err } loop2, err := beep.Loop2(streamer, opts...) if err != nil { logger.Infoln("循环播放异常: ", err) - return nil, streamer.Close + return nil, streamer.Close, err } s := beep.Resample(4, format.SampleRate, DefaultSampleRate, loop2) @@ -27,5 +27,5 @@ func PlayBgmMP3(r io.ReadCloser, opts ...beep.LoopOption) (*beep.Ctrl, func() er streamer.Close() }))) - return ctrl, streamer.Close + return ctrl, streamer.Close, nil } diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go index 43fd52b..dc2cadd 100644 --- a/pkg/logger/logger.go +++ b/pkg/logger/logger.go @@ -18,10 +18,16 @@ func NewLogger() *Logger { } } +// ZapLogger 返回 zap 原始的 zap.Logger func (l *Logger) ZapLogger() *zap.Logger { return l.zl } +// Sync 刷新所有缓冲的日志条目 +func (l *Logger) Sync() error { + return l.zs.Sync() +} + var DefaultLogger = NewLogger() // Debug logs the provided arguments at [DebugLevel]. diff --git a/pkg/tts/aliyun.go b/pkg/tts/aliyun.go index ed1a8b2..60c3dec 100644 --- a/pkg/tts/aliyun.go +++ b/pkg/tts/aliyun.go @@ -49,7 +49,7 @@ func (tts *AliTTS) Sound(text string) { if err == nil && buf != nil { audio.PlayWav(tts.ctx, buf) } else { - logger.Panicln("AliTTS 请求异常: ", err) + logger.Errorln("AliTTS 请求异常: ", err) } } diff --git a/pkg/utils/link_audio.go b/pkg/utils/link_audio.go index 1aecc19..371b7bf 100644 --- a/pkg/utils/link_audio.go +++ b/pkg/utils/link_audio.go @@ -2,6 +2,7 @@ package utils import ( "bytes" + "fmt" "game-driver/pkg/logger" "io" "net/http" @@ -48,21 +49,20 @@ func get(u string) io.ReadCloser { } // LinkAudio 链接音频,解析链接,直接提取为数据流 -func LinkAudio(link string) (bgm io.ReadCloser) { +func LinkAudio(link string) (bgm io.ReadCloser, err error) { if link == "" { - return nil + return } u, err := url.Parse(link) if err != nil { - logger.Infoln("音频 URL 解析错误: ", err) + err = fmt.Errorf("URL 解析错误: %v", err) } else { if u.Scheme == "file" { bgm = open(link) } else if u.Scheme == "http" || u.Scheme == "https" { bgm = get(link) } else { - logger.Infof("不支持的音频文件协议: %v\n", u.String()) - return + err = fmt.Errorf("不支持的链接协议: %v", u.String()) } bgm = toSeeker(bgm) } diff --git a/pkg/utils/link_video.go b/pkg/utils/link_video.go index 293b786..52415c1 100644 --- a/pkg/utils/link_video.go +++ b/pkg/utils/link_video.go @@ -1,7 +1,7 @@ package utils import ( - "game-driver/pkg/logger" + "fmt" "io" "net/http" "net/url" @@ -11,13 +11,13 @@ import ( ) // LinkVideo 链接视频,解析链接,网络文件会下载到临时目录并返回本地路径 -func LinkVideo(link string) (local string) { +func LinkVideo(link string) (local string, err error) { if link == "" { return } u, err := url.Parse(link) if err != nil { - logger.Infoln("音频 URL 解析错误: ", err) + err = fmt.Errorf("URL 解析错误: %v", err) } else { if u.Scheme == "file" { local, _ = strings.CutPrefix(link, "file://") @@ -26,13 +26,12 @@ func LinkVideo(link string) (local string) { tmpLocal := path.Join(os.TempDir(), path.Base(p)) err = Download(link, tmpLocal) if err != nil { - logger.Infoln("音频文件下载失败: ", err) + err = fmt.Errorf("链接文件获取失败: %v", err) return } local = tmpLocal } else { - logger.Infof("不支持的视频链接协议: %v\n", u.String()) - return + err = fmt.Errorf("不支持的链接协议: %v", u.String()) } } return diff --git a/pkg/video/paly.go b/pkg/video/paly.go index 3919c08..a80e78e 100644 --- a/pkg/video/paly.go +++ b/pkg/video/paly.go @@ -25,8 +25,8 @@ func Play(ctx context.Context, file string) error { a := make(chan struct{}) defer close(a) + wait.Add(1) go func() { - wait.Add(1) defer wait.Done() for { select { diff --git a/readme.md b/readme.md index dadd6af..1b0eaae 100644 --- a/readme.md +++ b/readme.md @@ -149,37 +149,3 @@ Payload: ``` > 同一个类型的待机任务只能有一个,当有新的任务到达时会覆盖之前的任务 - - -## 说明 - -1. linux 下播放音频 - ```bash - sudo apt install libasound2-dev alsa-utils - ``` -2. linux 下播放视频 - ```bash - sudo apt install ffmpeg - ``` - 驱动安装 - ```bash - libdirectfb-dev - ``` -3. 当前用户加入播放音频与视频的组中 - ```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 -``` - -### 播放视频 -```bash -ffplay -autoexit -fs -i video.mp4 -``` \ No newline at end of file diff --git a/todo.md b/todo.md new file mode 100644 index 0000000..9926258 --- /dev/null +++ b/todo.md @@ -0,0 +1,32 @@ +## 技术点记录 + +1. linux 下播放音频 + ```bash + sudo apt install libasound2-dev alsa-utils + ``` +2. linux 下播放视频 + ```bash + sudo apt install ffmpeg + ``` + 驱动安装 + ```bash + libdirectfb-dev + ``` +3. 当前用户加入播放音频与视频的组中 + ```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 +``` + +### 播放视频 +```bash +ffplay -autoexit -fs -i video.mp4 +```