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()) } }