152 lines
3.3 KiB
Go
152 lines
3.3 KiB
Go
package main
|
|
|
|
// BLE scanner example.
|
|
// Scans for nearby BLE devices and shows their names and signal strength
|
|
// (RSSI) on the display. Press button A to clear the list.
|
|
|
|
import (
|
|
"image/color"
|
|
"machine"
|
|
"strconv"
|
|
"time"
|
|
|
|
"tinygo.org/x/bluetooth"
|
|
"tinygo.org/x/drivers/st7789"
|
|
"tinygo.org/x/tinyfont"
|
|
"tinygo.org/x/tinyfont/freesans"
|
|
)
|
|
|
|
const maxDevices = 5
|
|
const maxNameLen = 16
|
|
const scanWindow = 500 * time.Millisecond
|
|
|
|
// Fixed-size byte arrays avoid dangling pointers into SoftDevice buffers.
|
|
type bleDevice struct {
|
|
name [maxNameLen]byte
|
|
nLen uint8
|
|
addr bluetooth.Address
|
|
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() {
|
|
time.Sleep(2 * time.Second)
|
|
initDisplay()
|
|
|
|
btnA := machine.P1_06
|
|
btnA.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
|
|
|
|
must("enable BLE", adapter.Enable())
|
|
drawHeader()
|
|
|
|
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()
|
|
adapter.Scan(func(a *bluetooth.Adapter, result bluetooth.ScanResult) {
|
|
if time.Since(scanStart) >= scanWindow {
|
|
adapter.StopScan()
|
|
return
|
|
}
|
|
for i := 0; i < count; i++ {
|
|
if devices[i].addr == result.Address {
|
|
devices[i].rssi = result.RSSI
|
|
return
|
|
}
|
|
}
|
|
if count >= maxDevices {
|
|
return
|
|
}
|
|
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++
|
|
})
|
|
|
|
// Scan has stopped — safe to use SPI now.
|
|
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() {
|
|
tinyfont.WriteLine(&display, &freesans.Regular9pt7b, 10, 24, "BLE Scanner", cyan)
|
|
}
|
|
|
|
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)
|
|
name := string(devices[i].name[:devices[i].nLen])
|
|
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())
|
|
}
|
|
}
|