examples: add examples that makes connections to multiple peripherals

Signed-off-by: deadprogram <ron@hybridgroup.com>
This commit is contained in:
deadprogram 2024-01-19 16:36:45 +01:00
parent 6c4166d882
commit 73acc91d67
3 changed files with 232 additions and 0 deletions

146
examples/multiples/main.go Normal file
View file

@ -0,0 +1,146 @@
// This example scans and then connects to multiple Bluetooth peripherals
// that provide the Heart Rate Service (HRS).
//
// Once connected to all the desired devices, it subscribes to notifications.
//
// To run on bare metal microcontroller:
// tinygo flash -target metro-m4-airlift -ldflags="-X main.wanted=D9:2A:A1:5C:ED:56,4D:A1:3C:24:F0:46" -monitor ./examples/multiples/
//
// To run on OS:
// go run ./examples/multiples/ D9:2A:A1:5C:ED:56,64:0B:1D:46:D8:1D
package main
import (
"context"
"os"
"slices"
"time"
"tinygo.org/x/bluetooth"
)
var (
adapter = bluetooth.DefaultAdapter
heartRateServiceUUID = bluetooth.ServiceUUIDHeartRate
heartRateCharacteristicUUID = bluetooth.CharacteristicUUIDHeartRateMeasurement
exitCtx context.Context
)
func main() {
exitCtx = initExitHandler()
println("enabling")
// Enable BLE interface.
must("enable BLE stack", adapter.Enable())
scanResults := make(map[string]bluetooth.ScanResult)
finished := make(chan bool, 1)
searchList, _ := connectAddresses()
// Start scanning.
println("scanning...")
err := adapter.Scan(func(adapter *bluetooth.Adapter, result bluetooth.ScanResult) {
print(".")
// is the scanned device one of the ones we want?
if slices.Contains(searchList, result.Address.String()) {
if _, ok := scanResults[result.Address.String()]; !ok {
println(".")
println("found device:", result.Address.String(), result.RSSI, result.LocalName())
scanResults[result.Address.String()] = result
}
if len(scanResults) == len(searchList) {
println(".")
adapter.StopScan()
finished <- true
}
}
select {
case <-exitCtx.Done():
println("exiting.")
os.Exit(0)
default:
}
})
must("scan", err)
devices := []bluetooth.Device{}
select {
case <-time.After(5 * time.Second):
failMessage("timed out")
return
case <-exitCtx.Done():
println("exiting.")
return
case <-finished:
}
defer func() {
for _, device := range devices {
device.Disconnect()
}
}()
// now connect to all devices
for _, result := range scanResults {
device, err := adapter.Connect(result.Address, bluetooth.ConnectionParams{})
if err != nil {
failMessage(err.Error())
return
}
println("connected to", result.Address.String())
devices = append(devices, device)
}
// get services
println("discovering services/characteristics")
for _, device := range devices {
srvcs, err := device.DiscoverServices([]bluetooth.UUID{heartRateServiceUUID})
must("discover services", err)
if len(srvcs) == 0 {
failMessage("could not find heart rate service")
return
}
srvc := srvcs[0]
println("found service", srvc.UUID().String(), "for device", device.Address.String())
chars, err := srvc.DiscoverCharacteristics([]bluetooth.UUID{heartRateCharacteristicUUID})
if err != nil {
failMessage(err.Error())
return
}
if len(chars) == 0 {
failMessage("could not find heart rate characteristic")
return
}
char := chars[0]
addr := device.Address.String()
println("found characteristic", char.UUID().String(), "for device", addr)
char.EnableNotifications(func(buf []byte) {
println(addr, "data:", uint8(buf[1]))
})
}
// wait for exit
<-exitCtx.Done()
println("exiting.")
}
func must(action string, err error) {
if err != nil {
failMessage("failed to " + action + ": " + err.Error())
return
}
}

37
examples/multiples/mcu.go Normal file
View file

@ -0,0 +1,37 @@
//go:build baremetal
package main
import (
"context"
"errors"
"strings"
"time"
)
// Devices are the MAC addresses of the Bluetooth peripherals you want to connect to.
// Replace this by using -ldflags="-X main.Devices='[MAC ADDRESS],[MAC ADDRESS]'"
// where [MAC ADDRESS] is the actual MAC address of the peripheral.
// For example:
// tinygo flash -target nano-rp2040 -ldflags="-X main.Devices='7B:36:98:8C:41:1C,7B:36:98:8C:41:1D" ./examples/heartrate-monitor/
var Devices string
func initExitHandler() context.Context {
return context.Background()
}
func connectAddresses() ([]string, error) {
addrs := strings.Split(Devices, ",")
if len(addrs) == 0 {
return nil, errors.New("no devices specified")
}
return addrs, nil
}
func failMessage(msg string) {
for {
println(msg)
time.Sleep(1 * time.Second)
}
}

49
examples/multiples/os.go Normal file
View file

@ -0,0 +1,49 @@
//go:build !baremetal
package main
import (
"context"
"errors"
"os"
"os/signal"
"strings"
"syscall"
)
func initExitHandler() context.Context {
return contextWithSignal(context.Background())
}
// ContextWithSignal creates a context canceled when SIGINT or SIGTERM are notified
func contextWithSignal(ctx context.Context) context.Context {
newCtx, cancel := context.WithCancel(ctx)
signals := make(chan os.Signal)
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)
go func() {
select {
case <-signals:
cancel()
}
}()
return newCtx
}
func connectAddresses() ([]string, error) {
if len(os.Args) < 2 {
println("usage: multiples [address],[address]")
os.Exit(1)
}
addrs := strings.Split(os.Args[1], ",")
if len(addrs) == 0 {
return nil, errors.New("no devices specified")
}
return addrs, nil
}
func failMessage(msg string) {
println(msg)
exitCtx.Done()
}