diff --git a/cmd/root.go b/cmd/root.go index b754c2b..af1ac93 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -22,7 +22,7 @@ var cfgFile string // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ Use: "game-driver", - Version: "1.0.0", + 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: diff --git a/config/cache_publish.go b/config/cache_publish.go new file mode 100644 index 0000000..6216dff --- /dev/null +++ b/config/cache_publish.go @@ -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 +} diff --git a/config/config.go b/config/config.go index 91b543f..6fa0061 100644 --- a/config/config.go +++ b/config/config.go @@ -34,13 +34,14 @@ type Logger struct { } type config struct { - Location string - Point int - Relay string - Log Logger - Mqtt MqttConfig - Aliyun AliyunConfig - MaxTimeout int + Location string + Point int + Relay string + Log Logger + Mqtt MqttConfig + Aliyun AliyunConfig + MaxTimeout int + StandbyCache Cache } var C config diff --git a/init_device.md b/init_device.md new file mode 100644 index 0000000..a81eed3 --- /dev/null +++ b/init_device.md @@ -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 --noclear %I $TERM +``` + +其中: + +- :替换为你想自动登录的用户名。 diff --git a/internal/middleware/cache.go b/internal/middleware/cache.go new file mode 100644 index 0000000..23e9d4c --- /dev/null +++ b/internal/middleware/cache.go @@ -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() + } +} diff --git a/internal/routes/wait.go b/internal/routes/standby.go similarity index 92% rename from internal/routes/wait.go rename to internal/routes/standby.go index 17b895e..ed3fea0 100644 --- a/internal/routes/wait.go +++ b/internal/routes/standby.go @@ -13,8 +13,8 @@ import ( "sync" ) -// WaitAction 待机任务,支持音乐、TTS、继电器、视频、网页、投影仪、大型激光秀 ctrl -func WaitAction(ctrl *common.CtrlWait, device *common.Device) leaf.HandlerFunc { +// StandbyAction 待机任务,支持音乐、TTS、继电器、视频、网页、投影仪、大型激光秀 ctrl +func StandbyAction(ctrl *common.CtrlWait, device *common.Device) leaf.HandlerFunc { ps := common.NewPauseSub(ctrl) return func(c *leaf.Context) { diff --git a/internal/routes/standby/pjlink.go b/internal/routes/standby/pjlink.go index bfed900..767c89d 100644 --- a/internal/routes/standby/pjlink.go +++ b/internal/routes/standby/pjlink.go @@ -15,20 +15,20 @@ func PJLink(_ schema.WaitItemModel) func(c context.Context) error { pjc := pjlink.NewClient(cfg.Ip, cfg.Port, cfg.Password, cfg.Id) zap.S().Infoln("打开待机投影仪") - resp, err := pjc.PowerOn() + resp, err := pjc.PowerOnSync() if err != nil { return fmt.Errorf("打开投影仪异常: %w", err) } - zap.S().Infoln("投影仪返回报文:", resp) + zap.S().Infoln("打开投影仪结果:", resp) <-c.Done() zap.S().Infoln("关闭待机投影仪") - resp, err = pjc.PowerOff() + resp, err = pjc.PowerOffSync() if err != nil { return fmt.Errorf("关闭投影仪异常: %w", err) } - zap.S().Infoln("投影仪返回报文:", resp) + zap.S().Infoln("关闭投影仪结果:", resp) return nil } diff --git a/internal/routes/standby_ctrl/device.go b/internal/routes/standby_ctrl/device.go index fb41845..38cc5fa 100644 --- a/internal/routes/standby_ctrl/device.go +++ b/internal/routes/standby_ctrl/device.go @@ -6,6 +6,7 @@ import ( "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 { diff --git a/internal/routes/standby_ctrl/interval.go b/internal/routes/standby_ctrl/interval.go index b1a6dba..773bb6c 100644 --- a/internal/routes/standby_ctrl/interval.go +++ b/internal/routes/standby_ctrl/interval.go @@ -6,6 +6,7 @@ import ( "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) diff --git a/internal/routes/standby_ctrl/pause.go b/internal/routes/standby_ctrl/pause.go index 68bf0b1..7ce3083 100644 --- a/internal/routes/standby_ctrl/pause.go +++ b/internal/routes/standby_ctrl/pause.go @@ -7,6 +7,7 @@ import ( "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 diff --git a/internal/server.go b/internal/server.go index 84eb41a..0f9c471 100644 --- a/internal/server.go +++ b/internal/server.go @@ -13,9 +13,6 @@ 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" @@ -23,6 +20,10 @@ import ( "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 { @@ -153,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(common.PassCtrl, device), + routes.StandbyAction(common.PassCtrl, device), ) // 处理指令 router.RegisterHandler(topicPrefix+"command", @@ -163,13 +165,21 @@ 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 + // 开始连接 MQTT cm, err := autopaho.NewConnection(ctx, mqttBuild) if err != nil { - zap.S().Panicln("连接 MQTT 异常: ", err) + zap.S().Panicln("创建 MQTT 连接器异常: ", err) } utils.GlobalMqttClient = cm diff --git a/leaf/leaf.go b/leaf/leaf.go index 1e5d92d..9c89302 100644 --- a/leaf/leaf.go +++ b/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) } @@ -92,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() @@ -115,18 +131,7 @@ 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) - e.queueWg.Add(1) - go func() { - defer e.queueWg.Done() - WithLeafContext(e.ctx, m, e, handlers).Next() - }() - handlerCalled = true - } - } + handlerCalled := e.HandlerRun(topic, m) if !handlerCalled && e.defaultHandler != nil { e.queueWg.Add(1) diff --git a/pkg/pjlink/pjlink.go b/pkg/pjlink/pjlink.go index bb926fc..8d4b250 100644 --- a/pkg/pjlink/pjlink.go +++ b/pkg/pjlink/pjlink.go @@ -111,6 +111,7 @@ func (c *Client) sendCommand(command string) (string, error) { return result, nil } +// PowerOn 打开投影机 func (c *Client) PowerOn() (string, error) { err := c.connect() if err != nil { @@ -131,6 +132,7 @@ func (c *Client) PowerOn() (string, error) { return response, nil } +// PowerOff 关闭投影机 func (c *Client) PowerOff() (string, error) { err := c.connect() if err != nil { @@ -151,6 +153,79 @@ func (c *Client) PowerOff() (string, error) { 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()