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
在这里使用带有输入通道和循环内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()
}
这个解决方案的关键点:
- 使用独立的goroutine:LED序列在单独的goroutine中运行,不会阻塞Gobot的事件循环
- 通道控制:使用
stopChan来控制LED序列的启动和停止 - 互斥锁保护:使用
sync.Mutex来保护共享状态的并发访问 - 非阻塞检查:在LED循环中使用
select语句来检查停止信号,确保能够及时响应停止请求
当按钮按下时:
- 如果LED序列正在运行,则停止它
- 如果LED序列未运行,则切换模式并启动序列
这样按钮事件可以正常处理,LED序列也不会阻塞其他事件的处理。

