nicebadge/tutorial/ble/step3/main.go

153 lines
3.3 KiB
Go
Raw Normal View History

2026-04-18 11:01:06 +00:00
package main
// BLE scanner example.
2026-04-20 21:47:00 +00:00
// Scans for nearby BLE devices and shows their names and signal strength
// (RSSI) on the display. Press button A to clear the list.
2026-04-18 11:01:06 +00:00
import (
"image/color"
"machine"
"strconv"
"time"
2026-04-20 21:47:00 +00:00
"tinygo.org/x/bluetooth"
2026-04-18 11:01:06 +00:00
"tinygo.org/x/drivers/st7789"
"tinygo.org/x/tinyfont"
"tinygo.org/x/tinyfont/freesans"
)
const maxDevices = 5
2026-04-20 21:47:00 +00:00
const maxNameLen = 16
const scanWindow = 500 * time.Millisecond
2026-04-18 11:01:06 +00:00
2026-04-20 21:47:00 +00:00
// Fixed-size byte arrays avoid dangling pointers into SoftDevice buffers.
2026-04-18 11:01:06 +00:00
type bleDevice struct {
2026-04-20 21:47:00 +00:00
name [maxNameLen]byte
nLen uint8
addr bluetooth.Address
2026-04-18 11:01:06 +00:00
rssi int16
}
var (
adapter = bluetooth.DefaultAdapter
display st7789.Device
devices [maxDevices]bleDevice
count int
)
var (
black = color.RGBA{0, 0, 0, 255}
cyan = color.RGBA{0, 200, 255, 255}
white = color.RGBA{255, 255, 255, 255}
yellow = color.RGBA{255, 220, 0, 255}
green = color.RGBA{0, 220, 100, 255}
gray = color.RGBA{150, 150, 150, 255}
)
func main() {
2026-04-20 21:47:00 +00:00
time.Sleep(2 * time.Second)
2026-04-18 11:01:06 +00:00
initDisplay()
btnA := machine.P1_06
btnA.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
must("enable BLE", adapter.Enable())
drawHeader()
2026-04-20 21:47:00 +00:00
for {
if !btnA.Get() {
count = 0
time.Sleep(200 * time.Millisecond)
}
// adapter.Scan never yields to TinyGo's scheduler, so a goroutine
// calling StopScan() never gets CPU time.
// Fix: check elapsed time inside the callback and stop from there.
scanStart := time.Now()
2026-04-18 11:01:06 +00:00
adapter.Scan(func(a *bluetooth.Adapter, result bluetooth.ScanResult) {
2026-04-20 21:47:00 +00:00
if time.Since(scanStart) >= scanWindow {
adapter.StopScan()
return
2026-04-18 11:01:06 +00:00
}
for i := 0; i < count; i++ {
2026-04-20 21:47:00 +00:00
if devices[i].addr == result.Address {
2026-04-18 11:01:06 +00:00
devices[i].rssi = result.RSSI
2026-04-20 21:47:00 +00:00
return
2026-04-18 11:01:06 +00:00
}
}
2026-04-20 21:47:00 +00:00
if count >= maxDevices {
return
2026-04-18 11:01:06 +00:00
}
2026-04-20 21:47:00 +00:00
n := result.LocalName()
if n == "" {
n = result.Address.String()
}
nLen := len(n)
if nLen > maxNameLen {
nLen = maxNameLen
}
copy(devices[count].name[:], n)
devices[count].nLen = uint8(nLen)
devices[count].addr = result.Address
devices[count].rssi = result.RSSI
count++
2026-04-18 11:01:06 +00:00
})
2026-04-20 21:47:00 +00:00
// Scan has stopped — safe to use SPI now.
2026-04-18 11:01:06 +00:00
drawDevices()
}
}
func initDisplay() {
machine.SPI0.Configure(machine.SPIConfig{
SCK: machine.P1_01,
SDO: machine.P1_02,
Frequency: 8000000,
Mode: 0,
})
display = st7789.New(machine.SPI0,
machine.P1_15, machine.P1_13, machine.P0_10, machine.P0_09)
display.Configure(st7789.Config{
Rotation: st7789.ROTATION_90,
Width: 135,
Height: 240,
RowOffset: 40,
ColumnOffset: 53,
})
display.FillScreen(black)
}
func drawHeader() {
2026-04-20 21:47:00 +00:00
tinyfont.WriteLine(&display, &freesans.Regular9pt7b, 10, 24, "BLE Scanner", cyan)
2026-04-18 11:01:06 +00:00
}
func drawDevices() {
display.FillRectangle(0, 32, 240, 103, black)
if count == 0 {
tinyfont.WriteLine(&display, &freesans.Regular9pt7b, 10, 55, "No devices found...", gray)
return
}
for i := 0; i < count; i++ {
y := int16(52 + i*20)
2026-04-20 21:47:00 +00:00
name := string(devices[i].name[:devices[i].nLen])
2026-04-18 11:01:06 +00:00
rssi := " " + strconv.Itoa(int(devices[i].rssi)) + "dB"
c := white
if devices[i].rssi > -60 {
c = green
} else if devices[i].rssi > -80 {
c = yellow
}
tinyfont.WriteLine(&display, &freesans.Regular9pt7b, 5, y, name+rssi, c)
}
}
func must(action string, err error) {
if err != nil {
panic("failed to " + action + ": " + err.Error())
}
}