diff --git a/cmd/root.go b/cmd/root.go index dd1da4d..b754c2b 100644 --- a/cmd/root.go +++ b/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" @@ -70,11 +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) } } + + // 初始化游戏节点待机时配置 + 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) + } + } } diff --git a/config.yml b/config.yml index f404df6..fab929e 100755 --- a/config.yml +++ b/config.yml @@ -4,6 +4,11 @@ relay: maxTimeout: 60 # 单位 s,必须大于 0 log: level: debug + tencentCLS: + endpoint: + secretID: + secretKey: + topicID: file: filename: logs/app.log maxSize: 10 @@ -20,6 +25,11 @@ aliyun: volume: 100 # 音量,取值范围:0~100 voice: zhifeng_emo # 发音人 speechRate: 50 # 语速,取值范围:-500~500 +wait: + ip: + port: + password: + id: #game: # addr: /dev/ttyUSB0 # 点位 11 的串口地址 # pushGroups: # 点位 10 的发卡器配置 diff --git a/config/game/game.go b/config/game/game.go index 9861178..daad170 100644 --- a/config/game/game.go +++ b/config/game/game.go @@ -13,4 +13,4 @@ func NewConfig(point int) Config { } } -var G Config +var C Config diff --git a/config/wait/pjlink.go b/config/wait/pjlink.go new file mode 100644 index 0000000..a4ff981 --- /dev/null +++ b/config/wait/pjlink.go @@ -0,0 +1,8 @@ +package wait + +type PJLink struct { + Ip string + Port string + Password string + Id string +} diff --git a/config/wait/wait.go b/config/wait/wait.go new file mode 100644 index 0000000..0b0a1e3 --- /dev/null +++ b/config/wait/wait.go @@ -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 diff --git a/internal/routes/play/push_card.go b/internal/routes/play/push_card.go index 9f7d93b..6aea43e 100644 --- a/internal/routes/play/push_card.go +++ b/internal/routes/play/push_card.go @@ -28,7 +28,7 @@ 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)) diff --git a/internal/routes/play/wait_card.go b/internal/routes/play/wait_card.go index 8d5ce41..74133f6 100644 --- a/internal/routes/play/wait_card.go +++ b/internal/routes/play/wait_card.go @@ -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 { diff --git a/internal/routes/wait.go b/internal/routes/wait.go index 189b285..47ca1ec 100644 --- a/internal/routes/wait.go +++ b/internal/routes/wait.go @@ -3,11 +3,13 @@ package routes import ( "context" "fmt" + "game-driver/config/wait" "game-driver/internal/middleware" "game-driver/internal/schema" "game-driver/leaf" "game-driver/pkg/audio" "game-driver/pkg/browser" + "game-driver/pkg/pjlink" "game-driver/pkg/relay" "game-driver/pkg/tts" "game-driver/pkg/utils" @@ -151,6 +153,13 @@ func WaitAction(c *leaf.Context) { defer wait.Done() runAction(c, item, rules, webAction) }() + case schema.WaitPJLink: + // 执行投影仪打开 + wait.Add(1) + go func() { + defer wait.Done() + runAction(c, item, rules, pjlinkAction) + }() default: zap.S().Infof("不支持的类型: %d\n", item.Type) } @@ -255,3 +264,29 @@ func webAction(c context.Context, item schema.WaitItemModel) error { browser.OpenApp(c, item.Data) return nil } + +func pjlinkAction(c context.Context, _ schema.WaitItemModel) error { + cfg := (wait.C).(wait.PJLink) + pjc := pjlink.NewClient(cfg.Ip, cfg.Port, cfg.Password, cfg.Id) + err := pjc.Connect() + if err != nil { + return fmt.Errorf("连接 PJLink 设备异常: %w", err) + } + defer pjc.Close() + + zap.S().Infoln("打开待机投影仪") + err = pjc.PowerOn() + if err != nil { + return fmt.Errorf("打开投影仪异常: %w", err) + } + + <-c.Done() + + zap.S().Infoln("关闭待机投影仪") + err = pjc.PowerOff() + if err != nil { + return fmt.Errorf("关闭投影仪异常: %w", err) + } + + return nil +} diff --git a/internal/schema/wait.go b/internal/schema/wait.go index 8ebeb96..a263603 100644 --- a/internal/schema/wait.go +++ b/internal/schema/wait.go @@ -8,6 +8,7 @@ const ( WaitTTS WaitRelay WaitWeb + WaitPJLink ) type WaitItemModel struct { diff --git a/json.md b/json.md index 0cea813..c748d4a 100644 --- a/json.md +++ b/json.md @@ -5,7 +5,7 @@ url: `server/wushan/2/wait` ```json { - "cron": "* * * *", + "cron": "08:00-22:00 * * *", "items": [ { "data": "file://./三峡龙脊BGM.mp3" @@ -35,18 +35,36 @@ url: `server/wushan/2/play` } ``` -### STOP 待机 +## 点位5(登龙云台) +### 待机 -url: `server/wushan/2/command` +url: `server/wushan/5/wait` -```text -stop-bg +```json +{ + "cron": "08:00-22:00 * * *", + "items": [ + { + "data": "file://./三峡龙脊BGM.mp3" + } + ] +} ``` -### STOP GAME +### Game -url: `server/wushan/2/command` +url: `server/wushan/5/play` -```text -stop -``` \ No newline at end of file +```json +{ + "bgm": "file://./青云龙台.mp3", + "power": true, + "volume":1, + "tts": { + "start": "刘佳勇者,恭喜你成功通关!" + }, + "game": { + "wait": 10 + } +} +``` diff --git a/pkg/pjlink/pjlink.go b/pkg/pjlink/pjlink.go new file mode 100644 index 0000000..60000ae --- /dev/null +++ b/pkg/pjlink/pjlink.go @@ -0,0 +1,141 @@ +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 err + } + c.conn = conn + + // Read challenge + reader := bufio.NewReader(c.conn) + response, err := reader.ReadString('\r') + if err != nil { + c.Close() + return 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 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 + } + + return result, nil +} + +func (c *Client) PowerOn() error { + response, err := c.sendCommand("POWR 1") + if err != nil { + return err + } + + if response != "OK" { + return fmt.Errorf("unexpected response: %s", response) + } + return nil +} + +func (c *Client) PowerOff() error { + response, err := c.sendCommand("POWR 0") + if err != nil { + return err + } + + if response != "OK" { + return fmt.Errorf("unexpected response: %s", response) + } + return nil +} + +func (c *Client) Close() { + if c.conn != nil { + c.conn.Close() + c.conn = nil + } +}