hybridgroup.gobot/platforms/bleclient/btwrapper.go

155 lines
4.7 KiB
Go

package bleclient
import (
"fmt"
"time"
"tinygo.org/x/bluetooth"
)
// bluetoothExtDevicer is the interface usually implemented by bluetooth.Device
type bluetoothExtDevicer interface {
DiscoverServices(uuids []bluetooth.UUID) ([]bluetooth.DeviceService, error)
Disconnect() error
}
// bluetoothExtAdapterer is the interface usually implemented by bluetooth.Adapter
type bluetoothExtAdapterer interface {
Enable() error
Scan(callback func(*bluetooth.Adapter, bluetooth.ScanResult)) error
StopScan() error
Connect(address bluetooth.Address, params bluetooth.ConnectionParams) (bluetooth.Device, error)
}
type bluetoothExtCharacteristicer interface {
Read(data []byte) (int, error)
WriteWithoutResponse(p []byte) (n int, err error)
EnableNotifications(callback func(buf []byte)) error
}
// btAdptCreatorFunc is just a convenience type, used in the BLE client to ensure testability
type btAdptCreatorFunc func(bluetoothExtAdapterer, bool) *btAdapter
// btAdapter is the wrapper for an external adapter implementation
type btAdapter struct {
extAdapter bluetoothExtAdapterer
btDeviceCreator func(bluetoothExtDevicer, string, string) *btDevice
debug bool
}
// newBtAdapter creates a new wrapper around the given external implementation
func newBtAdapter(a bluetoothExtAdapterer, debug bool) *btAdapter {
bta := btAdapter{
extAdapter: a,
btDeviceCreator: newBtDevice,
debug: debug,
}
return &bta
}
// Enable configures the BLE stack. It must be called before any Bluetooth-related calls (unless otherwise indicated).
// It pass through the function of the external implementation.
func (bta *btAdapter) enable() error {
return bta.extAdapter.Enable()
}
// StopScan stops any in-progress scan. It can be called from within a Scan callback to stop the current scan.
// If no scan is in progress, an error will be returned.
func (bta *btAdapter) stopScan() error {
return bta.extAdapter.StopScan()
}
// Connect starts a connection attempt to the given peripheral device address.
//
// On Linux and Windows, the IsRandom part of the address is ignored.
func (bta *btAdapter) connect(address bluetooth.Address, devName string) (*btDevice, error) {
extDev, err := bta.extAdapter.Connect(address, bluetooth.ConnectionParams{})
if err != nil {
return nil, err
}
return bta.btDeviceCreator(extDev, address.String(), devName), nil
}
// Scan starts a BLE scan for the given identifier (address or name).
func (bta *btAdapter) scan(identifier string, scanTimeout time.Duration) (*bluetooth.ScanResult, error) {
resultChan := make(chan bluetooth.ScanResult, 1)
errChan := make(chan error)
go func() {
callback := func(_ *bluetooth.Adapter, result bluetooth.ScanResult) {
if bta.debug {
fmt.Printf("[scan result]: address: '%s', rssi: %d, name: '%s', manufacturer: %v\n",
result.Address, result.RSSI, result.LocalName(), result.ManufacturerData())
}
if result.Address.String() == identifier || result.LocalName() == identifier {
resultChan <- result
}
}
err := bta.extAdapter.Scan(callback)
if err != nil {
errChan <- err
}
}()
select {
case result := <-resultChan:
if err := bta.stopScan(); err != nil {
return nil, err
}
return &result, nil
case err := <-errChan:
return nil, err
case <-time.After(scanTimeout):
_ = bta.stopScan()
return nil, fmt.Errorf("scan timeout (%s) elapsed", scanTimeout)
}
}
// btDevice is the wrapper for an external device implementation
type btDevice struct {
extDevice bluetoothExtDevicer
devAddress string
devName string
}
// newBtDevice creates a new wrapper around the given external implementation
func newBtDevice(d bluetoothExtDevicer, address, name string) *btDevice {
return &btDevice{extDevice: d, devAddress: address, devName: name}
}
func (btd *btDevice) name() string { return btd.devName }
func (btd *btDevice) address() string { return btd.devAddress }
func (btd *btDevice) discoverServices(uuids []bluetooth.UUID) ([]bluetooth.DeviceService, error) {
return btd.extDevice.DiscoverServices(uuids)
}
// Disconnect from the BLE device. This method is non-blocking and does not wait until the connection is fully gone.
func (btd *btDevice) disconnect() error {
return btd.extDevice.Disconnect()
}
func readFromCharacteristic(chara bluetoothExtCharacteristicer) ([]byte, error) {
buf := make([]byte, 255)
n, err := chara.Read(buf)
if err != nil {
return nil, err
}
return buf[:n], nil
}
func writeToCharacteristicWithoutResponse(chara bluetoothExtCharacteristicer, data []byte) error {
if _, err := chara.WriteWithoutResponse(data); err != nil {
return err
}
return nil
}
func enableNotificationsForCharacteristic(chara bluetoothExtCharacteristicer, f func(data []byte)) error {
return chara.EnableNotifications(f)
}