Golang中gobot的ButtonPush事件循环无法停止

Golang中gobot的ButtonPush事件循环无法停止 我正在尝试一些树莓派相关的项目,同时使用Go语言来学习它。我有着Java和JavaScript(Node.js)的开发背景。虽然有些概念很相似,但下面这段代码让我感到困惑:

package main

import (
	"fmt"
	"time"

	"gobot.io/x/gobot"
	"gobot.io/x/gobot/drivers/gpio"
	"gobot.io/x/gobot/platforms/raspi"
)

var (
	r         = raspi.NewAdaptor()
	button    = gpio.NewButtonDriver(r, "33")
	redLed    = gpio.NewLedDriver(r, "18")
	blueLed   = gpio.NewLedDriver(r, "16")
	greenLed  = gpio.NewLedDriver(r, "22")
	yellowLed = gpio.NewLedDriver(r, "32")
	leds      = [4]*gpio.LedDriver{redLed, blueLed, greenLed, yellowLed}
)

func toggleLed(led *gpio.LedDriver) {
	led.On()
	time.Sleep(250 * time.Millisecond)
	led.Off()
}

func main() {
	button.DefaultState = 1

	isActive := false
	work := func() {
		button.On(gpio.ButtonPush, func(data interface{}) {
			fmt.Println("Button pressed")
			isActive = !isActive
			fmt.Println("is active", isActive)

			for isActive {
				fmt.Println("is active")
				for _, led := range leds {
					toggleLed(led)
				}
			}

			for !isActive {
				fmt.Println("is inactive")
				for i := len(leds) - 1; i >= 0; i-- {
					led := leds[i]

					toggleLed(led)
				}
			}
		})

		button.On(gpio.ButtonRelease, func(data interface{}) {
			fmt.Println("Button released")
		})

	}

	robot := gobot.NewRobot("buttonBot",
		[]gobot.Connection{r},
		[]gobot.Device{button, redLed},
		work)

	robot.Start()
}

ButtonPush事件函数内部的for循环被触发后就永远不会停止。ButtonRelease事件会被触发,但ButtonPush事件在第一次按下后就不再触发了。我相当确定这与并发有关,但不知道该如何解决这个问题。基本上,我希望在按下按钮时按一个方向迭代,再次按下时按另一个方向迭代。


更多关于Golang中gobot的ButtonPush事件循环无法停止的实战教程也可以访问 https://www.itying.com/category-94-b0.html

6 回复

在这里使用带有输入通道和循环内select语句的goroutine可能是一个合适的选择。让button.On将isActive值发送给goroutine,然后select语句可以相应地设置goroutine本地的isActive标志。

更多关于Golang中gobot的ButtonPush事件循环无法停止的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


感谢回复!

昨晚辗转反侧时(我一直在思考这个问题)我意识到这在 Node.js 中甚至都无法正常工作,因为循环从不允许"回调"(这里看起来是这样,虽然我知道实际上不是)跳出循环。

我尝试了使用协程但没有返回。我觉得这就是问题所在。我准备再尝试一下。感谢您的建议!

我建议采用更好的方法。异步事件必须被拦截并在队列中序列化,而不是在事件发生时立即处理。使用能够获取并对输入进行消抖,然后使用队列存储事件。接下来在主函数中,可以使用循环从队列中提取并处理每个事件。

在我将循环包装在go协程中并返回后,它起作用了!

然而,我觉得这可能不是处理这个问题的最佳方式。我想每次"ButtonPush"事件触发时,我都会创建一个带有新的无限循环的完全独立的go协程?

我可以使用哪种go结构来使用包含while循环的单一go协程,但更新while循环的条件?

我不是 gobot 用户,但看起来 button.On(gpio.ButtonPush…) 代码卡在了 for isActive 循环中,因此无法接收任何后续事件。

button.On 进入一个循环,该循环从通道读取事件并调用传入的函数(https://github.com/hybridgroup/gobot/blob/59aaba08eba8b081a9541f77aed19aba919437ac/eventer.go#L126)。如果函数从不返回,select 就会被阻塞。为工作负载使用单独的 goroutine 可能会有所帮助。

在你的代码中,ButtonPush 事件处理函数内部的 for 循环阻塞了Gobot的事件循环,导致后续的按钮事件无法被处理。这是因为Gobot使用单个goroutine来处理所有设备事件,当你在事件处理函数中执行长时间运行的循环时,会阻止其他事件的执行。

以下是修复后的代码:

package main

import (
	"fmt"
	"sync"
	"time"

	"gobot.io/x/gobot"
	"gobot.io/x/gobot/drivers/gpio"
	"gobot.io/x/gobot/platforms/raspi"
)

var (
	r         = raspi.NewAdaptor()
	button    = gpio.NewButtonDriver(r, "33")
	redLed    = gpio.NewLedDriver(r, "18")
	blueLed   = gpio.NewLedDriver(r, "16")
	greenLed  = gpio.NewLedDriver(r, "22")
	yellowLed = gpio.NewLedDriver(r, "32")
	leds      = [4]*gpio.LedDriver{redLed, blueLed, greenLed, yellowLed}
)

type LedController struct {
	isActive    bool
	mu          sync.Mutex
	stopChan    chan struct{}
	currentMode int
}

func NewLedController() *LedController {
	return &LedController{
		stopChan: make(chan struct{}),
	}
}

func (lc *LedController) SetActive(active bool) {
	lc.mu.Lock()
	defer lc.mu.Unlock()
	
	if lc.isActive == active {
		return
	}
	
	lc.isActive = active
	
	if active {
		lc.stopChan = make(chan struct{})
		go lc.runLedSequence()
	} else {
		close(lc.stopChan)
	}
}

func (lc *LedController) runLedSequence() {
	for {
		select {
		case <-lc.stopChan:
			return
		default:
			lc.mu.Lock()
			mode := lc.currentMode
			lc.mu.Unlock()
			
			if mode == 0 {
				for _, led := range leds {
					select {
					case <-lc.stopChan:
						return
					default:
						led.On()
						time.Sleep(250 * time.Millisecond)
						led.Off()
					}
				}
			} else {
				for i := len(leds) - 1; i >= 0; i-- {
					select {
					case <-lc.stopChan:
						return
					default:
						led := leds[i]
						led.On()
						time.Sleep(250 * time.Millisecond)
						led.Off()
					}
				}
			}
		}
	}
}

func (lc *LedController) ToggleMode() {
	lc.mu.Lock()
	defer lc.mu.Unlock()
	
	lc.currentMode = 1 - lc.currentMode
	fmt.Println("Mode changed to:", lc.currentMode)
}

func main() {
	button.DefaultState = 1

	ledController := NewLedController()
	
	work := func() {
		button.On(gpio.ButtonPush, func(data interface{}) {
			fmt.Println("Button pressed")
			
			ledController.mu.Lock()
			currentActive := ledController.isActive
			ledController.mu.Unlock()
			
			if currentActive {
				ledController.SetActive(false)
			} else {
				ledController.ToggleMode()
				ledController.SetActive(true)
			}
		})

		button.On(gpio.ButtonRelease, func(data interface{}) {
			fmt.Println("Button released")
		})
	}

	robot := gobot.NewRobot("buttonBot",
		[]gobot.Connection{r},
		[]gobot.Device{button, redLed, blueLed, greenLed, yellowLed},
		work)

	robot.Start()
}

这个解决方案的关键点:

  1. 使用独立的goroutine:LED序列在单独的goroutine中运行,不会阻塞Gobot的事件循环
  2. 通道控制:使用stopChan来控制LED序列的启动和停止
  3. 互斥锁保护:使用sync.Mutex来保护共享状态的并发访问
  4. 非阻塞检查:在LED循环中使用select语句来检查停止信号,确保能够及时响应停止请求

当按钮按下时:

  • 如果LED序列正在运行,则停止它
  • 如果LED序列未运行,则切换模式并启动序列

这样按钮事件可以正常处理,LED序列也不会阻塞其他事件的处理。

回到顶部