增加待机报文缓存,无网状态也能执行待机任务;投影仪指令结果以设备状态为准
This commit is contained in:
@@ -22,7 +22,7 @@ var cfgFile string
|
|||||||
// rootCmd represents the base command when called without any subcommands
|
// rootCmd represents the base command when called without any subcommands
|
||||||
var rootCmd = &cobra.Command{
|
var rootCmd = &cobra.Command{
|
||||||
Use: "game-driver",
|
Use: "game-driver",
|
||||||
Version: "1.0.0",
|
Version: "1.0.1",
|
||||||
Short: "A brief description of your application",
|
Short: "A brief description of your application",
|
||||||
Long: `A longer description that spans multiple lines and likely contains
|
Long: `A longer description that spans multiple lines and likely contains
|
||||||
examples and usage of using your application. For example:
|
examples and usage of using your application. For example:
|
||||||
|
|||||||
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
|
||||||
|
}
|
||||||
@@ -41,6 +41,7 @@ type config struct {
|
|||||||
Mqtt MqttConfig
|
Mqtt MqttConfig
|
||||||
Aliyun AliyunConfig
|
Aliyun AliyunConfig
|
||||||
MaxTimeout int
|
MaxTimeout int
|
||||||
|
StandbyCache Cache
|
||||||
}
|
}
|
||||||
|
|
||||||
var C config
|
var C config
|
||||||
|
|||||||
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>:替换为你想自动登录的用户名。
|
||||||
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,8 +13,8 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
// WaitAction 待机任务,支持音乐、TTS、继电器、视频、网页、投影仪、大型激光秀 ctrl
|
// StandbyAction 待机任务,支持音乐、TTS、继电器、视频、网页、投影仪、大型激光秀 ctrl
|
||||||
func WaitAction(ctrl *common.CtrlWait, device *common.Device) leaf.HandlerFunc {
|
func StandbyAction(ctrl *common.CtrlWait, device *common.Device) leaf.HandlerFunc {
|
||||||
ps := common.NewPauseSub(ctrl)
|
ps := common.NewPauseSub(ctrl)
|
||||||
|
|
||||||
return func(c *leaf.Context) {
|
return func(c *leaf.Context) {
|
||||||
@@ -15,20 +15,20 @@ func PJLink(_ schema.WaitItemModel) func(c context.Context) error {
|
|||||||
pjc := pjlink.NewClient(cfg.Ip, cfg.Port, cfg.Password, cfg.Id)
|
pjc := pjlink.NewClient(cfg.Ip, cfg.Port, cfg.Password, cfg.Id)
|
||||||
|
|
||||||
zap.S().Infoln("打开待机投影仪")
|
zap.S().Infoln("打开待机投影仪")
|
||||||
resp, err := pjc.PowerOn()
|
resp, err := pjc.PowerOnSync()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("打开投影仪异常: %w", err)
|
return fmt.Errorf("打开投影仪异常: %w", err)
|
||||||
}
|
}
|
||||||
zap.S().Infoln("投影仪返回报文:", resp)
|
zap.S().Infoln("打开投影仪结果:", resp)
|
||||||
|
|
||||||
<-c.Done()
|
<-c.Done()
|
||||||
|
|
||||||
zap.S().Infoln("关闭待机投影仪")
|
zap.S().Infoln("关闭待机投影仪")
|
||||||
resp, err = pjc.PowerOff()
|
resp, err = pjc.PowerOffSync()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("关闭投影仪异常: %w", err)
|
return fmt.Errorf("关闭投影仪异常: %w", err)
|
||||||
}
|
}
|
||||||
zap.S().Infoln("投影仪返回报文:", resp)
|
zap.S().Infoln("关闭投影仪结果:", resp)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Device 设备锁定控制器
|
||||||
func Device(d *common.Device, lock bool, play func(c context.Context) error) func(c context.Context) error {
|
func Device(d *common.Device, lock bool, play func(c context.Context) error) func(c context.Context) error {
|
||||||
return func(c context.Context) error {
|
return func(c context.Context) error {
|
||||||
if lock {
|
if lock {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Interval 循环间隔控制器
|
||||||
func Interval(interval int64, play func(c context.Context) error) func(c context.Context) error {
|
func Interval(interval int64, play func(c context.Context) error) func(c context.Context) error {
|
||||||
return func(c context.Context) error {
|
return func(c context.Context) error {
|
||||||
zap.S().Infoln("待机间隔控制器: ", interval)
|
zap.S().Infoln("待机间隔控制器: ", interval)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Pause 暂停控制器
|
||||||
func Pause(ps *common.PauseSub, isPause bool, play func(c context.Context) error) func(c context.Context) error {
|
func Pause(ps *common.PauseSub, isPause bool, play func(c context.Context) error) func(c context.Context) error {
|
||||||
return func(c context.Context) error {
|
return func(c context.Context) error {
|
||||||
var cancel context.CancelFunc
|
var cancel context.CancelFunc
|
||||||
|
|||||||
@@ -13,9 +13,6 @@ import (
|
|||||||
"game-driver/pkg/relay"
|
"game-driver/pkg/relay"
|
||||||
"game-driver/pkg/tts"
|
"game-driver/pkg/tts"
|
||||||
"game-driver/pkg/utils"
|
"game-driver/pkg/utils"
|
||||||
"github.com/eclipse/paho.golang/autopaho"
|
|
||||||
"github.com/eclipse/paho.golang/paho"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -23,6 +20,10 @@ import (
|
|||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"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 {
|
func buildMqtt(c config.MqttConfig, r *leaf.Engine, subTopics ...string) autopaho.ClientConfig {
|
||||||
@@ -153,9 +154,10 @@ func Run() {
|
|||||||
router.RegisterHandler(topicPrefix+"wait",
|
router.RegisterHandler(topicPrefix+"wait",
|
||||||
middleware.RunLog(),
|
middleware.RunLog(),
|
||||||
middleware.PayloadJSON[schema.WaitModel](),
|
middleware.PayloadJSON[schema.WaitModel](),
|
||||||
|
middleware.Cache(config.C.StandbyCache),
|
||||||
middleware.Unique(common.GlobalBgStopper),
|
middleware.Unique(common.GlobalBgStopper),
|
||||||
middleware.EmergencyStop(common.GlobalBgStopper),
|
middleware.EmergencyStop(common.GlobalBgStopper),
|
||||||
routes.WaitAction(common.PassCtrl, device),
|
routes.StandbyAction(common.PassCtrl, device),
|
||||||
)
|
)
|
||||||
// 处理指令
|
// 处理指令
|
||||||
router.RegisterHandler(topicPrefix+"command",
|
router.RegisterHandler(topicPrefix+"command",
|
||||||
@@ -163,13 +165,21 @@ func Run() {
|
|||||||
routes.Command(device),
|
routes.Command(device),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 从缓存中读取待机报文,如果存在则直接执行
|
||||||
|
publish, err := config.C.StandbyCache.Get()
|
||||||
|
if err != nil {
|
||||||
|
zap.S().Infoln("读取待机缓存失败: ", err)
|
||||||
|
} else {
|
||||||
|
router.HandlerRun(topicPrefix+"wait", publish)
|
||||||
|
}
|
||||||
|
|
||||||
// 构建 MQTT 连接
|
// 构建 MQTT 连接
|
||||||
mqttBuild := buildMqtt(config.C.Mqtt, router, topicPrefix+"#")
|
mqttBuild := buildMqtt(config.C.Mqtt, router, topicPrefix+"#")
|
||||||
|
|
||||||
// 连接 MQTT
|
// 开始连接 MQTT
|
||||||
cm, err := autopaho.NewConnection(ctx, mqttBuild)
|
cm, err := autopaho.NewConnection(ctx, mqttBuild)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zap.S().Panicln("连接 MQTT 异常: ", err)
|
zap.S().Panicln("创建 MQTT 连接器异常: ", err)
|
||||||
}
|
}
|
||||||
utils.GlobalMqttClient = cm
|
utils.GlobalMqttClient = cm
|
||||||
|
|
||||||
|
|||||||
29
leaf/leaf.go
29
leaf/leaf.go
@@ -12,6 +12,7 @@ type Router interface {
|
|||||||
RegisterHandler(string, ...HandlerFunc)
|
RegisterHandler(string, ...HandlerFunc)
|
||||||
UnregisterHandler(string)
|
UnregisterHandler(string)
|
||||||
Route(*packets.Publish)
|
Route(*packets.Publish)
|
||||||
|
HandlerRun(t string, p *paho.Publish) bool
|
||||||
SetDebugLogger(log.Logger)
|
SetDebugLogger(log.Logger)
|
||||||
Use(...HandlerFunc)
|
Use(...HandlerFunc)
|
||||||
}
|
}
|
||||||
@@ -92,6 +93,21 @@ func (e *Engine) UnregisterHandler(topic string) {
|
|||||||
delete(e.subscriptions, topic)
|
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) {
|
func (e *Engine) Route(pb *packets.Publish) {
|
||||||
e.debug.Println("routing message for:", pb.Topic)
|
e.debug.Println("routing message for:", pb.Topic)
|
||||||
e.mu.Lock()
|
e.mu.Lock()
|
||||||
@@ -115,18 +131,7 @@ func (e *Engine) Route(pb *packets.Publish) {
|
|||||||
topic = m.Topic
|
topic = m.Topic
|
||||||
}
|
}
|
||||||
|
|
||||||
handlerCalled := false
|
handlerCalled := e.HandlerRun(topic, m)
|
||||||
for route, handlers := range e.subscriptions {
|
|
||||||
if match(route, topic) {
|
|
||||||
e.debug.Println("found handler for:", route)
|
|
||||||
e.queueWg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer e.queueWg.Done()
|
|
||||||
WithLeafContext(e.ctx, m, e, handlers).Next()
|
|
||||||
}()
|
|
||||||
handlerCalled = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !handlerCalled && e.defaultHandler != nil {
|
if !handlerCalled && e.defaultHandler != nil {
|
||||||
e.queueWg.Add(1)
|
e.queueWg.Add(1)
|
||||||
|
|||||||
@@ -111,6 +111,7 @@ func (c *Client) sendCommand(command string) (string, error) {
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PowerOn 打开投影机
|
||||||
func (c *Client) PowerOn() (string, error) {
|
func (c *Client) PowerOn() (string, error) {
|
||||||
err := c.connect()
|
err := c.connect()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -131,6 +132,7 @@ func (c *Client) PowerOn() (string, error) {
|
|||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PowerOff 关闭投影机
|
||||||
func (c *Client) PowerOff() (string, error) {
|
func (c *Client) PowerOff() (string, error) {
|
||||||
err := c.connect()
|
err := c.connect()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -151,6 +153,79 @@ func (c *Client) PowerOff() (string, error) {
|
|||||||
return response, nil
|
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() {
|
func (c *Client) close() {
|
||||||
if c.conn != nil {
|
if c.conn != nil {
|
||||||
c.conn.Close()
|
c.conn.Close()
|
||||||
|
|||||||
Reference in New Issue
Block a user