2015-06-07 12:38:19 -07:00
|
|
|
package ble
|
|
|
|
|
|
|
|
import (
|
2020-10-25 10:17:11 +01:00
|
|
|
"fmt"
|
2016-03-02 22:00:05 -08:00
|
|
|
"log"
|
2017-01-24 13:36:16 +01:00
|
|
|
"sync"
|
2016-07-13 10:16:58 -07:00
|
|
|
|
2020-10-25 10:17:11 +01:00
|
|
|
"github.com/pkg/errors"
|
2017-02-02 15:56:26 +01:00
|
|
|
"gobot.io/x/gobot"
|
|
|
|
|
2020-10-25 10:17:11 +01:00
|
|
|
"tinygo.org/x/bluetooth"
|
2015-06-07 12:38:19 -07:00
|
|
|
)
|
|
|
|
|
2020-10-25 10:17:11 +01:00
|
|
|
//var currentDevice *blelib.Device
|
|
|
|
var currentAdapter *bluetooth.Adapter
|
2017-01-24 13:36:16 +01:00
|
|
|
var bleMutex sync.Mutex
|
|
|
|
|
2017-04-08 17:35:18 +02:00
|
|
|
// BLEConnector is the interface that a BLE ClientAdaptor must implement
|
2017-04-05 11:47:28 +02:00
|
|
|
type BLEConnector interface {
|
|
|
|
Connect() error
|
|
|
|
Reconnect() error
|
|
|
|
Disconnect() error
|
|
|
|
Finalize() error
|
|
|
|
Name() string
|
|
|
|
SetName(string)
|
|
|
|
Address() string
|
|
|
|
ReadCharacteristic(string) ([]byte, error)
|
|
|
|
WriteCharacteristic(string, []byte) error
|
|
|
|
Subscribe(string, func([]byte, error)) error
|
2018-02-18 08:47:28 +01:00
|
|
|
WithoutResponses(bool)
|
2017-01-14 12:57:37 +01:00
|
|
|
}
|
|
|
|
|
2016-12-28 17:53:41 +01:00
|
|
|
// ClientAdaptor represents a Client Connection to a BLE Peripheral
|
2016-09-25 20:14:05 +02:00
|
|
|
type ClientAdaptor struct {
|
2020-10-25 10:17:11 +01:00
|
|
|
name string
|
|
|
|
address string
|
|
|
|
AdapterName string
|
2016-12-28 17:53:41 +01:00
|
|
|
|
2020-10-25 10:17:11 +01:00
|
|
|
addr bluetooth.Address
|
|
|
|
adpt *bluetooth.Adapter
|
|
|
|
device *bluetooth.Device
|
|
|
|
characteristics map[string]bluetooth.DeviceCharacteristic
|
2016-12-28 17:53:41 +01:00
|
|
|
|
2018-02-18 08:47:28 +01:00
|
|
|
connected bool
|
|
|
|
ready chan struct{}
|
|
|
|
withoutResponses bool
|
2015-06-07 12:38:19 -07:00
|
|
|
}
|
|
|
|
|
2020-10-25 10:17:11 +01:00
|
|
|
// NewClientAdaptor returns a new ClientAdaptor given an address
|
2016-12-28 17:53:41 +01:00
|
|
|
func NewClientAdaptor(address string) *ClientAdaptor {
|
2016-09-25 20:14:05 +02:00
|
|
|
return &ClientAdaptor{
|
2018-02-18 08:47:28 +01:00
|
|
|
name: gobot.DefaultName("BLEClient"),
|
|
|
|
address: address,
|
2020-10-25 10:17:11 +01:00
|
|
|
AdapterName: "default",
|
2018-02-18 08:47:28 +01:00
|
|
|
connected: false,
|
|
|
|
withoutResponses: false,
|
2020-10-25 10:17:11 +01:00
|
|
|
characteristics: make(map[string]bluetooth.DeviceCharacteristic),
|
2015-06-07 12:38:19 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-14 17:45:48 +01:00
|
|
|
// Name returns the name for the adaptor
|
|
|
|
func (b *ClientAdaptor) Name() string { return b.name }
|
|
|
|
|
|
|
|
// SetName sets the name for the adaptor
|
2016-12-28 17:53:41 +01:00
|
|
|
func (b *ClientAdaptor) SetName(n string) { b.name = n }
|
|
|
|
|
2017-01-14 17:45:48 +01:00
|
|
|
// Address returns the Bluetooth LE address for the adaptor
|
|
|
|
func (b *ClientAdaptor) Address() string { return b.address }
|
2015-06-07 12:38:19 -07:00
|
|
|
|
2018-02-18 08:47:28 +01:00
|
|
|
// WithoutResponses sets if the adaptor should expect responses after
|
2017-06-08 13:41:59 +02:00
|
|
|
// writing characteristics for this device
|
2018-02-18 08:47:28 +01:00
|
|
|
func (b *ClientAdaptor) WithoutResponses(use bool) { b.withoutResponses = use }
|
2017-06-08 13:41:59 +02:00
|
|
|
|
2015-06-07 12:38:19 -07:00
|
|
|
// Connect initiates a connection to the BLE peripheral. Returns true on successful connection.
|
2016-11-07 17:38:14 +01:00
|
|
|
func (b *ClientAdaptor) Connect() (err error) {
|
2017-01-24 13:36:16 +01:00
|
|
|
bleMutex.Lock()
|
|
|
|
defer bleMutex.Unlock()
|
|
|
|
|
2020-10-25 10:17:11 +01:00
|
|
|
// enable adaptor
|
|
|
|
b.adpt, err = getBLEAdapter(b.AdapterName)
|
2016-12-28 17:53:41 +01:00
|
|
|
if err != nil {
|
2020-10-25 10:17:11 +01:00
|
|
|
return errors.Wrap(err, "can't enable adapter "+b.AdapterName)
|
2015-06-07 12:38:19 -07:00
|
|
|
}
|
|
|
|
|
2020-10-25 10:17:11 +01:00
|
|
|
// handle address
|
|
|
|
b.addr.Set(b.Address())
|
|
|
|
|
|
|
|
// scan for the address
|
2020-10-25 22:06:45 +01:00
|
|
|
ch := make(chan bluetooth.ScanResult)
|
2020-10-25 10:17:11 +01:00
|
|
|
err = b.adpt.Scan(func(adapter *bluetooth.Adapter, result bluetooth.ScanResult) {
|
|
|
|
if result.Address.String() == b.Address() {
|
|
|
|
b.adpt.StopScan()
|
2020-10-25 12:09:53 +01:00
|
|
|
b.SetName(result.LocalName())
|
2020-10-25 10:17:11 +01:00
|
|
|
ch <- result
|
|
|
|
}
|
|
|
|
})
|
2015-06-07 12:38:19 -07:00
|
|
|
|
2016-12-28 18:06:45 +01:00
|
|
|
if err != nil {
|
2020-10-25 10:17:11 +01:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// wait to connect to peripheral device
|
|
|
|
select {
|
|
|
|
case result := <-ch:
|
|
|
|
b.device, err = b.adpt.Connect(result.Address, bluetooth.ConnectionParams{})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2016-12-28 17:53:41 +01:00
|
|
|
}
|
2016-12-28 18:29:46 +01:00
|
|
|
|
2020-10-25 10:17:11 +01:00
|
|
|
// get all services/characteristics
|
|
|
|
srvcs, err := b.device.DiscoverServices(nil)
|
|
|
|
for _, srvc := range srvcs {
|
|
|
|
chars, err := srvc.DiscoverCharacteristics(nil)
|
|
|
|
if err != nil {
|
|
|
|
log.Println(err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
for _, char := range chars {
|
|
|
|
b.characteristics[char.UUID().String()] = char
|
|
|
|
}
|
2016-12-28 17:53:41 +01:00
|
|
|
}
|
2015-06-07 12:38:19 -07:00
|
|
|
|
2016-12-28 18:29:46 +01:00
|
|
|
b.connected = true
|
2016-12-28 17:53:41 +01:00
|
|
|
return
|
2015-06-07 12:38:19 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Reconnect attempts to reconnect to the BLE peripheral. If it has an active connection
|
|
|
|
// it will first close that connection and then establish a new connection.
|
|
|
|
// Returns true on Successful reconnection
|
2016-11-07 17:38:14 +01:00
|
|
|
func (b *ClientAdaptor) Reconnect() (err error) {
|
2015-06-07 12:38:19 -07:00
|
|
|
if b.connected {
|
|
|
|
b.Disconnect()
|
|
|
|
}
|
|
|
|
return b.Connect()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Disconnect terminates the connection to the BLE peripheral. Returns true on successful disconnect.
|
2016-11-07 17:38:14 +01:00
|
|
|
func (b *ClientAdaptor) Disconnect() (err error) {
|
2020-10-25 10:17:11 +01:00
|
|
|
return b.device.Disconnect()
|
2015-06-07 12:38:19 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Finalize finalizes the BLEAdaptor
|
2016-11-07 17:38:14 +01:00
|
|
|
func (b *ClientAdaptor) Finalize() (err error) {
|
2015-06-07 12:38:19 -07:00
|
|
|
return b.Disconnect()
|
|
|
|
}
|
|
|
|
|
2016-03-02 22:00:05 -08:00
|
|
|
// ReadCharacteristic returns bytes from the BLE device for the
|
2016-12-28 17:53:41 +01:00
|
|
|
// requested characteristic uuid
|
|
|
|
func (b *ClientAdaptor) ReadCharacteristic(cUUID string) (data []byte, err error) {
|
2015-06-29 09:47:18 -07:00
|
|
|
if !b.connected {
|
|
|
|
log.Fatalf("Cannot read from BLE device until connected")
|
|
|
|
return
|
|
|
|
}
|
2015-06-07 12:38:19 -07:00
|
|
|
|
2020-10-25 12:09:53 +01:00
|
|
|
cUUID = convertUUID(cUUID)
|
2015-06-29 15:25:59 -07:00
|
|
|
|
2020-10-25 10:17:11 +01:00
|
|
|
if char, ok := b.characteristics[cUUID]; ok {
|
|
|
|
buf := make([]byte, 255)
|
|
|
|
n, err := char.Read(buf)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return buf[:n], nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, fmt.Errorf("Unknown characteristic: %s", cUUID)
|
2015-06-07 12:38:19 -07:00
|
|
|
}
|
|
|
|
|
2016-07-04 17:00:36 +02:00
|
|
|
// WriteCharacteristic writes bytes to the BLE device for the
|
|
|
|
// requested service and characteristic
|
2016-12-28 17:53:41 +01:00
|
|
|
func (b *ClientAdaptor) WriteCharacteristic(cUUID string, data []byte) (err error) {
|
2016-07-04 17:00:36 +02:00
|
|
|
if !b.connected {
|
2017-06-08 13:41:59 +02:00
|
|
|
log.Println("Cannot write to BLE device until connected")
|
2016-07-04 17:00:36 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-10-25 12:09:53 +01:00
|
|
|
cUUID = convertUUID(cUUID)
|
|
|
|
|
2020-10-25 10:17:11 +01:00
|
|
|
if char, ok := b.characteristics[cUUID]; ok {
|
|
|
|
_, err := char.WriteWithoutResponse(data)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
2016-07-04 17:00:36 +02:00
|
|
|
}
|
|
|
|
|
2020-10-25 10:17:11 +01:00
|
|
|
return fmt.Errorf("Unknown characteristic: %s", cUUID)
|
2016-07-04 17:00:36 +02:00
|
|
|
}
|
|
|
|
|
2016-07-04 22:53:54 +02:00
|
|
|
// Subscribe subscribes to notifications from the BLE device for the
|
2016-07-04 19:46:42 +02:00
|
|
|
// requested service and characteristic
|
2016-12-28 17:53:41 +01:00
|
|
|
func (b *ClientAdaptor) Subscribe(cUUID string, f func([]byte, error)) (err error) {
|
2016-07-04 19:46:42 +02:00
|
|
|
if !b.connected {
|
|
|
|
log.Fatalf("Cannot subscribe to BLE device until connected")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-10-25 12:09:53 +01:00
|
|
|
cUUID = convertUUID(cUUID)
|
|
|
|
|
2020-10-25 10:17:11 +01:00
|
|
|
if char, ok := b.characteristics[cUUID]; ok {
|
|
|
|
fn := func(d []byte) {
|
|
|
|
f(d, nil)
|
2016-03-02 22:00:05 -08:00
|
|
|
}
|
2020-10-25 10:17:11 +01:00
|
|
|
err = char.EnableNotifications(fn)
|
|
|
|
return
|
2016-07-08 11:44:45 +02:00
|
|
|
}
|
|
|
|
|
2020-10-25 10:17:11 +01:00
|
|
|
return fmt.Errorf("Unknown characteristic: %s", cUUID)
|
2016-03-02 22:00:05 -08:00
|
|
|
}
|
|
|
|
|
2020-10-25 10:17:11 +01:00
|
|
|
// getBLEDevice is singleton for bluetooth adapter connection
|
|
|
|
func getBLEAdapter(impl string) (*bluetooth.Adapter, error) {
|
|
|
|
if currentAdapter != nil {
|
|
|
|
return currentAdapter, nil
|
2017-04-05 11:47:28 +02:00
|
|
|
}
|
|
|
|
|
2020-10-25 10:17:11 +01:00
|
|
|
currentAdapter = bluetooth.DefaultAdapter
|
|
|
|
err := currentAdapter.Enable()
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "can't get device")
|
2017-04-05 11:47:28 +02:00
|
|
|
}
|
|
|
|
|
2020-10-25 10:17:11 +01:00
|
|
|
return currentAdapter, nil
|
2016-03-02 22:00:05 -08:00
|
|
|
}
|