examples: add examples that makes connections to multiple peripherals
Signed-off-by: deadprogram <ron@hybridgroup.com>
This commit is contained in:
parent
6c4166d882
commit
73acc91d67
3 changed files with 232 additions and 0 deletions
146
examples/multiples/main.go
Normal file
146
examples/multiples/main.go
Normal 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
37
examples/multiples/mcu.go
Normal 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
49
examples/multiples/os.go
Normal 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()
|
||||||
|
}
|
Loading…
Reference in a new issue