Merge branch 'main' into clean_beep
# Conflicts: # internal/routes/wait.go
This commit is contained in:
20
cmd/root.go
20
cmd/root.go
@@ -7,6 +7,7 @@ import (
|
||||
"errors"
|
||||
"game-driver/config"
|
||||
"game-driver/config/game"
|
||||
"game-driver/config/wait"
|
||||
"game-driver/internal"
|
||||
"io/fs"
|
||||
"log"
|
||||
@@ -21,7 +22,7 @@ var cfgFile string
|
||||
// rootCmd represents the base command when called without any subcommands
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "game-driver",
|
||||
Version: "0.0.1",
|
||||
Version: "1.0.1",
|
||||
Short: "A brief description of your application",
|
||||
Long: `A longer description that spans multiple lines and likely contains
|
||||
examples and usage of using your application. For example:
|
||||
@@ -70,13 +71,20 @@ func initConfig() {
|
||||
}
|
||||
|
||||
// 初始化游戏节点配置
|
||||
game.G = game.NewConfig(config.C.Point)
|
||||
if game.G != nil { // 如果需要游戏配置
|
||||
err = viper.UnmarshalKey("game", &game.G)
|
||||
game.C = game.NewConfig(config.C.Point)
|
||||
if game.C != nil { // 如果需要游戏配置
|
||||
err = viper.UnmarshalKey("game", &game.C)
|
||||
if err != nil {
|
||||
log.Panicln("unmarshal game config failed: ", err)
|
||||
}
|
||||
} else {
|
||||
log.Panicln("game config not found")
|
||||
}
|
||||
|
||||
// 初始化游戏节点待机时配置
|
||||
wait.C = wait.NewConfig(config.C.Point)
|
||||
if wait.C != nil { // 如果需要游戏配置
|
||||
err = viper.UnmarshalKey("wait", &wait.C)
|
||||
if err != nil {
|
||||
log.Panicln("unmarshal wait config failed: ", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
77
config.yml
77
config.yml
@@ -1,9 +1,15 @@
|
||||
location: wushan
|
||||
point: 4
|
||||
relay: /dev/ttyUSB1
|
||||
location: wushan # 项目名称
|
||||
point: 3 # 点位
|
||||
relay:
|
||||
maxTimeout: 60 # 单位 s,必须大于 0
|
||||
standbyCache: # 待机缓存文件路径
|
||||
log:
|
||||
level: debug
|
||||
tencentCLS:
|
||||
endpoint:
|
||||
secretID:
|
||||
secretKey:
|
||||
topicID:
|
||||
file:
|
||||
filename: logs/app.log
|
||||
maxSize: 10
|
||||
@@ -11,31 +17,52 @@ log:
|
||||
maxAge: 30
|
||||
compress: true
|
||||
mqtt:
|
||||
url: mqtt://36.138.38.16:1883
|
||||
url: mqtt://wushan-mqtt.chaoshengshuzi.com:1883
|
||||
clientID: wushan-3
|
||||
password: wushan@1013
|
||||
aliyun:
|
||||
accessKeyID:
|
||||
accessKeySecret:
|
||||
appKey:
|
||||
timeout: 10 # 单位 s
|
||||
voice: zhifeng_emo
|
||||
volume: 100 # 音量,取值范围:0~100
|
||||
voice: zhifeng_emo # 发音人
|
||||
speechRate: 50 # 语速,取值范围:-500~500
|
||||
|
||||
# 激光秀点位 osc 参数
|
||||
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
|
||||
host: 192.168.0.167
|
||||
port: 3033
|
||||
|
||||
# 待机投影仪控制参数
|
||||
#wait:
|
||||
# ip:
|
||||
# port:
|
||||
# password:
|
||||
# id:
|
||||
|
||||
# 11 点位 读卡器串口配置
|
||||
#game:
|
||||
# addr: /dev/ttyUSB0
|
||||
|
||||
# 10 点位 发卡器串口配置
|
||||
#game:
|
||||
# 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
|
||||
63
config/cache_publish.go
Normal file
63
config/cache_publish.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/eclipse/paho.golang/paho"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Cache MQTT消息缓存
|
||||
type Cache string
|
||||
|
||||
// Get 读取缓存数据
|
||||
func (s Cache) Get() (*paho.Publish, error) {
|
||||
// 判断文件是否存在
|
||||
if _, err := os.Stat(string(s)); os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("文件不存在: %s", err)
|
||||
}
|
||||
// 读取文件内容
|
||||
file, err := os.ReadFile(string(s))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("读取文件失败: %w", err)
|
||||
}
|
||||
|
||||
// 解析数据
|
||||
data := &paho.Publish{}
|
||||
err = json.Unmarshal(file, data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("解析数据失败: %w", err)
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// Set 设置缓存数据
|
||||
func (s Cache) Set(data *paho.Publish) error {
|
||||
if s == "" {
|
||||
return fmt.Errorf("缓存路径不能为空")
|
||||
}
|
||||
if data == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 创建文件
|
||||
file, err := os.Create(string(s))
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建文件失败: %w", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// 序列化数据
|
||||
dataBytes, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("序列化数据失败: %w", err)
|
||||
}
|
||||
|
||||
// 写入数据
|
||||
_, err = file.Write(dataBytes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("写入数据失败: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
|
||||
type MqttConfig struct {
|
||||
Url string
|
||||
ClientID string
|
||||
Password string
|
||||
}
|
||||
|
||||
type AliyunConfig struct {
|
||||
@@ -13,11 +15,21 @@ type AliyunConfig struct {
|
||||
AccessKeySecret string
|
||||
AppKey string
|
||||
Timeout int
|
||||
Volume int
|
||||
Voice string
|
||||
SpeechRate int
|
||||
}
|
||||
|
||||
type TencentCLS struct {
|
||||
Endpoint string
|
||||
SecretID string
|
||||
SecretKey string
|
||||
TopicID string
|
||||
}
|
||||
|
||||
type Logger struct {
|
||||
File *lumberjack.Logger
|
||||
TencentCLS TencentCLS
|
||||
Level string
|
||||
}
|
||||
|
||||
@@ -29,6 +41,7 @@ type config struct {
|
||||
Mqtt MqttConfig
|
||||
Aliyun AliyunConfig
|
||||
MaxTimeout int
|
||||
StandbyCache Cache
|
||||
}
|
||||
|
||||
var C config
|
||||
|
||||
@@ -4,13 +4,15 @@ type Config any
|
||||
|
||||
func NewConfig(point int) Config {
|
||||
switch point {
|
||||
case 4:
|
||||
case 10:
|
||||
return ConfigPush{}
|
||||
case 5:
|
||||
case 11:
|
||||
return ConfigWait{}
|
||||
case 5:
|
||||
return LaserConfig{}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var G Config
|
||||
var C Config
|
||||
|
||||
6
config/game/laser.go
Normal file
6
config/game/laser.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package game
|
||||
|
||||
type LaserConfig struct {
|
||||
Host string
|
||||
Port int
|
||||
}
|
||||
8
config/wait/pjlink.go
Normal file
8
config/wait/pjlink.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package wait
|
||||
|
||||
type PJLink struct {
|
||||
Ip string
|
||||
Port string
|
||||
Password string
|
||||
Id string
|
||||
}
|
||||
14
config/wait/wait.go
Normal file
14
config/wait/wait.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package wait
|
||||
|
||||
type Config any
|
||||
|
||||
func NewConfig(point int) Config {
|
||||
switch point {
|
||||
case 2:
|
||||
return PJLink{}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var C Config
|
||||
10
demo/osc/main.go
Normal file
10
demo/osc/main.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"game-driver/pkg/oscx"
|
||||
)
|
||||
|
||||
func main() {
|
||||
c := oscx.New("192.168.0.167", 3033)
|
||||
c.StartCue("Emu")
|
||||
}
|
||||
2
game.md
2
game.md
@@ -56,7 +56,7 @@
|
||||
{
|
||||
// 发卡执行时间,单位秒
|
||||
"action": 0,
|
||||
// 等待时间,单位秒
|
||||
// 等待时间,单位秒(不能小于 1)
|
||||
"wait": 10
|
||||
}
|
||||
```
|
||||
|
||||
45
go.mod
45
go.mod
@@ -3,22 +3,28 @@ module game-driver
|
||||
go 1.23.2
|
||||
|
||||
require (
|
||||
github.com/adrg/libvlc-go/v3 v3.1.6
|
||||
github.com/aliyun/alibabacloud-nls-go-sdk v1.1.1
|
||||
github.com/eclipse/paho.golang v0.22.0
|
||||
github.com/gopxl/beep/v2 v2.1.0
|
||||
github.com/grid-x/modbus v0.0.0-20241004123532-f6c6fb5201b3
|
||||
github.com/spf13/cobra v1.8.1
|
||||
github.com/go-pkgz/cronrange v0.2.0
|
||||
github.com/go-rod/rod v0.116.2
|
||||
github.com/gopxl/beep/v2 v2.1.1
|
||||
github.com/grid-x/modbus v0.0.0-20250219144522-2b18d136199f
|
||||
github.com/hypebeast/go-osc v0.0.0-20220308234300-cec5a8a1e5f5
|
||||
github.com/spf13/cobra v1.9.1
|
||||
github.com/spf13/viper v1.19.0
|
||||
github.com/tencentcloud/tencentcloud-cls-sdk-go v1.0.11
|
||||
github.com/warthog618/go-gpiocdev v0.9.1
|
||||
go.uber.org/zap v1.27.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.63.53 // indirect
|
||||
github.com/ebitengine/oto/v3 v3.3.1 // indirect
|
||||
github.com/ebitengine/purego v0.8.1 // indirect
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.63.93 // indirect
|
||||
github.com/ebitengine/oto/v3 v3.3.2 // indirect
|
||||
github.com/ebitengine/purego v0.8.2 // indirect
|
||||
github.com/fsnotify/fsnotify v1.8.0 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // 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
|
||||
@@ -26,26 +32,35 @@ require (
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/magiconair/properties v1.8.9 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||
github.com/pierrec/lz4 v2.6.1+incompatible // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/sagikazarmark/locafero v0.6.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.7.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/satori/go.uuid v1.2.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.11.0 // indirect
|
||||
github.com/spf13/cast v1.7.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/afero v1.12.0 // indirect
|
||||
github.com/spf13/cast v1.7.1 // indirect
|
||||
github.com/spf13/pflag v1.0.6 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/ysmood/fetchup v0.3.0 // indirect
|
||||
github.com/ysmood/goob v0.4.0 // indirect
|
||||
github.com/ysmood/got v0.40.0 // indirect
|
||||
github.com/ysmood/gson v0.7.3 // indirect
|
||||
github.com/ysmood/leakless v0.9.0 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f // indirect
|
||||
golang.org/x/net v0.31.0 // indirect
|
||||
golang.org/x/sys v0.27.0 // indirect
|
||||
golang.org/x/text v0.20.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
|
||||
golang.org/x/net v0.37.0 // indirect
|
||||
golang.org/x/sys v0.31.0 // indirect
|
||||
golang.org/x/text v0.23.0 // indirect
|
||||
google.golang.org/protobuf v1.36.5 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
123
go.sum
123
go.sum
@@ -1,44 +1,58 @@
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
|
||||
github.com/adrg/libvlc-go/v3 v3.1.6 h1:Cm22w6xNMDdzYCW8koHgAvjonYm4xbPP5TrlVTtMdl4=
|
||||
github.com/adrg/libvlc-go/v3 v3.1.6/go.mod h1:xJK0YD8cyMDejnrTFQinStE6RYCV1nlfS8KmqTpszSc=
|
||||
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1376/go.mod h1:9CMdKNL3ynIGPpfTcdwTvIm8SGuAZYYC4jFVSSvE1YQ=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.63.53 h1:I93ILTm5ytF4e5+lEQXSXcydS26D9eVyJ4H6z3rJqMA=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.63.53/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.63.92 h1:qespx4b6EexlXkvQUow9x0v1GnWUJYGU5FWYw3a4Wlg=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.63.92/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.63.93 h1:yHRWq/QmBJ3lC15zy1A1+TkvcAN+6dr1bgHsFghKvmk=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.63.93/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ=
|
||||
github.com/aliyun/alibabacloud-nls-go-sdk v1.1.1 h1:LjItoNZuu5xHlsByFo+kr3nGa4LRIESCGWhfurayxBg=
|
||||
github.com/aliyun/alibabacloud-nls-go-sdk v1.1.1/go.mod h1:4BDMUKpEaP/Ct79w0ozR0nbnEj49g1k3mrgX/IKG5I4=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/ebitengine/oto/v3 v3.3.1 h1:d4McwGQuXOT0GL7bA5g9ZnaUEIEjQvG3hafzMy+T3qE=
|
||||
github.com/ebitengine/oto/v3 v3.3.1/go.mod h1:MZeb/lwoC4DCOdiTIxYezrURTw7EvK/yF863+tmBI+U=
|
||||
github.com/ebitengine/purego v0.8.1 h1:sdRKd6plj7KYW33EH5As6YKfe8m9zbN9JMrOjNVF/BE=
|
||||
github.com/ebitengine/purego v0.8.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/ebitengine/oto/v3 v3.3.2 h1:VTWBsKX9eb+dXzaF4jEwQbs4yWIdXukJ0K40KgkpYlg=
|
||||
github.com/ebitengine/oto/v3 v3.3.2/go.mod h1:MZeb/lwoC4DCOdiTIxYezrURTw7EvK/yF863+tmBI+U=
|
||||
github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I=
|
||||
github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/eclipse/paho.golang v0.22.0 h1:JhhUngr8TBlyUZDZw/L6WVayPi9qmSmdWeki48i5AVE=
|
||||
github.com/eclipse/paho.golang v0.22.0/go.mod h1:9ZiYJ93iEfGRJri8tErNeStPKLXIGBHiqbHV74t5pqI=
|
||||
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
||||
github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
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=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gopxl/beep/v2 v2.1.0 h1:Jv95iHw3aNWoAa/J78YyXvOvMHH2ZGeAYD5ug8tVt8c=
|
||||
github.com/gopxl/beep/v2 v2.1.0/go.mod h1:sQvj2oSsu8fmmDWH3t0DzIe0OZzTW6/TJEHW4Ku+22o=
|
||||
github.com/gopxl/beep/v2 v2.1.1 h1:6FYIYMm2qPAdWkjX+7xwKrViS1x0Po5kDMdRkq8NVbU=
|
||||
github.com/gopxl/beep/v2 v2.1.1/go.mod h1:ZAm9TGQ9lvpoiFLd4zf5B1IuyxZhgRACMId1XJbaW0E=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grid-x/modbus v0.0.0-20241004123532-f6c6fb5201b3 h1:TfBJ561lUg0i0GLsxKeRaWoBGN8nyCLNt0OMGRx7R2M=
|
||||
github.com/grid-x/modbus v0.0.0-20241004123532-f6c6fb5201b3/go.mod h1:WpbUAyptAAi0VAriSRopZa6uhiJOJCTz7KFvgGtNRXc=
|
||||
github.com/grid-x/modbus v0.0.0-20250219144522-2b18d136199f h1:i5NSZj4IehIvyDSIa2CLbqSeglX8Ngre8Qck64Wr63Q=
|
||||
github.com/grid-x/modbus v0.0.0-20250219144522-2b18d136199f/go.mod h1:WpbUAyptAAi0VAriSRopZa6uhiJOJCTz7KFvgGtNRXc=
|
||||
github.com/grid-x/serial v0.0.0-20211107191517-583c7356b3aa h1:Rsn6ARgNkXrsXJIzhkE4vQr5Gbx2LvtEMv4BJOK4LyU=
|
||||
github.com/grid-x/serial v0.0.0-20211107191517-583c7356b3aa/go.mod h1:kdOd86/VGFWRrtkNwf1MPk0u1gIjc4Y7R2j7nhwc7Rk=
|
||||
github.com/hajimehoshi/go-mp3 v0.3.4 h1:NUP7pBYH8OguP4diaTZ9wJbUbk3tC0KlfzsEpWmYj68=
|
||||
@@ -46,6 +60,8 @@ github.com/hajimehoshi/go-mp3 v0.3.4/go.mod h1:fRtZraRFcWb0pu7ok0LqyFhCUrPeMsGRS
|
||||
github.com/hajimehoshi/oto/v2 v2.3.1/go.mod h1:seWLbgHH7AyUMYKfKYT9pg7PhUu9/SisyJvNTT+ASQo=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hypebeast/go-osc v0.0.0-20220308234300-cec5a8a1e5f5 h1:fqwINudmUrvGCuw+e3tedZ2UJ0hklSw6t8UPomctKyQ=
|
||||
github.com/hypebeast/go-osc v0.0.0-20220308234300-cec5a8a1e5f5/go.mod h1:lqMjoCs0y0GoRRujSPZRBaGb4c5ER6TfkFKSClxkMbY=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
@@ -58,15 +74,19 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
|
||||
github.com/klauspost/compress v1.15.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM=
|
||||
github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
@@ -83,16 +103,19 @@ github.com/orcaman/writerseeker v0.0.0-20200621085525-1d3f536ff85e h1:s2RNOM/IGd
|
||||
github.com/orcaman/writerseeker v0.0.0-20200621085525-1d3f536ff85e/go.mod h1:nBdnFKj15wFbf94Rwfq4m30eAcyY9V/IyKAGQFtqkW0=
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
||||
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
||||
github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM=
|
||||
github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk=
|
||||
github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0=
|
||||
github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo=
|
||||
github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
||||
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
|
||||
@@ -101,23 +124,25 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
||||
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
|
||||
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
||||
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs=
|
||||
github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4=
|
||||
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
|
||||
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
|
||||
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
|
||||
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
|
||||
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/tencentcloud/tencentcloud-cls-sdk-go v1.0.11 h1:LJshkcQ14A/7XCgqalheBHv8qLwwOXr/xqttQbjWdHM=
|
||||
github.com/tencentcloud/tencentcloud-cls-sdk-go v1.0.11/go.mod h1:WU+0TXfVbSctEsUUf4KmIKnfr+tknbjcsnx/TrEIPH4=
|
||||
github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o=
|
||||
github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
|
||||
github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg=
|
||||
@@ -126,8 +151,25 @@ github.com/warthog618/go-gpiocdev v0.9.1 h1:pwHPaqjJfhCipIQl78V+O3l9OKHivdRDdmgX
|
||||
github.com/warthog618/go-gpiocdev v0.9.1/go.mod h1:dN3e3t/S2aSNC+hgigGE/dBW8jE1ONk9bDSEYfoPyl8=
|
||||
github.com/warthog618/go-gpiosim v0.1.1 h1:MRAEv+T+itmw+3GeIGpQJBfanUVyg0l3JCTwHtwdre4=
|
||||
github.com/warthog618/go-gpiosim v0.1.1/go.mod h1:YXsnB+I9jdCMY4YAlMSRrlts25ltjmuIsrnoUrBLdqU=
|
||||
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
||||
github.com/ysmood/fetchup v0.2.4 h1:2kfWr/UrdiHg4KYRrxL2Jcrqx4DZYD+OtWu7WPBZl5o=
|
||||
github.com/ysmood/fetchup v0.2.4/go.mod h1:hbysoq65PXL0NQeNzUczNYIKpwpkwFL4LXMDEvIQq9A=
|
||||
github.com/ysmood/fetchup v0.3.0 h1:UhYz9xnLEVn2ukSuK3KCgcznWpHMdrmbsPpllcylyu8=
|
||||
github.com/ysmood/fetchup v0.3.0/go.mod h1:hbysoq65PXL0NQeNzUczNYIKpwpkwFL4LXMDEvIQq9A=
|
||||
github.com/ysmood/goob v0.4.0 h1:HsxXhyLBeGzWXnqVKtmT9qM7EuVs/XOgkX7T6r1o1AQ=
|
||||
github.com/ysmood/goob v0.4.0/go.mod h1:u6yx7ZhS4Exf2MwciFr6nIM8knHQIE22lFpWHnfql18=
|
||||
github.com/ysmood/gop v0.2.0 h1:+tFrG0TWPxT6p9ZaZs+VY+opCvHU8/3Fk6BaNv6kqKg=
|
||||
github.com/ysmood/gop v0.2.0/go.mod h1:rr5z2z27oGEbyB787hpEcx4ab8cCiPnKxn0SUHt6xzk=
|
||||
github.com/ysmood/got v0.40.0 h1:ZQk1B55zIvS7zflRrkGfPDrPG3d7+JOza1ZkNxcc74Q=
|
||||
github.com/ysmood/got v0.40.0/go.mod h1:W7DdpuX6skL3NszLmAsC5hT7JAhuLZhByVzHTq874Qg=
|
||||
github.com/ysmood/gotrace v0.6.0 h1:SyI1d4jclswLhg7SWTL6os3L1WOKeNn/ZtzVQF8QmdY=
|
||||
github.com/ysmood/gotrace v0.6.0/go.mod h1:TzhIG7nHDry5//eYZDYcTzuJLYQIkykJzCRIo4/dzQM=
|
||||
github.com/ysmood/gson v0.7.3 h1:QFkWbTH8MxyUTKPkVWAENJhxqdBa4lYTQWqZCiLG6kE=
|
||||
github.com/ysmood/gson v0.7.3/go.mod h1:3Kzs5zDl21g5F/BlLTNcuAGAYLKt2lV5G8D1zF3RNmg=
|
||||
github.com/ysmood/leakless v0.9.0 h1:qxCG5VirSBvmi3uynXFkcnLMzkphdh3xx5FtrORwDCU=
|
||||
github.com/ysmood/leakless v0.9.0/go.mod h1:R8iAXPRaG97QJwqxs74RdwzcRHT1SWCGTNqY8q0JvMQ=
|
||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
@@ -141,8 +183,10 @@ golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL
|
||||
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo=
|
||||
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak=
|
||||
golang.org/x/exp v0.0.0-20250228200357-dead58393ab7 h1:aWwlzYV971S4BXRS9AmqwDLAD85ouC6X+pocatKY58c=
|
||||
golang.org/x/exp v0.0.0-20250228200357-dead58393ab7/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk=
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
|
||||
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
@@ -151,18 +195,24 @@ golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
|
||||
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
|
||||
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
||||
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
||||
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
|
||||
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
|
||||
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
|
||||
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
|
||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
@@ -174,10 +224,15 @@ gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJ
|
||||
gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
|
||||
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
|
||||
gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
||||
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
|
||||
59
init_device.md
Normal file
59
init_device.md
Normal file
@@ -0,0 +1,59 @@
|
||||
### ubuntu 24 开机慢优化
|
||||
|
||||
```bash
|
||||
# 在 systemd-networkd-wait-online.service Service 加入 TimeoutStartSec=2sec
|
||||
sudo vim /etc/systemd/system/network-online.target.wants/systemd-networkd-wait-online.service
|
||||
```
|
||||
|
||||
### 初始化设备
|
||||
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt install curl gpg
|
||||
sudo add-apt-repository ppa:xtradeb/apps
|
||||
sudo add-apt-repository ppa:trzsz/ppa
|
||||
sudo apt install -y ungoogled-chromium fonts-noto-cjk fonts-noto-color-emoji unclutter xorg i3-wm libvlc-dev libasound2-dev alsa-utils trzsz wireguard wireguard-tools
|
||||
sudo timedatectl set-timezone Asia/Shanghai
|
||||
sudo usermod -aG audio,video,dialout $USER
|
||||
```
|
||||
|
||||
### 配置 wireguard
|
||||
|
||||
从服务器获取配置文件,保存到 `/etc/wireguard/wg0.conf`,并修改配置文件
|
||||
|
||||
> Interface 的 DNS 移除掉,不要配置
|
||||
|
||||
```bash
|
||||
sudo vim /etc/wireguard/wg0.conf
|
||||
```
|
||||
|
||||
### 开启 wireguard
|
||||
|
||||
```bash
|
||||
sudo systemctl enable wg-quick@wg0
|
||||
sudo systemctl start wg-quick@wg0
|
||||
```
|
||||
|
||||
### 自动启动 Xorg 和窗口管理器
|
||||
|
||||
编辑 `.bashrc`文件,在文件的末尾添加以下行:
|
||||
|
||||
```bash
|
||||
if [ -z "$DISPLAY" ] && [ "$(tty)" = "/dev/tty1" ]; then
|
||||
startx
|
||||
fi
|
||||
```
|
||||
|
||||
这会在你登录后,自动启动 Xorg 和窗口管理器。该脚本检查当前是否在 tty1 控制台(默认终端)上
|
||||
|
||||
### 自动登录
|
||||
|
||||
编辑 `/etc/systemd/system/getty.target.wants/getty@tty1.service` 文件,将 `ExecStart` 行修改为:
|
||||
|
||||
```bash
|
||||
ExecStart=-/sbin/agetty --autologin <your_username> --noclear %I $TERM
|
||||
```
|
||||
|
||||
其中:
|
||||
|
||||
- <your_username>:替换为你想自动登录的用户名。
|
||||
53
internal/common/pause.go
Normal file
53
internal/common/pause.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package common
|
||||
|
||||
import "sync"
|
||||
|
||||
// CtrlWait 待机暂停控制器
|
||||
type CtrlWait struct {
|
||||
C chan int8
|
||||
|
||||
// 状态
|
||||
s bool
|
||||
m sync.RWMutex
|
||||
}
|
||||
|
||||
// Pause 暂停
|
||||
func (c *CtrlWait) Pause() {
|
||||
c.m.RLock()
|
||||
defer c.m.RUnlock()
|
||||
if c.s {
|
||||
c.C <- 1
|
||||
}
|
||||
}
|
||||
|
||||
// Resume 恢复
|
||||
func (c *CtrlWait) Resume() {
|
||||
c.m.RLock()
|
||||
defer c.m.RUnlock()
|
||||
if c.s {
|
||||
c.C <- 0
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CtrlWait) Open() {
|
||||
c.m.Lock()
|
||||
defer c.m.Unlock()
|
||||
c.s = true
|
||||
}
|
||||
|
||||
func (c *CtrlWait) Close() {
|
||||
c.m.Lock()
|
||||
defer c.m.Unlock()
|
||||
c.s = false
|
||||
}
|
||||
|
||||
// NewCtrlWait 创建一个控制等待
|
||||
func NewCtrlWait() *CtrlWait {
|
||||
return &CtrlWait{
|
||||
C: make(chan int8),
|
||||
s: false,
|
||||
}
|
||||
}
|
||||
|
||||
// PassCtrl 全局控制等待
|
||||
var PassCtrl = NewCtrlWait()
|
||||
59
internal/common/pause_sub.go
Normal file
59
internal/common/pause_sub.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type PauseSub struct {
|
||||
ctrl *CtrlWait
|
||||
items []chan int8
|
||||
m sync.RWMutex
|
||||
}
|
||||
|
||||
func (p *PauseSub) GetNew() chan int8 {
|
||||
p.m.Lock()
|
||||
defer p.m.Unlock()
|
||||
sub := make(chan int8)
|
||||
p.items = append(p.items, sub)
|
||||
return sub
|
||||
}
|
||||
|
||||
func (p *PauseSub) Close(sub chan int8) {
|
||||
p.m.Lock()
|
||||
defer p.m.Unlock()
|
||||
close(sub)
|
||||
for i, v := range p.items {
|
||||
if v == sub {
|
||||
p.items = append(p.items[:i], p.items[i+1:]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Run 开始监听
|
||||
func (p *PauseSub) Run(ctx context.Context) {
|
||||
p.ctrl.Open()
|
||||
defer p.ctrl.Close()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case c := <-p.ctrl.C:
|
||||
go func() {
|
||||
p.m.RLock()
|
||||
defer p.m.RUnlock()
|
||||
for _, item := range p.items {
|
||||
item <- c
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func NewPauseSub(c *CtrlWait) *PauseSub {
|
||||
return &PauseSub{
|
||||
ctrl: c,
|
||||
items: make([]chan int8, 0),
|
||||
}
|
||||
}
|
||||
18
internal/middleware/cache.go
Normal file
18
internal/middleware/cache.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"game-driver/config"
|
||||
"game-driver/leaf"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// Cache 缓存中间件
|
||||
func Cache(cache config.Cache) leaf.HandlerFunc {
|
||||
return func(c *leaf.Context) {
|
||||
err := cache.Set(c.Publish)
|
||||
if err != nil {
|
||||
zap.S().Errorln("缓存数据失败: ", err)
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
15
internal/middleware/pause_wait.go
Normal file
15
internal/middleware/pause_wait.go
Normal file
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"game-driver/internal/common"
|
||||
"game-driver/leaf"
|
||||
"go.uber.org/zap"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// EmergencyStop 紧急停止中间件
|
||||
@@ -12,14 +13,21 @@ func EmergencyStop(stopper common.Stopper) leaf.HandlerFunc {
|
||||
cancel := leaf.WithCancel(c)
|
||||
defer stopper.Reset()
|
||||
|
||||
zap.S().Infoln("监听停止信号")
|
||||
defer zap.S().Infoln("结束停止信号监听")
|
||||
|
||||
// 等待组
|
||||
var wait sync.WaitGroup
|
||||
defer wait.Wait()
|
||||
|
||||
// 结束信号通道
|
||||
a := make(chan struct{})
|
||||
// 发送结束信号
|
||||
defer close(a)
|
||||
|
||||
zap.S().Infoln("监听停止信号")
|
||||
wait.Add(1)
|
||||
go func() {
|
||||
defer zap.S().Infoln("结束停止信号监听")
|
||||
defer wait.Done()
|
||||
|
||||
select {
|
||||
case <-a:
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// TickerAction 定时器动作,用于在指定时间点执行打印和语音播报
|
||||
func TickerAction() leaf.HandlerFunc {
|
||||
return func(c *leaf.Context) {
|
||||
pm := leaf.Value[*schema.PlayModal](c, PayloadJSONKey)
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -12,7 +12,7 @@ func Unique(stopper common.Stopper) leaf.HandlerFunc {
|
||||
var lock sync.Mutex
|
||||
return func(c *leaf.Context) {
|
||||
if !lock.TryLock() {
|
||||
zap.S().Infoln("尝试加锁失败,执行停止任务")
|
||||
zap.S().Infoln("停止之前的任务,再尝试加锁")
|
||||
stopper.Stop()
|
||||
lock.Lock()
|
||||
}
|
||||
|
||||
@@ -19,11 +19,14 @@ func switchPoint(ctx context.Context, point int) leaf.HandlerFunc {
|
||||
switch point {
|
||||
case 2: // 镇水塔点位
|
||||
return play.OnlyVideo
|
||||
case 4:
|
||||
// 4号点位(发卡机)
|
||||
return play.PushCard(ctx)
|
||||
case 5:
|
||||
// 5号点位(等待插卡)
|
||||
// 登龙云台(激光秀)
|
||||
return play.LaserShow
|
||||
case 10:
|
||||
// 10号点位(发卡机)
|
||||
return play.PushCard(ctx)
|
||||
case 11:
|
||||
// 11号点位(等待插卡)
|
||||
return play.WaitCard(ctx)
|
||||
default:
|
||||
return play.Default
|
||||
|
||||
45
internal/routes/play/laser.go
Normal file
45
internal/routes/play/laser.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package play
|
||||
|
||||
import (
|
||||
"game-driver/config/game"
|
||||
"game-driver/internal/middleware"
|
||||
"game-driver/internal/schema"
|
||||
"game-driver/leaf"
|
||||
"game-driver/pkg/oscx"
|
||||
"go.uber.org/zap"
|
||||
"time"
|
||||
)
|
||||
|
||||
func LaserShow(c *leaf.Context) {
|
||||
cfg := (game.C).(game.LaserConfig)
|
||||
|
||||
payload := leaf.Value[*schema.PlayModal](c, middleware.PayloadJSONKey)
|
||||
|
||||
if data, ok := payload.Game["osc"].(string); ok {
|
||||
zap.S().Infoln("开始播放激光秀:", data)
|
||||
|
||||
o := oscx.New(cfg.Host, cfg.Port)
|
||||
err := o.EnableLaserOutput()
|
||||
if err != nil {
|
||||
zap.S().Warnln("激光打开异常:", err)
|
||||
return
|
||||
} else {
|
||||
defer zap.S().Infoln("激光秀播放结束:", data)
|
||||
defer o.DisableLaserOutput()
|
||||
|
||||
err = o.StartCue(data)
|
||||
if err != nil {
|
||||
zap.S().Warnln("播放激光节目异常:", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
if w, ok := payload.Game["wait"]; ok {
|
||||
if v, ok := w.(float64); ok {
|
||||
select {
|
||||
case <-c.Done():
|
||||
case <-time.After(time.Duration(v) * time.Second):
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,11 +16,11 @@ func OnlyVideo(c *leaf.Context) {
|
||||
defer utils.BlankClose()
|
||||
|
||||
if url, ok := payload.Game["video"]; ok {
|
||||
local, err := utils.LinkVideo(url.(string))
|
||||
path, local, err := utils.LinkVideo(url.(string))
|
||||
if err != nil {
|
||||
zap.S().Errorln("视频文件获取异常: ", err)
|
||||
return
|
||||
}
|
||||
_ = video.Play(c, local)
|
||||
_ = video.Play(c, path, local)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,14 +28,14 @@ type ResponseBody struct {
|
||||
}
|
||||
|
||||
func PushCard(ctx context.Context) leaf.HandlerFunc {
|
||||
g := (game.G).(game.ConfigPush)
|
||||
g := (game.C).(game.ConfigPush)
|
||||
|
||||
readers := make([]card_reader.Reader, len(g.PushGroups))
|
||||
devices := make([]*card_pusher.Device, len(g.PushGroups))
|
||||
for i, group := range g.PushGroups {
|
||||
// 解析配置
|
||||
gv, _ := json.Marshal(group)
|
||||
zap.S().Info("关卡配置:", string(gv))
|
||||
zap.S().Infof("关卡配置[%v]: %s", i, string(gv))
|
||||
|
||||
// 开始连接读卡器
|
||||
if group.Read != "" {
|
||||
@@ -63,7 +63,7 @@ func PushCard(ctx context.Context) leaf.HandlerFunc {
|
||||
zap.S().Panicln("初始化发卡器失败: ", err)
|
||||
}
|
||||
|
||||
// 保存读卡器和发卡器
|
||||
// 保存发卡器
|
||||
devices[i] = device
|
||||
}
|
||||
go func() {
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
)
|
||||
|
||||
func WaitCard(ctx context.Context) leaf.HandlerFunc {
|
||||
g := (game.G).(game.ConfigWait)
|
||||
g := (game.C).(game.ConfigWait)
|
||||
|
||||
reader, err := card_reader.NewReader(g.Addr)
|
||||
if err != nil {
|
||||
|
||||
101
internal/routes/standby.go
Normal file
101
internal/routes/standby.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"game-driver/internal/common"
|
||||
"game-driver/internal/middleware"
|
||||
"game-driver/internal/routes/standby"
|
||||
"game-driver/internal/routes/standby_ctrl"
|
||||
"game-driver/internal/schema"
|
||||
"game-driver/leaf"
|
||||
"github.com/go-pkgz/cronrange"
|
||||
"go.uber.org/zap"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// StandbyAction 待机任务,支持音乐、TTS、继电器、视频、网页、投影仪、大型激光秀 ctrl
|
||||
func StandbyAction(ctrl *common.CtrlWait, device *common.Device) leaf.HandlerFunc {
|
||||
ps := common.NewPauseSub(ctrl)
|
||||
|
||||
return func(c *leaf.Context) {
|
||||
payload := leaf.Value[*schema.WaitModel](c, middleware.PayloadJSONKey)
|
||||
|
||||
// 设定默认时间规则,ctrl
|
||||
if payload.Cron == "" {
|
||||
payload.Cron = "* * * *"
|
||||
}
|
||||
|
||||
rules, err := cronrange.Parse(payload.Cron)
|
||||
if err != nil {
|
||||
zap.S().Errorln("解析时间规则异常: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 等待组
|
||||
var waitGroup sync.WaitGroup
|
||||
defer waitGroup.Wait()
|
||||
|
||||
// 开启暂停监听
|
||||
waitGroup.Add(1)
|
||||
go func() {
|
||||
defer waitGroup.Done()
|
||||
ps.Run(c)
|
||||
}()
|
||||
|
||||
// 处理每个待机控制
|
||||
handleItem := func(title string, item schema.WaitItemModel, f func(c context.Context) error) {
|
||||
waitGroup.Add(1)
|
||||
go func() {
|
||||
defer waitGroup.Done()
|
||||
if f == nil {
|
||||
return
|
||||
}
|
||||
f = standby_ctrl.Duration(item.Duration, f)
|
||||
if f == nil {
|
||||
return
|
||||
}
|
||||
f = standby_ctrl.Device(device, item.Lock, f)
|
||||
if f == nil {
|
||||
return
|
||||
}
|
||||
f = standby_ctrl.Interval(item.Interval, f)
|
||||
if f == nil {
|
||||
return
|
||||
}
|
||||
f = standby_ctrl.Pause(ps, item.Pause, f)
|
||||
if f == nil {
|
||||
return
|
||||
}
|
||||
f = standby_ctrl.Cron(rules, item.Cron, f)
|
||||
if f == nil {
|
||||
return
|
||||
}
|
||||
e := f(c)
|
||||
if e != nil {
|
||||
zap.S().Errorf("%s异常: %s\n", title, e)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
for _, item := range payload.Items {
|
||||
switch item.Type {
|
||||
case schema.WaitAudio:
|
||||
handleItem("音乐待机控制", item, standby.Audio(item))
|
||||
case schema.WaitTTS:
|
||||
handleItem("TTS待机控制", item, standby.TTS(item))
|
||||
case schema.WaitRelay:
|
||||
handleItem("继电器待机控制", item, standby.Relay(item))
|
||||
case schema.WaitVideo:
|
||||
handleItem("视频待机控制", item, standby.Video(item))
|
||||
case schema.WaitWeb:
|
||||
handleItem("网页待机控制", item, standby.Web(item))
|
||||
case schema.WaitPJLink:
|
||||
handleItem("投影仪待机控制", item, standby.PJLink(item))
|
||||
case schema.WaitLaserShow:
|
||||
handleItem("大型激光秀控制", item, standby.LaserShow(item))
|
||||
default:
|
||||
zap.S().Infof("不支持的类型: %d\n", item.Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
40
internal/routes/standby/audio.go
Normal file
40
internal/routes/standby/audio.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package standby
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"game-driver/internal/schema"
|
||||
"game-driver/pkg/audio"
|
||||
"game-driver/pkg/utils"
|
||||
"github.com/gopxl/beep/v2/speaker"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func Audio(item schema.WaitItemModel) func(c context.Context) error {
|
||||
return func(c context.Context) error {
|
||||
data, err := utils.LinkAudio(item.Data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("音频数据获取异常: %w", err)
|
||||
}
|
||||
if data == nil {
|
||||
return fmt.Errorf("音频数据获取为空")
|
||||
}
|
||||
|
||||
zap.S().Infoln("播放待机音乐")
|
||||
defer zap.S().Infoln("结束待机音乐")
|
||||
|
||||
ctrl, closer, e := audio.PlayBgmMP3(data)
|
||||
defer closer()
|
||||
if e != nil {
|
||||
return fmt.Errorf("播放待机音乐异常: %w", e)
|
||||
}
|
||||
|
||||
<-c.Done()
|
||||
|
||||
speaker.Lock()
|
||||
ctrl.Streamer = nil
|
||||
speaker.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
35
internal/routes/standby/laser.go
Normal file
35
internal/routes/standby/laser.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package standby
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"game-driver/config/game"
|
||||
"game-driver/internal/schema"
|
||||
"game-driver/pkg/oscx"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func LaserShow(item schema.WaitItemModel) func(c context.Context) error {
|
||||
cfg := (game.C).(game.LaserConfig)
|
||||
|
||||
return func(c context.Context) error {
|
||||
zap.S().Infoln("开始播放大型激光秀")
|
||||
|
||||
o := oscx.New(cfg.Host, cfg.Port)
|
||||
err := o.EnableLaserOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("激光打开异常: %w", err)
|
||||
} else {
|
||||
defer zap.S().Infoln("大型激光秀播放结束:", item.Data)
|
||||
defer o.DisableLaserOutput()
|
||||
|
||||
err = o.StartCue(item.Data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("播放大型激光节目异常: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
<-c.Done()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
49
internal/routes/standby/pjlink.go
Normal file
49
internal/routes/standby/pjlink.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package standby
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"game-driver/config/wait"
|
||||
"game-driver/internal/schema"
|
||||
"game-driver/pkg/pjlink"
|
||||
"go.uber.org/zap"
|
||||
"time"
|
||||
)
|
||||
|
||||
func PJLink(_ schema.WaitItemModel) func(c context.Context) error {
|
||||
return func(c context.Context) error {
|
||||
cfg := (wait.C).(wait.PJLink)
|
||||
pjc := pjlink.NewClient(cfg.Ip, cfg.Port, cfg.Password, cfg.Id)
|
||||
|
||||
zap.S().Infoln("打开待机投影仪")
|
||||
resp, err := pjc.PowerOnSync()
|
||||
if err != nil {
|
||||
return fmt.Errorf("打开投影仪异常: %w", err)
|
||||
}
|
||||
zap.S().Infoln("打开投影仪结果:", resp)
|
||||
|
||||
run := true
|
||||
for run {
|
||||
select {
|
||||
case <-c.Done():
|
||||
zap.S().Infoln("关闭待机投影仪")
|
||||
resp, err = pjc.PowerOffSync()
|
||||
if err != nil {
|
||||
return fmt.Errorf("关闭投影仪异常: %w", err)
|
||||
}
|
||||
zap.S().Infoln("关闭投影仪结果:", resp)
|
||||
run = false
|
||||
break
|
||||
case <-time.After(time.Minute * 30):
|
||||
zap.S().Infoln("轮询待机投影仪")
|
||||
resp, err = pjc.PowerOnSync()
|
||||
if err != nil {
|
||||
return fmt.Errorf("轮询投影仪异常: %w", err)
|
||||
}
|
||||
zap.S().Infoln("轮询投影仪结果:", resp)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
28
internal/routes/standby/relay.go
Normal file
28
internal/routes/standby/relay.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package standby
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"game-driver/internal/schema"
|
||||
"game-driver/pkg/relay"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func Relay(item schema.WaitItemModel) func(c context.Context) error {
|
||||
return func(c context.Context) error {
|
||||
r, err := relay.New(item.Data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("继电器初始化异常: %w", err)
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
zap.S().Infoln("待机继电器供电")
|
||||
defer zap.S().Infoln("待机继电器断电")
|
||||
|
||||
_ = r.On(0)
|
||||
<-c.Done()
|
||||
_ = r.Off(0)
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
26
internal/routes/standby/tts.go
Normal file
26
internal/routes/standby/tts.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package standby
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"game-driver/internal/schema"
|
||||
"game-driver/pkg/audio"
|
||||
"game-driver/pkg/tts"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func TTS(item schema.WaitItemModel) func(c context.Context) error {
|
||||
return func(c context.Context) error {
|
||||
reader, err := tts.DefaultTTS.Get(item.Data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("语音合成异常: %w", err)
|
||||
}
|
||||
|
||||
zap.S().Infoln("播放待机 TTS 语音")
|
||||
defer zap.S().Infoln("结束待机 TTS 语音")
|
||||
|
||||
audio.PlayWav(c, reader)
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
31
internal/routes/standby/video.go
Normal file
31
internal/routes/standby/video.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package standby
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"game-driver/internal/schema"
|
||||
"game-driver/pkg/utils"
|
||||
"game-driver/pkg/video"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func Video(item schema.WaitItemModel) func(c context.Context) error {
|
||||
return func(c context.Context) error {
|
||||
path, local, err := utils.LinkVideo(item.Data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("视频文件获取异常: %w", err)
|
||||
}
|
||||
|
||||
zap.S().Infoln("播放待机视频")
|
||||
defer zap.S().Infoln("结束待机视频")
|
||||
|
||||
utils.BlankOpen()
|
||||
defer utils.BlankClose()
|
||||
|
||||
err = video.Play(c, path, local)
|
||||
if err != nil {
|
||||
return fmt.Errorf("视频播放异常: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
22
internal/routes/standby/web.go
Normal file
22
internal/routes/standby/web.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package standby
|
||||
|
||||
import (
|
||||
"context"
|
||||
"game-driver/internal/schema"
|
||||
"game-driver/pkg/browser"
|
||||
"game-driver/pkg/utils"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func Web(item schema.WaitItemModel) func(c context.Context) error {
|
||||
return func(c context.Context) error {
|
||||
zap.S().Infoln("打开待机网页")
|
||||
|
||||
// 控制背光
|
||||
utils.BlankOpen()
|
||||
defer utils.BlankClose()
|
||||
|
||||
browser.OpenApp(c, item.Data)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
94
internal/routes/standby_ctrl/cron.go
Normal file
94
internal/routes/standby_ctrl/cron.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package standby_ctrl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/go-pkgz/cronrange"
|
||||
"go.uber.org/zap"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Cron 时间控制器
|
||||
func Cron(rootRules []cronrange.Rule, cron string, play func(c context.Context) error) func(c context.Context) error {
|
||||
// 设定默认时间规则
|
||||
if cron == "" {
|
||||
cron = "* * * *"
|
||||
}
|
||||
|
||||
rules, err := cronrange.Parse(cron)
|
||||
if err != nil {
|
||||
zap.S().Errorln("解析时间规则异常: ", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
return func(c context.Context) error {
|
||||
a := make(chan bool)
|
||||
defer close(a)
|
||||
|
||||
zap.S().Infoln("待机时间控制器")
|
||||
defer zap.S().Infoln("待机时间控制器结束")
|
||||
|
||||
// 等待组
|
||||
var waitGroup sync.WaitGroup
|
||||
defer waitGroup.Wait()
|
||||
|
||||
ticker := time.NewTicker(time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
waitGroup.Add(1)
|
||||
go func() {
|
||||
defer waitGroup.Done()
|
||||
for {
|
||||
select {
|
||||
case <-c.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
if cronrange.Match(rules, time.Now()) && cronrange.Match(rootRules, time.Now()) {
|
||||
a <- true
|
||||
} else {
|
||||
a <- false
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
var cancel context.CancelFunc
|
||||
var m sync.Mutex
|
||||
for {
|
||||
select {
|
||||
case <-c.Done():
|
||||
if cancel != nil {
|
||||
cancel()
|
||||
cancel = nil
|
||||
}
|
||||
return nil
|
||||
case r := <-a:
|
||||
if r {
|
||||
if ok := m.TryLock(); ok {
|
||||
ctx, cc := context.WithCancel(context.TODO())
|
||||
cancel = cc
|
||||
waitGroup.Add(1)
|
||||
go func() {
|
||||
defer waitGroup.Done()
|
||||
defer m.Unlock()
|
||||
defer func() { cancel = nil }()
|
||||
|
||||
err := play(ctx)
|
||||
if err != nil {
|
||||
zap.S().Errorln("执行动作异常: ", err)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-time.After(time.Minute):
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
} else if cancel != nil {
|
||||
cancel()
|
||||
cancel = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
22
internal/routes/standby_ctrl/device.go
Normal file
22
internal/routes/standby_ctrl/device.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package standby_ctrl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"game-driver/internal/common"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// Device 设备锁定控制器
|
||||
func Device(d *common.Device, lock bool, play func(c context.Context) error) func(c context.Context) error {
|
||||
return func(c context.Context) error {
|
||||
if lock {
|
||||
zap.S().Infoln("待机任务锁定设备")
|
||||
defer zap.S().Infoln("待机任务解锁设备")
|
||||
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
}
|
||||
|
||||
return play(c)
|
||||
}
|
||||
}
|
||||
23
internal/routes/standby_ctrl/duration.go
Normal file
23
internal/routes/standby_ctrl/duration.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package standby_ctrl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"go.uber.org/zap"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Duration 持续时长控制器
|
||||
func Duration(duration int64, play func(c context.Context) error) func(c context.Context) error {
|
||||
return func(c context.Context) error {
|
||||
zap.S().Infoln("待机持续时长控制器: ", duration)
|
||||
defer zap.S().Infoln("待机持续时长控制器结束: ", duration)
|
||||
|
||||
if duration > 0 {
|
||||
ctx, cancel := context.WithTimeout(c, time.Duration(duration)*time.Second)
|
||||
defer cancel()
|
||||
c = ctx
|
||||
}
|
||||
|
||||
return play(c)
|
||||
}
|
||||
}
|
||||
35
internal/routes/standby_ctrl/interval.go
Normal file
35
internal/routes/standby_ctrl/interval.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package standby_ctrl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"go.uber.org/zap"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Interval 循环间隔控制器
|
||||
func Interval(interval int64, play func(c context.Context) error) func(c context.Context) error {
|
||||
return func(c context.Context) error {
|
||||
zap.S().Infoln("待机间隔控制器: ", interval)
|
||||
defer zap.S().Infoln("待机间隔控制器结束: ", interval)
|
||||
|
||||
for {
|
||||
err := play(c)
|
||||
if err != nil {
|
||||
zap.S().Errorln("执行后续操作异常: ", err)
|
||||
}
|
||||
if interval > 0 {
|
||||
select {
|
||||
case <-c.Done():
|
||||
return nil
|
||||
case <-time.After(time.Duration(interval) * time.Second):
|
||||
}
|
||||
} else {
|
||||
select {
|
||||
case <-c.Done():
|
||||
return nil
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
64
internal/routes/standby_ctrl/pause.go
Normal file
64
internal/routes/standby_ctrl/pause.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package standby_ctrl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"game-driver/internal/common"
|
||||
"go.uber.org/zap"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Pause 暂停控制器
|
||||
func Pause(ps *common.PauseSub, isPause bool, play func(c context.Context) error) func(c context.Context) error {
|
||||
return func(c context.Context) error {
|
||||
var cancel context.CancelFunc
|
||||
run := true
|
||||
|
||||
if isPause {
|
||||
zap.S().Infoln("待机暂停控制器")
|
||||
defer zap.S().Infoln("待机暂停控制器结束")
|
||||
|
||||
p := ps.GetNew()
|
||||
defer ps.Close(p)
|
||||
|
||||
// 等待组
|
||||
var wait sync.WaitGroup
|
||||
defer wait.Wait()
|
||||
|
||||
wait.Add(1)
|
||||
go func() {
|
||||
defer wait.Done()
|
||||
for {
|
||||
select {
|
||||
case <-c.Done():
|
||||
return
|
||||
case v := <-p:
|
||||
if v == 1 {
|
||||
zap.S().Infoln("待机控制器 Pause 触发")
|
||||
run = false
|
||||
cancel()
|
||||
} else {
|
||||
zap.S().Infoln("待机控制器 Resume 触发")
|
||||
run = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-c.Done():
|
||||
return nil
|
||||
default:
|
||||
if run {
|
||||
nc, cc := context.WithCancel(c)
|
||||
cancel = cc
|
||||
err := play(nc)
|
||||
if err != nil {
|
||||
zap.S().Infoln("执行后续操作异常: ", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ type TTSModal struct {
|
||||
Start string `json:"start"`
|
||||
End string `json:"end"`
|
||||
Stop string `json:"stop"`
|
||||
Timeout string `json:"timeout"`
|
||||
Timer []TTSTimer `json:"timer"`
|
||||
}
|
||||
|
||||
|
||||
@@ -8,22 +8,22 @@ const (
|
||||
WaitTTS
|
||||
WaitRelay
|
||||
WaitWeb
|
||||
WaitPJLink
|
||||
WaitLaserShow
|
||||
)
|
||||
|
||||
type TimeModel struct {
|
||||
Start int64 `json:"start"`
|
||||
End int64 `json:"end"`
|
||||
}
|
||||
|
||||
type WaitItemModel struct {
|
||||
TimeModel
|
||||
Type WaitType `json:"type"`
|
||||
Data string `json:"data"`
|
||||
Interval int64 `json:"interval"`
|
||||
Cron string `json:"cron"` // 时间规则
|
||||
Type WaitType `json:"type"` // 类型
|
||||
Data string `json:"data"` // 执行数据
|
||||
Duration int64 `json:"duration"` // 持续时长
|
||||
Interval int64 `json:"interval"` // 间隔时间
|
||||
Pause bool `json:"pause"` // 是否暂停
|
||||
Lock bool `json:"lock"` // 是否锁定
|
||||
}
|
||||
|
||||
type WaitModel struct {
|
||||
JsonModel
|
||||
TimeModel
|
||||
Cron string `json:"cron"`
|
||||
Items []WaitItemModel `json:"items"`
|
||||
}
|
||||
|
||||
@@ -13,14 +13,17 @@ import (
|
||||
"game-driver/pkg/relay"
|
||||
"game-driver/pkg/tts"
|
||||
"game-driver/pkg/utils"
|
||||
"github.com/eclipse/paho.golang/autopaho"
|
||||
"github.com/eclipse/paho.golang/paho"
|
||||
"go.uber.org/zap"
|
||||
"log"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/eclipse/paho.golang/autopaho"
|
||||
"github.com/eclipse/paho.golang/paho"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func buildMqtt(c config.MqttConfig, r *leaf.Engine, subTopics ...string) autopaho.ClientConfig {
|
||||
@@ -38,6 +41,7 @@ func buildMqtt(c config.MqttConfig, r *leaf.Engine, subTopics ...string) autopah
|
||||
mqttConfig := autopaho.ClientConfig{
|
||||
ServerUrls: []*url.URL{u},
|
||||
KeepAlive: 20,
|
||||
ConnectPassword: []byte(c.Password),
|
||||
CleanStartOnInitialConnection: false,
|
||||
SessionExpiryInterval: 60,
|
||||
OnConnectionUp: func(cm *autopaho.ConnectionManager, _ *paho.Connack) {
|
||||
@@ -54,7 +58,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: c.ClientID,
|
||||
OnPublishReceived: []func(paho.PublishReceived) (bool, error){
|
||||
func(pr paho.PublishReceived) (bool, error) {
|
||||
r.Route(pr.Packet.Packet())
|
||||
@@ -76,10 +80,29 @@ func buildMqtt(c config.MqttConfig, r *leaf.Engine, subTopics ...string) autopah
|
||||
}
|
||||
|
||||
func Run() {
|
||||
logger.InitLogger()
|
||||
cls, err := logger.NewTenCls(fmt.Sprintf("game-driver-%s-%v", config.C.Location, config.C.Point))
|
||||
if err != nil {
|
||||
log.Println("初始化腾讯云日志服务异常: ", err)
|
||||
}
|
||||
cls.Start()
|
||||
defer cls.Close()
|
||||
|
||||
logger.InitProLogger(cls)
|
||||
|
||||
//logger.InitDevLogger()
|
||||
// 应用退出时刷新所有缓冲日志
|
||||
defer logger.Sync()
|
||||
|
||||
// 获取当前IP,并打印当前IP
|
||||
addrs, err := net.InterfaceAddrs()
|
||||
if err != nil {
|
||||
log.Panicln("网络连接异常: ", err)
|
||||
}
|
||||
zap.S().Infoln("当前IP: ", addrs)
|
||||
|
||||
// 启动时关闭屏幕
|
||||
utils.BlankClose()
|
||||
|
||||
topicPrefix := fmt.Sprintf("server/%s/%v/", config.C.Location, config.C.Point)
|
||||
publishTopic := fmt.Sprintf("device/%s/%v/status", config.C.Location, config.C.Point)
|
||||
|
||||
@@ -88,23 +111,13 @@ func Run() {
|
||||
|
||||
router := leaf.Default(ctx)
|
||||
|
||||
log, _ := zap.NewStdLogAt(zap.L(), zap.DebugLevel)
|
||||
router.SetDebugLogger(log)
|
||||
logAt, _ := zap.NewStdLogAt(zap.L(), zap.DebugLevel)
|
||||
router.SetDebugLogger(logAt)
|
||||
|
||||
router.DefaultHandler(func(c *leaf.Context) {
|
||||
zap.S().Infof("未处理消息,topic: %s\n payload: %s\n", c.Topic, c.Payload)
|
||||
})
|
||||
|
||||
// 构建 MQTT 连接
|
||||
mqttBuild := buildMqtt(config.C.Mqtt, router, topicPrefix+"#")
|
||||
|
||||
// 连接 MQTT
|
||||
cm, err := autopaho.NewConnection(ctx, mqttBuild)
|
||||
if err != nil {
|
||||
zap.S().Panicln("连接 MQTT 异常: ", err)
|
||||
}
|
||||
utils.GlobalMqttClient = cm
|
||||
|
||||
// 构建语音合成对象
|
||||
tts.DefaultTTS = tts.New(ctx, config.C.Aliyun)
|
||||
|
||||
@@ -113,7 +126,7 @@ func Run() {
|
||||
if config.C.Relay != "" {
|
||||
r, err = relay.New(config.C.Relay)
|
||||
if err != nil {
|
||||
zap.S().Panicln("继电器连接异常: ", err)
|
||||
zap.S().Errorln("继电器连接异常: ", err)
|
||||
}
|
||||
defer r.Close()
|
||||
}
|
||||
@@ -128,6 +141,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),
|
||||
@@ -140,9 +154,10 @@ func Run() {
|
||||
router.RegisterHandler(topicPrefix+"wait",
|
||||
middleware.RunLog(),
|
||||
middleware.PayloadJSON[schema.WaitModel](),
|
||||
middleware.Cache(config.C.StandbyCache),
|
||||
middleware.Unique(common.GlobalBgStopper),
|
||||
middleware.EmergencyStop(common.GlobalBgStopper),
|
||||
routes.WaitAction,
|
||||
routes.StandbyAction(common.PassCtrl, device),
|
||||
)
|
||||
// 处理指令
|
||||
router.RegisterHandler(topicPrefix+"command",
|
||||
@@ -150,10 +165,26 @@ func Run() {
|
||||
routes.Command(device),
|
||||
)
|
||||
|
||||
// 从缓存中读取待机报文,如果存在则直接执行
|
||||
publish, err := config.C.StandbyCache.Get()
|
||||
if err != nil {
|
||||
zap.S().Infoln("读取待机缓存失败: ", err)
|
||||
} else {
|
||||
router.HandlerRun(topicPrefix+"wait", publish)
|
||||
}
|
||||
|
||||
// 构建 MQTT 连接
|
||||
mqttBuild := buildMqtt(config.C.Mqtt, router, topicPrefix+"#")
|
||||
|
||||
// 开始连接 MQTT
|
||||
cm, err := autopaho.NewConnection(ctx, mqttBuild)
|
||||
if err != nil {
|
||||
zap.S().Panicln("创建 MQTT 连接器异常: ", err)
|
||||
}
|
||||
utils.GlobalMqttClient = cm
|
||||
|
||||
// 启动完成发送一次设备状态
|
||||
device.PublishStatus()
|
||||
// 启动完成关闭屏幕
|
||||
utils.BlankClose()
|
||||
|
||||
sig := make(chan os.Signal, 1)
|
||||
signal.Notify(sig, os.Interrupt, syscall.SIGTERM)
|
||||
@@ -166,6 +197,9 @@ func Run() {
|
||||
if e := cm.Disconnect(ctx); e != nil {
|
||||
zap.S().Errorln("断开连接异常", e)
|
||||
}
|
||||
if e := router.Disconnect(ctx); e != nil {
|
||||
zap.S().Errorln("停止所以任务超时", e)
|
||||
}
|
||||
|
||||
zap.S().Infoln("关闭完成")
|
||||
}
|
||||
|
||||
231
json.md
Normal file
231
json.md
Normal file
@@ -0,0 +1,231 @@
|
||||
## 点位0(起点)
|
||||
|
||||
### Game
|
||||
|
||||
url: `server/wushan/0/play`
|
||||
|
||||
```json
|
||||
{
|
||||
"bgm": "file://./节点bgm.mp3",
|
||||
"game": {
|
||||
"wait": 12
|
||||
},
|
||||
"tts": {
|
||||
"timer": [
|
||||
{
|
||||
"time": 1,
|
||||
"value": "ni听得到勇士欢迎来到三峡龙脊,你即将踏上勇者之路,祝你一往无前,登龙折桂!"
|
||||
}
|
||||
]
|
||||
},
|
||||
"volume": 1
|
||||
}
|
||||
```
|
||||
|
||||
## 点位1(击缶台)
|
||||
|
||||
### 待机
|
||||
|
||||
url: `server/wushan/1/wait`
|
||||
|
||||
```json
|
||||
{
|
||||
"cron": "18:00-22:00 * * *",
|
||||
"items": [
|
||||
{
|
||||
"type": 3,
|
||||
"data": "/dev/ttyACM0"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Game
|
||||
|
||||
url: `server/wushan/1/play`
|
||||
|
||||
```json
|
||||
{
|
||||
"bgm": "file://./节点bgm.mp3",
|
||||
"game": {
|
||||
"wait": 30
|
||||
},
|
||||
"power": true,
|
||||
"tts": {
|
||||
"timer": [
|
||||
{
|
||||
"time": 1,
|
||||
"value": "ni听得到勇士,请于击缶台就位!完成你的挑战!"
|
||||
},
|
||||
{
|
||||
"time": 15,
|
||||
"value": "游戏时长已过半"
|
||||
}
|
||||
],
|
||||
"stop": "游戏终止",
|
||||
"end": "游戏结束"
|
||||
},
|
||||
"volume": 1
|
||||
}
|
||||
```
|
||||
|
||||
## 点位2(镇水塔)
|
||||
### 待机
|
||||
|
||||
url: `server/wushan/2/wait`
|
||||
|
||||
```json
|
||||
{
|
||||
"cron": "08:00-22:00 * * *",
|
||||
"items": [
|
||||
{
|
||||
"data": "file://./三峡龙脊BGM.mp3",
|
||||
"pause": true
|
||||
},
|
||||
{
|
||||
"cron": "18:00-22:00 * * *",
|
||||
"type": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Game
|
||||
|
||||
url: `server/wushan/2/play`
|
||||
|
||||
```json
|
||||
{
|
||||
"tts": {
|
||||
"start": "刘佳勇者,恭喜你成功通关!",
|
||||
"timer": [
|
||||
{
|
||||
"time": 2,
|
||||
"value": "你的荣耀将获得法阵加持,请迅速移步到法阵位置!"
|
||||
}
|
||||
]
|
||||
},
|
||||
"game": {
|
||||
"video": "file://./镇水塔法阵.mp4"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 点位3(镇压异兽)
|
||||
|
||||
### 待机
|
||||
|
||||
url: `server/wushan/3/wait`
|
||||
|
||||
```json
|
||||
{
|
||||
"cron": "08:00-22:00 * * *",
|
||||
"items": [
|
||||
{
|
||||
"data": "file://./三峡龙脊BGM.mp3",
|
||||
"pause": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Game
|
||||
|
||||
url: `server/wushan/3/play`
|
||||
|
||||
```json
|
||||
{
|
||||
"bgm": "file://./节点bgm.mp3",
|
||||
"game": {
|
||||
"wait": 12
|
||||
},
|
||||
"tts": {
|
||||
"timer": [
|
||||
{
|
||||
"time": 1,
|
||||
"value": "ni听得到勇者,恭喜你成功通关,神力澎湃,可与星辰争辉!"
|
||||
}
|
||||
]
|
||||
},
|
||||
"volume": 1
|
||||
}
|
||||
```
|
||||
|
||||
## 点位4(神女影像)
|
||||
|
||||
### 待机
|
||||
|
||||
url: `server/wushan/4/wait`
|
||||
|
||||
```json
|
||||
{
|
||||
"cron": "08:00-22:00 * * *",
|
||||
"items": [
|
||||
{
|
||||
"data": "file://./三峡龙脊BGM.mp3",
|
||||
"pause": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Game
|
||||
|
||||
url: `server/wushan/4/play`
|
||||
|
||||
```json
|
||||
{
|
||||
"bgm": "file://./节点bgm.mp3",
|
||||
"game": {
|
||||
"wait": 12
|
||||
},
|
||||
"tts": {
|
||||
"timer": [
|
||||
{
|
||||
"time": 1,
|
||||
"value": "ni听得到勇者,恭喜你成功通关,神格加载,胜利曙光已现!"
|
||||
}
|
||||
]
|
||||
},
|
||||
"volume": 1
|
||||
}
|
||||
```
|
||||
|
||||
## 点位5(登龙云台)
|
||||
### 待机
|
||||
|
||||
url: `server/wushan/5/wait`
|
||||
|
||||
```json
|
||||
{
|
||||
"cron": "08:00-22:00 * * *",
|
||||
"items": [
|
||||
{
|
||||
"data": "file://./三峡龙脊BGM.mp3",
|
||||
"pause": true
|
||||
},
|
||||
{
|
||||
"cron": "12:05-12:10 * * *",
|
||||
"type": 6,
|
||||
"data": "wushan",
|
||||
"lock": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Game
|
||||
|
||||
url: `server/wushan/5/play`
|
||||
|
||||
```json
|
||||
{
|
||||
"bgm": "file://./登龙云台.mp3",
|
||||
"power": true,
|
||||
"volume":1,
|
||||
"game": {
|
||||
"osc": "dragon",
|
||||
"wait": 36
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -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,
|
||||
|
||||
61
leaf/leaf.go
61
leaf/leaf.go
@@ -12,6 +12,7 @@ type Router interface {
|
||||
RegisterHandler(string, ...HandlerFunc)
|
||||
UnregisterHandler(string)
|
||||
Route(*packets.Publish)
|
||||
HandlerRun(t string, p *paho.Publish) bool
|
||||
SetDebugLogger(log.Logger)
|
||||
Use(...HandlerFunc)
|
||||
}
|
||||
@@ -35,7 +36,12 @@ func (c HandlersChain) Last() HandlerFunc {
|
||||
|
||||
type Engine struct {
|
||||
mu sync.RWMutex
|
||||
queueWg sync.WaitGroup
|
||||
|
||||
ctx context.Context
|
||||
cancelCtx context.CancelFunc
|
||||
done chan struct{}
|
||||
|
||||
Handlers HandlersChain
|
||||
defaultHandler HandlersChain
|
||||
subscriptions map[string]HandlersChain
|
||||
@@ -44,8 +50,11 @@ type Engine struct {
|
||||
}
|
||||
|
||||
func New(ctx context.Context) *Engine {
|
||||
c, cancel := context.WithCancel(ctx)
|
||||
return &Engine{
|
||||
ctx: ctx,
|
||||
ctx: c,
|
||||
cancelCtx: cancel,
|
||||
done: make(chan struct{}),
|
||||
Handlers: make(HandlersChain, 0),
|
||||
subscriptions: make(map[string]HandlersChain),
|
||||
aliases: make(map[uint16]string),
|
||||
@@ -54,8 +63,11 @@ func New(ctx context.Context) *Engine {
|
||||
}
|
||||
|
||||
func Default(ctx context.Context) *Engine {
|
||||
c, cancel := context.WithCancel(ctx)
|
||||
engine := &Engine{
|
||||
ctx: ctx,
|
||||
ctx: c,
|
||||
cancelCtx: cancel,
|
||||
done: make(chan struct{}),
|
||||
Handlers: make(HandlersChain, 0),
|
||||
subscriptions: make(map[string]HandlersChain),
|
||||
aliases: make(map[uint16]string),
|
||||
@@ -81,6 +93,21 @@ func (e *Engine) UnregisterHandler(topic string) {
|
||||
delete(e.subscriptions, topic)
|
||||
}
|
||||
|
||||
func (e *Engine) HandlerRun(t string, p *paho.Publish) bool {
|
||||
for route, handlers := range e.subscriptions {
|
||||
if match(route, t) {
|
||||
e.debug.Println("found handler for:", route)
|
||||
e.queueWg.Add(1)
|
||||
go func() {
|
||||
defer e.queueWg.Done()
|
||||
WithLeafContext(e.ctx, p, e, handlers).Next()
|
||||
}()
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (e *Engine) Route(pb *packets.Publish) {
|
||||
e.debug.Println("routing message for:", pb.Topic)
|
||||
e.mu.Lock()
|
||||
@@ -104,17 +131,14 @@ func (e *Engine) Route(pb *packets.Publish) {
|
||||
topic = m.Topic
|
||||
}
|
||||
|
||||
handlerCalled := false
|
||||
for route, handlers := range e.subscriptions {
|
||||
if match(route, topic) {
|
||||
e.debug.Println("found handler for:", route)
|
||||
go WithLeafContext(e.ctx, m, e, handlers).Next()
|
||||
handlerCalled = true
|
||||
}
|
||||
}
|
||||
handlerCalled := e.HandlerRun(topic, m)
|
||||
|
||||
if !handlerCalled && e.defaultHandler != nil {
|
||||
go WithLeafContext(e.ctx, m, e, e.defaultHandler).Next()
|
||||
e.queueWg.Add(1)
|
||||
go func() {
|
||||
defer e.queueWg.Done()
|
||||
WithLeafContext(e.ctx, m, e, e.defaultHandler).Next()
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,6 +158,21 @@ func (e *Engine) DefaultHandler(h HandlerFunc) {
|
||||
e.defaultHandler = e.combineHandlers(HandlersChain{h})
|
||||
}
|
||||
|
||||
func (e *Engine) Disconnect(ctx context.Context) error {
|
||||
go func() {
|
||||
e.queueWg.Wait()
|
||||
close(e.done)
|
||||
}()
|
||||
|
||||
e.cancelCtx()
|
||||
select {
|
||||
case <-e.done: // wait for goroutine to exit
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Engine) combineHandlers(handlers HandlersChain) HandlersChain {
|
||||
finalSize := len(e.Handlers) + len(handlers)
|
||||
assert1(finalSize < int(abortIndex), "too many handlers")
|
||||
|
||||
49
logger/cls.go
Normal file
49
logger/cls.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"game-driver/config"
|
||||
tencentcloud_cls_sdk_go "github.com/tencentcloud/tencentcloud-cls-sdk-go"
|
||||
"time"
|
||||
)
|
||||
|
||||
type TenCls struct {
|
||||
p *tencentcloud_cls_sdk_go.AsyncProducerClient
|
||||
key string
|
||||
}
|
||||
|
||||
func (t *TenCls) Write(p []byte) (n int, err error) {
|
||||
s := make(map[string]string)
|
||||
_ = json.Unmarshal(p, &s)
|
||||
s["key"] = t.key
|
||||
delete(s, "ts")
|
||||
clsLog := tencentcloud_cls_sdk_go.NewCLSLog(time.Now().Unix(), s)
|
||||
err = t.p.SendLog(config.C.Log.TencentCLS.TopicID, clsLog, nil)
|
||||
return len(p), err
|
||||
}
|
||||
|
||||
func (t *TenCls) Start() {
|
||||
t.p.Start()
|
||||
}
|
||||
|
||||
func (t *TenCls) Close() error {
|
||||
return t.p.Close(60_000)
|
||||
}
|
||||
|
||||
func NewTenCls(code string) (*TenCls, error) {
|
||||
producerConfig := tencentcloud_cls_sdk_go.GetDefaultAsyncProducerClientConfig()
|
||||
producerConfig.Endpoint = config.C.Log.TencentCLS.Endpoint
|
||||
producerConfig.AccessKeyID = config.C.Log.TencentCLS.SecretID
|
||||
producerConfig.AccessKeySecret = config.C.Log.TencentCLS.SecretKey
|
||||
|
||||
producerInstance, err := tencentcloud_cls_sdk_go.NewAsyncProducerClient(producerConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t := &TenCls{
|
||||
p: producerInstance,
|
||||
key: code,
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
@@ -13,7 +13,7 @@ func DefaultLogger() {
|
||||
zap.ReplaceGlobals(logger)
|
||||
}
|
||||
|
||||
func InitLogger() {
|
||||
func InitDevLogger() {
|
||||
// 解析日志级别
|
||||
level, err := zapcore.ParseLevel(config.C.Log.Level)
|
||||
if err != nil {
|
||||
@@ -38,6 +38,31 @@ func InitLogger() {
|
||||
zap.ReplaceGlobals(logger)
|
||||
}
|
||||
|
||||
func InitProLogger(cls *TenCls) {
|
||||
// 解析日志级别
|
||||
level, err := zapcore.ParseLevel(config.C.Log.Level)
|
||||
if err != nil {
|
||||
log.Panicln("日志级别配置错误: ", err)
|
||||
}
|
||||
|
||||
// 默认开发模式
|
||||
nc := zap.NewProductionEncoderConfig()
|
||||
// json 格式输出
|
||||
cn := zapcore.NewJSONEncoder(nc)
|
||||
|
||||
// 多个输出
|
||||
mws := zapcore.NewMultiWriteSyncer(zapcore.AddSync(cls), zapcore.AddSync(config.C.Log.File), zapcore.AddSync(os.Stdout))
|
||||
|
||||
// 核心配置
|
||||
core := zapcore.NewCore(cn, mws, level)
|
||||
|
||||
// 构建 logger
|
||||
logger := zap.New(core, zap.AddCaller())
|
||||
|
||||
// 替换全局 logger
|
||||
zap.ReplaceGlobals(logger)
|
||||
}
|
||||
|
||||
func Sync() {
|
||||
zap.L().Sync()
|
||||
}
|
||||
|
||||
28
pkg/browser/browser.go
Normal file
28
pkg/browser/browser.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package browser
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/go-rod/rod"
|
||||
"github.com/go-rod/rod/lib/launcher"
|
||||
"github.com/go-rod/rod/lib/launcher/flags"
|
||||
)
|
||||
|
||||
// OpenApp 用APP模式打开网页
|
||||
func OpenApp(c context.Context, url string) {
|
||||
path, _ := launcher.LookPath()
|
||||
l := launcher.NewAppMode(url).
|
||||
Delete(flags.Env).
|
||||
Set("kiosk").
|
||||
Set("hide-scrollbars").
|
||||
Set("disable-sync").
|
||||
Set("disable-features", "GoogleSignin,IdentityConsistency,OmniboxUIExperimentation,GoogleSearch,Autofill,SafeSearch,SpeechRecognition").
|
||||
Delete("disable-site-isolation-trials").
|
||||
Bin(path)
|
||||
p := l.MustLaunch()
|
||||
defer l.Cleanup()
|
||||
|
||||
b := rod.New().ControlURL(p).MustConnect()
|
||||
defer b.MustClose()
|
||||
|
||||
<-c.Done()
|
||||
}
|
||||
65
pkg/oscx/osc.go
Normal file
65
pkg/oscx/osc.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package oscx
|
||||
|
||||
import (
|
||||
"github.com/hypebeast/go-osc/osc"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
o *osc.Client
|
||||
}
|
||||
|
||||
func New(host string, port int) *Client {
|
||||
return &Client{
|
||||
o: osc.NewClient(host, port),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) StartCue(data string) error {
|
||||
msg := osc.NewMessage("/beyond/general/StartCue", data)
|
||||
return c.o.Send(msg)
|
||||
}
|
||||
|
||||
func (c *Client) EnableLaserOutput() error {
|
||||
msg := osc.NewMessage("/beyond/general/EnableLaserOutput")
|
||||
return c.o.Send(msg)
|
||||
}
|
||||
|
||||
func (c *Client) DisableLaserOutput() error {
|
||||
msg := osc.NewMessage("/beyond/general/DisableLaserOutput")
|
||||
return c.o.Send(msg)
|
||||
}
|
||||
|
||||
func (c *Client) SetLaserOutput(data string) error {
|
||||
msg := osc.NewMessage("/beyond/general/SetLaserOutput", data)
|
||||
return c.o.Send(msg)
|
||||
}
|
||||
|
||||
func (c *Client) SetLaserOutputColor(data string) error {
|
||||
msg := osc.NewMessage("/beyond/general/SetLaserOutputColor", data)
|
||||
return c.o.Send(msg)
|
||||
}
|
||||
|
||||
func (c *Client) SetLaserOutputIntensity(data string) error {
|
||||
msg := osc.NewMessage("/beyond/general/SetLaserOutputIntensity", data)
|
||||
return c.o.Send(msg)
|
||||
}
|
||||
|
||||
func (c *Client) SetLaserOutputPosition(data string) error {
|
||||
msg := osc.NewMessage("/beyond/general/SetLaserOutputPosition", data)
|
||||
return c.o.Send(msg)
|
||||
}
|
||||
|
||||
func (c *Client) SetLaserOutputSize(data string) error {
|
||||
msg := osc.NewMessage("/beyond/general/SetLaserOutputSize", data)
|
||||
return c.o.Send(msg)
|
||||
}
|
||||
|
||||
func (c *Client) SetLaserOutputSpeed(data string) error {
|
||||
msg := osc.NewMessage("/beyond/general/SetLaserOutputSpeed", data)
|
||||
return c.o.Send(msg)
|
||||
}
|
||||
|
||||
func (c *Client) Status() error {
|
||||
msg := osc.NewMessage("/beyond/general/Status")
|
||||
return c.o.Send(msg)
|
||||
}
|
||||
234
pkg/pjlink/pjlink.go
Normal file
234
pkg/pjlink/pjlink.go
Normal file
@@ -0,0 +1,234 @@
|
||||
package pjlink
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/md5"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrAuthFailed = errors.New("授权验证失败")
|
||||
ErrCommandError = errors.New("命令执行异常")
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
Host string
|
||||
Port string
|
||||
Password string
|
||||
ID string
|
||||
conn net.Conn
|
||||
}
|
||||
|
||||
func NewClient(host, port, password, id string) *Client {
|
||||
return &Client{
|
||||
Host: host,
|
||||
Port: port,
|
||||
Password: password,
|
||||
ID: id,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) connect() error {
|
||||
address := net.JoinHostPort(c.Host, c.Port)
|
||||
conn, err := net.DialTimeout("tcp", address, 5*time.Second)
|
||||
if err != nil {
|
||||
return fmt.Errorf("连接异常: %w", err)
|
||||
}
|
||||
c.conn = conn
|
||||
|
||||
// Read challenge
|
||||
reader := bufio.NewReader(c.conn)
|
||||
response, err := reader.ReadString('\r')
|
||||
if err != nil {
|
||||
c.close()
|
||||
return fmt.Errorf("读取异常: %w", err)
|
||||
}
|
||||
|
||||
// Handle authentication
|
||||
if strings.HasPrefix(response, "PJLINK 1") {
|
||||
if c.Password == "" {
|
||||
c.close()
|
||||
return ErrAuthFailed
|
||||
}
|
||||
|
||||
challenge := strings.TrimSpace(strings.Split(response, " ")[2])
|
||||
authString := fmt.Sprintf("%s%s", challenge, c.Password)
|
||||
hashed := md5.Sum([]byte(authString))
|
||||
authHash := fmt.Sprintf("%x", hashed)
|
||||
|
||||
_, err = fmt.Fprintf(c.conn, "%s\r", authHash)
|
||||
if err != nil {
|
||||
c.close()
|
||||
return fmt.Errorf("写入异常: %w", err)
|
||||
}
|
||||
|
||||
authResponse, err := reader.ReadString('\r')
|
||||
if err != nil || !strings.Contains(authResponse, "OK") {
|
||||
c.close()
|
||||
return ErrAuthFailed
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) sendCommand(command string) (string, error) {
|
||||
if c.conn == nil {
|
||||
return "", errors.New("not connected")
|
||||
}
|
||||
|
||||
fullCommand := fmt.Sprintf("%%%s%s\r", c.ID, command)
|
||||
_, err := c.conn.Write([]byte(fullCommand))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
reader := bufio.NewReader(c.conn)
|
||||
response, err := reader.ReadString('\r')
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Remove prefix and parse response
|
||||
parts := strings.SplitN(response, "=", 2)
|
||||
if len(parts) < 2 {
|
||||
return "", ErrCommandError
|
||||
}
|
||||
|
||||
result := strings.TrimSpace(parts[1])
|
||||
if result == "ERR1" {
|
||||
return "", ErrAuthFailed
|
||||
} else if result == "ERR2" {
|
||||
return "", ErrCommandError
|
||||
} else if result == "ERR3" {
|
||||
return "YES", nil
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// PowerOn 打开投影机
|
||||
func (c *Client) PowerOn() (string, error) {
|
||||
err := c.connect()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("连接异常: %w", err)
|
||||
}
|
||||
defer c.close()
|
||||
|
||||
response, err := c.sendCommand("POWR 1")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if response == "YES" {
|
||||
return response, nil
|
||||
} else if response != "OK" {
|
||||
return response, fmt.Errorf("unexpected response: %s", response)
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// PowerOff 关闭投影机
|
||||
func (c *Client) PowerOff() (string, error) {
|
||||
err := c.connect()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("连接异常: %w", err)
|
||||
}
|
||||
defer c.close()
|
||||
|
||||
response, err := c.sendCommand("POWR 0")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if response == "YES" {
|
||||
return response, nil
|
||||
} else if response != "OK" {
|
||||
return response, fmt.Errorf("unexpected response: %s", response)
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// PowerOnSync 打开投影机
|
||||
func (c *Client) PowerOnSync() (string, error) {
|
||||
_, err := c.GetStatus()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
_, _ = c.PowerOn()
|
||||
|
||||
// 轮询检查投影机状态,直到打开成功
|
||||
for {
|
||||
status, err := c.GetStatus()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if status == "1" {
|
||||
return "投影机已打开", nil
|
||||
} else {
|
||||
// 如果投影机处于关闭状态,则尝试重新打开
|
||||
_, _ = c.PowerOn()
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
// PowerOffSync 关闭投影机
|
||||
func (c *Client) PowerOffSync() (string, error) {
|
||||
_, err := c.GetStatus()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
_, _ = c.PowerOff()
|
||||
|
||||
// 轮询检查投影机状态,直到关闭成功
|
||||
for {
|
||||
status, err := c.GetStatus()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if status == "0" {
|
||||
return "投影机已关闭", nil
|
||||
} else {
|
||||
// 如果投影机处于打开状态,则尝试重新关闭
|
||||
_, _ = c.PowerOff()
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
// GetStatus 获取投影机状态
|
||||
func (c *Client) GetStatus() (string, error) {
|
||||
err := c.connect()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("连接异常: %w", err)
|
||||
}
|
||||
defer c.close()
|
||||
|
||||
response, err := c.sendCommand("POWR ?")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if !strings.Contains("0123", response) {
|
||||
return response, fmt.Errorf("unexpected response: %s", response)
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (c *Client) close() {
|
||||
if c.conn != nil {
|
||||
c.conn.Close()
|
||||
c.conn = nil
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package main
|
||||
|
||||
import "game-driver/pkg/relay"
|
||||
|
||||
func main() {
|
||||
relay.PrintPorts()
|
||||
}
|
||||
@@ -1,9 +1,15 @@
|
||||
package relay
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/grid-x/modbus"
|
||||
"go.uber.org/zap"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Relay interface {
|
||||
@@ -56,6 +62,29 @@ func (r *device) Off(num int) error {
|
||||
func New(address string) (Relay, error) {
|
||||
zap.S().Infoln("连接继电器: ", address)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||
defer cancel()
|
||||
a := make(chan struct{})
|
||||
go func() {
|
||||
defer close(a)
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
zap.S().Infoln("等待继电器资源释放超时:", address)
|
||||
return
|
||||
case <-time.After(3 * time.Second):
|
||||
_, err := os.OpenFile(address, os.O_RDWR, 0666)
|
||||
var e *fs.PathError
|
||||
if errors.As(err, &e) && strings.Contains(e.Error(), "busy") {
|
||||
zap.S().Infoln("等待继电器资源释放:", address)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
<-a
|
||||
|
||||
h := modbus.NewRTUClientHandler(address)
|
||||
h.SlaveID = 1
|
||||
h.BaudRate = 9600
|
||||
|
||||
@@ -61,6 +61,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
|
||||
@@ -69,8 +70,9 @@ func (tts *AliTTS) getToken() error {
|
||||
|
||||
func (tts *AliTTS) Get(text string) (io.Reader, error) {
|
||||
param := nls.DefaultSpeechSynthesisParam()
|
||||
param.Volume = 100
|
||||
param.Volume = tts.Volume
|
||||
param.Voice = tts.Voice
|
||||
param.SpeechRate = tts.SpeechRate
|
||||
|
||||
err := tts.getToken()
|
||||
if err != nil {
|
||||
|
||||
@@ -1,13 +1,24 @@
|
||||
package utils
|
||||
|
||||
import "os"
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// BlankOpen 打开屏幕
|
||||
func BlankOpen() {
|
||||
if found, err := exec.LookPath("xset"); err == nil {
|
||||
exec.Command(found, "dpms", "force", "on").Run()
|
||||
return
|
||||
}
|
||||
os.WriteFile("/sys/class/graphics/fb0/blank", []byte("0"), 0644)
|
||||
}
|
||||
|
||||
// BlankClose 关闭屏幕
|
||||
func BlankClose() {
|
||||
if found, err := exec.LookPath("xset"); err == nil {
|
||||
exec.Command(found, "dpms", "force", "off").Run()
|
||||
return
|
||||
}
|
||||
os.WriteFile("/sys/class/graphics/fb0/blank", []byte("1"), 0644)
|
||||
}
|
||||
|
||||
@@ -64,7 +64,9 @@ func LinkAudio(link string) (bgm io.ReadCloser, err error) {
|
||||
} else {
|
||||
err = fmt.Errorf("不支持的链接协议: %v", u.String())
|
||||
}
|
||||
if bgm != nil {
|
||||
bgm = toSeeker(bgm)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -2,16 +2,12 @@ package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// LinkVideo 链接视频,解析链接,网络文件会下载到临时目录并返回本地路径
|
||||
func LinkVideo(link string) (local string, err error) {
|
||||
func LinkVideo(link string) (path string, local bool, err error) {
|
||||
if link == "" {
|
||||
return
|
||||
}
|
||||
@@ -20,35 +16,14 @@ func LinkVideo(link string) (local string, err error) {
|
||||
err = fmt.Errorf("URL 解析错误: %v", err)
|
||||
} else {
|
||||
if u.Scheme == "file" {
|
||||
local, _ = strings.CutPrefix(link, "file://")
|
||||
local = true
|
||||
path, _ = strings.CutPrefix(link, "file://")
|
||||
} else if u.Scheme == "http" || u.Scheme == "https" {
|
||||
p, _ := url.PathUnescape(u.EscapedPath())
|
||||
tmpLocal := path.Join(os.TempDir(), path.Base(p))
|
||||
err = Download(link, tmpLocal)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("链接文件获取失败: %v", err)
|
||||
return
|
||||
}
|
||||
local = tmpLocal
|
||||
local = false
|
||||
path = link
|
||||
} else {
|
||||
err = fmt.Errorf("不支持的链接协议: %v", u.String())
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Download 下载文件
|
||||
func Download(link string, local string) (err error) {
|
||||
resp, err := http.Get(link)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
f, err := os.OpenFile(local, os.O_CREATE|os.O_WRONLY, 0666)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
_, err = io.Copy(f, resp.Body)
|
||||
return
|
||||
}
|
||||
|
||||
74
pkg/video/play.go
Normal file
74
pkg/video/play.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package video
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
libvlc "github.com/adrg/libvlc-go/v3"
|
||||
)
|
||||
|
||||
func Play(ctx context.Context, path string, local bool) error {
|
||||
// 1. 初始化 VLC
|
||||
if err := libvlc.Init("--no-xlib"); err != nil {
|
||||
return fmt.Errorf("VLC初始化失败: %w", err)
|
||||
}
|
||||
defer libvlc.Release()
|
||||
|
||||
// 2. 创建播放器
|
||||
player, err := libvlc.NewPlayer()
|
||||
if err != nil {
|
||||
return fmt.Errorf("播放器创建失败: %w", err)
|
||||
}
|
||||
defer player.Stop()
|
||||
defer player.Release()
|
||||
|
||||
// 3. 注册结束事件
|
||||
eventManager, err := player.EventManager()
|
||||
if err != nil {
|
||||
return fmt.Errorf("事件管理器获取失败: %w", err)
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
defer close(done)
|
||||
|
||||
_, err = eventManager.Attach(libvlc.MediaPlayerEndReached, func(libvlc.Event, interface{}) {
|
||||
done <- struct{}{}
|
||||
}, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("事件绑定失败: %w", err)
|
||||
}
|
||||
|
||||
// 4. 加载并播放文件
|
||||
if local {
|
||||
if _, err := player.LoadMediaFromPath(path); err != nil {
|
||||
return fmt.Errorf("文件加载失败: %w", err)
|
||||
}
|
||||
} else {
|
||||
if _, err := player.LoadMediaFromURL(path); err != nil {
|
||||
return fmt.Errorf("文件加载失败: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := player.Play(); err != nil {
|
||||
return fmt.Errorf("播放启动失败: %w", err)
|
||||
}
|
||||
|
||||
// 设置全屏模式
|
||||
if err := player.SetFullScreen(true); err != nil {
|
||||
return fmt.Errorf("设置全屏模式失败: %w", err)
|
||||
}
|
||||
|
||||
// 设置音量为最大
|
||||
if err := player.SetVolume(100); err != nil {
|
||||
return fmt.Errorf("设置音量失败: %w", err)
|
||||
}
|
||||
|
||||
// 5. 等待事件
|
||||
fmt.Printf("正在播放: %s\n", path)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return fmt.Errorf("播放被用户中断")
|
||||
case <-done:
|
||||
fmt.Println("播放正常结束")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
package video
|
||||
package video_old
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"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 {
|
||||
@@ -23,16 +29,14 @@ func Play(ctx context.Context, file string) error {
|
||||
var wait sync.WaitGroup
|
||||
defer wait.Wait()
|
||||
|
||||
a := make(chan struct{})
|
||||
defer close(a)
|
||||
|
||||
wait.Add(1)
|
||||
go func() {
|
||||
defer wait.Done()
|
||||
reader := bufio.NewReader(pipe)
|
||||
for {
|
||||
select {
|
||||
case <-a:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
line, _, err := reader.ReadLine()
|
||||
if err != nil {
|
||||
63
puml/游戏.puml
63
puml/游戏.puml
@@ -1,43 +1,46 @@
|
||||
@startmindmap 游戏
|
||||
+ 游戏
|
||||
++ 入口(0)
|
||||
+++ Game
|
||||
++++ bgm
|
||||
++++ tts播报欢迎词
|
||||
++ 击缶台(1)
|
||||
+++ 待机
|
||||
++++ 背景音乐
|
||||
++++ bgm
|
||||
+++ Game
|
||||
++++ 播放欢迎语音
|
||||
++ 一阶段门头(无)
|
||||
++ 召唤神女(1)
|
||||
+++ Game
|
||||
++++ 等待结束
|
||||
++ 神女低语(无)
|
||||
+++ 按下按钮
|
||||
+++ 播放语音
|
||||
+++ 发送结果数据
|
||||
++ 镇水神力(2)
|
||||
++++ bgm
|
||||
++++ 供电
|
||||
++++ tts 播报游客名
|
||||
++++ tts 播报游戏过半
|
||||
++++ tts 游戏结束
|
||||
++ 镇水塔(2)
|
||||
+++ 待机
|
||||
++++ 投影仪待机
|
||||
++++ bgm
|
||||
++++ 投影仪待机(晚上)
|
||||
+++ Game
|
||||
++++ tts 播报恭喜通关词
|
||||
++++ 播放法阵视频
|
||||
-- 二阶段门头(无)
|
||||
-- 神女除妖(3)
|
||||
--- Game
|
||||
---- 控制设备启动
|
||||
---- 接收游戏结果
|
||||
---- 发送结果数据
|
||||
-- 流光寻踪(无)
|
||||
-- 神女授书(4)
|
||||
-- 镇压异兽(3)
|
||||
--- 待机
|
||||
---- bgm
|
||||
--- Game
|
||||
---- 控制设备吐卡
|
||||
---- 播放取卡提示语音
|
||||
---- 异常状态处理
|
||||
-- 青云龙台(5)
|
||||
---- bgm
|
||||
---- tts 播报恭喜通关词
|
||||
-- 神女影像(4)
|
||||
--- 待机
|
||||
---- bgm
|
||||
--- Game
|
||||
---- bgm
|
||||
---- tts 播报恭喜通关词
|
||||
-- 登龙云台(5)
|
||||
--- 待机
|
||||
---- bgm
|
||||
--- Game
|
||||
---- 供电
|
||||
---- bgm
|
||||
---- 激光控制
|
||||
-- 俱乐部(6)
|
||||
--- 待机
|
||||
---- 启动屏幕
|
||||
---- 网页默认页面
|
||||
--- Game
|
||||
---- 等待卡片插入
|
||||
---- 播放恭喜语音
|
||||
---- 屏幕恭喜页面
|
||||
---- 灯光播放
|
||||
---- 结束屏幕提示
|
||||
@endmindmap
|
||||
|
||||
58
readme.md
58
readme.md
@@ -22,13 +22,15 @@ Payload:
|
||||
"default-print": "",
|
||||
// 文本转语音整体控制
|
||||
"tts": {
|
||||
// 开始语音
|
||||
// 开始播报语音
|
||||
"start": "",
|
||||
// 结束语音
|
||||
// 超时自动停止时播报语音
|
||||
"timeout": "",
|
||||
// 结束播报语音
|
||||
"end": "",
|
||||
// 终止语音
|
||||
// 终止播报语音
|
||||
"stop": "",
|
||||
// 固定节点语音
|
||||
// 固定节点播报语音
|
||||
"timer": [
|
||||
{
|
||||
// 时间节点(s)
|
||||
@@ -125,22 +127,24 @@ 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
|
||||
// 持续时长(s), 待机任务执行时持续的时长。为 0 表示 音频、视频、TTS 按播放时长,继电器、网页、投影仪、激光秀持续整个时间段。 default 0
|
||||
"duration": 0,
|
||||
// 事件类型(0: 音频; 1: 视频; 2: TTS; 3: 继电器; 4: 网页; 5: 投影仪; 6: 激光秀;) default 0
|
||||
"type": 2,
|
||||
// 事件数据(TTS为文字, 继电器为端口号, 其他都为地址链接。支持 file:// 本地文件地址、 http(s):// 远程文件地址)
|
||||
// Game 指令执行时是否暂停。default false
|
||||
"pause": true,
|
||||
// 待机任务执行时,是否锁定设备。default false
|
||||
"lock": false,
|
||||
// 事件数据(TTS为文字, 继电器为端口号,激光秀为节目名,投影仪忽略该参数, 其他都为地址链接。支持 file:// 本地文件地址、 http(s):// 远程文件地址)
|
||||
"data": "",
|
||||
},
|
||||
...
|
||||
@@ -149,3 +153,29 @@ Payload:
|
||||
```
|
||||
|
||||
> 同一个类型的待机任务只能有一个,当有新的任务到达时会覆盖之前的任务
|
||||
|
||||
### 配置时区
|
||||
```bash
|
||||
sudo timedatectl set-timezone Asia/Shanghai
|
||||
```
|
||||
|
||||
### 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
|
||||
|
||||
160
todo.md
160
todo.md
@@ -1,42 +1,66 @@
|
||||
## 技术点记录
|
||||
# 技术点记录
|
||||
|
||||
### ubuntu 24 开机慢优化
|
||||
|
||||
```bash
|
||||
# 在 systemd-networkd-wait-online.service Service 加入 TimeoutStartSec=2sec
|
||||
sudo vim /etc/systemd/system/network-online.target.wants/systemd-networkd-wait-online.service
|
||||
```
|
||||
|
||||
### 配置时区
|
||||
```bash
|
||||
sudo timedatectl set-timezone Asia/Shanghai
|
||||
```
|
||||
|
||||
## linux 下播放音频
|
||||
|
||||
1. linux 下播放音频
|
||||
```bash
|
||||
sudo apt install libasound2-dev alsa-utils
|
||||
```
|
||||
2. linux 下播放视频
|
||||
```bash
|
||||
sudo apt install ffmpeg
|
||||
```
|
||||
驱动安装
|
||||
```bash
|
||||
sudo apt install libdirectfb-dev
|
||||
```
|
||||
3. 当前用户加入播放音频与视频的组中
|
||||
```bash
|
||||
sudo usermod -aG audio,video $USER
|
||||
```
|
||||
|
||||
### 关闭屏幕帧缓冲
|
||||
## linux 下播放视频
|
||||
|
||||
```bash
|
||||
# 关闭帧缓冲设备
|
||||
echo 1 | sudo tee /sys/class/graphics/fb0/blank
|
||||
# 重新打开帧缓冲设备
|
||||
echo 0 | sudo tee /sys/class/graphics/fb0/blank
|
||||
sudo apt install libvlc-dev vlc
|
||||
```
|
||||
|
||||
### 播放视频
|
||||
## 显示安装
|
||||
|
||||
```bash
|
||||
ffplay -autoexit -fs -i video.mp4
|
||||
sudo apt install xorg
|
||||
```
|
||||
|
||||
### 编译 arm64 架构
|
||||
### 当前用户加入播放音频与视频的组中
|
||||
|
||||
```bash
|
||||
sudo usermod -aG audio,video,dialout $USER
|
||||
```
|
||||
|
||||
### 关闭背光
|
||||
|
||||
```bash
|
||||
# xorg 环境,关闭背光
|
||||
xset dpms force off
|
||||
# xorg 环境,重新打开背光
|
||||
xset dpms force on
|
||||
```
|
||||
|
||||
### 注册为 service ,并开机启动
|
||||
|
||||
```bash
|
||||
sudo cp /script/game-driver.service /etc/systemd/system/
|
||||
sudo systemctl enable game-driver
|
||||
sudo systemctl start game-driver
|
||||
```
|
||||
|
||||
## 编译 arm64 架构
|
||||
|
||||
```bash
|
||||
CC=aarch64-linux-gnu-gcc CGO_ENABLED=1 GOOS=linux GOARCH=arm64 go build -o game-driver-arm64 .
|
||||
```
|
||||
|
||||
### J8引脚
|
||||
## J8引脚
|
||||
|
||||
```bash
|
||||
J8:
|
||||
3V3 (1) (2) 5V
|
||||
@@ -61,3 +85,93 @@ GPIO26 (37) (38) GPIO20
|
||||
GND (39) (40) GPIO21
|
||||
```
|
||||
|
||||
## 极简桌面环境并自动登录
|
||||
|
||||
### 安装 xorg i3
|
||||
|
||||
```bash
|
||||
sudo apt install xorg i3-wm
|
||||
```
|
||||
|
||||
### 自动启动 Xorg 和窗口管理器
|
||||
|
||||
编辑 `.bashrc`文件,在文件的末尾添加以下行:
|
||||
|
||||
```bash
|
||||
if [ -z "$DISPLAY" ] && [ "$(tty)" = "/dev/tty1" ]; then
|
||||
startx
|
||||
fi
|
||||
```
|
||||
|
||||
这会在你登录后,自动启动 Xorg 和窗口管理器。该脚本检查当前是否在 tty1 控制台(默认终端)上
|
||||
|
||||
### 自动登录
|
||||
|
||||
编辑 `/etc/systemd/system/getty.target.wants/getty@tty1.service` 文件,将 `ExecStart` 行修改为:
|
||||
|
||||
```bash
|
||||
ExecStart=-/sbin/agetty --autologin <your_username> --noclear %I $TERM
|
||||
```
|
||||
|
||||
其中:
|
||||
|
||||
- <your_username>:替换为你想自动登录的用户名。
|
||||
|
||||
### 禁用 i3bar 状态栏
|
||||
|
||||
编辑 `~/.config/i3/config`,将如下行注释掉:
|
||||
|
||||
```bash
|
||||
# bar {
|
||||
# status_command i3status
|
||||
# }
|
||||
```
|
||||
|
||||
如果没有 `~/.config/i3/config` 文件,需要重启系统,进入 i3 后,根据提示创建默认的配置文件
|
||||
|
||||
### 配置 i3
|
||||
|
||||
安装 `unclutter`:
|
||||
|
||||
```bash
|
||||
sudo apt install unclutter
|
||||
```
|
||||
|
||||
编辑 `~/.config/i3/config`,添加如下行:
|
||||
|
||||
```bash
|
||||
exec --no-startup-id unclutter -root # 隐藏鼠标
|
||||
exec --no-startup-id xset dpms 0 0 0 # 关闭屏幕自动关闭
|
||||
exec --no-startup-id xset s off # 关闭屏幕保护
|
||||
```
|
||||
|
||||
不使用 `xset -dpms` 来禁用屏幕自动关闭,因为当使用 `xset dpms force off` 或 `xset dpms force on` 时,会导致禁用失效
|
||||
|
||||
### 安装中文字体和 emoji 字体
|
||||
|
||||
```bash
|
||||
sudo apt install fonts-noto-cjk fonts-noto-color-emoji
|
||||
```
|
||||
|
||||
## 安装浏览器作为默认启动页面
|
||||
|
||||
### 安装浏览器
|
||||
|
||||
```bash
|
||||
sudo add-apt-repository ppa:xtradeb/apps
|
||||
sudo apt install ungoogled-chromium
|
||||
```
|
||||
|
||||
### 设置默认启动页面
|
||||
|
||||
编辑 `~/.config/i3/config`,添加如下行:
|
||||
|
||||
```bash
|
||||
exec --no-startup-id ungoogled-chromium --kiosk --disable-extensions --disable-translate --app=<your_url>
|
||||
```
|
||||
|
||||
### 设置系统默认音量
|
||||
|
||||
输入命令 `alsamixer` 进入音量控制界面,调节 `master` 后,按 `esc` 退出
|
||||
|
||||
然后输入 `sudo alsactl store` 保存音量设置
|
||||
|
||||
24
vpn.md
Normal file
24
vpn.md
Normal file
@@ -0,0 +1,24 @@
|
||||
## 安装配置 vpn
|
||||
|
||||
### 安装 wireguard
|
||||
|
||||
```bash
|
||||
sudo apt install wireguard wireguard-tools -y
|
||||
```
|
||||
|
||||
### 配置 wireguard
|
||||
|
||||
从服务器获取配置文件,保存到 `/etc/wireguard/wg0.conf`,并修改配置文件
|
||||
|
||||
> Interface 的 DNS 移除掉,不要配置
|
||||
|
||||
```bash
|
||||
sudo vim /etc/wireguard/wg0.conf
|
||||
```
|
||||
|
||||
### 开启 wireguard
|
||||
|
||||
```bash
|
||||
sudo systemctl enable wg-quick@wg0
|
||||
sudo systemctl start wg-quick@wg0
|
||||
```
|
||||
Reference in New Issue
Block a user