From ff97e25a55271c882537937344344ec13466d40b Mon Sep 17 00:00:00 2001 From: mapleafgo Date: Mon, 30 Dec 2024 12:16:10 +0800 Subject: [PATCH 01/29] =?UTF-8?q?=E5=AE=8C=E5=96=84=E8=83=8C=E5=85=89?= =?UTF-8?q?=E6=8E=A7=E5=88=B6=EF=BC=8C=E4=BC=98=E5=85=88=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=20xset=20=E6=8E=A7=E5=88=B6=E3=80=82=E5=AE=8C=E6=88=90?= =?UTF-8?q?=E6=B5=8F=E8=A7=88=E5=99=A8=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/root.go | 2 - go.mod | 22 ++++--- go.sum | 48 ++++++++++----- internal/routes/play/push_card.go | 4 +- internal/routes/wait.go | 33 +++++++++++ pkg/browser/browser.go | 40 +++++++++++++ pkg/utils/blank.go | 13 +++- todo.md | 99 +++++++++++++++++++++++++++---- 8 files changed, 222 insertions(+), 39 deletions(-) create mode 100644 pkg/browser/browser.go diff --git a/cmd/root.go b/cmd/root.go index f1d4864..5b28ca5 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -76,7 +76,5 @@ func initConfig() { if err != nil { log.Panicln("unmarshal game config failed: ", err) } - } else { - log.Panicln("game config not found") } } diff --git a/go.mod b/go.mod index 4e9e33c..c823bde 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.23.2 require ( github.com/aliyun/alibabacloud-nls-go-sdk v1.1.1 github.com/eclipse/paho.golang v0.22.0 + github.com/go-rod/rod v0.116.2 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 @@ -15,8 +16,8 @@ require ( ) require ( - github.com/aliyun/alibaba-cloud-sdk-go v1.63.53 // indirect - github.com/ebitengine/oto/v3 v3.3.1 // indirect + github.com/aliyun/alibaba-cloud-sdk-go v1.63.76 // indirect + github.com/ebitengine/oto/v3 v3.3.2 // indirect github.com/ebitengine/purego v0.8.1 // indirect github.com/fsnotify/fsnotify v1.8.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect @@ -26,7 +27,7 @@ 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/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 @@ -38,14 +39,19 @@ require ( 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/cast v1.7.1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.6.0 // indirect + github.com/ysmood/fetchup v0.2.4 // 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/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-20241217172543-b2144cdd0a67 // indirect + golang.org/x/net v0.33.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 56bc9df..6de3c48 100644 --- a/go.sum +++ b/go.sum @@ -3,8 +3,8 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= 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.76 h1:mg/+23+/gAw6zdxv9I5dPCj666WJPLk8S1nXm0dOumQ= +github.com/aliyun/alibaba-cloud-sdk-go v1.63.76/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= @@ -13,8 +13,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs 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/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.1 h1:sdRKd6plj7KYW33EH5As6YKfe8m9zbN9JMrOjNVF/BE= github.com/ebitengine/purego v0.8.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/eclipse/paho.golang v0.22.0 h1:JhhUngr8TBlyUZDZw/L6WVayPi9qmSmdWeki48i5AVE= @@ -25,6 +25,8 @@ github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7z 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-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/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -65,8 +67,8 @@ 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= @@ -103,8 +105,8 @@ github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9yS 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/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= +github.com/spf13/cast v1.7.1/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= @@ -126,6 +128,20 @@ 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= +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/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 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -141,8 +157,8 @@ 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-20241217172543-b2144cdd0a67 h1:1UoZQm6f0P/ZO0w1Ri+f+ifG/gXhegadRdwBIXEFWDo= +golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= 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 +167,18 @@ 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.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= 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.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= 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= diff --git a/internal/routes/play/push_card.go b/internal/routes/play/push_card.go index bee0eb2..9f7d93b 100644 --- a/internal/routes/play/push_card.go +++ b/internal/routes/play/push_card.go @@ -35,7 +35,7 @@ func PushCard(ctx context.Context) leaf.HandlerFunc { 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() { diff --git a/internal/routes/wait.go b/internal/routes/wait.go index 0e55710..b40a8ea 100644 --- a/internal/routes/wait.go +++ b/internal/routes/wait.go @@ -5,6 +5,7 @@ import ( "game-driver/internal/schema" "game-driver/leaf" "game-driver/pkg/audio" + "game-driver/pkg/browser" "game-driver/pkg/relay" "game-driver/pkg/tts" "game-driver/pkg/utils" @@ -80,6 +81,12 @@ func WaitAction(c *leaf.Context) { videoAction(c, item, payload.TimeModel) }() case schema.WaitWeb: + // 执行网页打开 + wait.Add(1) + go func() { + defer wait.Done() + webAction(c, item, payload.TimeModel) + }() default: zap.S().Infof("不支持的类型: %d\n", item.Type) } @@ -240,3 +247,29 @@ func videoAction(c *leaf.Context, item schema.WaitItemModel, root schema.TimeMod } } } + +func webAction(c *leaf.Context, item schema.WaitItemModel, root schema.TimeModel) { + if item.Start != 0 && time.Unix(item.Start, 0).Before(time.Unix(root.Start, 0)) { + zap.S().Infoln("开始时间小于根任务开始时间") + return + } + + if item.End != 0 { + cancel := leaf.WithDeadline(c, time.Unix(item.End, 0)) + defer cancel() + } + + select { + case <-c.Done(): + case <-timerAction(item.Start): + { + zap.S().Infoln("打开待机网页") + + // 控制背光 + utils.BlankOpen() + defer utils.BlankClose() + + browser.OpenApp(c, item.Data) + } + } +} diff --git a/pkg/browser/browser.go b/pkg/browser/browser.go new file mode 100644 index 0000000..4dc3b0e --- /dev/null +++ b/pkg/browser/browser.go @@ -0,0 +1,40 @@ +package browser + +import ( + "context" + "github.com/go-rod/rod" + "github.com/go-rod/rod/lib/launcher" + "github.com/go-rod/rod/lib/launcher/flags" + "github.com/go-rod/rod/lib/proto" + "go.uber.org/zap" +) + +// OpenApp 用APP模式打开网页 +func OpenApp(c context.Context, url string) { + path, _ := launcher.LookPath() + l := launcher.NewAppMode(url). + Delete(flags.Env). + Set("kiosk"). + Delete("disable-site-isolation-trials"). + Bin(path) + p := l.MustLaunch() + defer l.Cleanup() + + b := rod.New().ControlURL(p).MustConnect() + defer b.MustClose() + + s := make(chan struct{}) + + wait := b.EachEvent(func(e *proto.TargetTargetDestroyed) { + zap.S().Infoln("关闭待机网页") + s <- struct{}{} + }) + go wait() + + select { + case <-c.Done(): + b.MustClose() + <-s + case <-s: + } +} diff --git a/pkg/utils/blank.go b/pkg/utils/blank.go index 5853190..8135f03 100644 --- a/pkg/utils/blank.go +++ b/pkg/utils/blank.go @@ -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) } diff --git a/todo.md b/todo.md index 20bb930..0da090b 100644 --- a/todo.md +++ b/todo.md @@ -1,29 +1,36 @@ -## 技术点记录 - -1. linux 下播放音频 +# 技术点记录 +## linux 下播放音频 ```bash sudo apt install libasound2-dev alsa-utils ``` -2. linux 下播放视频 +## linux 下播放视频 ```bash sudo apt install ffmpeg ``` - 驱动安装 + 显示安装 ```bash - sudo apt install libdirectfb-dev + sudo apt install libdirectfb-dev # 轻量级显示服务器 + # 或 + # sudo apt install xorg ``` -3. 当前用户加入播放音频与视频的组中 + +### 当前用户加入播放音频与视频的组中 ```bash sudo usermod -aG audio,video $USER ``` -### 关闭屏幕帧缓冲 +### 关闭屏幕帧缓冲(关闭背光) ```bash # 关闭帧缓冲设备 echo 1 | sudo tee /sys/class/graphics/fb0/blank # 重新打开帧缓冲设备 echo 0 | sudo tee /sys/class/graphics/fb0/blank + +# xorg 环境,关闭背光 +xset dpms force off +# xorg 环境,重新打开背光 +xset dpms force on ``` ### 播放视频 @@ -31,12 +38,12 @@ echo 0 | sudo tee /sys/class/graphics/fb0/blank ffplay -autoexit -fs -i video.mp4 ``` -### 编译 arm64 架构 +## 编译 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 +68,75 @@ 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 --noclear %I $TERM +``` +其中: + - :替换为你想自动登录的用户名。 + +### 禁用 i3bar 状态栏 + +编辑 `~/.config/i3/config`,将如下行注释掉: +```bash +# bar { +# status_command i3status +# } +``` + +### 配置 i3 + +安装 `unclutter`: +```bash +sudo apt install unclutter +``` + +编辑 `~/.config/i3/config`,添加如下行: +```bash +exec --no-startup-id unclutter -root # 隐藏鼠标 +exec --no-startup-id xset -dpms # 关闭屏幕自动关闭 +exec --no-startup-id xset s off # 关闭屏幕保护 +``` + +### 安装中文字体和 emoji 字体 + +```bash +sudo apt install fonts-noto-cjk fonts-noto-color-emoji +``` + +## 安装浏览器作为默认启动页面 + +### 安装浏览器 + +```bash +sudo add-apt-repository ppa:xtradeb/apps +sudo apt update +sudo apt install ungoogled-chromium +``` + +### 设置默认启动页面 + +编辑 `~/.config/i3/config`,添加如下行: +```bash +exec --no-startup-id ungoogled-chromium --kiosk --disable-extensions --disable-translate --app= +``` From 8aa502b734c9d94aa8fac501996c8e7aeae7c09c Mon Sep 17 00:00:00 2001 From: mapleafgo Date: Mon, 30 Dec 2024 14:44:42 +0800 Subject: [PATCH 02/29] =?UTF-8?q?=E6=B5=8F=E8=A7=88=E5=99=A8=E6=89=93?= =?UTF-8?q?=E5=BC=80=E9=A1=B5=E9=9D=A2=E9=9A=90=E8=97=8F=E6=BB=9A=E5=8A=A8?= =?UTF-8?q?=E6=9D=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkg/browser/browser.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/browser/browser.go b/pkg/browser/browser.go index 4dc3b0e..d1073b5 100644 --- a/pkg/browser/browser.go +++ b/pkg/browser/browser.go @@ -15,6 +15,7 @@ func OpenApp(c context.Context, url string) { l := launcher.NewAppMode(url). Delete(flags.Env). Set("kiosk"). + Set("hide-scrollbars"). Delete("disable-site-isolation-trials"). Bin(path) p := l.MustLaunch() From 1fdf111b86c6dd30576b6ad6beccb771fb24552f Mon Sep 17 00:00:00 2001 From: mapleafgo Date: Mon, 30 Dec 2024 18:21:08 +0800 Subject: [PATCH 03/29] =?UTF-8?q?=E7=86=84=E5=B1=8F=E9=97=AE=E9=A2=98?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkg/browser/browser.go | 2 +- todo.md | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/browser/browser.go b/pkg/browser/browser.go index d1073b5..68f3eec 100644 --- a/pkg/browser/browser.go +++ b/pkg/browser/browser.go @@ -27,7 +27,7 @@ func OpenApp(c context.Context, url string) { s := make(chan struct{}) wait := b.EachEvent(func(e *proto.TargetTargetDestroyed) { - zap.S().Infoln("关闭待机网页") + zap.S().Infoln("浏览器关闭事件") s <- struct{}{} }) go wait() diff --git a/todo.md b/todo.md index 0da090b..b82770b 100644 --- a/todo.md +++ b/todo.md @@ -114,10 +114,12 @@ sudo apt install unclutter 编辑 `~/.config/i3/config`,添加如下行: ```bash exec --no-startup-id unclutter -root # 隐藏鼠标 -exec --no-startup-id xset -dpms # 关闭屏幕自动关闭 +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 From 121c7706fc001731ae3cf7b08475949aa4eff8e4 Mon Sep 17 00:00:00 2001 From: mapleafgo Date: Tue, 31 Dec 2024 17:38:18 +0800 Subject: [PATCH 04/29] =?UTF-8?q?=E5=B0=86=E5=88=9D=E5=A7=8B=E5=B1=8F?= =?UTF-8?q?=E5=B9=95=E7=9A=84=E6=8E=A7=E5=88=B6=E6=94=BE=E5=88=B0=E6=9C=80?= =?UTF-8?q?=E5=89=8D=E6=96=B9=E7=94=A8=E4=BB=A5=E8=A7=A3=E5=86=B3=E5=B1=8F?= =?UTF-8?q?=E5=B9=95=E4=BF=9D=E7=95=99=E6=B6=88=E6=81=AF=E8=A7=A6=E5=8F=91?= =?UTF-8?q?=E7=9A=84=E5=B1=8F=E5=B9=95=E6=8E=A7=E5=88=B6=E8=A2=AB=E8=A6=86?= =?UTF-8?q?=E7=9B=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/server.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/server.go b/internal/server.go index 5120c1b..c36db6e 100644 --- a/internal/server.go +++ b/internal/server.go @@ -80,6 +80,9 @@ func Run() { // 应用退出时刷新所有缓冲日志 defer logger.Sync() + // 启动时关闭屏幕 + 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) @@ -152,8 +155,6 @@ func Run() { // 启动完成发送一次设备状态 device.PublishStatus() - // 启动完成关闭屏幕 - utils.BlankClose() sig := make(chan os.Signal, 1) signal.Notify(sig, os.Interrupt, syscall.SIGTERM) From 8b080a808158d4d4f1b9522cef197b8c2dd7aa43 Mon Sep 17 00:00:00 2001 From: mapleafgo Date: Wed, 8 Jan 2025 10:46:29 +0800 Subject: [PATCH 05/29] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game.md | 2 +- pkg/browser/browser.go | 2 ++ pkg/ports.go | 7 ------- 3 files changed, 3 insertions(+), 8 deletions(-) delete mode 100644 pkg/ports.go diff --git a/game.md b/game.md index 66d37a8..d76a59d 100644 --- a/game.md +++ b/game.md @@ -56,7 +56,7 @@ { // 发卡执行时间,单位秒 "action": 0, - // 等待时间,单位秒 + // 等待时间,单位秒(不能小于 1) "wait": 10 } ``` diff --git a/pkg/browser/browser.go b/pkg/browser/browser.go index 68f3eec..b547ebb 100644 --- a/pkg/browser/browser.go +++ b/pkg/browser/browser.go @@ -16,6 +16,8 @@ func OpenApp(c context.Context, url string) { 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() diff --git a/pkg/ports.go b/pkg/ports.go deleted file mode 100644 index 4febd40..0000000 --- a/pkg/ports.go +++ /dev/null @@ -1,7 +0,0 @@ -package main - -import "game-driver/pkg/relay" - -func main() { - relay.PrintPorts() -} From 593d7758bf0deecd77fda6859ff8282326f98a7d Mon Sep 17 00:00:00 2001 From: mapleafgo Date: Wed, 26 Feb 2025 19:45:47 +0800 Subject: [PATCH 06/29] =?UTF-8?q?=E6=92=AD=E6=94=BE=E6=B8=B8=E6=88=8F?= =?UTF-8?q?=E6=97=B6=EF=BC=8C=E5=81=9C=E6=AD=A2=E5=BE=85=E6=9C=BA=E4=BB=BB?= =?UTF-8?q?=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.yml | 7 +- config/config.go | 1 + go.mod | 1 + go.sum | 2 + internal/common/pause.go | 29 +++ internal/middleware/pause.go | 70 ++++++ internal/middleware/pause_wait.go | 15 ++ internal/middleware/sound_start.go | 4 +- internal/middleware/timer.go | 2 +- internal/routes/wait.go | 340 ++++++++++++----------------- internal/schema/play.go | 9 +- internal/schema/wait.go | 9 +- internal/server.go | 4 +- json.md | 52 +++++ leaf/context.go | 18 +- pkg/tts/aliyun.go | 4 +- puml/游戏.puml | 48 ++-- readme.md | 43 +++- todo.md | 39 ++-- 19 files changed, 422 insertions(+), 275 deletions(-) create mode 100644 internal/common/pause.go create mode 100644 internal/middleware/pause.go create mode 100644 internal/middleware/pause_wait.go create mode 100644 json.md diff --git a/config.yml b/config.yml index 40ee209..acfd2cf 100755 --- a/config.yml +++ b/config.yml @@ -1,6 +1,6 @@ location: wushan -point: 4 -relay: /dev/ttyUSB1 +point: 2 +relay: maxTimeout: 60 # 单位 s,必须大于 0 log: level: debug @@ -11,13 +11,14 @@ log: maxAge: 30 compress: true mqtt: - url: mqtt://36.138.38.16:1883 + url: mqtt://58.144.199.46:1883 aliyun: accessKeyID: accessKeySecret: appKey: timeout: 10 # 单位 s voice: zhifeng_emo + speechRate: 50 # 语速 game: # addr: /dev/ttyUSB0 # 点位 5 的串口地址 pushGroups: # 点位 4 的发卡器配置 diff --git a/config/config.go b/config/config.go index 3158ea1..5aa16b7 100644 --- a/config/config.go +++ b/config/config.go @@ -14,6 +14,7 @@ type AliyunConfig struct { AppKey string Timeout int Voice string + SpeechRate int } type Logger struct { diff --git a/go.mod b/go.mod index c823bde..6530a9f 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,7 @@ require ( github.com/ebitengine/oto/v3 v3.3.2 // indirect github.com/ebitengine/purego v0.8.1 // indirect github.com/fsnotify/fsnotify v1.8.0 // indirect + github.com/go-pkgz/cronrange v0.2.0 // 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 diff --git a/go.sum b/go.sum index 6de3c48..db7d2f6 100644 --- a/go.sum +++ b/go.sum @@ -25,6 +25,8 @@ github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7z 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= diff --git a/internal/common/pause.go b/internal/common/pause.go new file mode 100644 index 0000000..eb6724e --- /dev/null +++ b/internal/common/pause.go @@ -0,0 +1,29 @@ +package common + +type CtrlWait struct { + // 用于暂停的chan + P chan struct{} + // 用于恢复的chan + R chan struct{} +} + +// Pause 暂停 +func (c *CtrlWait) Pause() { + c.P <- struct{}{} +} + +// Resume 恢复 +func (c *CtrlWait) Resume() { + c.R <- struct{}{} +} + +// NewCtrlWait 创建一个控制等待 +func NewCtrlWait() *CtrlWait { + return &CtrlWait{ + P: make(chan struct{}), + R: make(chan struct{}), + } +} + +// PassCtrl 全局控制等待 +var PassCtrl = NewCtrlWait() diff --git a/internal/middleware/pause.go b/internal/middleware/pause.go new file mode 100644 index 0000000..f712f98 --- /dev/null +++ b/internal/middleware/pause.go @@ -0,0 +1,70 @@ +package middleware + +import ( + "context" + "game-driver/internal/common" + "game-driver/leaf" + "go.uber.org/zap" + "sync" +) + +func Pause(ctrl *common.CtrlWait) leaf.HandlerFunc { + return func(c *leaf.Context) { + var cancel context.CancelFunc + + // 获取锚点 + holdPoint := c.Hold() + + // 等待组 + var wait sync.WaitGroup + defer wait.Wait() + + // 结束信号通道 + a := make(chan struct{}) + // 发送结束信号 + defer close(a) + + run := true + + // 保存原始的 Context + originalCtx := c.Context + + wait.Add(1) + go func() { + defer wait.Done() + zap.S().Infoln("待机控制器") + + for { + select { + case <-originalCtx.Done(): + zap.S().Infoln("待机控制器监听结束") + return + case <-ctrl.R: + { + zap.S().Infoln("待机控制器 Resume 触发") + c.Context = originalCtx + run = true + } + case <-ctrl.P: + { + zap.S().Infoln("待机控制器 Pause 触发") + run = false + cancel() + } + } + } + }() + + for { + select { + case <-originalCtx.Done(): + return + default: + if run { + cancel = leaf.WithCancel(c) + c.Resume(holdPoint) + } + } + } + } +} diff --git a/internal/middleware/pause_wait.go b/internal/middleware/pause_wait.go new file mode 100644 index 0000000..bfa819e --- /dev/null +++ b/internal/middleware/pause_wait.go @@ -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() + } +} diff --git a/internal/middleware/sound_start.go b/internal/middleware/sound_start.go index 1300f25..de6e98f 100644 --- a/internal/middleware/sound_start.go +++ b/internal/middleware/sound_start.go @@ -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) } diff --git a/internal/middleware/timer.go b/internal/middleware/timer.go index bdb56a0..66a3e87 100644 --- a/internal/middleware/timer.go +++ b/internal/middleware/timer.go @@ -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) } } }() diff --git a/internal/routes/wait.go b/internal/routes/wait.go index b40a8ea..7813ed0 100644 --- a/internal/routes/wait.go +++ b/internal/routes/wait.go @@ -1,6 +1,7 @@ package routes import ( + "context" "game-driver/internal/middleware" "game-driver/internal/schema" "game-driver/leaf" @@ -10,180 +11,165 @@ import ( "game-driver/pkg/tts" "game-driver/pkg/utils" "game-driver/pkg/video" + "github.com/go-pkgz/cronrange" "github.com/gopxl/beep/v2/speaker" "go.uber.org/zap" "sync" "time" ) -func timerAction(timestamp int64) <-chan struct{} { - a := make(chan struct{}) - - go func() { - if timestamp == 0 { - close(a) - } else { - <-time.After(time.Until(time.Unix(timestamp, 0))) - close(a) - } - }() - - return a -} - -func WaitAction(c *leaf.Context) { - payload := leaf.Value[*schema.WaitModel](c, middleware.PayloadJSONKey) - - if payload.Start != 0 && payload.End != 0 && time.Unix(payload.Start, 0).After(time.Unix(payload.End, 0)) { - zap.S().Infoln("开始时间大于结束时间") +func runAction(c *leaf.Context, item schema.WaitItemModel, rootRules []cronrange.Rule, play func(c context.Context, item schema.WaitItemModel)) { + if item.Cron == "" { + item.Cron = "* * * *" + } + rules, err := cronrange.Parse(item.Cron) + if err != nil { + zap.S().Errorln("解析时间规则异常: ", err) return } - if payload.End != 0 { - cancel := leaf.WithDeadline(c, time.Unix(payload.End, 0)) - defer cancel() - } + // 等待组 + var wait sync.WaitGroup + defer wait.Wait() - select { - case <-c.Done(): - case <-timerAction(payload.Start): - // 等待组 - var wait sync.WaitGroup - defer wait.Wait() - for _, item := range payload.Items { - switch item.Type { - case schema.WaitAudio: - // 执行音乐播放 - wait.Add(1) - go func() { - defer wait.Done() - audioAction(c, item, payload.TimeModel) - }() - case schema.WaitTTS: - // 执行TTS播放 - wait.Add(1) - go func() { - defer wait.Done() - ttsAction(c, item, payload.TimeModel) - }() - case schema.WaitRelay: - // 执行继电器供电 - wait.Add(1) - go func() { - defer wait.Done() - relayAction(c, item, payload.TimeModel) - }() - case schema.WaitVideo: - // 执行视频播放 - wait.Add(1) - go func() { - defer wait.Done() - videoAction(c, item, payload.TimeModel) - }() - case schema.WaitWeb: - // 执行网页打开 - wait.Add(1) - go func() { - defer wait.Done() - webAction(c, item, payload.TimeModel) - }() - default: - zap.S().Infof("不支持的类型: %d\n", item.Type) + ticker := time.NewTicker(time.Second) + defer ticker.Stop() + + ctx, cancel := context.WithCancel(context.TODO()) + + var run bool + + wait.Add(1) + go func() { + defer wait.Done() + for { + select { + case <-c.Done(): + cancel() + return + case <-ticker.C: + if cronrange.Match(rules, time.Now()) && cronrange.Match(rootRules, time.Now()) { + run = true + } else { + run = false + cancel() + } + } + } + }() + + for { + select { + case <-c.Done(): + return + default: + if run { + play(ctx, item) } } } } -func audioAction(c *leaf.Context, item schema.WaitItemModel, root schema.TimeModel) { - if item.Start != 0 && time.Unix(item.Start, 0).Before(time.Unix(root.Start, 0)) { - zap.S().Infoln("开始时间小于根任务开始时间") +func WaitAction(c *leaf.Context) { + payload := leaf.Value[*schema.WaitModel](c, middleware.PayloadJSONKey) + + rules, err := cronrange.Parse(payload.Cron) + if err != nil { + zap.S().Errorln("解析时间规则异常: ", err) return } - if item.End != 0 { - cancel := leaf.WithDeadline(c, time.Unix(item.End, 0)) - defer cancel() + // 等待组 + var wait sync.WaitGroup + defer wait.Wait() + for _, item := range payload.Items { + switch item.Type { + case schema.WaitAudio: + // 执行音乐播放 + wait.Add(1) + go func() { + defer wait.Done() + runAction(c, item, rules, audioAction) + }() + case schema.WaitTTS: + // 执行TTS播放 + wait.Add(1) + go func() { + defer wait.Done() + runAction(c, item, rules, ttsAction) + }() + case schema.WaitRelay: + // 执行继电器供电 + wait.Add(1) + go func() { + defer wait.Done() + runAction(c, item, rules, relayAction) + }() + case schema.WaitVideo: + // 执行视频播放 + wait.Add(1) + go func() { + defer wait.Done() + runAction(c, item, rules, videoAction) + }() + case schema.WaitWeb: + // 执行网页打开 + wait.Add(1) + go func() { + defer wait.Done() + runAction(c, item, rules, webAction) + }() + default: + zap.S().Infof("不支持的类型: %d\n", item.Type) + } } +} +func audioAction(c context.Context, item schema.WaitItemModel) { data, err := utils.LinkAudio(item.Data) if err != nil { zap.S().Errorln("音频数据获取异常: ", err) return } - select { - case <-c.Done(): - case <-timerAction(item.Start): - { - zap.S().Infoln("播放待机音乐") - defer zap.S().Infoln("结束待机音乐") + zap.S().Infoln("播放待机音乐") + defer zap.S().Infoln("结束待机音乐") - ctrl, closer, e := audio.PlayBgmMP3(data) - defer closer() - if e != nil { - zap.S().Errorln("播放待机音乐异常", e) - return - } - - select { - case <-c.Done(): - { - speaker.Lock() - ctrl.Streamer = nil - speaker.Unlock() - } - } - } - } -} - -func ttsAction(c *leaf.Context, item schema.WaitItemModel, root schema.TimeModel) { - if item.Start != 0 && time.Unix(item.Start, 0).Before(time.Unix(root.Start, 0)) { - zap.S().Infoln("开始时间小于根任务开始时间") + ctrl, closer, e := audio.PlayBgmMP3(data) + defer closer() + if e != nil { + zap.S().Errorln("播放待机音乐异常", e) return } - if item.End != 0 { - cancel := leaf.WithDeadline(c, time.Unix(item.End, 0)) - defer cancel() - } + <-c.Done() + speaker.Lock() + ctrl.Streamer = nil + speaker.Unlock() +} + +func ttsAction(c context.Context, item schema.WaitItemModel) { reader, err := tts.DefaultTTS.Get(item.Data) if err != nil { zap.S().Errorln("语音合成异常: ", err) return } - select { - case <-c.Done(): - case <-timerAction(item.Start): - { - zap.S().Infoln("循环播放待机 TTS 语音") - defer zap.S().Infoln("结束待机 TTS 语音") + zap.S().Infoln("循环播放待机 TTS 语音") + defer zap.S().Infoln("结束待机 TTS 语音") - for { - audio.PlayWav(c, reader) - select { - case <-c.Done(): - return - case <-time.After(time.Duration(item.Interval) * time.Second): - } - } + for { + audio.PlayWav(c, reader) + select { + case <-c.Done(): + return + case <-time.After(time.Duration(item.Interval) * time.Second): } } } -func relayAction(c *leaf.Context, item schema.WaitItemModel, root schema.TimeModel) { - if item.Start != 0 && time.Unix(item.Start, 0).Before(time.Unix(root.Start, 0)) { - zap.S().Infoln("开始时间小于根任务开始时间") - return - } - - if item.End != 0 { - cancel := leaf.WithDeadline(c, time.Unix(item.End, 0)) - defer cancel() - } - +func relayAction(c context.Context, item schema.WaitItemModel) { r, err := relay.New(item.Data) if err != nil { zap.S().Errorln("继电器初始化异常: ", err) @@ -191,85 +177,47 @@ func relayAction(c *leaf.Context, item schema.WaitItemModel, root schema.TimeMod } defer r.Close() - select { - case <-c.Done(): - case <-timerAction(item.Start): - { - zap.S().Infoln("待机继电器供电") - defer zap.S().Infoln("待机继电器断电") + zap.S().Infoln("待机继电器供电") + defer zap.S().Infoln("待机继电器断电") - r.On(0) - <-c.Done() - r.Off(0) - } - } + _ = r.On(0) + <-c.Done() + _ = r.Off(0) } -func videoAction(c *leaf.Context, item schema.WaitItemModel, root schema.TimeModel) { - if item.Start != 0 && time.Unix(item.Start, 0).Before(time.Unix(root.Start, 0)) { - zap.S().Infoln("开始时间小于根任务开始时间") - return - } - - if item.End != 0 { - cancel := leaf.WithDeadline(c, time.Unix(item.End, 0)) - defer cancel() - } - +func videoAction(c context.Context, item schema.WaitItemModel) { local, err := utils.LinkVideo(item.Data) if err != nil { zap.S().Errorln("视频文件获取异常: ", err) return } - select { - case <-c.Done(): - case <-timerAction(item.Start): - { - zap.S().Infoln("循环播放待机视频") - defer zap.S().Infoln("结束待机视频") + zap.S().Infoln("循环播放待机视频") + defer zap.S().Infoln("结束待机视频") - utils.BlankOpen() - defer utils.BlankClose() + utils.BlankOpen() + defer utils.BlankClose() - for { - err := video.Play(c, local) - if err != nil { - zap.S().Infof("视频播放异常: %s", err) - return - } - select { - case <-c.Done(): - return - case <-time.After(time.Duration(item.Interval) * time.Second): - } - } + for { + err := video.Play(c, local) + if err != nil { + zap.S().Infof("视频播放异常: %s", err) + return + } + select { + case <-c.Done(): + return + case <-time.After(time.Duration(item.Interval) * time.Second): } } } -func webAction(c *leaf.Context, item schema.WaitItemModel, root schema.TimeModel) { - if item.Start != 0 && time.Unix(item.Start, 0).Before(time.Unix(root.Start, 0)) { - zap.S().Infoln("开始时间小于根任务开始时间") - return - } +func webAction(c context.Context, item schema.WaitItemModel) { + zap.S().Infoln("打开待机网页") - if item.End != 0 { - cancel := leaf.WithDeadline(c, time.Unix(item.End, 0)) - defer cancel() - } + // 控制背光 + utils.BlankOpen() + defer utils.BlankClose() - select { - case <-c.Done(): - case <-timerAction(item.Start): - { - zap.S().Infoln("打开待机网页") - - // 控制背光 - utils.BlankOpen() - defer utils.BlankClose() - - browser.OpenApp(c, item.Data) - } - } + browser.OpenApp(c, item.Data) } diff --git a/internal/schema/play.go b/internal/schema/play.go index 592bb3e..3958adc 100644 --- a/internal/schema/play.go +++ b/internal/schema/play.go @@ -6,10 +6,11 @@ type TTSTimer struct { } type TTSModal struct { - Start string `json:"start"` - End string `json:"end"` - Stop string `json:"stop"` - Timer []TTSTimer `json:"timer"` + Start string `json:"start"` + End string `json:"end"` + Stop string `json:"stop"` + Timeout string `json:"timeout"` + Timer []TTSTimer `json:"timer"` } type PrintModal struct { diff --git a/internal/schema/wait.go b/internal/schema/wait.go index 8f5c648..8ebeb96 100644 --- a/internal/schema/wait.go +++ b/internal/schema/wait.go @@ -10,13 +10,8 @@ const ( WaitWeb ) -type TimeModel struct { - Start int64 `json:"start"` - End int64 `json:"end"` -} - type WaitItemModel struct { - TimeModel + Cron string `json:"cron"` Type WaitType `json:"type"` Data string `json:"data"` Interval int64 `json:"interval"` @@ -24,6 +19,6 @@ type WaitItemModel struct { type WaitModel struct { JsonModel - TimeModel + Cron string `json:"cron"` Items []WaitItemModel `json:"items"` } diff --git a/internal/server.go b/internal/server.go index c36db6e..e654c91 100644 --- a/internal/server.go +++ b/internal/server.go @@ -54,7 +54,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: fmt.Sprintf("game-driver-%s-%v", config.C.Location, config.C.Point), OnPublishReceived: []func(paho.PublishReceived) (bool, error){ func(pr paho.PublishReceived) (bool, error) { r.Route(pr.Packet.Packet()) @@ -131,6 +131,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), @@ -145,6 +146,7 @@ func Run() { middleware.PayloadJSON[schema.WaitModel](), middleware.Unique(common.GlobalBgStopper), middleware.EmergencyStop(common.GlobalBgStopper), + middleware.Pause(common.PassCtrl), routes.WaitAction, ) // 处理指令 diff --git a/json.md b/json.md new file mode 100644 index 0000000..0cea813 --- /dev/null +++ b/json.md @@ -0,0 +1,52 @@ +## 点位2(镇水塔) +### 待机 + +url: `server/wushan/2/wait` + +```json +{ + "cron": "* * * *", + "items": [ + { + "data": "file://./三峡龙脊BGM.mp3" + } + ] +} +``` + +### Game + +url: `server/wushan/2/play` + +```json +{ + "tts": { + "start": "刘佳勇者,恭喜你成功通关!", + "timer": [ + { + "time": 2, + "value": "你的荣耀将获得法阵加持,请迅速移步到法阵位置!" + } + ] + }, + "game": { + "video": "file://./镇水塔法阵.mp4" + } +} +``` + +### STOP 待机 + +url: `server/wushan/2/command` + +```text +stop-bg +``` + +### STOP GAME + +url: `server/wushan/2/command` + +```text +stop +``` \ No newline at end of file diff --git a/leaf/context.go b/leaf/context.go index 8967dd8..66ae35a 100644 --- a/leaf/context.go +++ b/leaf/context.go @@ -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, diff --git a/pkg/tts/aliyun.go b/pkg/tts/aliyun.go index c9ae784..7f5a9d1 100644 --- a/pkg/tts/aliyun.go +++ b/pkg/tts/aliyun.go @@ -62,6 +62,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 @@ -70,8 +71,9 @@ func (tts *AliTTS) getToken() error { func (tts *AliTTS) Get(text string) (io.Reader, error) { param := nls.DefaultSpeechSynthesisParam() - param.Volume = 100 + param.Volume = 200 param.Voice = tts.Voice + param.SpeechRate = tts.SpeechRate err := tts.getToken() if err != nil { diff --git a/puml/游戏.puml b/puml/游戏.puml index 4325618..e5a42cd 100644 --- a/puml/游戏.puml +++ b/puml/游戏.puml @@ -4,40 +4,36 @@ +++ 待机 ++++ 背景音乐 +++ Game -++++ 播放欢迎语音 -++ 一阶段门头(无) -++ 召唤神女(1) +++++ tts播报欢迎词 +++ 击缶台(1) ++++ 待机 +++ Game -++++ 等待结束 -++ 神女低语(无) -+++ 按下按钮 -+++ 播放语音 -+++ 发送结果数据 -++ 镇水神力(2) +++++ bgm +++++ 供电 +++++ tts播报游客名 +++++ tts游戏结束 +++ 镇水塔(2) +++ 待机 ++++ 投影仪待机 +++ Game +++++ bgm ++++ 播放法阵视频 --- 二阶段门头(无) --- 神女除妖(3) ---- Game ----- 控制设备启动 ----- 接收游戏结果 ----- 发送结果数据 --- 流光寻踪(无) --- 神女授书(4) +-- 神女镇邪祟(3) --- 待机 --- Game ----- 控制设备吐卡 ----- 播放取卡提示语音 ----- 异常状态处理 --- 青云龙台(5) +---- tts播报恭喜通关词 +-- 神女影像(4) +--- 待机 +--- Game +---- tts播报恭喜通关词 +-- 青龙云台(5) --- 待机 ---- 网页默认页面 --- Game ----- 等待卡片插入 ----- 播放恭喜语音 ----- 屏幕恭喜页面 ----- 灯光播放 ----- 结束屏幕提示 +---- 供电 +---- 播放语音 +-- 俱乐部(6) +--- 待机 +---- 启动屏幕 +--- Game @endmindmap diff --git a/readme.md b/readme.md index 1b0eaae..d78ef3d 100644 --- a/readme.md +++ b/readme.md @@ -22,13 +22,15 @@ Payload: "default-print": "", // 文本转语音整体控制 "tts": { - // 开始语音 + // 开始播报语音 "start": "", - // 结束语音 + // 超时自动停止时播报语音 + "timeout": "", + // 结束播报语音 "end": "", - // 终止语音 + // 终止播报语音 "stop": "", - // 固定节点语音 + // 固定节点播报语音 "timer": [ { // 时间节点(s) @@ -125,17 +127,13 @@ 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 @@ -149,3 +147,24 @@ Payload: ``` > 同一个类型的待机任务只能有一个,当有新的任务到达时会覆盖之前的任务 + +### 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 diff --git a/todo.md b/todo.md index b82770b..809e34f 100644 --- a/todo.md +++ b/todo.md @@ -1,32 +1,27 @@ # 技术点记录 ## linux 下播放音频 - ```bash - sudo apt install libasound2-dev alsa-utils - ``` +```bash +sudo apt install libasound2-dev alsa-utils +``` ## linux 下播放视频 - ```bash - sudo apt install ffmpeg - ``` - 显示安装 - ```bash - sudo apt install libdirectfb-dev # 轻量级显示服务器 - # 或 - # sudo apt install xorg - ``` +```bash +sudo apt install ffmpeg +``` -### 当前用户加入播放音频与视频的组中 - ```bash - sudo usermod -aG audio,video $USER - ``` - -### 关闭屏幕帧缓冲(关闭背光) +显示安装 ```bash -# 关闭帧缓冲设备 -echo 1 | sudo tee /sys/class/graphics/fb0/blank -# 重新打开帧缓冲设备 -echo 0 | sudo tee /sys/class/graphics/fb0/blank +sudo apt install xorg +``` +### 当前用户加入播放音频与视频的组中 +```bash +sudo usermod -aG audio,video $USER +``` + +### 关闭背光 + +```bash # xorg 环境,关闭背光 xset dpms force off # xorg 环境,重新打开背光 From 8780f8555efa00b13c8a36c08ca29aefed06d409 Mon Sep 17 00:00:00 2001 From: mapleafgo Date: Thu, 27 Feb 2025 11:36:40 +0800 Subject: [PATCH 07/29] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E9=83=A8=E5=88=86?= =?UTF-8?q?=E5=B7=B2=E7=9F=A5bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.yml | 49 +++++++++++++-------------- config/config.go | 1 + config/game/game.go | 4 +-- go.mod | 2 +- internal/middleware/stop.go | 7 ++++ internal/middleware/ticker.go | 1 + internal/middleware/timer.go | 2 +- internal/routes/play.go | 8 ++--- internal/routes/wait.go | 62 +++++++++++++++++++++++------------ json.md | 4 +-- pkg/tts/aliyun.go | 2 +- pkg/utils/link_audio.go | 4 ++- pkg/video/paly.go | 6 ++++ puml/游戏.puml | 3 +- 14 files changed, 96 insertions(+), 59 deletions(-) diff --git a/config.yml b/config.yml index acfd2cf..8f014ea 100755 --- a/config.yml +++ b/config.yml @@ -1,5 +1,5 @@ -location: wushan -point: 2 +location: wushan # 项目名称 +point: 2 # 点位 relay: maxTimeout: 60 # 单位 s,必须大于 0 log: @@ -17,26 +17,27 @@ aliyun: accessKeySecret: appKey: timeout: 10 # 单位 s - voice: zhifeng_emo + volume: 200 # 音量 + voice: zhifeng_emo # 发音人 speechRate: 50 # 语速 -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 \ No newline at end of file +#game: +# addr: /dev/ttyUSB0 # 点位 11 的串口地址 +# 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 \ No newline at end of file diff --git a/config/config.go b/config/config.go index 5aa16b7..387cc8a 100644 --- a/config/config.go +++ b/config/config.go @@ -13,6 +13,7 @@ type AliyunConfig struct { AccessKeySecret string AppKey string Timeout int + Volume int Voice string SpeechRate int } diff --git a/config/game/game.go b/config/game/game.go index e549618..9861178 100644 --- a/config/game/game.go +++ b/config/game/game.go @@ -4,9 +4,9 @@ type Config any func NewConfig(point int) Config { switch point { - case 4: + case 10: return ConfigPush{} - case 5: + case 11: return ConfigWait{} default: return nil diff --git a/go.mod b/go.mod index 6530a9f..6053126 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.23.2 require ( github.com/aliyun/alibabacloud-nls-go-sdk v1.1.1 github.com/eclipse/paho.golang v0.22.0 + github.com/go-pkgz/cronrange v0.2.0 github.com/go-rod/rod v0.116.2 github.com/gopxl/beep/v2 v2.1.0 github.com/grid-x/modbus v0.0.0-20241004123532-f6c6fb5201b3 @@ -20,7 +21,6 @@ require ( github.com/ebitengine/oto/v3 v3.3.2 // indirect github.com/ebitengine/purego v0.8.1 // indirect github.com/fsnotify/fsnotify v1.8.0 // indirect - github.com/go-pkgz/cronrange v0.2.0 // 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 diff --git a/internal/middleware/stop.go b/internal/middleware/stop.go index c2b37f5..cc1c3a3 100644 --- a/internal/middleware/stop.go +++ b/internal/middleware/stop.go @@ -4,6 +4,7 @@ import ( "game-driver/internal/common" "game-driver/leaf" "go.uber.org/zap" + "sync" ) // EmergencyStop 紧急停止中间件 @@ -12,13 +13,19 @@ func EmergencyStop(stopper common.Stopper) leaf.HandlerFunc { cancel := leaf.WithCancel(c) defer stopper.Reset() + // 等待组 + var wait sync.WaitGroup + defer wait.Wait() + // 结束信号通道 a := make(chan struct{}) // 发送结束信号 defer close(a) zap.S().Infoln("监听停止信号") + wait.Add(1) go func() { + defer wait.Done() defer zap.S().Infoln("结束停止信号监听") select { diff --git a/internal/middleware/ticker.go b/internal/middleware/ticker.go index 3fbbe97..83c5168 100644 --- a/internal/middleware/ticker.go +++ b/internal/middleware/ticker.go @@ -9,6 +9,7 @@ import ( "time" ) +// TickerAction 定时器动作,用于在指定时间点执行打印和语音播报 func TickerAction() leaf.HandlerFunc { return func(c *leaf.Context) { pm := leaf.Value[*schema.PlayModal](c, PayloadJSONKey) diff --git a/internal/middleware/timer.go b/internal/middleware/timer.go index 66a3e87..0dcecd9 100644 --- a/internal/middleware/timer.go +++ b/internal/middleware/timer.go @@ -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) diff --git a/internal/routes/play.go b/internal/routes/play.go index 3f3f11d..288cf33 100644 --- a/internal/routes/play.go +++ b/internal/routes/play.go @@ -19,11 +19,11 @@ func switchPoint(ctx context.Context, point int) leaf.HandlerFunc { switch point { case 2: // 镇水塔点位 return play.OnlyVideo - case 4: - // 4号点位(发卡机) + case 10: + // 10号点位(发卡机) return play.PushCard(ctx) - case 5: - // 5号点位(等待插卡) + case 11: + // 11号点位(等待插卡) return play.WaitCard(ctx) default: return play.Default diff --git a/internal/routes/wait.go b/internal/routes/wait.go index 7813ed0..2201633 100644 --- a/internal/routes/wait.go +++ b/internal/routes/wait.go @@ -2,6 +2,7 @@ package routes import ( "context" + "fmt" "game-driver/internal/middleware" "game-driver/internal/schema" "game-driver/leaf" @@ -18,10 +19,12 @@ import ( "time" ) -func runAction(c *leaf.Context, item schema.WaitItemModel, rootRules []cronrange.Rule, play func(c context.Context, item schema.WaitItemModel)) { +func runAction(c *leaf.Context, item schema.WaitItemModel, rootRules []cronrange.Rule, play func(c context.Context, item schema.WaitItemModel) error) { + // 设定默认时间规则 if item.Cron == "" { item.Cron = "* * * *" } + rules, err := cronrange.Parse(item.Cron) if err != nil { zap.S().Errorln("解析时间规则异常: ", err) @@ -64,7 +67,17 @@ func runAction(c *leaf.Context, item schema.WaitItemModel, rootRules []cronrange return default: if run { - play(ctx, item) + err := play(ctx, item) + if err != nil { + zap.S().Errorln("执行动作异常: ", err) + <-time.After(time.Minute) + } + } else { + select { + case <-c.Done(): + return + case <-time.After(time.Second): + } } } } @@ -73,6 +86,11 @@ func runAction(c *leaf.Context, item schema.WaitItemModel, rootRules []cronrange func WaitAction(c *leaf.Context) { payload := leaf.Value[*schema.WaitModel](c, middleware.PayloadJSONKey) + // 设定默认时间规则 + if payload.Cron == "" { + payload.Cron = "* * * *" + } + rules, err := cronrange.Parse(payload.Cron) if err != nil { zap.S().Errorln("解析时间规则异常: ", err) @@ -125,11 +143,13 @@ func WaitAction(c *leaf.Context) { } } -func audioAction(c context.Context, item schema.WaitItemModel) { +func audioAction(c context.Context, item schema.WaitItemModel) error { data, err := utils.LinkAudio(item.Data) if err != nil { - zap.S().Errorln("音频数据获取异常: ", err) - return + return fmt.Errorf("音频数据获取异常: %w", err) + } + if data == nil { + return fmt.Errorf("音频数据获取为空") } zap.S().Infoln("播放待机音乐") @@ -138,8 +158,7 @@ func audioAction(c context.Context, item schema.WaitItemModel) { ctrl, closer, e := audio.PlayBgmMP3(data) defer closer() if e != nil { - zap.S().Errorln("播放待机音乐异常", e) - return + return fmt.Errorf("播放待机音乐异常: %w", e) } <-c.Done() @@ -147,13 +166,14 @@ func audioAction(c context.Context, item schema.WaitItemModel) { speaker.Lock() ctrl.Streamer = nil speaker.Unlock() + + return nil } -func ttsAction(c context.Context, item schema.WaitItemModel) { +func ttsAction(c context.Context, item schema.WaitItemModel) error { reader, err := tts.DefaultTTS.Get(item.Data) if err != nil { - zap.S().Errorln("语音合成异常: ", err) - return + return fmt.Errorf("语音合成异常: %w", err) } zap.S().Infoln("循环播放待机 TTS 语音") @@ -163,17 +183,16 @@ func ttsAction(c context.Context, item schema.WaitItemModel) { audio.PlayWav(c, reader) select { case <-c.Done(): - return + return nil case <-time.After(time.Duration(item.Interval) * time.Second): } } } -func relayAction(c context.Context, item schema.WaitItemModel) { +func relayAction(c context.Context, item schema.WaitItemModel) error { r, err := relay.New(item.Data) if err != nil { - zap.S().Errorln("继电器初始化异常: ", err) - return + return fmt.Errorf("继电器初始化异常: %w", err) } defer r.Close() @@ -183,13 +202,14 @@ func relayAction(c context.Context, item schema.WaitItemModel) { _ = r.On(0) <-c.Done() _ = r.Off(0) + + return nil } -func videoAction(c context.Context, item schema.WaitItemModel) { +func videoAction(c context.Context, item schema.WaitItemModel) error { local, err := utils.LinkVideo(item.Data) if err != nil { - zap.S().Errorln("视频文件获取异常: ", err) - return + return fmt.Errorf("视频文件获取异常: %w", err) } zap.S().Infoln("循环播放待机视频") @@ -201,18 +221,17 @@ func videoAction(c context.Context, item schema.WaitItemModel) { for { err := video.Play(c, local) if err != nil { - zap.S().Infof("视频播放异常: %s", err) - return + return fmt.Errorf("视频播放异常: %w", err) } select { case <-c.Done(): - return + return nil case <-time.After(time.Duration(item.Interval) * time.Second): } } } -func webAction(c context.Context, item schema.WaitItemModel) { +func webAction(c context.Context, item schema.WaitItemModel) error { zap.S().Infoln("打开待机网页") // 控制背光 @@ -220,4 +239,5 @@ func webAction(c context.Context, item schema.WaitItemModel) { defer utils.BlankClose() browser.OpenApp(c, item.Data) + return nil } diff --git a/json.md b/json.md index 0cea813..05af5e1 100644 --- a/json.md +++ b/json.md @@ -8,7 +8,7 @@ url: `server/wushan/2/wait` "cron": "* * * *", "items": [ { - "data": "file://./三峡龙脊BGM.mp3" + "data": "file:///opt/game/三峡龙脊BGM.mp3" } ] } @@ -30,7 +30,7 @@ url: `server/wushan/2/play` ] }, "game": { - "video": "file://./镇水塔法阵.mp4" + "video": "file:///opt/game/镇水塔法阵.mp4" } } ``` diff --git a/pkg/tts/aliyun.go b/pkg/tts/aliyun.go index 7f5a9d1..4e9ae61 100644 --- a/pkg/tts/aliyun.go +++ b/pkg/tts/aliyun.go @@ -71,7 +71,7 @@ func (tts *AliTTS) getToken() error { func (tts *AliTTS) Get(text string) (io.Reader, error) { param := nls.DefaultSpeechSynthesisParam() - param.Volume = 200 + param.Volume = tts.Volume param.Voice = tts.Voice param.SpeechRate = tts.SpeechRate diff --git a/pkg/utils/link_audio.go b/pkg/utils/link_audio.go index 0618318..36a4f65 100644 --- a/pkg/utils/link_audio.go +++ b/pkg/utils/link_audio.go @@ -64,7 +64,9 @@ func LinkAudio(link string) (bgm io.ReadCloser, err error) { } else { err = fmt.Errorf("不支持的链接协议: %v", u.String()) } - bgm = toSeeker(bgm) + if bgm != nil { + bgm = toSeeker(bgm) + } } return } diff --git a/pkg/video/paly.go b/pkg/video/paly.go index 2b09f1f..f8adbda 100644 --- a/pkg/video/paly.go +++ b/pkg/video/paly.go @@ -5,6 +5,7 @@ import ( "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 { diff --git a/puml/游戏.puml b/puml/游戏.puml index e5a42cd..78da3bb 100644 --- a/puml/游戏.puml +++ b/puml/游戏.puml @@ -28,12 +28,11 @@ ---- tts播报恭喜通关词 -- 青龙云台(5) --- 待机 ----- 网页默认页面 --- Game ---- 供电 ---- 播放语音 -- 俱乐部(6) --- 待机 ---- 启动屏幕 ---- Game +---- 网页默认页面 @endmindmap From 6e4bf4a2c07253f082dc9e98db469ca9e0ed6991 Mon Sep 17 00:00:00 2001 From: mapleafgo Date: Thu, 27 Feb 2025 11:42:37 +0800 Subject: [PATCH 08/29] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/middleware/pause.go | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/internal/middleware/pause.go b/internal/middleware/pause.go index f712f98..de0f0df 100644 --- a/internal/middleware/pause.go +++ b/internal/middleware/pause.go @@ -12,6 +12,9 @@ func Pause(ctrl *common.CtrlWait) leaf.HandlerFunc { return func(c *leaf.Context) { var cancel context.CancelFunc + // 保存原始的 Context + originalCtx := c.Context + // 获取锚点 holdPoint := c.Hold() @@ -19,16 +22,8 @@ func Pause(ctrl *common.CtrlWait) leaf.HandlerFunc { var wait sync.WaitGroup defer wait.Wait() - // 结束信号通道 - a := make(chan struct{}) - // 发送结束信号 - defer close(a) - run := true - // 保存原始的 Context - originalCtx := c.Context - wait.Add(1) go func() { defer wait.Done() From b36e67f826421ce91525c3a1b1ff0adbd3d56f21 Mon Sep 17 00:00:00 2001 From: mapleafgo Date: Thu, 27 Feb 2025 13:53:15 +0800 Subject: [PATCH 09/29] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/root.go | 2 +- config.yml | 4 ++-- internal/common/pause.go | 31 +++++++++++++++++++++++++++++-- internal/middleware/pause.go | 3 +++ json.md | 4 ++-- todo.md | 7 +++++++ 6 files changed, 44 insertions(+), 7 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 5b28ca5..dd1da4d 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -21,7 +21,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.0", 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.yml b/config.yml index 8f014ea..ba0c17c 100755 --- a/config.yml +++ b/config.yml @@ -17,9 +17,9 @@ aliyun: accessKeySecret: appKey: timeout: 10 # 单位 s - volume: 200 # 音量 + volume: 100 # 音量,取值范围:0~100 voice: zhifeng_emo # 发音人 - speechRate: 50 # 语速 + speechRate: 50 # 语速,取值范围:-500~500 #game: # addr: /dev/ttyUSB0 # 点位 11 的串口地址 # pushGroups: # 点位 10 的发卡器配置 diff --git a/internal/common/pause.go b/internal/common/pause.go index eb6724e..c157207 100644 --- a/internal/common/pause.go +++ b/internal/common/pause.go @@ -1,20 +1,46 @@ package common +import "sync" + type CtrlWait struct { // 用于暂停的chan P chan struct{} // 用于恢复的chan R chan struct{} + // 状态 + s bool + + m sync.RWMutex } // Pause 暂停 func (c *CtrlWait) Pause() { - c.P <- struct{}{} + c.m.RLock() + defer c.m.RUnlock() + if c.s { + c.P <- struct{}{} + } } // Resume 恢复 func (c *CtrlWait) Resume() { - c.R <- struct{}{} + c.m.RLock() + defer c.m.RUnlock() + if c.s { + c.R <- struct{}{} + } +} + +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 创建一个控制等待 @@ -22,6 +48,7 @@ func NewCtrlWait() *CtrlWait { return &CtrlWait{ P: make(chan struct{}), R: make(chan struct{}), + s: false, } } diff --git a/internal/middleware/pause.go b/internal/middleware/pause.go index de0f0df..2ff5ef5 100644 --- a/internal/middleware/pause.go +++ b/internal/middleware/pause.go @@ -29,6 +29,9 @@ func Pause(ctrl *common.CtrlWait) leaf.HandlerFunc { defer wait.Done() zap.S().Infoln("待机控制器") + ctrl.Open() + defer ctrl.Close() + for { select { case <-originalCtx.Done(): diff --git a/json.md b/json.md index 05af5e1..0cea813 100644 --- a/json.md +++ b/json.md @@ -8,7 +8,7 @@ url: `server/wushan/2/wait` "cron": "* * * *", "items": [ { - "data": "file:///opt/game/三峡龙脊BGM.mp3" + "data": "file://./三峡龙脊BGM.mp3" } ] } @@ -30,7 +30,7 @@ url: `server/wushan/2/play` ] }, "game": { - "video": "file:///opt/game/镇水塔法阵.mp4" + "video": "file://./镇水塔法阵.mp4" } } ``` diff --git a/todo.md b/todo.md index 809e34f..1ed277f 100644 --- a/todo.md +++ b/todo.md @@ -33,6 +33,13 @@ xset dpms force on ffplay -autoexit -fs -i video.mp4 ``` +### 注册为 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 . From f251df5ce323021ac7e305ae83802e62c85521dd Mon Sep 17 00:00:00 2001 From: mapleafgo Date: Thu, 27 Feb 2025 17:41:14 +0800 Subject: [PATCH 10/29] =?UTF-8?q?=E7=A8=B3=E5=AE=9A=E5=8F=91=E5=B8=831.0.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.yml | 2 +- internal/middleware/pause.go | 1 + internal/middleware/unique.go | 2 +- internal/routes/wait.go | 54 ++++++++++++++++++++++------------- pkg/video/paly.go | 6 ++-- readme.md | 7 ++++- todo.md | 2 ++ 7 files changed, 47 insertions(+), 27 deletions(-) diff --git a/config.yml b/config.yml index ba0c17c..b383814 100755 --- a/config.yml +++ b/config.yml @@ -1,5 +1,5 @@ location: wushan # 项目名称 -point: 2 # 点位 +point: 3 # 点位 relay: maxTimeout: 60 # 单位 s,必须大于 0 log: diff --git a/internal/middleware/pause.go b/internal/middleware/pause.go index 2ff5ef5..0fd2c49 100644 --- a/internal/middleware/pause.go +++ b/internal/middleware/pause.go @@ -35,6 +35,7 @@ func Pause(ctrl *common.CtrlWait) leaf.HandlerFunc { for { select { case <-originalCtx.Done(): + cancel() zap.S().Infoln("待机控制器监听结束") return case <-ctrl.R: diff --git a/internal/middleware/unique.go b/internal/middleware/unique.go index d1fcb9a..c0d2eff 100644 --- a/internal/middleware/unique.go +++ b/internal/middleware/unique.go @@ -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() } diff --git a/internal/routes/wait.go b/internal/routes/wait.go index 2201633..189b285 100644 --- a/internal/routes/wait.go +++ b/internal/routes/wait.go @@ -31,6 +31,9 @@ func runAction(c *leaf.Context, item schema.WaitItemModel, rootRules []cronrange return } + a := make(chan bool) + defer close(a) + // 等待组 var wait sync.WaitGroup defer wait.Wait() @@ -38,46 +41,57 @@ func runAction(c *leaf.Context, item schema.WaitItemModel, rootRules []cronrange ticker := time.NewTicker(time.Second) defer ticker.Stop() - ctx, cancel := context.WithCancel(context.TODO()) - - var run bool - wait.Add(1) go func() { defer wait.Done() for { select { case <-c.Done(): - cancel() return case <-ticker.C: if cronrange.Match(rules, time.Now()) && cronrange.Match(rootRules, time.Now()) { - run = true + a <- true } else { - run = false - cancel() + a <- false } } } }() + var cancel context.CancelFunc + var m sync.Mutex for { select { case <-c.Done(): + if cancel != nil { + cancel() + } return - default: - if run { - err := play(ctx, item) - if err != nil { - zap.S().Errorln("执行动作异常: ", err) - <-time.After(time.Minute) - } - } else { - select { - case <-c.Done(): - return - case <-time.After(time.Second): + case r := <-a: + if r { + if ok := m.TryLock(); ok { + ctx, cc := context.WithCancel(context.TODO()) + cancel = cc + wait.Add(1) + go func() { + defer wait.Done() + defer m.Unlock() + defer func() { cancel = nil }() + + err := play(ctx, item) + if err != nil { + zap.S().Errorln("执行动作异常: ", err) + select { + case <-ctx.Done(): + return + case <-time.After(time.Minute): + } + } + }() } + } else if cancel != nil { + cancel() + cancel = nil } } } diff --git a/pkg/video/paly.go b/pkg/video/paly.go index f8adbda..ee8b75c 100644 --- a/pkg/video/paly.go +++ b/pkg/video/paly.go @@ -29,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 { diff --git a/readme.md b/readme.md index d78ef3d..bdc63c4 100644 --- a/readme.md +++ b/readme.md @@ -127,7 +127,7 @@ Payload: ```json lines { - // 执行的时间区间 + // 执行的时间区间(需要系统配置正确时区) "cron": "17:20-21:35 1-5 * *", // 执行项 "items": [ @@ -148,6 +148,11 @@ Payload: > 同一个类型的待机任务只能有一个,当有新的任务到达时会覆盖之前的任务 +### 配置时区 +```bash +sudo timedatectl set-timezone Asia/Shanghai +``` + ### Cron Format The format consists of four fields separated by whitespace: diff --git a/todo.md b/todo.md index 1ed277f..1007d1f 100644 --- a/todo.md +++ b/todo.md @@ -106,6 +106,8 @@ ExecStart=-/sbin/agetty --autologin --noclear %I $TERM # } ``` +如果没有 `~/.config/i3/config` 文件,需要重启系统,进入 i3 后,根据提示创建默认的配置文件 + ### 配置 i3 安装 `unclutter`: From 4973b8471e6515b47e41bc297250fb37ef05658b Mon Sep 17 00:00:00 2001 From: mapleafgo Date: Fri, 28 Feb 2025 12:34:13 +0800 Subject: [PATCH 11/29] =?UTF-8?q?=E6=8E=A5=E5=85=A5=E8=85=BE=E8=AE=AF?= =?UTF-8?q?=E4=BA=91=E6=97=A5=E5=BF=97=E6=9C=8D=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/config.go | 12 ++++++++++-- go.mod | 6 ++++++ go.sum | 21 ++++++++++++++++++++ internal/server.go | 12 +++++++++++- logger/cls.go | 49 ++++++++++++++++++++++++++++++++++++++++++++++ logger/logger.go | 27 ++++++++++++++++++++++++- 6 files changed, 123 insertions(+), 4 deletions(-) create mode 100644 logger/cls.go diff --git a/config/config.go b/config/config.go index 387cc8a..3a75f00 100644 --- a/config/config.go +++ b/config/config.go @@ -18,9 +18,17 @@ type AliyunConfig struct { SpeechRate int } +type TencentCLS struct { + Endpoint string + SecretID string + SecretKey string + TopicID string +} + type Logger struct { - File *lumberjack.Logger - Level string + File *lumberjack.Logger + TencentCLS TencentCLS + Level string } type config struct { diff --git a/go.mod b/go.mod index 6053126..afe1b0c 100644 --- a/go.mod +++ b/go.mod @@ -21,6 +21,7 @@ require ( github.com/ebitengine/oto/v3 v3.3.2 // indirect github.com/ebitengine/purego v0.8.1 // indirect github.com/fsnotify/fsnotify v1.8.0 // indirect + github.com/golang/protobuf v1.5.3 // 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 @@ -28,12 +29,14 @@ 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/klauspost/compress v1.17.2 // 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/slog-shim v0.1.0 // indirect @@ -43,16 +46,19 @@ require ( github.com/spf13/cast v1.7.1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.6.0 // indirect + github.com/tencentcloud/tencentcloud-cls-sdk-go v1.0.11 // indirect github.com/ysmood/fetchup v0.2.4 // 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.9.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 // indirect golang.org/x/net v0.33.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/text v0.21.0 // indirect + google.golang.org/protobuf v1.33.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index db7d2f6..7dbc9e7 100644 --- a/go.sum +++ b/go.sum @@ -20,6 +20,7 @@ github.com/ebitengine/purego v0.8.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI 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= @@ -31,7 +32,12 @@ 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.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 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= @@ -62,7 +68,11 @@ 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.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= +github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= 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= @@ -87,11 +97,14 @@ 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= @@ -122,6 +135,8 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT github.com/stretchr/testify v1.9.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= @@ -192,10 +207,16 @@ 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.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 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= diff --git a/internal/server.go b/internal/server.go index e654c91..66079a7 100644 --- a/internal/server.go +++ b/internal/server.go @@ -16,6 +16,7 @@ import ( "github.com/eclipse/paho.golang/autopaho" "github.com/eclipse/paho.golang/paho" "go.uber.org/zap" + "log" "net/url" "os" "os/signal" @@ -76,7 +77,16 @@ 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() diff --git a/logger/cls.go b/logger/cls.go new file mode 100644 index 0000000..902427b --- /dev/null +++ b/logger/cls.go @@ -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 +} diff --git a/logger/logger.go b/logger/logger.go index 55ec51e..0f5911e 100644 --- a/logger/logger.go +++ b/logger/logger.go @@ -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(os.Stdout)) + + // 核心配置 + core := zapcore.NewCore(cn, mws, level) + + // 构建 logger + logger := zap.New(core, zap.AddCaller()) + + // 替换全局 logger + zap.ReplaceGlobals(logger) +} + func Sync() { zap.L().Sync() } From 3a2fc431ac0f9e9304cb47b3be8ca590e9c37b46 Mon Sep 17 00:00:00 2001 From: mapleafgo Date: Fri, 28 Feb 2025 17:05:07 +0800 Subject: [PATCH 12/29] =?UTF-8?q?=E5=8A=A0=E5=85=A5=E7=AD=89=E5=BE=85?= =?UTF-8?q?=E7=BB=A7=E7=94=B5=E5=99=A8=E9=87=8A=E6=94=BE=EF=BC=8C=E5=8A=A0?= =?UTF-8?q?=E5=85=A5=E7=A8=8B=E5=BA=8F=E5=81=9C=E6=AD=A2=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=E6=97=B6=E9=87=8A=E6=94=BE=E6=89=80=E6=9C=89=E8=B5=84=E6=BA=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.yml | 2 +- internal/server.go | 5 ++++- leaf/leaf.go | 46 ++++++++++++++++++++++++++++++++++++++++------ logger/logger.go | 2 +- pkg/relay/relay.go | 29 +++++++++++++++++++++++++++++ 5 files changed, 75 insertions(+), 9 deletions(-) diff --git a/config.yml b/config.yml index b383814..f404df6 100755 --- a/config.yml +++ b/config.yml @@ -11,7 +11,7 @@ log: maxAge: 30 compress: true mqtt: - url: mqtt://58.144.199.46:1883 + url: mqtt://wushan-mqtt.chaoshengshuzi.com:1883 aliyun: accessKeyID: accessKeySecret: diff --git a/internal/server.go b/internal/server.go index 66079a7..305dbf9 100644 --- a/internal/server.go +++ b/internal/server.go @@ -126,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() } @@ -179,6 +179,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("关闭完成") } diff --git a/leaf/leaf.go b/leaf/leaf.go index d63d2b4..1e5d92d 100644 --- a/leaf/leaf.go +++ b/leaf/leaf.go @@ -34,8 +34,13 @@ func (c HandlersChain) Last() HandlerFunc { } type Engine struct { - mu sync.RWMutex - ctx context.Context + 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 +49,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 +62,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), @@ -108,13 +119,21 @@ func (e *Engine) Route(pb *packets.Publish) { 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() + e.queueWg.Add(1) + go func() { + defer e.queueWg.Done() + WithLeafContext(e.ctx, m, e, handlers).Next() + }() handlerCalled = true } } 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 +153,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") diff --git a/logger/logger.go b/logger/logger.go index 0f5911e..ab1f722 100644 --- a/logger/logger.go +++ b/logger/logger.go @@ -51,7 +51,7 @@ func InitProLogger(cls *TenCls) { cn := zapcore.NewJSONEncoder(nc) // 多个输出 - mws := zapcore.NewMultiWriteSyncer(zapcore.AddSync(cls), zapcore.AddSync(os.Stdout)) + mws := zapcore.NewMultiWriteSyncer(zapcore.AddSync(cls), zapcore.AddSync(config.C.Log.File), zapcore.AddSync(os.Stdout)) // 核心配置 core := zapcore.NewCore(cn, mws, level) diff --git a/pkg/relay/relay.go b/pkg/relay/relay.go index d1fc53c..73d0b3a 100644 --- a/pkg/relay/relay.go +++ b/pkg/relay/relay.go @@ -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 From 81f31f15a5a763171d0df7b8588c7931356b778f Mon Sep 17 00:00:00 2001 From: mapleafgo Date: Fri, 28 Feb 2025 20:34:50 +0800 Subject: [PATCH 13/29] =?UTF-8?q?=E5=8A=A0=E5=85=A5=20pjlink=20=E6=8E=A7?= =?UTF-8?q?=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/root.go | 16 +++- config.yml | 10 +++ config/game/game.go | 2 +- config/wait/pjlink.go | 8 ++ config/wait/wait.go | 14 +++ internal/routes/play/push_card.go | 2 +- internal/routes/play/wait_card.go | 2 +- internal/routes/wait.go | 35 ++++++++ internal/schema/wait.go | 1 + json.md | 38 +++++--- pkg/pjlink/pjlink.go | 141 ++++++++++++++++++++++++++++++ 11 files changed, 253 insertions(+), 16 deletions(-) create mode 100644 config/wait/pjlink.go create mode 100644 config/wait/wait.go create mode 100644 pkg/pjlink/pjlink.go 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 + } +} From 53d9df6e0adc9c7dbe12c0b6245ffa2985c019a9 Mon Sep 17 00:00:00 2001 From: mapleafgo Date: Sat, 1 Mar 2025 13:28:57 +0800 Subject: [PATCH 14/29] =?UTF-8?q?=E7=94=A8=20vlc=20=E6=9B=BF=E6=8D=A2=20ff?= =?UTF-8?q?play=20=E7=9A=84=E6=92=AD=E6=94=BE=E6=96=B9=E5=BC=8F=E7=94=A8?= =?UTF-8?q?=E6=9D=A5=E4=BF=AE=E5=A4=8D=E8=A7=86=E9=A2=91=E6=92=AD=E6=94=BE?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go.mod | 1 + go.sum | 2 + internal/routes/wait.go | 4 +- pkg/video/play.go | 68 ++++++++++++++++++++++++++++++++ pkg/{video => video_old}/paly.go | 2 +- 5 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 pkg/video/play.go rename pkg/{video => video_old}/paly.go (97%) diff --git a/go.mod b/go.mod index afe1b0c..c5cf2d0 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( ) require ( + github.com/adrg/libvlc-go/v3 v3.1.6 // indirect github.com/aliyun/alibaba-cloud-sdk-go v1.63.76 // indirect github.com/ebitengine/oto/v3 v3.3.2 // indirect github.com/ebitengine/purego v0.8.1 // indirect diff --git a/go.sum b/go.sum index 7dbc9e7..54b7e8d 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,8 @@ 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.76 h1:mg/+23+/gAw6zdxv9I5dPCj666WJPLk8S1nXm0dOumQ= diff --git a/internal/routes/wait.go b/internal/routes/wait.go index 47ca1ec..5bf5903 100644 --- a/internal/routes/wait.go +++ b/internal/routes/wait.go @@ -199,7 +199,7 @@ func ttsAction(c context.Context, item schema.WaitItemModel) error { return fmt.Errorf("语音合成异常: %w", err) } - zap.S().Infoln("循环播放待机 TTS 语音") + zap.S().Infoln("播放待机 TTS 语音") defer zap.S().Infoln("结束待机 TTS 语音") for { @@ -235,7 +235,7 @@ func videoAction(c context.Context, item schema.WaitItemModel) error { return fmt.Errorf("视频文件获取异常: %w", err) } - zap.S().Infoln("循环播放待机视频") + zap.S().Infoln("播放待机视频") defer zap.S().Infoln("结束待机视频") utils.BlankOpen() diff --git a/pkg/video/play.go b/pkg/video/play.go new file mode 100644 index 0000000..dda8be4 --- /dev/null +++ b/pkg/video/play.go @@ -0,0 +1,68 @@ +package video + +import ( + "context" + "fmt" + libvlc "github.com/adrg/libvlc-go/v3" +) + +func Play(ctx context.Context, file string) 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 _, err := player.LoadMediaFromPath(file); 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", file) + select { + case <-ctx.Done(): + return fmt.Errorf("播放被用户中断") + case <-done: + fmt.Println("播放正常结束") + return nil + } +} diff --git a/pkg/video/paly.go b/pkg/video_old/paly.go similarity index 97% rename from pkg/video/paly.go rename to pkg/video_old/paly.go index ee8b75c..d9f7062 100644 --- a/pkg/video/paly.go +++ b/pkg/video_old/paly.go @@ -1,4 +1,4 @@ -package video +package video_old import ( "bufio" From 6d52ddb572a582fae03ee4aca83f942ddc38997d Mon Sep 17 00:00:00 2001 From: mapleafgo Date: Sat, 1 Mar 2025 15:05:53 +0800 Subject: [PATCH 15/29] =?UTF-8?q?=E5=9C=A8=E8=AE=A2=E9=98=85=E4=B9=8B?= =?UTF-8?q?=E5=90=8E=E5=86=8D=E8=BF=9E=E6=8E=A5mqtt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/server.go | 20 ++++++++++---------- json.md | 5 +---- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/internal/server.go b/internal/server.go index 305dbf9..63078ec 100644 --- a/internal/server.go +++ b/internal/server.go @@ -108,16 +108,6 @@ func Run() { 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) @@ -165,6 +155,16 @@ func Run() { routes.Command(device), ) + // 构建 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() diff --git a/json.md b/json.md index c748d4a..47fbdd8 100644 --- a/json.md +++ b/json.md @@ -60,11 +60,8 @@ url: `server/wushan/5/play` "bgm": "file://./青云龙台.mp3", "power": true, "volume":1, - "tts": { - "start": "刘佳勇者,恭喜你成功通关!" - }, "game": { - "wait": 10 + "wait": 36 } } ``` From 363047c078f0c4170d980d77fc63e91500b18e30 Mon Sep 17 00:00:00 2001 From: mapleafgo Date: Sat, 1 Mar 2025 15:25:00 +0800 Subject: [PATCH 16/29] =?UTF-8?q?=E5=90=AF=E5=8A=A8=E6=97=B6=E6=89=93?= =?UTF-8?q?=E5=8D=B0IP=E5=9C=B0=E5=9D=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/server.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/internal/server.go b/internal/server.go index 63078ec..7229b96 100644 --- a/internal/server.go +++ b/internal/server.go @@ -17,6 +17,7 @@ import ( "github.com/eclipse/paho.golang/paho" "go.uber.org/zap" "log" + "net" "net/url" "os" "os/signal" @@ -90,6 +91,13 @@ func Run() { // 应用退出时刷新所有缓冲日志 defer logger.Sync() + // 获取当前IP,并打印当前IP + addrs, err := net.InterfaceAddrs() + if err != nil { + log.Panicln("网络连接异常: ", err) + } + zap.S().Infoln("当前IP: ", addrs) + // 启动时关闭屏幕 utils.BlankClose() From c71e8bc13d947529ef7cb7b7f781a2ded6ee06c8 Mon Sep 17 00:00:00 2001 From: mapleafgo Date: Wed, 5 Mar 2025 11:03:19 +0800 Subject: [PATCH 17/29] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=8A=95=E5=BD=B1?= =?UTF-8?q?=E4=BB=AA=E6=8E=A7=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go.mod | 36 +-- go.sum | 33 +++ internal/common/pause.go | 14 +- internal/common/pause_sub.go | 53 ++++ internal/middleware/pause.go | 69 ------ internal/routes/standby/audio.go | 40 +++ internal/routes/standby/pjlink.go | 35 +++ internal/routes/standby/relay.go | 28 +++ internal/routes/standby/tts.go | 32 +++ internal/routes/standby/video.go | 38 +++ internal/routes/standby/web.go | 22 ++ internal/routes/standby_ctrl/pause.go | 66 +++++ internal/routes/standby_ctrl/time.go | 91 +++++++ internal/routes/wait.go | 336 +++++--------------------- internal/schema/wait.go | 1 + internal/server.go | 3 +- pkg/pjlink/pjlink.go | 56 +++-- todo.md | 44 +++- 18 files changed, 598 insertions(+), 399 deletions(-) create mode 100644 internal/common/pause_sub.go delete mode 100644 internal/middleware/pause.go create mode 100644 internal/routes/standby/audio.go create mode 100644 internal/routes/standby/pjlink.go create mode 100644 internal/routes/standby/relay.go create mode 100644 internal/routes/standby/tts.go create mode 100644 internal/routes/standby/video.go create mode 100644 internal/routes/standby/web.go create mode 100644 internal/routes/standby_ctrl/pause.go create mode 100644 internal/routes/standby_ctrl/time.go diff --git a/go.mod b/go.mod index c5cf2d0..b1a9fbe 100644 --- a/go.mod +++ b/go.mod @@ -3,26 +3,27 @@ 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/go-pkgz/cronrange v0.2.0 github.com/go-rod/rod v0.116.2 - 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/gopxl/beep/v2 v2.1.1 + github.com/grid-x/modbus v0.0.0-20250219144522-2b18d136199f + 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/adrg/libvlc-go/v3 v3.1.6 // indirect - github.com/aliyun/alibaba-cloud-sdk-go v1.63.76 // indirect + github.com/aliyun/alibaba-cloud-sdk-go v1.63.92 // indirect github.com/ebitengine/oto/v3 v3.3.2 // indirect - github.com/ebitengine/purego v0.8.1 // indirect + github.com/ebitengine/purego v0.8.2 // indirect github.com/fsnotify/fsnotify v1.8.0 // indirect - github.com/golang/protobuf v1.5.3 // 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 @@ -30,7 +31,7 @@ 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/klauspost/compress v1.17.2 // 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 @@ -39,27 +40,26 @@ require ( 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/afero v1.12.0 // indirect github.com/spf13/cast v1.7.1 // indirect - github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/pflag v1.0.6 // indirect github.com/subosito/gotenv v1.6.0 // indirect - github.com/tencentcloud/tencentcloud-cls-sdk-go v1.0.11 // indirect github.com/ysmood/fetchup v0.2.4 // 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.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-20241217172543-b2144cdd0a67 // indirect - golang.org/x/net v0.33.0 // indirect - golang.org/x/sys v0.28.0 // indirect - golang.org/x/text v0.21.0 // indirect - google.golang.org/protobuf v1.33.0 // indirect + golang.org/x/exp v0.0.0-20250228200357-dead58393ab7 // indirect + golang.org/x/net v0.35.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/text v0.22.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 ) diff --git a/go.sum b/go.sum index 54b7e8d..210c440 100644 --- a/go.sum +++ b/go.sum @@ -7,9 +7,12 @@ github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3 github.com/aliyun/alibaba-cloud-sdk-go v1.61.1376/go.mod h1:9CMdKNL3ynIGPpfTcdwTvIm8SGuAZYYC4jFVSSvE1YQ= github.com/aliyun/alibaba-cloud-sdk-go v1.63.76 h1:mg/+23+/gAw6zdxv9I5dPCj666WJPLk8S1nXm0dOumQ= github.com/aliyun/alibaba-cloud-sdk-go v1.63.76/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/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= @@ -19,6 +22,8 @@ github.com/ebitengine/oto/v3 v3.3.2 h1:VTWBsKX9eb+dXzaF4jEwQbs4yWIdXukJ0K40KgkpY github.com/ebitengine/oto/v3 v3.3.2/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/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= @@ -37,6 +42,8 @@ github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGw github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +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= @@ -46,11 +53,15 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ 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= @@ -73,6 +84,8 @@ github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E github.com/klauspost/compress v1.15.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +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= @@ -112,6 +125,8 @@ github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/f 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= @@ -122,12 +137,18 @@ github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9yS 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/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.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +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.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +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= @@ -163,6 +184,8 @@ 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 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= 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= @@ -178,6 +201,8 @@ golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 h1:1UoZQm6f0P/ZO0w1Ri+f+ifG/gXhegadRdwBIXEFWDo= golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= +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/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= @@ -188,6 +213,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= 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= @@ -195,9 +222,13 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +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/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= @@ -214,6 +245,8 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +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= diff --git a/internal/common/pause.go b/internal/common/pause.go index c157207..792710d 100644 --- a/internal/common/pause.go +++ b/internal/common/pause.go @@ -3,13 +3,10 @@ package common import "sync" type CtrlWait struct { - // 用于暂停的chan - P chan struct{} - // 用于恢复的chan - R chan struct{} + C chan int8 + // 状态 s bool - m sync.RWMutex } @@ -18,7 +15,7 @@ func (c *CtrlWait) Pause() { c.m.RLock() defer c.m.RUnlock() if c.s { - c.P <- struct{}{} + c.C <- 1 } } @@ -27,7 +24,7 @@ func (c *CtrlWait) Resume() { c.m.RLock() defer c.m.RUnlock() if c.s { - c.R <- struct{}{} + c.C <- 0 } } @@ -46,8 +43,7 @@ func (c *CtrlWait) Close() { // NewCtrlWait 创建一个控制等待 func NewCtrlWait() *CtrlWait { return &CtrlWait{ - P: make(chan struct{}), - R: make(chan struct{}), + C: make(chan int8), s: false, } } diff --git a/internal/common/pause_sub.go b/internal/common/pause_sub.go new file mode 100644 index 0000000..1b211a8 --- /dev/null +++ b/internal/common/pause_sub.go @@ -0,0 +1,53 @@ +package common + +import "sync" + +type PauseSub struct { + ctrl *CtrlWait + // 回调函数 + items []chan int8 + m sync.RWMutex +} + +// Add 添加一个暂停项 +func (p *PauseSub) Add(item chan int8) { + p.m.Lock() + defer p.m.Unlock() + p.items = append(p.items, item) +} + +// Remove 移除一个暂停项 +func (p *PauseSub) Remove(item chan int8) { + p.m.Lock() + defer p.m.Unlock() + for i, v := range p.items { + if v == item { + p.items = append(p.items[:i], p.items[i+1:]...) + } + } +} + +// Run 开始监听 +func (p *PauseSub) Run() { + p.ctrl.Open() + defer p.ctrl.Close() + + for { + select { + case <-p.ctrl.C: + go func() { + p.m.RLock() + defer p.m.RUnlock() + for _, item := range p.items { + item <- 1 + } + }() + } + } +} + +func NewPauseSub(c *CtrlWait) *PauseSub { + return &PauseSub{ + ctrl: c, + } +} diff --git a/internal/middleware/pause.go b/internal/middleware/pause.go deleted file mode 100644 index 0fd2c49..0000000 --- a/internal/middleware/pause.go +++ /dev/null @@ -1,69 +0,0 @@ -package middleware - -import ( - "context" - "game-driver/internal/common" - "game-driver/leaf" - "go.uber.org/zap" - "sync" -) - -func Pause(ctrl *common.CtrlWait) leaf.HandlerFunc { - return func(c *leaf.Context) { - var cancel context.CancelFunc - - // 保存原始的 Context - originalCtx := c.Context - - // 获取锚点 - holdPoint := c.Hold() - - // 等待组 - var wait sync.WaitGroup - defer wait.Wait() - - run := true - - wait.Add(1) - go func() { - defer wait.Done() - zap.S().Infoln("待机控制器") - - ctrl.Open() - defer ctrl.Close() - - for { - select { - case <-originalCtx.Done(): - cancel() - zap.S().Infoln("待机控制器监听结束") - return - case <-ctrl.R: - { - zap.S().Infoln("待机控制器 Resume 触发") - c.Context = originalCtx - run = true - } - case <-ctrl.P: - { - zap.S().Infoln("待机控制器 Pause 触发") - run = false - cancel() - } - } - } - }() - - for { - select { - case <-originalCtx.Done(): - return - default: - if run { - cancel = leaf.WithCancel(c) - c.Resume(holdPoint) - } - } - } - } -} diff --git a/internal/routes/standby/audio.go b/internal/routes/standby/audio.go new file mode 100644 index 0000000..44c6178 --- /dev/null +++ b/internal/routes/standby/audio.go @@ -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 + } +} diff --git a/internal/routes/standby/pjlink.go b/internal/routes/standby/pjlink.go new file mode 100644 index 0000000..bfed900 --- /dev/null +++ b/internal/routes/standby/pjlink.go @@ -0,0 +1,35 @@ +package standby + +import ( + "context" + "fmt" + "game-driver/config/wait" + "game-driver/internal/schema" + "game-driver/pkg/pjlink" + "go.uber.org/zap" +) + +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.PowerOn() + if err != nil { + return fmt.Errorf("打开投影仪异常: %w", err) + } + zap.S().Infoln("投影仪返回报文:", resp) + + <-c.Done() + + zap.S().Infoln("关闭待机投影仪") + resp, err = pjc.PowerOff() + if err != nil { + return fmt.Errorf("关闭投影仪异常: %w", err) + } + zap.S().Infoln("投影仪返回报文:", resp) + + return nil + } +} diff --git a/internal/routes/standby/relay.go b/internal/routes/standby/relay.go new file mode 100644 index 0000000..a910d6f --- /dev/null +++ b/internal/routes/standby/relay.go @@ -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 + } +} diff --git a/internal/routes/standby/tts.go b/internal/routes/standby/tts.go new file mode 100644 index 0000000..3539d90 --- /dev/null +++ b/internal/routes/standby/tts.go @@ -0,0 +1,32 @@ +package standby + +import ( + "context" + "fmt" + "game-driver/internal/schema" + "game-driver/pkg/audio" + "game-driver/pkg/tts" + "go.uber.org/zap" + "time" +) + +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 语音") + + for { + audio.PlayWav(c, reader) + select { + case <-c.Done(): + return nil + case <-time.After(time.Duration(item.Interval) * time.Second): + } + } + } +} diff --git a/internal/routes/standby/video.go b/internal/routes/standby/video.go new file mode 100644 index 0000000..6cf1846 --- /dev/null +++ b/internal/routes/standby/video.go @@ -0,0 +1,38 @@ +package standby + +import ( + "context" + "fmt" + "game-driver/internal/schema" + "game-driver/pkg/utils" + "game-driver/pkg/video" + "go.uber.org/zap" + "time" +) + +func Video(item schema.WaitItemModel) func(c context.Context) error { + return func(c context.Context) error { + 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() + + for { + err := video.Play(c, local) + if err != nil { + return fmt.Errorf("视频播放异常: %w", err) + } + select { + case <-c.Done(): + return nil + case <-time.After(time.Duration(item.Interval) * time.Second): + } + } + } +} diff --git a/internal/routes/standby/web.go b/internal/routes/standby/web.go new file mode 100644 index 0000000..4bb488f --- /dev/null +++ b/internal/routes/standby/web.go @@ -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 + } +} diff --git a/internal/routes/standby_ctrl/pause.go b/internal/routes/standby_ctrl/pause.go new file mode 100644 index 0000000..82ba5da --- /dev/null +++ b/internal/routes/standby_ctrl/pause.go @@ -0,0 +1,66 @@ +package standby_ctrl + +import ( + "context" + "game-driver/internal/common" + "go.uber.org/zap" + "sync" +) + +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 { + p := make(chan int8) + defer close(p) + + ps.Add(p) + defer ps.Remove(p) + + zap.S().Infoln("待机控制器") + defer zap.S().Infoln("待机控制器结束") + + // 等待组 + 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) + } + } + } + } + } +} diff --git a/internal/routes/standby_ctrl/time.go b/internal/routes/standby_ctrl/time.go new file mode 100644 index 0000000..3bd2e0e --- /dev/null +++ b/internal/routes/standby_ctrl/time.go @@ -0,0 +1,91 @@ +package standby_ctrl + +import ( + "context" + "github.com/go-pkgz/cronrange" + "go.uber.org/zap" + "sync" + "time" +) + +// Time 时间控制器 +func Time(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) + + // 等待组 + 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 + } + } + } + } +} diff --git a/internal/routes/wait.go b/internal/routes/wait.go index 5bf5903..c5476bf 100644 --- a/internal/routes/wait.go +++ b/internal/routes/wait.go @@ -2,291 +2,85 @@ package routes import ( "context" - "fmt" - "game-driver/config/wait" + "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" - "game-driver/pkg/audio" - "game-driver/pkg/browser" - "game-driver/pkg/pjlink" - "game-driver/pkg/relay" - "game-driver/pkg/tts" - "game-driver/pkg/utils" - "game-driver/pkg/video" "github.com/go-pkgz/cronrange" - "github.com/gopxl/beep/v2/speaker" "go.uber.org/zap" "sync" - "time" ) -func runAction(c *leaf.Context, item schema.WaitItemModel, rootRules []cronrange.Rule, play func(c context.Context, item schema.WaitItemModel) error) { - // 设定默认时间规则 - if item.Cron == "" { - item.Cron = "* * * *" - } +func WaitAction(ctrl *common.CtrlWait) leaf.HandlerFunc { + ps := common.NewPauseSub(ctrl) - rules, err := cronrange.Parse(item.Cron) - if err != nil { - zap.S().Errorln("解析时间规则异常: ", err) - return - } + return func(c *leaf.Context) { + payload := leaf.Value[*schema.WaitModel](c, middleware.PayloadJSONKey) - a := make(chan bool) - defer close(a) - - // 等待组 - var wait sync.WaitGroup - defer wait.Wait() - - ticker := time.NewTicker(time.Second) - defer ticker.Stop() - - wait.Add(1) - go func() { - defer wait.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 - } - } + // 设定默认时间规则,ctrl + if payload.Cron == "" { + payload.Cron = "* * * *" } - }() - var cancel context.CancelFunc - var m sync.Mutex - for { - select { - case <-c.Done(): - if cancel != nil { - cancel() - } - return - case r := <-a: - if r { - if ok := m.TryLock(); ok { - ctx, cc := context.WithCancel(context.TODO()) - cancel = cc - wait.Add(1) - go func() { - defer wait.Done() - defer m.Unlock() - defer func() { cancel = nil }() - - err := play(ctx, item) - if err != nil { - zap.S().Errorln("执行动作异常: ", err) - select { - case <-ctx.Done(): - return - case <-time.After(time.Minute): - } - } - }() - } - } else if cancel != nil { - cancel() - cancel = nil - } - } - } -} - -func WaitAction(c *leaf.Context) { - payload := leaf.Value[*schema.WaitModel](c, middleware.PayloadJSONKey) - - // 设定默认时间规则 - if payload.Cron == "" { - payload.Cron = "* * * *" - } - - rules, err := cronrange.Parse(payload.Cron) - if err != nil { - zap.S().Errorln("解析时间规则异常: ", err) - return - } - - // 等待组 - var wait sync.WaitGroup - defer wait.Wait() - for _, item := range payload.Items { - switch item.Type { - case schema.WaitAudio: - // 执行音乐播放 - wait.Add(1) - go func() { - defer wait.Done() - runAction(c, item, rules, audioAction) - }() - case schema.WaitTTS: - // 执行TTS播放 - wait.Add(1) - go func() { - defer wait.Done() - runAction(c, item, rules, ttsAction) - }() - case schema.WaitRelay: - // 执行继电器供电 - wait.Add(1) - go func() { - defer wait.Done() - runAction(c, item, rules, relayAction) - }() - case schema.WaitVideo: - // 执行视频播放 - wait.Add(1) - go func() { - defer wait.Done() - runAction(c, item, rules, videoAction) - }() - case schema.WaitWeb: - // 执行网页打开 - wait.Add(1) - go func() { - 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) - } - } -} - -func audioAction(c context.Context, item schema.WaitItemModel) 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 -} - -func ttsAction(c context.Context, item schema.WaitItemModel) 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 语音") - - for { - audio.PlayWav(c, reader) - select { - case <-c.Done(): - return nil - case <-time.After(time.Duration(item.Interval) * time.Second): - } - } -} - -func relayAction(c context.Context, item schema.WaitItemModel) 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 -} - -func videoAction(c context.Context, item schema.WaitItemModel) error { - 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() - - for { - err := video.Play(c, local) + rules, err := cronrange.Parse(payload.Cron) if err != nil { - return fmt.Errorf("视频播放异常: %w", err) + zap.S().Errorln("解析时间规则异常: ", err) + return } - select { - case <-c.Done(): - return nil - case <-time.After(time.Duration(item.Interval) * time.Second): + + // 等待组 + var waitGroup sync.WaitGroup + defer waitGroup.Wait() + + // 开启暂停监听 + waitGroup.Add(1) + go func() { + defer waitGroup.Done() + ps.Run() + }() + + // 处理每个待机控制 + 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.Time(rules, item.Cron, f) + if f == nil { + return + } + f = standby_ctrl.Pause(ps, item.Pause, 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)) + default: + zap.S().Infof("不支持的类型: %d\n", item.Type) + } } } } - -func webAction(c context.Context, item schema.WaitItemModel) error { - zap.S().Infoln("打开待机网页") - - // 控制背光 - utils.BlankOpen() - defer utils.BlankClose() - - 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 a263603..d1b8692 100644 --- a/internal/schema/wait.go +++ b/internal/schema/wait.go @@ -16,6 +16,7 @@ type WaitItemModel struct { Type WaitType `json:"type"` Data string `json:"data"` Interval int64 `json:"interval"` + Pause bool `json:"pause"` } type WaitModel struct { diff --git a/internal/server.go b/internal/server.go index 7229b96..79e7434 100644 --- a/internal/server.go +++ b/internal/server.go @@ -154,8 +154,7 @@ func Run() { middleware.PayloadJSON[schema.WaitModel](), middleware.Unique(common.GlobalBgStopper), middleware.EmergencyStop(common.GlobalBgStopper), - middleware.Pause(common.PassCtrl), - routes.WaitAction, + routes.WaitAction(common.PassCtrl), ) // 处理指令 router.RegisterHandler(topicPrefix+"command", diff --git a/pkg/pjlink/pjlink.go b/pkg/pjlink/pjlink.go index 60000ae..bb926fc 100644 --- a/pkg/pjlink/pjlink.go +++ b/pkg/pjlink/pjlink.go @@ -32,11 +32,11 @@ func NewClient(host, port, password, id string) *Client { } } -func (c *Client) Connect() error { +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 + return fmt.Errorf("连接异常: %w", err) } c.conn = conn @@ -44,14 +44,14 @@ func (c *Client) Connect() error { reader := bufio.NewReader(c.conn) response, err := reader.ReadString('\r') if err != nil { - c.Close() - return err + c.close() + return fmt.Errorf("读取异常: %w", err) } // Handle authentication if strings.HasPrefix(response, "PJLINK 1") { if c.Password == "" { - c.Close() + c.close() return ErrAuthFailed } @@ -62,13 +62,13 @@ func (c *Client) Connect() error { _, err = fmt.Fprintf(c.conn, "%s\r", authHash) if err != nil { - c.Close() - return err + c.close() + return fmt.Errorf("写入异常: %w", err) } authResponse, err := reader.ReadString('\r') if err != nil || !strings.Contains(authResponse, "OK") { - c.Close() + c.close() return ErrAuthFailed } } @@ -104,36 +104,54 @@ func (c *Client) sendCommand(command string) (string, error) { return "", ErrAuthFailed } else if result == "ERR2" { return "", ErrCommandError + } else if result == "ERR3" { + return "YES", nil } return result, nil } -func (c *Client) PowerOn() error { +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 + return "", err } - if response != "OK" { - return fmt.Errorf("unexpected response: %s", response) + if response == "YES" { + return response, nil + } else if response != "OK" { + return response, fmt.Errorf("unexpected response: %s", response) } - return nil + return response, nil } -func (c *Client) PowerOff() error { +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 + return "", err } - if response != "OK" { - return fmt.Errorf("unexpected response: %s", response) + if response == "YES" { + return response, nil + } else if response != "OK" { + return response, fmt.Errorf("unexpected response: %s", response) } - return nil + return response, nil } -func (c *Client) Close() { +func (c *Client) close() { if c.conn != nil { c.conn.Close() c.conn = nil diff --git a/todo.md b/todo.md index 1007d1f..9b1c954 100644 --- a/todo.md +++ b/todo.md @@ -1,22 +1,34 @@ # 技术点记录 + +### 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 +``` + ## linux 下播放音频 + ```bash sudo apt install libasound2-dev alsa-utils ``` + ## linux 下播放视频 + ```bash -sudo apt install ffmpeg +sudo apt install libvlc-dev vlc ``` -显示安装 +## 显示安装 ```bash sudo apt install xorg ``` ### 当前用户加入播放音频与视频的组中 + ```bash -sudo usermod -aG audio,video $USER +sudo usermod -aG audio,video,dialout $USER ``` ### 关闭背光 @@ -28,12 +40,8 @@ xset dpms force off xset dpms force on ``` -### 播放视频 -```bash -ffplay -autoexit -fs -i video.mp4 -``` - ### 注册为 service ,并开机启动 + ```bash sudo cp /script/game-driver.service /etc/systemd/system/ sudo systemctl enable game-driver @@ -41,11 +49,13 @@ 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引脚 + ```bash J8: 3V3 (1) (2) 5V @@ -73,6 +83,7 @@ GPIO26 (37) (38) GPIO20 ## 极简桌面环境并自动登录 ### 安装 xorg i3 + ```bash sudo apt install xorg i3-wm ``` @@ -80,6 +91,7 @@ sudo apt install xorg i3-wm ### 自动启动 Xorg 和窗口管理器 编辑 `.bashrc`文件,在文件的末尾添加以下行: + ```bash if [ -z "$DISPLAY" ] && [ "$(tty)" = "/dev/tty1" ]; then startx @@ -90,16 +102,20 @@ fi ### 自动登录 -编辑 `/etc/systemd/system/getty.target.wants/getty@tty1.service` 文件,将 `ExecStart` 行修改为: +编辑 `/etc/systemd/system/getty.target.wants/getty@tty1.service` 文件,将 `ExecStart` 行修改为: + ```bash ExecStart=-/sbin/agetty --autologin --noclear %I $TERM ``` + 其中: - - :替换为你想自动登录的用户名。 + +- :替换为你想自动登录的用户名。 ### 禁用 i3bar 状态栏 编辑 `~/.config/i3/config`,将如下行注释掉: + ```bash # bar { # status_command i3status @@ -111,11 +127,13 @@ ExecStart=-/sbin/agetty --autologin --noclear %I $TERM ### 配置 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 # 关闭屏幕自动关闭 @@ -136,13 +154,17 @@ sudo apt install fonts-noto-cjk fonts-noto-color-emoji ```bash sudo add-apt-repository ppa:xtradeb/apps -sudo apt update sudo apt install ungoogled-chromium ``` ### 设置默认启动页面 编辑 `~/.config/i3/config`,添加如下行: + ```bash exec --no-startup-id ungoogled-chromium --kiosk --disable-extensions --disable-translate --app= ``` + +### 设置系统默认音量 + +`alsamixer` `sudo alsactl store` From febcdfdbf79522b762417e5945a0bc2eb051cf1f Mon Sep 17 00:00:00 2001 From: mapleafgo Date: Fri, 7 Mar 2025 16:04:19 +0800 Subject: [PATCH 18/29] =?UTF-8?q?mqtt=E5=8A=A0=E5=85=A5=E8=AE=A4=E8=AF=81?= =?UTF-8?q?=EF=BC=8C=E4=BC=98=E5=8C=96=E8=A7=86=E9=A2=91=E6=92=AD=E6=94=BE?= =?UTF-8?q?=E4=B8=8E=E6=B5=8F=E8=A7=88=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/config.go | 4 ++- internal/common/pause_sub.go | 32 ++++++++++++++---------- internal/middleware/stop.go | 5 ++-- internal/routes/play/only_video.go | 4 +-- internal/routes/standby/video.go | 4 +-- internal/routes/standby_ctrl/pause.go | 11 +++------ internal/routes/standby_ctrl/time.go | 3 +++ internal/routes/wait.go | 2 +- internal/server.go | 7 +++--- pkg/browser/browser.go | 17 +------------ pkg/utils/link_video.go | 35 ++++----------------------- pkg/video/play.go | 14 ++++++++--- readme.md | 4 ++- 13 files changed, 60 insertions(+), 82 deletions(-) diff --git a/config/config.go b/config/config.go index 3a75f00..91b543f 100644 --- a/config/config.go +++ b/config/config.go @@ -5,7 +5,9 @@ import ( ) type MqttConfig struct { - Url string + Url string + ClientID string + Password string } type AliyunConfig struct { diff --git a/internal/common/pause_sub.go b/internal/common/pause_sub.go index 1b211a8..2fa1e0b 100644 --- a/internal/common/pause_sub.go +++ b/internal/common/pause_sub.go @@ -1,45 +1,50 @@ package common -import "sync" +import ( + "context" + "sync" +) type PauseSub struct { - ctrl *CtrlWait - // 回调函数 + ctrl *CtrlWait items []chan int8 m sync.RWMutex } -// Add 添加一个暂停项 -func (p *PauseSub) Add(item chan int8) { +func (p *PauseSub) GetNew() chan int8 { p.m.Lock() defer p.m.Unlock() - p.items = append(p.items, item) + sub := make(chan int8) + p.items = append(p.items, sub) + return sub } -// Remove 移除一个暂停项 -func (p *PauseSub) Remove(item chan int8) { +func (p *PauseSub) Close(sub chan int8) { p.m.Lock() defer p.m.Unlock() + close(sub) for i, v := range p.items { - if v == item { + if v == sub { p.items = append(p.items[:i], p.items[i+1:]...) } } } // Run 开始监听 -func (p *PauseSub) Run() { +func (p *PauseSub) Run(ctx context.Context) { p.ctrl.Open() defer p.ctrl.Close() for { select { - case <-p.ctrl.C: + case <-ctx.Done(): + return + case c := <-p.ctrl.C: go func() { p.m.RLock() defer p.m.RUnlock() for _, item := range p.items { - item <- 1 + item <- c } }() } @@ -48,6 +53,7 @@ func (p *PauseSub) Run() { func NewPauseSub(c *CtrlWait) *PauseSub { return &PauseSub{ - ctrl: c, + ctrl: c, + items: make([]chan int8, 0), } } diff --git a/internal/middleware/stop.go b/internal/middleware/stop.go index cc1c3a3..366eb7c 100644 --- a/internal/middleware/stop.go +++ b/internal/middleware/stop.go @@ -13,6 +13,9 @@ 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() @@ -22,11 +25,9 @@ func EmergencyStop(stopper common.Stopper) leaf.HandlerFunc { // 发送结束信号 defer close(a) - zap.S().Infoln("监听停止信号") wait.Add(1) go func() { defer wait.Done() - defer zap.S().Infoln("结束停止信号监听") select { case <-a: diff --git a/internal/routes/play/only_video.go b/internal/routes/play/only_video.go index 94d1d71..f42c368 100644 --- a/internal/routes/play/only_video.go +++ b/internal/routes/play/only_video.go @@ -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) } } diff --git a/internal/routes/standby/video.go b/internal/routes/standby/video.go index 6cf1846..d99d03d 100644 --- a/internal/routes/standby/video.go +++ b/internal/routes/standby/video.go @@ -12,7 +12,7 @@ import ( func Video(item schema.WaitItemModel) func(c context.Context) error { return func(c context.Context) error { - local, err := utils.LinkVideo(item.Data) + path, local, err := utils.LinkVideo(item.Data) if err != nil { return fmt.Errorf("视频文件获取异常: %w", err) } @@ -24,7 +24,7 @@ func Video(item schema.WaitItemModel) func(c context.Context) error { defer utils.BlankClose() for { - err := video.Play(c, local) + err := video.Play(c, path, local) if err != nil { return fmt.Errorf("视频播放异常: %w", err) } diff --git a/internal/routes/standby_ctrl/pause.go b/internal/routes/standby_ctrl/pause.go index 82ba5da..68bf0b1 100644 --- a/internal/routes/standby_ctrl/pause.go +++ b/internal/routes/standby_ctrl/pause.go @@ -13,14 +13,11 @@ func Pause(ps *common.PauseSub, isPause bool, play func(c context.Context) error run := true if isPause { - p := make(chan int8) - defer close(p) + zap.S().Infoln("待机暂停控制器") + defer zap.S().Infoln("待机暂停控制器结束") - ps.Add(p) - defer ps.Remove(p) - - zap.S().Infoln("待机控制器") - defer zap.S().Infoln("待机控制器结束") + p := ps.GetNew() + defer ps.Close(p) // 等待组 var wait sync.WaitGroup diff --git a/internal/routes/standby_ctrl/time.go b/internal/routes/standby_ctrl/time.go index 3bd2e0e..7a4e987 100644 --- a/internal/routes/standby_ctrl/time.go +++ b/internal/routes/standby_ctrl/time.go @@ -25,6 +25,9 @@ func Time(rootRules []cronrange.Rule, cron string, play func(c context.Context) a := make(chan bool) defer close(a) + zap.S().Infoln("待机时间控制器") + defer zap.S().Infoln("待机时间控制器结束") + // 等待组 var waitGroup sync.WaitGroup defer waitGroup.Wait() diff --git a/internal/routes/wait.go b/internal/routes/wait.go index c5476bf..a296c14 100644 --- a/internal/routes/wait.go +++ b/internal/routes/wait.go @@ -38,7 +38,7 @@ func WaitAction(ctrl *common.CtrlWait) leaf.HandlerFunc { waitGroup.Add(1) go func() { defer waitGroup.Done() - ps.Run() + ps.Run(c) }() // 处理每个待机控制 diff --git a/internal/server.go b/internal/server.go index 79e7434..ef82b57 100644 --- a/internal/server.go +++ b/internal/server.go @@ -40,6 +40,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) { @@ -56,7 +57,7 @@ func buildMqtt(c config.MqttConfig, r *leaf.Engine, subTopics ...string) autopah zap.S().Infof("MQTT 连接异常: %s\n", err) }, ClientConfig: paho.ClientConfig{ - ClientID: fmt.Sprintf("game-driver-%s-%v", config.C.Location, config.C.Point), + ClientID: c.ClientID, OnPublishReceived: []func(paho.PublishReceived) (bool, error){ func(pr paho.PublishReceived) (bool, error) { r.Route(pr.Packet.Packet()) @@ -109,8 +110,8 @@ 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) diff --git a/pkg/browser/browser.go b/pkg/browser/browser.go index b547ebb..5bffcac 100644 --- a/pkg/browser/browser.go +++ b/pkg/browser/browser.go @@ -5,8 +5,6 @@ import ( "github.com/go-rod/rod" "github.com/go-rod/rod/lib/launcher" "github.com/go-rod/rod/lib/launcher/flags" - "github.com/go-rod/rod/lib/proto" - "go.uber.org/zap" ) // OpenApp 用APP模式打开网页 @@ -26,18 +24,5 @@ func OpenApp(c context.Context, url string) { b := rod.New().ControlURL(p).MustConnect() defer b.MustClose() - s := make(chan struct{}) - - wait := b.EachEvent(func(e *proto.TargetTargetDestroyed) { - zap.S().Infoln("浏览器关闭事件") - s <- struct{}{} - }) - go wait() - - select { - case <-c.Done(): - b.MustClose() - <-s - case <-s: - } + <-c.Done() } diff --git a/pkg/utils/link_video.go b/pkg/utils/link_video.go index 52415c1..7aa4f38 100644 --- a/pkg/utils/link_video.go +++ b/pkg/utils/link_video.go @@ -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 -} diff --git a/pkg/video/play.go b/pkg/video/play.go index dda8be4..a69cd80 100644 --- a/pkg/video/play.go +++ b/pkg/video/play.go @@ -6,7 +6,7 @@ import ( libvlc "github.com/adrg/libvlc-go/v3" ) -func Play(ctx context.Context, file string) error { +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) @@ -38,8 +38,14 @@ func Play(ctx context.Context, file string) error { } // 4. 加载并播放文件 - if _, err := player.LoadMediaFromPath(file); err != nil { - return fmt.Errorf("文件加载失败: %w", err) + 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 { @@ -57,7 +63,7 @@ func Play(ctx context.Context, file string) error { } // 5. 等待事件 - fmt.Printf("正在播放: %s\n", file) + fmt.Printf("正在播放: %s\n", path) select { case <-ctx.Done(): return fmt.Errorf("播放被用户中断") diff --git a/readme.md b/readme.md index bdc63c4..6813eee 100644 --- a/readme.md +++ b/readme.md @@ -133,11 +133,13 @@ Payload: "items": [ { // 执行的时间区间 - "cron": "17:20-21:35 1-5 * *", + "cron": "17:20-21:35 1-5 * *", // 间隔时间(s), 类型>2时, 该项无效, default 0 "interval": 0, // 事件类型(0: 音频; 1: 视频; 2: TTS; 3: 继电器; 4: 网页), default 0 "type": 2, + // Game 指令执行时是否暂停(默认 false) + "pause": true, // 事件数据(TTS为文字, 继电器为端口号, 其他都为地址链接。支持 file:// 本地文件地址、 http(s):// 远程文件地址) "data": "", }, From 902cd1eb0aac155f2ebb9e8a24b0c114a4005149 Mon Sep 17 00:00:00 2001 From: mapleafgo Date: Fri, 7 Mar 2025 19:17:25 +0800 Subject: [PATCH 19/29] =?UTF-8?q?=E6=9C=80=E7=BB=88=E7=A1=AE=E5=AE=9A?= =?UTF-8?q?=E6=8A=A5=E6=96=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- json.md | 95 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/json.md b/json.md index 47fbdd8..c38b0c0 100644 --- a/json.md +++ b/json.md @@ -1,3 +1,54 @@ +### Game + +url: `server/wushan/0/play` + +```json +{ + "bgm": "file://./节点bgm.mp3", + "game": { + "wait": 12 + }, + "tts": { + "timer": [ + { + "time": 1, + "value": "ni听得到勇士欢迎来到三峡龙脊,你即将踏上勇者之路,祝你一往无前,登龙折桂!" + } + ] + }, + "volume": 1 +} +``` + +### 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(镇水塔) ### 待机 @@ -35,6 +86,50 @@ url: `server/wushan/2/play` } ``` +### Game + +url: `server/wushan/3/play` + +```json +{ + "bgm": "file://./节点bgm.mp3", + "game": { + "wait": 12 + }, + "tts": { + "timer": [ + { + "time": 1, + "value": "ni听得到勇者,恭喜你成功通关,神力澎湃,可与星辰争辉!" + } + ] + }, + "volume": 1 +} +``` + +### Game + +url: `server/wushan/4/play` + +```json +{ + "bgm": "file://./节点bgm.mp3", + "game": { + "wait": 12 + }, + "tts": { + "timer": [ + { + "time": 1, + "value": "ni听得到勇者,恭喜你成功通关,神格加载,胜利曙光已现!" + } + ] + }, + "volume": 1 +} +``` + ## 点位5(登龙云台) ### 待机 From 1e0119c5c4e49dbd77ef77f70100595ee7e402d9 Mon Sep 17 00:00:00 2001 From: mapleafgo Date: Mon, 10 Mar 2025 10:42:39 +0800 Subject: [PATCH 20/29] =?UTF-8?q?=E6=B8=B8=E6=88=8F=E8=8A=82=E7=82=B9?= =?UTF-8?q?=E7=A1=AE=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- puml/游戏.puml | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/puml/游戏.puml b/puml/游戏.puml index 78da3bb..6ba3993 100644 --- a/puml/游戏.puml +++ b/puml/游戏.puml @@ -1,36 +1,44 @@ @startmindmap 游戏 + 游戏 ++ 入口(0) -+++ 待机 -++++ 背景音乐 +++ Game +++++ bgm ++++ tts播报欢迎词 ++ 击缶台(1) +++ 待机 +++++ bgm +++ Game ++++ bgm ++++ 供电 -++++ tts播报游客名 -++++ tts游戏结束 +++++ tts 播报游客名 +++++ tts 播报游戏过半 +++++ tts 游戏结束 ++ 镇水塔(2) +++ 待机 -++++ 投影仪待机 -+++ Game ++++ bgm +++++ 投影仪待机(晚上) ++++ Game +++++ tts 播报恭喜通关词 ++++ 播放法阵视频 --- 神女镇邪祟(3) +-- 镇压异兽(3) --- 待机 +---- bgm --- Game ----- tts播报恭喜通关词 +---- bgm +---- tts 播报恭喜通关词 -- 神女影像(4) --- 待机 +---- bgm --- Game ----- tts播报恭喜通关词 --- 青龙云台(5) +---- bgm +---- tts 播报恭喜通关词 +-- 登龙云台(5) --- 待机 +---- bgm --- Game ---- 供电 ----- 播放语音 +---- bgm +---- 激光控制 -- 俱乐部(6) --- 待机 ---- 启动屏幕 From 752446e5020074e6463783841aa7923a03563adb Mon Sep 17 00:00:00 2001 From: mapleafgo Date: Wed, 12 Mar 2025 19:13:35 +0800 Subject: [PATCH 21/29] =?UTF-8?q?=E6=BF=80=E5=85=89=E8=81=94=E5=8A=A8?= =?UTF-8?q?=E6=8E=A7=E5=88=B6=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.yml | 26 ++++++++++---- config/game/game.go | 2 ++ config/game/laser.go | 6 ++++ demo/osc/main.go | 10 ++++++ go.mod | 13 +++---- go.sum | 51 +++++++++------------------ internal/routes/play.go | 3 ++ internal/routes/play/laser.go | 45 ++++++++++++++++++++++++ json.md | 1 + pkg/oscx/osc.go | 65 +++++++++++++++++++++++++++++++++++ todo.md | 11 ++++-- 11 files changed, 184 insertions(+), 49 deletions(-) create mode 100644 config/game/laser.go create mode 100644 demo/osc/main.go create mode 100644 internal/routes/play/laser.go create mode 100644 pkg/oscx/osc.go diff --git a/config.yml b/config.yml index fab929e..3e3eabf 100755 --- a/config.yml +++ b/config.yml @@ -17,6 +17,8 @@ log: compress: true mqtt: url: mqtt://wushan-mqtt.chaoshengshuzi.com:1883 + clientID: wushan-3 + password: wushan@1013 aliyun: accessKeyID: accessKeySecret: @@ -25,13 +27,25 @@ aliyun: volume: 100 # 音量,取值范围:0~100 voice: zhifeng_emo # 发音人 speechRate: 50 # 语速,取值范围:-500~500 -wait: - ip: - port: - password: - id: + +# 激光秀点位 osc 参数 +game: + host: 192.168.0.167 + port: 3033 + +# 待机投影仪控制参数 +#wait: +# ip: +# port: +# password: +# id: + +# 11 点位 读卡器串口配置 +#game: +# addr: /dev/ttyUSB0 + +# 10 点位 发卡器串口配置 #game: -# addr: /dev/ttyUSB0 # 点位 11 的串口地址 # pushGroups: # 点位 10 的发卡器配置 # - name: gpiochip0 # read: /dev/ttyUSB0 diff --git a/config/game/game.go b/config/game/game.go index daad170..303eedf 100644 --- a/config/game/game.go +++ b/config/game/game.go @@ -8,6 +8,8 @@ func NewConfig(point int) Config { return ConfigPush{} case 11: return ConfigWait{} + case 5: + return LaserConfig{} default: return nil } diff --git a/config/game/laser.go b/config/game/laser.go new file mode 100644 index 0000000..f2c1cd9 --- /dev/null +++ b/config/game/laser.go @@ -0,0 +1,6 @@ +package game + +type LaserConfig struct { + Host string + Port int +} diff --git a/demo/osc/main.go b/demo/osc/main.go new file mode 100644 index 0000000..45bc26b --- /dev/null +++ b/demo/osc/main.go @@ -0,0 +1,10 @@ +package main + +import ( + "game-driver/pkg/oscx" +) + +func main() { + c := oscx.New("192.168.0.167", 3033) + c.StartCue("Emu") +} diff --git a/go.mod b/go.mod index b1a9fbe..1ec0657 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( 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 @@ -19,7 +20,7 @@ require ( ) require ( - github.com/aliyun/alibaba-cloud-sdk-go v1.63.92 // 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 @@ -48,17 +49,17 @@ require ( 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.2.4 // 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-20250228200357-dead58393ab7 // indirect - golang.org/x/net v0.35.0 // indirect - golang.org/x/sys v0.30.0 // indirect - golang.org/x/text v0.22.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 diff --git a/go.sum b/go.sum index 210c440..4f7ab88 100644 --- a/go.sum +++ b/go.sum @@ -5,13 +5,12 @@ github.com/adrg/libvlc-go/v3 v3.1.6 h1:Cm22w6xNMDdzYCW8koHgAvjonYm4xbPP5TrlVTtMd 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.76 h1:mg/+23+/gAw6zdxv9I5dPCj666WJPLk8S1nXm0dOumQ= -github.com/aliyun/alibaba-cloud-sdk-go v1.63.76/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= @@ -20,8 +19,6 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 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.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.1 h1:sdRKd6plj7KYW33EH5As6YKfe8m9zbN9JMrOjNVF/BE= -github.com/ebitengine/purego v0.8.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= 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= @@ -40,8 +37,6 @@ github.com/go-rod/rod v0.116.2/go.mod h1:H+CMO9SCNc2TJ2WfrG+pKhITz57uGNYU43qYHh4 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.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 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= @@ -51,15 +46,11 @@ 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= @@ -69,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= @@ -82,8 +75,6 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm 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.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= -github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= 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= @@ -123,8 +114,6 @@ github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTE 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= @@ -135,18 +124,12 @@ 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/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.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= 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.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 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= @@ -154,8 +137,8 @@ github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+ 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= @@ -170,6 +153,8 @@ github.com/warthog618/go-gpiosim v0.1.1 h1:MRAEv+T+itmw+3GeIGpQJBfanUVyg0l3JCTwH github.com/warthog618/go-gpiosim v0.1.1/go.mod h1:YXsnB+I9jdCMY4YAlMSRrlts25ltjmuIsrnoUrBLdqU= 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= @@ -182,7 +167,6 @@ 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 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= 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= @@ -199,10 +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-20241217172543-b2144cdd0a67 h1:1UoZQm6f0P/ZO0w1Ri+f+ifG/gXhegadRdwBIXEFWDo= -golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= 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= @@ -211,24 +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.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= -golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= 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.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.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.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= 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= @@ -241,10 +225,7 @@ 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.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 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= diff --git a/internal/routes/play.go b/internal/routes/play.go index 288cf33..01fdc88 100644 --- a/internal/routes/play.go +++ b/internal/routes/play.go @@ -25,6 +25,9 @@ func switchPoint(ctx context.Context, point int) leaf.HandlerFunc { case 11: // 11号点位(等待插卡) return play.WaitCard(ctx) + case 5: + // 登龙云台(激光秀) + return play.LaserShow default: return play.Default } diff --git a/internal/routes/play/laser.go b/internal/routes/play/laser.go new file mode 100644 index 0000000..0f2b070 --- /dev/null +++ b/internal/routes/play/laser.go @@ -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): + } + } + } +} diff --git a/json.md b/json.md index c38b0c0..c5434f9 100644 --- a/json.md +++ b/json.md @@ -156,6 +156,7 @@ url: `server/wushan/5/play` "power": true, "volume":1, "game": { + "osc": "", "wait": 36 } } diff --git a/pkg/oscx/osc.go b/pkg/oscx/osc.go new file mode 100644 index 0000000..057098b --- /dev/null +++ b/pkg/oscx/osc.go @@ -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) +} diff --git a/todo.md b/todo.md index 9b1c954..d113eb0 100644 --- a/todo.md +++ b/todo.md @@ -3,10 +3,15 @@ ### ubuntu 24 开机慢优化 ```bash -# 在 systemd-networkd-wait-online.service Service 加入 TImeoutStartSec=2sec +# 在 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 下播放音频 ```bash @@ -167,4 +172,6 @@ exec --no-startup-id ungoogled-chromium --kiosk --disable-extensions --disable-t ### 设置系统默认音量 -`alsamixer` `sudo alsactl store` +输入命令 `alsamixer` 进入音量控制界面,调节 `master` 后,按 `esc` 退出 + +然后输入 `sudo alsactl store` 保存音量设置 From 7a07f39f1b6e6200a60eb36c72d61c3ec92257db Mon Sep 17 00:00:00 2001 From: mapleafgo Date: Wed, 12 Mar 2025 19:15:19 +0800 Subject: [PATCH 22/29] =?UTF-8?q?vpn=20=E9=85=8D=E7=BD=AE=E6=96=87?= =?UTF-8?q?=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vpn.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 vpn.md diff --git a/vpn.md b/vpn.md new file mode 100644 index 0000000..9a2c81e --- /dev/null +++ b/vpn.md @@ -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 +``` From 41897059227b3098e39112fbcb7c79eb859c2533 Mon Sep 17 00:00:00 2001 From: mapleafgo Date: Thu, 13 Mar 2025 19:18:10 +0800 Subject: [PATCH 23/29] =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/common/pause.go | 1 + internal/routes/play.go | 6 ++-- internal/routes/standby/laser.go | 35 +++++++++++++++++++ internal/routes/standby/tts.go | 12 ++----- internal/routes/standby/video.go | 15 +++----- .../routes/standby_ctrl/{time.go => cron.go} | 4 +-- internal/routes/standby_ctrl/device.go | 21 +++++++++++ internal/routes/standby_ctrl/duration.go | 23 ++++++++++++ internal/routes/standby_ctrl/interval.go | 34 ++++++++++++++++++ internal/routes/wait.go | 23 +++++++++--- internal/schema/wait.go | 13 ++++--- internal/server.go | 2 +- json.md | 27 +++++++++++++- readme.md | 6 +++- 14 files changed, 185 insertions(+), 37 deletions(-) create mode 100644 internal/routes/standby/laser.go rename internal/routes/standby_ctrl/{time.go => cron.go} (95%) create mode 100644 internal/routes/standby_ctrl/device.go create mode 100644 internal/routes/standby_ctrl/duration.go create mode 100644 internal/routes/standby_ctrl/interval.go diff --git a/internal/common/pause.go b/internal/common/pause.go index 792710d..c94367d 100644 --- a/internal/common/pause.go +++ b/internal/common/pause.go @@ -2,6 +2,7 @@ package common import "sync" +// CtrlWait 待机暂停控制器 type CtrlWait struct { C chan int8 diff --git a/internal/routes/play.go b/internal/routes/play.go index 01fdc88..c44ffe3 100644 --- a/internal/routes/play.go +++ b/internal/routes/play.go @@ -19,15 +19,15 @@ func switchPoint(ctx context.Context, point int) leaf.HandlerFunc { switch point { case 2: // 镇水塔点位 return play.OnlyVideo + case 5: + // 登龙云台(激光秀) + return play.LaserShow case 10: // 10号点位(发卡机) return play.PushCard(ctx) case 11: // 11号点位(等待插卡) return play.WaitCard(ctx) - case 5: - // 登龙云台(激光秀) - return play.LaserShow default: return play.Default } diff --git a/internal/routes/standby/laser.go b/internal/routes/standby/laser.go new file mode 100644 index 0000000..ef60288 --- /dev/null +++ b/internal/routes/standby/laser.go @@ -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 + } +} diff --git a/internal/routes/standby/tts.go b/internal/routes/standby/tts.go index 3539d90..56043df 100644 --- a/internal/routes/standby/tts.go +++ b/internal/routes/standby/tts.go @@ -7,7 +7,6 @@ import ( "game-driver/pkg/audio" "game-driver/pkg/tts" "go.uber.org/zap" - "time" ) func TTS(item schema.WaitItemModel) func(c context.Context) error { @@ -20,13 +19,8 @@ func TTS(item schema.WaitItemModel) func(c context.Context) error { zap.S().Infoln("播放待机 TTS 语音") defer zap.S().Infoln("结束待机 TTS 语音") - for { - audio.PlayWav(c, reader) - select { - case <-c.Done(): - return nil - case <-time.After(time.Duration(item.Interval) * time.Second): - } - } + audio.PlayWav(c, reader) + + return nil } } diff --git a/internal/routes/standby/video.go b/internal/routes/standby/video.go index d99d03d..eb2ca56 100644 --- a/internal/routes/standby/video.go +++ b/internal/routes/standby/video.go @@ -7,7 +7,6 @@ import ( "game-driver/pkg/utils" "game-driver/pkg/video" "go.uber.org/zap" - "time" ) func Video(item schema.WaitItemModel) func(c context.Context) error { @@ -23,16 +22,10 @@ func Video(item schema.WaitItemModel) func(c context.Context) error { utils.BlankOpen() defer utils.BlankClose() - for { - err := video.Play(c, path, local) - if err != nil { - return fmt.Errorf("视频播放异常: %w", err) - } - select { - case <-c.Done(): - return nil - case <-time.After(time.Duration(item.Interval) * time.Second): - } + err = video.Play(c, path, local) + if err != nil { + return fmt.Errorf("视频播放异常: %w", err) } + return nil } } diff --git a/internal/routes/standby_ctrl/time.go b/internal/routes/standby_ctrl/cron.go similarity index 95% rename from internal/routes/standby_ctrl/time.go rename to internal/routes/standby_ctrl/cron.go index 7a4e987..9907a02 100644 --- a/internal/routes/standby_ctrl/time.go +++ b/internal/routes/standby_ctrl/cron.go @@ -8,8 +8,8 @@ import ( "time" ) -// Time 时间控制器 -func Time(rootRules []cronrange.Rule, cron string, play func(c context.Context) error) func(c context.Context) error { +// Cron 时间控制器 +func Cron(rootRules []cronrange.Rule, cron string, play func(c context.Context) error) func(c context.Context) error { // 设定默认时间规则 if cron == "" { cron = "* * * *" diff --git a/internal/routes/standby_ctrl/device.go b/internal/routes/standby_ctrl/device.go new file mode 100644 index 0000000..fb41845 --- /dev/null +++ b/internal/routes/standby_ctrl/device.go @@ -0,0 +1,21 @@ +package standby_ctrl + +import ( + "context" + "game-driver/internal/common" + "go.uber.org/zap" +) + +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) + } +} diff --git a/internal/routes/standby_ctrl/duration.go b/internal/routes/standby_ctrl/duration.go new file mode 100644 index 0000000..6006803 --- /dev/null +++ b/internal/routes/standby_ctrl/duration.go @@ -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) + } +} diff --git a/internal/routes/standby_ctrl/interval.go b/internal/routes/standby_ctrl/interval.go new file mode 100644 index 0000000..b1a6dba --- /dev/null +++ b/internal/routes/standby_ctrl/interval.go @@ -0,0 +1,34 @@ +package standby_ctrl + +import ( + "context" + "go.uber.org/zap" + "time" +) + +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: + } + } + } + } +} diff --git a/internal/routes/wait.go b/internal/routes/wait.go index a296c14..17b895e 100644 --- a/internal/routes/wait.go +++ b/internal/routes/wait.go @@ -13,7 +13,8 @@ import ( "sync" ) -func WaitAction(ctrl *common.CtrlWait) leaf.HandlerFunc { +// WaitAction 待机任务,支持音乐、TTS、继电器、视频、网页、投影仪、大型激光秀 ctrl +func WaitAction(ctrl *common.CtrlWait, device *common.Device) leaf.HandlerFunc { ps := common.NewPauseSub(ctrl) return func(c *leaf.Context) { @@ -49,7 +50,15 @@ func WaitAction(ctrl *common.CtrlWait) leaf.HandlerFunc { if f == nil { return } - f = standby_ctrl.Time(rules, item.Cron, f) + 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 } @@ -57,6 +66,10 @@ func WaitAction(ctrl *common.CtrlWait) leaf.HandlerFunc { 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) @@ -75,9 +88,11 @@ func WaitAction(ctrl *common.CtrlWait) leaf.HandlerFunc { case schema.WaitVideo: handleItem("视频待机控制", item, standby.Video(item)) case schema.WaitWeb: - handleItem("视频待机控制", item, standby.Web(item)) + handleItem("网页待机控制", item, standby.Web(item)) case schema.WaitPJLink: - handleItem("视频待机控制", item, standby.PJLink(item)) + handleItem("投影仪待机控制", item, standby.PJLink(item)) + case schema.WaitLaserShow: + handleItem("大型激光秀控制", item, standby.LaserShow(item)) default: zap.S().Infof("不支持的类型: %d\n", item.Type) } diff --git a/internal/schema/wait.go b/internal/schema/wait.go index d1b8692..2d4f5d9 100644 --- a/internal/schema/wait.go +++ b/internal/schema/wait.go @@ -9,14 +9,17 @@ const ( WaitRelay WaitWeb WaitPJLink + WaitLaserShow ) type WaitItemModel struct { - Cron string `json:"cron"` - Type WaitType `json:"type"` - Data string `json:"data"` - Interval int64 `json:"interval"` - Pause bool `json:"pause"` + 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 { diff --git a/internal/server.go b/internal/server.go index ef82b57..84eb41a 100644 --- a/internal/server.go +++ b/internal/server.go @@ -155,7 +155,7 @@ func Run() { middleware.PayloadJSON[schema.WaitModel](), middleware.Unique(common.GlobalBgStopper), middleware.EmergencyStop(common.GlobalBgStopper), - routes.WaitAction(common.PassCtrl), + routes.WaitAction(common.PassCtrl, device), ) // 处理指令 router.RegisterHandler(topicPrefix+"command", diff --git a/json.md b/json.md index c5434f9..cce0340 100644 --- a/json.md +++ b/json.md @@ -20,6 +20,24 @@ url: `server/wushan/0/play` } ``` +## 点位1(击缶台) + +### 待机 + +url: `server/wushan/1/wait` + +```json +{ + "cron": "08:00-22:00 * * *", + "items": [ + { + "type": 3, + "data": "/dev/ttyUSB0" + } + ] +} +``` + ### Game url: `server/wushan/1/play` @@ -140,7 +158,14 @@ url: `server/wushan/5/wait` "cron": "08:00-22:00 * * *", "items": [ { - "data": "file://./三峡龙脊BGM.mp3" + "data": "file://./三峡龙脊BGM.mp3", + "pause": true + }, + { + "cron": "12:05-12:10 * * *", + "type": 6, + "data": "wushan", + "lock": true } ] } diff --git a/readme.md b/readme.md index 6813eee..517a664 100644 --- a/readme.md +++ b/readme.md @@ -136,10 +136,14 @@ Payload: "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, // Game 指令执行时是否暂停(默认 false) "pause": true, + // 待机任务执行时,是否锁定设备(默认 false) + "lock": false, // 事件数据(TTS为文字, 继电器为端口号, 其他都为地址链接。支持 file:// 本地文件地址、 http(s):// 远程文件地址) "data": "", }, From 2df0ae7750273cbee8148143289be47826b8ffc6 Mon Sep 17 00:00:00 2001 From: mapleafgo Date: Mon, 17 Mar 2025 10:09:01 +0800 Subject: [PATCH 24/29] =?UTF-8?q?=E6=9C=80=E7=BB=88=E7=A1=AE=E5=AE=9A?= =?UTF-8?q?=E6=8A=A5=E6=96=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- json.md | 49 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/json.md b/json.md index cce0340..5372850 100644 --- a/json.md +++ b/json.md @@ -1,3 +1,5 @@ +## 点位0(起点) + ### Game url: `server/wushan/0/play` @@ -28,11 +30,11 @@ url: `server/wushan/1/wait` ```json { - "cron": "08:00-22:00 * * *", + "cron": "18:00-22:00 * * *", "items": [ { "type": 3, - "data": "/dev/ttyUSB0" + "data": "/dev/ttyACM0" } ] } @@ -77,7 +79,12 @@ url: `server/wushan/2/wait` "cron": "08:00-22:00 * * *", "items": [ { - "data": "file://./三峡龙脊BGM.mp3" + "data": "file://./三峡龙脊BGM.mp3", + "pause": true + }, + { + "cron": "18:00-22:00 * * *", + "type": 5 } ] } @@ -104,6 +111,24 @@ url: `server/wushan/2/play` } ``` +## 点位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` @@ -126,6 +151,24 @@ url: `server/wushan/3/play` } ``` +## 点位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` From 09196f68b5073d36e08924eb08737c5ca8f5cb74 Mon Sep 17 00:00:00 2001 From: mapleafgo Date: Mon, 17 Mar 2025 10:16:15 +0800 Subject: [PATCH 25/29] =?UTF-8?q?=E8=B0=83=E6=95=B4readme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- readme.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/readme.md b/readme.md index 517a664..1354c63 100644 --- a/readme.md +++ b/readme.md @@ -138,13 +138,13 @@ Payload: "interval": 0, // 持续时长(s), 待机任务执行时持续的时长。为 0 表示 音频、视频、TTS 按播放时长,继电器、网页、投影仪、激光秀持续整个时间段。 default 0 "duration": 0, - // 事件类型(0: 音频; 1: 视频; 2: TTS; 3: 继电器; 4: 网页; 5: 投影仪; 6: 激光秀;), default 0 + // 事件类型(0: 音频; 1: 视频; 2: TTS; 3: 继电器; 4: 网页; 5: 投影仪; 6: 激光秀;) default 0 "type": 2, - // Game 指令执行时是否暂停(默认 false) + // Game 指令执行时是否暂停。default false "pause": true, - // 待机任务执行时,是否锁定设备(默认 false) + // 待机任务执行时,是否锁定设备。default false "lock": false, - // 事件数据(TTS为文字, 继电器为端口号, 其他都为地址链接。支持 file:// 本地文件地址、 http(s):// 远程文件地址) + // 事件数据(TTS为文字, 继电器为端口号,激光秀为节目名,投影仪忽略该参数, 其他都为地址链接。支持 file:// 本地文件地址、 http(s):// 远程文件地址) "data": "", }, ... From 40293e5e9bc89153bb36faea57ffdd540fa68913 Mon Sep 17 00:00:00 2001 From: mapleafgo Date: Mon, 17 Mar 2025 15:38:35 +0800 Subject: [PATCH 26/29] =?UTF-8?q?=E5=A1=AB=E5=85=85=E9=BE=99=E5=8F=B0?= =?UTF-8?q?=E6=8A=A5=E6=96=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- json.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/json.md b/json.md index 5372850..683fbf5 100644 --- a/json.md +++ b/json.md @@ -220,11 +220,11 @@ url: `server/wushan/5/play` ```json { - "bgm": "file://./青云龙台.mp3", + "bgm": "file://./登龙云台.mp3", "power": true, "volume":1, "game": { - "osc": "", + "osc": "dragon", "wait": 36 } } From e1384504f1e971046ce78913fbfa83d46229688e Mon Sep 17 00:00:00 2001 From: mapleafgo Date: Tue, 29 Apr 2025 13:48:35 +0800 Subject: [PATCH 27/29] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=BE=85=E6=9C=BA?= =?UTF-8?q?=E6=8A=A5=E6=96=87=E7=BC=93=E5=AD=98=EF=BC=8C=E6=97=A0=E7=BD=91?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E4=B9=9F=E8=83=BD=E6=89=A7=E8=A1=8C=E5=BE=85?= =?UTF-8?q?=E6=9C=BA=E4=BB=BB=E5=8A=A1=EF=BC=9B=E6=8A=95=E5=BD=B1=E4=BB=AA?= =?UTF-8?q?=E6=8C=87=E4=BB=A4=E7=BB=93=E6=9E=9C=E4=BB=A5=E8=AE=BE=E5=A4=87?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E4=B8=BA=E5=87=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/root.go | 2 +- config/cache_publish.go | 63 ++++++++++++++++++++ config/config.go | 15 ++--- init_device.md | 59 +++++++++++++++++++ internal/middleware/cache.go | 18 ++++++ internal/routes/{wait.go => standby.go} | 4 +- internal/routes/standby/pjlink.go | 8 +-- internal/routes/standby_ctrl/device.go | 1 + internal/routes/standby_ctrl/interval.go | 1 + internal/routes/standby_ctrl/pause.go | 1 + internal/server.go | 22 +++++-- leaf/leaf.go | 29 +++++---- pkg/pjlink/pjlink.go | 75 ++++++++++++++++++++++++ 13 files changed, 266 insertions(+), 32 deletions(-) create mode 100644 config/cache_publish.go create mode 100644 init_device.md create mode 100644 internal/middleware/cache.go rename internal/routes/{wait.go => standby.go} (92%) 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() From 106bbc262bbf073f12cff925b26e17517bbf0486 Mon Sep 17 00:00:00 2001 From: mapleafgo Date: Tue, 29 Apr 2025 13:57:03 +0800 Subject: [PATCH 28/29] =?UTF-8?q?=E7=BC=93=E5=AD=98=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config.yml b/config.yml index 3e3eabf..82211c6 100755 --- a/config.yml +++ b/config.yml @@ -2,6 +2,7 @@ location: wushan # 项目名称 point: 3 # 点位 relay: maxTimeout: 60 # 单位 s,必须大于 0 +standbyCache: # 待机缓存文件路径 log: level: debug tencentCLS: From ec48fad615b205a2443df9084d888686e740b7c4 Mon Sep 17 00:00:00 2001 From: mapleafgo Date: Tue, 29 Apr 2025 14:15:02 +0800 Subject: [PATCH 29/29] =?UTF-8?q?=E5=AF=B9=E6=8A=95=E5=BD=B1=E6=9C=BA?= =?UTF-8?q?=E6=89=93=E5=BC=80=E7=8A=B6=E6=80=81=EF=BC=8C=E6=AF=8F=E5=8D=8A?= =?UTF-8?q?=E5=B0=8F=E6=97=B6=E8=BD=AE=E8=AF=A2=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/routes/standby/pjlink.go | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/internal/routes/standby/pjlink.go b/internal/routes/standby/pjlink.go index 767c89d..71e9c6c 100644 --- a/internal/routes/standby/pjlink.go +++ b/internal/routes/standby/pjlink.go @@ -7,6 +7,7 @@ import ( "game-driver/internal/schema" "game-driver/pkg/pjlink" "go.uber.org/zap" + "time" ) func PJLink(_ schema.WaitItemModel) func(c context.Context) error { @@ -21,14 +22,27 @@ func PJLink(_ schema.WaitItemModel) func(c context.Context) error { } zap.S().Infoln("打开投影仪结果:", resp) - <-c.Done() - - zap.S().Infoln("关闭待机投影仪") - resp, err = pjc.PowerOffSync() - if err != nil { - return fmt.Errorf("关闭投影仪异常: %w", err) + 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) + } } - zap.S().Infoln("关闭投影仪结果:", resp) return nil }