diff --git a/config/config.go b/config/config.go index c3097f8..b95bf02 100644 --- a/config/config.go +++ b/config/config.go @@ -1,6 +1,9 @@ package config -import "gopkg.in/natefinch/lumberjack.v2" +import ( + "game-driver/internal/routes/play/card_device" + "gopkg.in/natefinch/lumberjack.v2" +) type MqttConfig struct { Url string @@ -15,6 +18,7 @@ type AliyunConfig struct { type GameConfig struct { MaxTimeout int + CardGroups []*card_device.LineGroup } type Logger struct { diff --git a/game.md b/game.md index 9c33918..e59d79b 100644 --- a/game.md +++ b/game.md @@ -36,13 +36,37 @@ ## 神女授书(4) -待定 +参照 [请求响应文档](https://www.emqx.com/zh/blog/mqtt5-request-response),发送附带`ResponseTopic`,并订阅`ResponseTopic` +,才能接收到响应结果 + +### RequestPayload ```json lines { + // 发卡执行时间,单位秒 + "action": 0, + // 等待时间,单位秒 + "wait": 10 } ``` +### ResponsePayload + +```json lines +{ + // 空卡设备数量 + "empty": 0, + // 错误设备数量 + "error": 0, + // 发卡是否成功,1为成功,0为失败 + "out_ok": 0 +} +``` + +若`out_ok`,`empty`,`error`都为`0`,很有可能两台机器发卡口都被堵住了,需要人工处理。 + +发卡口堵住不属于`error`,不会在`error`里计数体现。 + ## 青云龙台(5) 待定 diff --git a/internal/routes/play.go b/internal/routes/play.go index 2993603..8ecf20b 100644 --- a/internal/routes/play.go +++ b/internal/routes/play.go @@ -1,26 +1,27 @@ package routes import ( + "context" "game-driver/internal/routes/play" "game-driver/leaf" ) -func PlayRouter(location string, point int) leaf.HandlerFunc { +func PlayRouter(ctx context.Context, location string, point int) leaf.HandlerFunc { switch location { case "wushan": - return switchPoint(point) + return switchPoint(ctx, point) default: return play.Default } } -func switchPoint(point int) leaf.HandlerFunc { +func switchPoint(ctx context.Context, point int) leaf.HandlerFunc { switch point { case 2: // 镇水塔点位 return play.OnlyVideo case 4: // 4号点位(发卡机) - return play.PushCard() + return play.PushCard(ctx) default: return play.Default } diff --git a/internal/routes/play/card_device/device.go b/internal/routes/play/card_device/device.go index 84f4f4d..b4388d3 100644 --- a/internal/routes/play/card_device/device.go +++ b/internal/routes/play/card_device/device.go @@ -9,40 +9,40 @@ import ( ) type Device struct { - chip *gpiocdev.Chip // GPIO 设备 - inLines *gpiocdev.Lines // 输入针脚 - pushLine *gpiocdev.Line // 发卡针脚 - pullLine *gpiocdev.Line // 回收针脚 - resetLine *gpiocdev.Line // 重置针脚 - status map[inGpioLine]int // 状态 + lines *LineGroup + chip *gpiocdev.Chip // GPIO 设备 + inLines *gpiocdev.Lines // 输入针脚 + pushLine *gpiocdev.Line // 发卡针脚 + pullLine *gpiocdev.Line // 回收针脚 + resetLine *gpiocdev.Line // 重置针脚 + status map[int]*StatusLine // 状态 + cache map[int]int } // statusEventHandler 状态事件处理 func (d *Device) statusEventHandler(evt gpiocdev.LineEvent) { - offset := inGpioLine(evt.Offset) + offset := evt.Offset eType := evt.Type - t := false - defer func() { - if t { - zap.S().Infof("状态: %s-%d", offset, d.status[offset]) - } - }() - if v, ok := d.status[offset]; ok { + defer func() { + v.SubEvent(func(i int) { + labels := d.lines.AllLabel() + zap.S().Infof("状态: %s-%d", labels[offset], i) + }) + }() + if eType == gpiocdev.LineEventFallingEdge { - if v == 0 { + if v.Get() == 0 { return } else { - t = true - d.status[offset] = 0 + v.AfterSet(0) } } else if eType == gpiocdev.LineEventRisingEdge { - if v == 1 { + if v.Get() == 1 { return } else { - t = true - d.status[offset] = 1 + v.AfterSet(1) } } } @@ -57,15 +57,17 @@ func (d *Device) initStatus() error { return err } for i := 0; i < len(status); i++ { - d.status[inGpioLine(offsets[i])] = status[i] + d.status[offsets[i]] = DefaultStatusLine(status[i]) } sv := make([]string, len(offsets)) - for _, g := range allInGpio() { - sv = append(sv, fmt.Sprintf("%s-%d", g, d.status[g])) + labels := d.lines.AllLabel() + for _, g := range d.lines.AllInLines() { + sv = append(sv, fmt.Sprintf("%s-%s", labels[g], d.status[g])) } strings.Join(sv, " ") zap.S().Infof("初始状态: %s", strings.Join(sv, " ")) + return nil } @@ -105,20 +107,41 @@ func (d *Device) Reset() { time.Sleep(500 * time.Millisecond) } -func New(name string) (*Device, error) { +func (d *Device) GetOutOk() int { + return d.status[d.lines.OutOK].Get() +} + +func (d *Device) GetLower() int { + return d.status[d.lines.Lower].Get() +} + +func (d *Device) GetError() int { + return d.status[d.lines.Error].Get() +} + +func (d *Device) GetEmpty() int { + return d.status[d.lines.Empty].Get() +} + +func New(name string, lines *LineGroup) (*Device, error) { + if !lines.Ok() { + return nil, fmt.Errorf("针脚配置错误") + } + chip, err := gpiocdev.NewChip(name, gpiocdev.AsActiveLow) if err != nil { return nil, fmt.Errorf("打开 GPIO 设备失败: %w", err) } d := &Device{ + lines: lines, chip: chip, - status: make(map[inGpioLine]int), + status: make(map[int]*StatusLine), } // 初始化输入针脚并监听针脚 inLines, err := chip.RequestLines( - allInGpioInt(), + lines.AllInLines(), // 请求所有输入引脚 gpiocdev.AsInput, // 请求引脚作为输入 gpiocdev.WithPullUp, // 使用上拉电阻 gpiocdev.WithRealtimeEventClock, // 使用实时时钟 @@ -137,23 +160,23 @@ func New(name string) (*Device, error) { } // 初始化发卡引脚 - push, err := chip.RequestLine(int(PushLine), gpiocdev.AsOutput()) + push, err := chip.RequestLine(lines.Push, gpiocdev.AsOutput()) if err != nil { - return nil, fmt.Errorf("请求引脚 %d 作为发卡失败: %w", PushLine, err) + return nil, fmt.Errorf("请求引脚 %d 作为发卡失败: %w", lines.Push, err) } d.pushLine = push // 初始化回收引脚 - pull, err := chip.RequestLine(int(PullLine), gpiocdev.AsOutput()) + pull, err := chip.RequestLine(lines.Pull, gpiocdev.AsOutput()) if err != nil { - return nil, fmt.Errorf("请求引脚 %d 作为回收失败: %w", PullLine, err) + return nil, fmt.Errorf("请求引脚 %d 作为回收失败: %w", lines.Pull, err) } d.pullLine = pull // 初始化重置引脚 - reset, err := chip.RequestLine(int(ResetLine), gpiocdev.AsOutput()) + reset, err := chip.RequestLine(lines.Reset, gpiocdev.AsOutput()) if err != nil { - return nil, fmt.Errorf("请求引脚 %d 作为重置失败: %w", ResetLine, err) + return nil, fmt.Errorf("请求引脚 %d 作为重置失败: %w", lines.Reset, err) } d.resetLine = reset diff --git a/internal/routes/play/card_device/in_gpio.go b/internal/routes/play/card_device/in_gpio.go deleted file mode 100644 index 94cfa8f..0000000 --- a/internal/routes/play/card_device/in_gpio.go +++ /dev/null @@ -1,38 +0,0 @@ -package card_device - -import ( - "fmt" - "github.com/warthog618/go-gpiocdev/device/rpi" -) - -type inGpioLine int - -const ( - OutOKLine inGpioLine = rpi.GPIO6 - LowerLine inGpioLine = rpi.GPIO13 - ErrorLine inGpioLine = rpi.GPIO19 - EmptyLine inGpioLine = rpi.GPIO26 -) - -func (g inGpioLine) String() string { - switch g { - case OutOKLine: - return "OutOKLine" - case LowerLine: - return "LowerLine" - case ErrorLine: - return "ErrorLine" - case EmptyLine: - return "EmptyLine" - default: - return fmt.Sprint(int(g)) - } -} - -func allInGpio() []inGpioLine { - return []inGpioLine{OutOKLine, LowerLine, ErrorLine, EmptyLine} -} - -func allInGpioInt() []int { - return []int{int(OutOKLine), int(LowerLine), int(ErrorLine), int(EmptyLine)} -} diff --git a/internal/routes/play/card_device/line_group.go b/internal/routes/play/card_device/line_group.go new file mode 100644 index 0000000..3aab56d --- /dev/null +++ b/internal/routes/play/card_device/line_group.go @@ -0,0 +1,32 @@ +package card_device + +type LineGroup struct { + OutOK int + Lower int + Error int + Empty int + + Push int + Reset int + Pull int +} + +func (g *LineGroup) AllInLines() []int { + return []int{g.OutOK, g.Lower, g.Error, g.Empty} +} + +func (g *LineGroup) AllLabel() map[int]string { + labels := make(map[int]string) + labels[g.OutOK] = "OutOk" + labels[g.Lower] = "Lower" + labels[g.Error] = "Error" + labels[g.Empty] = "Empty" + labels[g.Push] = "Push" + labels[g.Reset] = "Reset" + labels[g.Pull] = "Pull" + return labels +} + +func (g *LineGroup) Ok() bool { + return g.OutOK > 1 && g.Lower > 1 && g.Error > 1 && g.Empty > 1 && g.Push > 1 && g.Reset > 1 && g.Pull > 1 +} diff --git a/internal/routes/play/card_device/out_gpio.go b/internal/routes/play/card_device/out_gpio.go deleted file mode 100644 index 23eca6a..0000000 --- a/internal/routes/play/card_device/out_gpio.go +++ /dev/null @@ -1,35 +0,0 @@ -package card_device - -import ( - "fmt" - "github.com/warthog618/go-gpiocdev/device/rpi" -) - -type outGpioLine int - -const ( - PushLine outGpioLine = rpi.GPIO11 - ResetLine outGpioLine = rpi.GPIO22 - PullLine outGpioLine = rpi.GPIO27 -) - -func (g outGpioLine) String() string { - switch g { - case PushLine: - return "PushLine" - case ResetLine: - return "ResetLine" - case PullLine: - return "PullLine" - default: - return fmt.Sprint(int(g)) - } -} - -func allOutGpio() []outGpioLine { - return []outGpioLine{PushLine, ResetLine, PullLine} -} - -func allOutGpioInt() []int { - return []int{int(PushLine), int(ResetLine), int(PullLine)} -} diff --git a/internal/routes/play/card_device/status.go b/internal/routes/play/card_device/status.go new file mode 100644 index 0000000..bf9b1a7 --- /dev/null +++ b/internal/routes/play/card_device/status.go @@ -0,0 +1,52 @@ +package card_device + +import ( + "strconv" + "time" +) + +type StatusLine struct { + v int + t *time.Timer + d time.Duration + o func(int) +} + +func (s *StatusLine) String() string { + return strconv.Itoa(s.v) +} + +func (s *StatusLine) Set(v int) { + s.v = v +} + +func (s *StatusLine) Get() int { + return s.v +} + +func (s *StatusLine) AfterSet(v int) { + if s.t != nil { + s.t.Stop() + } + s.t = time.AfterFunc(s.d, func() { + s.v = v + if s.o != nil { + s.o(v) + } + }) +} + +func (s *StatusLine) SubEvent(handler func(int)) { + s.o = handler +} + +func DefaultStatusLine(v int) *StatusLine { + return NewStatusLine(v, 500*time.Millisecond) +} + +func NewStatusLine(v int, d time.Duration) *StatusLine { + return &StatusLine{ + v: v, + d: d, + } +} diff --git a/internal/routes/play/default.go b/internal/routes/play/default.go index 7e92ca1..409ea19 100644 --- a/internal/routes/play/default.go +++ b/internal/routes/play/default.go @@ -11,9 +11,11 @@ func Default(c *leaf.Context) { payload := leaf.Value[*schema.PlayModal](c, middleware.PayloadJSONKey) if w, ok := payload.Game["wait"]; ok { - select { - case <-c.Done(): - case <-time.After(time.Duration(w.(float64)) * time.Second): + if v, ok := w.(float64); ok { + select { + case <-c.Done(): + case <-time.After(time.Duration(v) * time.Second): + } } } } diff --git a/internal/routes/play/push_card.go b/internal/routes/play/push_card.go index a63316f..f830312 100644 --- a/internal/routes/play/push_card.go +++ b/internal/routes/play/push_card.go @@ -1,24 +1,44 @@ package play import ( + "context" + "encoding/json" + "game-driver/config" "game-driver/internal/middleware" "game-driver/internal/routes/play/card_device" "game-driver/internal/schema" "game-driver/leaf" "game-driver/pkg/utils" + "github.com/eclipse/paho.golang/paho" "go.uber.org/zap" "sync" "time" ) -func PushCard() leaf.HandlerFunc { - device, err := card_device.New("gpiochip0") - if err != nil { - zap.S().Panicln("初始化发卡器失败: ", err) +type ResponseBody struct { + Empty int `json:"empty"` + Error int `json:"error"` + OutOk int `json:"out_ok"` + num int +} + +func PushCard(ctx context.Context) leaf.HandlerFunc { + devices := make([]*card_device.Device, 0) + for _, group := range config.C.Game.CardGroups { + gv, _ := json.Marshal(group) + zap.S().Info("发卡指针初始化:", string(gv)) + + device, err := card_device.New("gpiochip0", group) + if err != nil { + zap.S().Panicln("初始化发卡器失败: ", err) + } + devices = append(devices, device) } go func() { - <-utils.GlobalMqttClient.Done() - device.Close() + <-ctx.Done() + for _, device := range devices { + device.Close() + } }() return func(c *leaf.Context) { @@ -26,7 +46,9 @@ func PushCard() leaf.HandlerFunc { var action time.Duration if a, ok := payload.Game["action"]; ok { - action = time.Duration(a.(float64)) + if v, ok := a.(float64); ok { + action = time.Duration(v) + } } // 等待组 @@ -44,7 +66,25 @@ func PushCard() leaf.HandlerFunc { select { case <-a: case <-time.After(action * time.Second): - device.PushCard() + body := &ResponseBody{} + for _, device := range devices { + body.Empty += device.GetEmpty() + body.Error += device.GetError() + } + for i, device := range devices { + if device.GetEmpty() == 0 && device.GetError() == 0 && device.GetOutOk() == 0 { + body.num = i + 1 + device.PushCard() + break + } + } + // 延迟1秒获取结果并发送消息 + time.AfterFunc(time.Second, func() { + if body.num != 0 { + body.OutOk += devices[body.num-1].GetOutOk() + } + publishBody(ctx, c.Properties.ResponseTopic, body) + }) } }() @@ -52,3 +92,15 @@ func PushCard() leaf.HandlerFunc { Default(c) } } + +// 发布消息 +func publishBody(ctx context.Context, topic string, body *ResponseBody) { + if topic != "" { + bytes, _ := json.Marshal(body) + utils.GlobalMqttClient.Publish(ctx, &paho.Publish{ + Topic: topic, + Payload: bytes, + QoS: 1, + }) + } +} diff --git a/internal/server.go b/internal/server.go index 21beb92..a0f056d 100644 --- a/internal/server.go +++ b/internal/server.go @@ -132,7 +132,7 @@ func Run() { middleware.TimeoutOver(config.C.Game.MaxTimeout), middleware.TickerAction(), middleware.PlayBgm(), - routes.PlayRouter(config.C.Location, config.C.Point), + routes.PlayRouter(ctx, config.C.Location, config.C.Point), ) // 处理待机报文 router.RegisterHandler(topicPrefix+"wait",