加入 pjlink 控制
This commit is contained in:
16
cmd/root.go
16
cmd/root.go
@@ -7,6 +7,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"game-driver/config"
|
"game-driver/config"
|
||||||
"game-driver/config/game"
|
"game-driver/config/game"
|
||||||
|
"game-driver/config/wait"
|
||||||
"game-driver/internal"
|
"game-driver/internal"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"log"
|
"log"
|
||||||
@@ -70,11 +71,20 @@ func initConfig() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 初始化游戏节点配置
|
// 初始化游戏节点配置
|
||||||
game.G = game.NewConfig(config.C.Point)
|
game.C = game.NewConfig(config.C.Point)
|
||||||
if game.G != nil { // 如果需要游戏配置
|
if game.C != nil { // 如果需要游戏配置
|
||||||
err = viper.UnmarshalKey("game", &game.G)
|
err = viper.UnmarshalKey("game", &game.C)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panicln("unmarshal game config failed: ", err)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
10
config.yml
10
config.yml
@@ -4,6 +4,11 @@ relay:
|
|||||||
maxTimeout: 60 # 单位 s,必须大于 0
|
maxTimeout: 60 # 单位 s,必须大于 0
|
||||||
log:
|
log:
|
||||||
level: debug
|
level: debug
|
||||||
|
tencentCLS:
|
||||||
|
endpoint:
|
||||||
|
secretID:
|
||||||
|
secretKey:
|
||||||
|
topicID:
|
||||||
file:
|
file:
|
||||||
filename: logs/app.log
|
filename: logs/app.log
|
||||||
maxSize: 10
|
maxSize: 10
|
||||||
@@ -20,6 +25,11 @@ aliyun:
|
|||||||
volume: 100 # 音量,取值范围:0~100
|
volume: 100 # 音量,取值范围:0~100
|
||||||
voice: zhifeng_emo # 发音人
|
voice: zhifeng_emo # 发音人
|
||||||
speechRate: 50 # 语速,取值范围:-500~500
|
speechRate: 50 # 语速,取值范围:-500~500
|
||||||
|
wait:
|
||||||
|
ip:
|
||||||
|
port:
|
||||||
|
password:
|
||||||
|
id:
|
||||||
#game:
|
#game:
|
||||||
# addr: /dev/ttyUSB0 # 点位 11 的串口地址
|
# addr: /dev/ttyUSB0 # 点位 11 的串口地址
|
||||||
# pushGroups: # 点位 10 的发卡器配置
|
# pushGroups: # 点位 10 的发卡器配置
|
||||||
|
|||||||
@@ -13,4 +13,4 @@ func NewConfig(point int) Config {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var G Config
|
var C Config
|
||||||
|
|||||||
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
|
||||||
@@ -28,7 +28,7 @@ type ResponseBody struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func PushCard(ctx context.Context) leaf.HandlerFunc {
|
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))
|
readers := make([]card_reader.Reader, len(g.PushGroups))
|
||||||
devices := make([]*card_pusher.Device, len(g.PushGroups))
|
devices := make([]*card_pusher.Device, len(g.PushGroups))
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func WaitCard(ctx context.Context) leaf.HandlerFunc {
|
func WaitCard(ctx context.Context) leaf.HandlerFunc {
|
||||||
g := (game.G).(game.ConfigWait)
|
g := (game.C).(game.ConfigWait)
|
||||||
|
|
||||||
reader, err := card_reader.NewReader(g.Addr)
|
reader, err := card_reader.NewReader(g.Addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -3,11 +3,13 @@ package routes
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"game-driver/config/wait"
|
||||||
"game-driver/internal/middleware"
|
"game-driver/internal/middleware"
|
||||||
"game-driver/internal/schema"
|
"game-driver/internal/schema"
|
||||||
"game-driver/leaf"
|
"game-driver/leaf"
|
||||||
"game-driver/pkg/audio"
|
"game-driver/pkg/audio"
|
||||||
"game-driver/pkg/browser"
|
"game-driver/pkg/browser"
|
||||||
|
"game-driver/pkg/pjlink"
|
||||||
"game-driver/pkg/relay"
|
"game-driver/pkg/relay"
|
||||||
"game-driver/pkg/tts"
|
"game-driver/pkg/tts"
|
||||||
"game-driver/pkg/utils"
|
"game-driver/pkg/utils"
|
||||||
@@ -151,6 +153,13 @@ func WaitAction(c *leaf.Context) {
|
|||||||
defer wait.Done()
|
defer wait.Done()
|
||||||
runAction(c, item, rules, webAction)
|
runAction(c, item, rules, webAction)
|
||||||
}()
|
}()
|
||||||
|
case schema.WaitPJLink:
|
||||||
|
// 执行投影仪打开
|
||||||
|
wait.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wait.Done()
|
||||||
|
runAction(c, item, rules, pjlinkAction)
|
||||||
|
}()
|
||||||
default:
|
default:
|
||||||
zap.S().Infof("不支持的类型: %d\n", item.Type)
|
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)
|
browser.OpenApp(c, item.Data)
|
||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ const (
|
|||||||
WaitTTS
|
WaitTTS
|
||||||
WaitRelay
|
WaitRelay
|
||||||
WaitWeb
|
WaitWeb
|
||||||
|
WaitPJLink
|
||||||
)
|
)
|
||||||
|
|
||||||
type WaitItemModel struct {
|
type WaitItemModel struct {
|
||||||
|
|||||||
38
json.md
38
json.md
@@ -5,7 +5,7 @@ url: `server/wushan/2/wait`
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"cron": "* * * *",
|
"cron": "08:00-22:00 * * *",
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"data": "file://./三峡龙脊BGM.mp3"
|
"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
|
```json
|
||||||
stop-bg
|
{
|
||||||
|
"cron": "08:00-22:00 * * *",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"data": "file://./三峡龙脊BGM.mp3"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### STOP GAME
|
### Game
|
||||||
|
|
||||||
url: `server/wushan/2/command`
|
url: `server/wushan/5/play`
|
||||||
|
|
||||||
```text
|
```json
|
||||||
stop
|
{
|
||||||
```
|
"bgm": "file://./青云龙台.mp3",
|
||||||
|
"power": true,
|
||||||
|
"volume":1,
|
||||||
|
"tts": {
|
||||||
|
"start": "刘佳勇者,恭喜你成功通关!"
|
||||||
|
},
|
||||||
|
"game": {
|
||||||
|
"wait": 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|||||||
141
pkg/pjlink/pjlink.go
Normal file
141
pkg/pjlink/pjlink.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user