2020-08-27 23:22:27 +03:00
|
|
|
package bluetooth
|
|
|
|
|
|
|
|
import (
|
2020-08-28 14:31:17 +03:00
|
|
|
"errors"
|
2021-03-17 02:49:19 +03:00
|
|
|
"sync"
|
2020-08-28 14:31:17 +03:00
|
|
|
"time"
|
|
|
|
|
2022-08-30 20:06:18 +03:00
|
|
|
"github.com/tinygo-org/cbgo"
|
2020-08-27 23:22:27 +03:00
|
|
|
)
|
|
|
|
|
2020-08-28 14:31:17 +03:00
|
|
|
// Adapter is a connection to BLE devices.
|
2020-08-27 23:22:27 +03:00
|
|
|
type Adapter struct {
|
2020-08-29 16:01:34 +03:00
|
|
|
cmd *centralManagerDelegate
|
|
|
|
pmd *peripheralManagerDelegate
|
2020-08-28 09:35:13 +03:00
|
|
|
|
|
|
|
cm cbgo.CentralManager
|
|
|
|
pm cbgo.PeripheralManager
|
|
|
|
|
|
|
|
peripheralFoundHandler func(*Adapter, ScanResult)
|
2020-08-28 14:31:17 +03:00
|
|
|
scanChan chan error
|
|
|
|
poweredChan chan error
|
2021-03-17 02:49:19 +03:00
|
|
|
|
|
|
|
// connectMap is a mapping of peripheralId -> chan cbgo.Peripheral,
|
|
|
|
// used to allow multiple callers to call Connect concurrently.
|
|
|
|
connectMap sync.Map
|
2020-09-10 18:17:45 +03:00
|
|
|
|
2023-12-25 16:52:10 +03:00
|
|
|
connectHandler func(device Device, connected bool)
|
2020-08-27 23:22:27 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// DefaultAdapter is the default adapter on the system.
|
|
|
|
//
|
|
|
|
// Make sure to call Enable() before using it to initialize the adapter.
|
2020-08-28 09:35:13 +03:00
|
|
|
var DefaultAdapter = &Adapter{
|
2021-03-17 02:49:19 +03:00
|
|
|
cm: cbgo.NewCentralManager(nil),
|
|
|
|
pm: cbgo.NewPeripheralManager(nil),
|
|
|
|
connectMap: sync.Map{},
|
|
|
|
|
2023-12-25 16:52:10 +03:00
|
|
|
connectHandler: func(device Device, connected bool) {
|
2020-09-10 18:17:45 +03:00
|
|
|
return
|
|
|
|
},
|
2020-08-28 09:35:13 +03:00
|
|
|
}
|
2020-08-27 23:22:27 +03:00
|
|
|
|
|
|
|
// Enable configures the BLE stack. It must be called before any
|
|
|
|
// Bluetooth-related calls (unless otherwise indicated).
|
|
|
|
func (a *Adapter) Enable() error {
|
2020-08-28 14:31:17 +03:00
|
|
|
if a.poweredChan != nil {
|
|
|
|
return errors.New("already calling Enable function")
|
|
|
|
}
|
|
|
|
|
|
|
|
// wait until powered
|
2020-10-25 19:58:27 +03:00
|
|
|
a.poweredChan = make(chan error, 1)
|
2020-08-29 16:01:34 +03:00
|
|
|
|
|
|
|
a.cmd = ¢ralManagerDelegate{a: a}
|
|
|
|
a.cm.SetDelegate(a.cmd)
|
2020-10-25 19:58:27 +03:00
|
|
|
|
|
|
|
if a.cm.State() != cbgo.ManagerStatePoweredOn {
|
|
|
|
select {
|
|
|
|
case <-a.poweredChan:
|
|
|
|
case <-time.NewTimer(10 * time.Second).C:
|
|
|
|
return errors.New("timeout enabling CentralManager")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// drain any extra powered-on events from channel
|
|
|
|
for len(a.poweredChan) > 0 {
|
|
|
|
<-a.poweredChan
|
2020-08-28 14:31:17 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// wait until powered?
|
2020-08-29 16:01:34 +03:00
|
|
|
a.pmd = &peripheralManagerDelegate{a: a}
|
|
|
|
a.pm.SetDelegate(a.pmd)
|
2020-08-28 09:35:13 +03:00
|
|
|
|
2020-08-27 23:22:27 +03:00
|
|
|
return nil
|
|
|
|
}
|
2020-08-28 09:35:13 +03:00
|
|
|
|
|
|
|
// CentralManager delegate functions
|
|
|
|
|
2020-08-29 16:01:34 +03:00
|
|
|
type centralManagerDelegate struct {
|
|
|
|
cbgo.CentralManagerDelegateBase
|
|
|
|
|
|
|
|
a *Adapter
|
|
|
|
}
|
|
|
|
|
2020-08-28 13:40:03 +03:00
|
|
|
// CentralManagerDidUpdateState when central manager state updated.
|
2020-08-29 16:01:34 +03:00
|
|
|
func (cmd *centralManagerDelegate) CentralManagerDidUpdateState(cmgr cbgo.CentralManager) {
|
2020-08-28 14:31:17 +03:00
|
|
|
// powered on?
|
|
|
|
if cmgr.State() == cbgo.ManagerStatePoweredOn {
|
2020-10-25 19:58:27 +03:00
|
|
|
cmd.a.poweredChan <- nil
|
2020-08-28 14:31:17 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: handle other state changes.
|
2020-08-28 09:35:13 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// DidDiscoverPeripheral when peripheral is discovered.
|
2020-08-29 16:01:34 +03:00
|
|
|
func (cmd *centralManagerDelegate) DidDiscoverPeripheral(cmgr cbgo.CentralManager, prph cbgo.Peripheral,
|
2020-08-28 09:35:13 +03:00
|
|
|
advFields cbgo.AdvFields, rssi int) {
|
2020-08-29 16:01:34 +03:00
|
|
|
if cmd.a.peripheralFoundHandler != nil {
|
2020-08-28 09:35:13 +03:00
|
|
|
sr := makeScanResult(prph, advFields, rssi)
|
2020-08-29 16:01:34 +03:00
|
|
|
cmd.a.peripheralFoundHandler(cmd.a, sr)
|
2020-08-28 09:35:13 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-03 06:55:14 +03:00
|
|
|
// DidDisconnectPeripheral when peripheral is disconnected.
|
|
|
|
func (cmd *centralManagerDelegate) DidDisconnectPeripheral(cmgr cbgo.CentralManager, prph cbgo.Peripheral, err error) {
|
|
|
|
id := prph.Identifier().String()
|
|
|
|
addr := Address{}
|
|
|
|
uuid, _ := ParseUUID(id)
|
|
|
|
addr.UUID = uuid
|
2023-12-25 16:52:10 +03:00
|
|
|
cmd.a.connectHandler(Device{Address: addr}, false)
|
2023-05-03 06:55:14 +03:00
|
|
|
|
|
|
|
// like with DidConnectPeripheral, check if we have a chan allocated for this and send through the peripheral
|
|
|
|
// this will only be true if the receiving side is still waiting for a connection to complete
|
|
|
|
if ch, ok := cmd.a.connectMap.LoadAndDelete(id); ok {
|
|
|
|
ch.(chan cbgo.Peripheral) <- prph
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-28 09:35:13 +03:00
|
|
|
// DidConnectPeripheral when peripheral is connected.
|
2020-08-29 16:01:34 +03:00
|
|
|
func (cmd *centralManagerDelegate) DidConnectPeripheral(cmgr cbgo.CentralManager, prph cbgo.Peripheral) {
|
2021-03-17 02:49:19 +03:00
|
|
|
id := prph.Identifier().String()
|
|
|
|
|
|
|
|
// Check if we have a chan allocated for this peripheral, and remove it
|
|
|
|
// from the map if so (it's single-use, will be garbage collected after
|
|
|
|
// receiver receives the peripheral).
|
|
|
|
//
|
|
|
|
// If we don't have a chan allocated, the receiving side timed out, so
|
|
|
|
// ignore this connection.
|
|
|
|
if ch, ok := cmd.a.connectMap.LoadAndDelete(id); ok {
|
|
|
|
// Unblock now that we're connected.
|
|
|
|
ch.(chan cbgo.Peripheral) <- prph
|
|
|
|
}
|
2020-08-28 09:35:13 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// makeScanResult creates a ScanResult when peripheral is found.
|
|
|
|
func makeScanResult(prph cbgo.Peripheral, advFields cbgo.AdvFields, rssi int) ScanResult {
|
2020-08-28 14:31:17 +03:00
|
|
|
uuid, _ := ParseUUID(prph.Identifier().String())
|
2020-08-28 09:35:13 +03:00
|
|
|
|
2020-08-28 14:31:17 +03:00
|
|
|
var serviceUUIDs []UUID
|
|
|
|
for _, u := range advFields.ServiceUUIDs {
|
|
|
|
parsedUUID, _ := ParseUUID(u.String())
|
|
|
|
serviceUUIDs = append(serviceUUIDs, parsedUUID)
|
|
|
|
}
|
2020-08-28 09:35:13 +03:00
|
|
|
|
2024-02-20 23:16:29 +03:00
|
|
|
var manufacturerData []ManufacturerDataElement
|
2022-05-11 17:11:52 +03:00
|
|
|
if len(advFields.ManufacturerData) > 2 {
|
2024-02-20 23:16:29 +03:00
|
|
|
// Note: CoreBluetooth seems to assume there can be only one
|
|
|
|
// manufacturer data fields in an advertisement packet, while the
|
|
|
|
// specification allows multiple such fields. See the Bluetooth Core
|
|
|
|
// Specification Supplement, table 1.1:
|
|
|
|
// https://www.bluetooth.com/specifications/css-11/
|
2022-05-11 17:11:52 +03:00
|
|
|
manufacturerID := uint16(advFields.ManufacturerData[0])
|
|
|
|
manufacturerID += uint16(advFields.ManufacturerData[1]) << 8
|
2024-02-20 23:16:29 +03:00
|
|
|
manufacturerData = append(manufacturerData, ManufacturerDataElement{
|
|
|
|
CompanyID: manufacturerID,
|
|
|
|
Data: advFields.ManufacturerData[2:],
|
|
|
|
})
|
2022-05-11 17:11:52 +03:00
|
|
|
}
|
|
|
|
|
2020-09-02 09:34:58 +03:00
|
|
|
// Peripheral UUID is randomized on macOS, which means to
|
|
|
|
// different centrals it will appear to have a different UUID.
|
2020-08-28 09:35:13 +03:00
|
|
|
return ScanResult{
|
2020-08-28 13:40:03 +03:00
|
|
|
RSSI: int16(rssi),
|
2020-08-28 09:35:13 +03:00
|
|
|
Address: Address{
|
2020-08-28 13:40:03 +03:00
|
|
|
UUID: uuid,
|
2020-08-28 09:35:13 +03:00
|
|
|
},
|
|
|
|
AdvertisementPayload: &advertisementFields{
|
|
|
|
AdvertisementFields{
|
2022-05-11 17:11:52 +03:00
|
|
|
LocalName: advFields.LocalName,
|
|
|
|
ServiceUUIDs: serviceUUIDs,
|
|
|
|
ManufacturerData: manufacturerData,
|
2020-08-28 09:35:13 +03:00
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
2020-08-29 16:01:34 +03:00
|
|
|
|
|
|
|
// PeripheralManager delegate functions
|
|
|
|
|
|
|
|
type peripheralManagerDelegate struct {
|
|
|
|
cbgo.PeripheralManagerDelegateBase
|
|
|
|
|
|
|
|
a *Adapter
|
|
|
|
}
|