2015-06-08 03:38:19 +08:00
|
|
|
package ble
|
|
|
|
|
|
|
|
import (
|
2016-12-29 00:53:41 +08:00
|
|
|
"context"
|
2016-03-03 14:00:05 +08:00
|
|
|
"log"
|
|
|
|
"strings"
|
2017-01-24 20:36:16 +08:00
|
|
|
"sync"
|
2016-07-14 01:16:58 +08:00
|
|
|
|
2017-02-02 22:56:26 +08:00
|
|
|
"gobot.io/x/gobot"
|
|
|
|
|
2016-12-29 00:53:41 +08:00
|
|
|
blelib "github.com/currantlabs/ble"
|
|
|
|
"github.com/pkg/errors"
|
2015-06-08 03:38:19 +08:00
|
|
|
)
|
|
|
|
|
2017-01-24 20:36:16 +08:00
|
|
|
var currentDevice *blelib.Device
|
|
|
|
var bleMutex sync.Mutex
|
|
|
|
var bleCtx context.Context
|
|
|
|
|
2017-04-05 17:47:28 +08: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
|
2017-01-14 19:57:37 +08:00
|
|
|
}
|
|
|
|
|
2016-12-29 00:53:41 +08:00
|
|
|
// ClientAdaptor represents a Client Connection to a BLE Peripheral
|
2016-09-26 02:14:05 +08:00
|
|
|
type ClientAdaptor struct {
|
2016-12-29 00:53:41 +08:00
|
|
|
name string
|
|
|
|
address string
|
|
|
|
|
|
|
|
addr blelib.Addr
|
2017-01-24 20:36:16 +08:00
|
|
|
device *blelib.Device
|
2016-12-29 00:53:41 +08:00
|
|
|
client blelib.Client
|
|
|
|
profile *blelib.Profile
|
|
|
|
|
|
|
|
connected bool
|
|
|
|
ready chan struct{}
|
2015-06-08 03:38:19 +08:00
|
|
|
}
|
|
|
|
|
2016-12-29 00:53:41 +08:00
|
|
|
// NewClientAdaptor returns a new ClientAdaptor given an address or peripheral name
|
|
|
|
func NewClientAdaptor(address string) *ClientAdaptor {
|
2016-09-26 02:14:05 +08:00
|
|
|
return &ClientAdaptor{
|
2017-02-02 22:56:26 +08:00
|
|
|
name: gobot.DefaultName("BLEClient"),
|
2016-12-29 00:53:41 +08:00
|
|
|
address: address,
|
2015-06-30 00:47:18 +08:00
|
|
|
connected: false,
|
2015-06-08 03:38:19 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-15 00:45:48 +08: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-29 00:53:41 +08:00
|
|
|
func (b *ClientAdaptor) SetName(n string) { b.name = n }
|
|
|
|
|
2017-01-15 00:45:48 +08:00
|
|
|
// Address returns the Bluetooth LE address for the adaptor
|
|
|
|
func (b *ClientAdaptor) Address() string { return b.address }
|
2015-06-08 03:38:19 +08:00
|
|
|
|
|
|
|
// Connect initiates a connection to the BLE peripheral. Returns true on successful connection.
|
2016-11-08 00:38:14 +08:00
|
|
|
func (b *ClientAdaptor) Connect() (err error) {
|
2017-01-24 20:36:16 +08:00
|
|
|
bleMutex.Lock()
|
|
|
|
defer bleMutex.Unlock()
|
|
|
|
|
|
|
|
b.device, err = getBLEDevice("default")
|
2016-12-29 00:53:41 +08:00
|
|
|
if err != nil {
|
2017-01-24 20:36:16 +08:00
|
|
|
return errors.Wrap(err, "can't connect")
|
2015-06-08 03:38:19 +08:00
|
|
|
}
|
|
|
|
|
2016-12-29 00:53:41 +08:00
|
|
|
var cln blelib.Client
|
2015-06-08 03:38:19 +08:00
|
|
|
|
2017-01-24 20:36:16 +08:00
|
|
|
cln, err = blelib.Connect(context.Background(), filter(b.Address()))
|
2016-12-29 01:06:45 +08:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "can't connect")
|
2016-12-29 00:53:41 +08:00
|
|
|
}
|
2016-12-29 01:29:46 +08:00
|
|
|
|
2016-12-29 01:06:45 +08:00
|
|
|
b.addr = cln.Address()
|
|
|
|
b.address = cln.Address().String()
|
|
|
|
b.SetName(cln.Name())
|
2016-12-29 00:53:41 +08:00
|
|
|
b.client = cln
|
|
|
|
|
|
|
|
p, err := b.client.DiscoverProfile(true)
|
|
|
|
if err != nil {
|
2016-12-29 01:06:45 +08:00
|
|
|
return errors.Wrap(err, "can't discover profile")
|
2016-12-29 00:53:41 +08:00
|
|
|
}
|
2015-06-08 03:38:19 +08:00
|
|
|
|
2016-12-29 00:53:41 +08:00
|
|
|
b.profile = p
|
2016-12-29 01:29:46 +08:00
|
|
|
b.connected = true
|
2016-12-29 00:53:41 +08:00
|
|
|
return
|
2015-06-08 03:38:19 +08: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-08 00:38:14 +08:00
|
|
|
func (b *ClientAdaptor) Reconnect() (err error) {
|
2015-06-08 03:38:19 +08:00
|
|
|
if b.connected {
|
|
|
|
b.Disconnect()
|
|
|
|
}
|
|
|
|
return b.Connect()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Disconnect terminates the connection to the BLE peripheral. Returns true on successful disconnect.
|
2016-11-08 00:38:14 +08:00
|
|
|
func (b *ClientAdaptor) Disconnect() (err error) {
|
2016-12-29 00:53:41 +08:00
|
|
|
b.client.CancelConnection()
|
2015-06-08 03:38:19 +08:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Finalize finalizes the BLEAdaptor
|
2016-11-08 00:38:14 +08:00
|
|
|
func (b *ClientAdaptor) Finalize() (err error) {
|
2015-06-08 03:38:19 +08:00
|
|
|
return b.Disconnect()
|
|
|
|
}
|
|
|
|
|
2016-03-03 14:00:05 +08:00
|
|
|
// ReadCharacteristic returns bytes from the BLE device for the
|
2016-12-29 00:53:41 +08:00
|
|
|
// requested characteristic uuid
|
|
|
|
func (b *ClientAdaptor) ReadCharacteristic(cUUID string) (data []byte, err error) {
|
2015-06-30 00:47:18 +08:00
|
|
|
if !b.connected {
|
|
|
|
log.Fatalf("Cannot read from BLE device until connected")
|
|
|
|
return
|
|
|
|
}
|
2015-06-08 03:38:19 +08:00
|
|
|
|
2017-01-24 20:36:16 +08:00
|
|
|
// bleMutex.Lock()
|
|
|
|
// defer bleMutex.Unlock()
|
|
|
|
|
2016-12-29 00:53:41 +08:00
|
|
|
uuid, _ := blelib.Parse(cUUID)
|
2016-07-08 17:44:45 +08:00
|
|
|
|
2016-12-29 00:53:41 +08:00
|
|
|
if u := b.profile.Find(blelib.NewCharacteristic(uuid)); u != nil {
|
|
|
|
data, err = b.client.ReadCharacteristic(u.(*blelib.Characteristic))
|
2015-06-30 06:25:59 +08:00
|
|
|
}
|
|
|
|
|
2017-01-17 20:54:03 +08:00
|
|
|
return
|
2015-06-08 03:38:19 +08:00
|
|
|
}
|
|
|
|
|
2016-07-04 23:00:36 +08:00
|
|
|
// WriteCharacteristic writes bytes to the BLE device for the
|
|
|
|
// requested service and characteristic
|
2016-12-29 00:53:41 +08:00
|
|
|
func (b *ClientAdaptor) WriteCharacteristic(cUUID string, data []byte) (err error) {
|
2016-07-04 23:00:36 +08:00
|
|
|
if !b.connected {
|
|
|
|
log.Fatalf("Cannot write to BLE device until connected")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-01-24 20:36:16 +08:00
|
|
|
// bleMutex.Lock()
|
|
|
|
// defer bleMutex.Unlock()
|
|
|
|
|
2016-12-29 00:53:41 +08:00
|
|
|
uuid, _ := blelib.Parse(cUUID)
|
2016-07-05 01:46:42 +08:00
|
|
|
|
2016-12-29 00:53:41 +08:00
|
|
|
if u := b.profile.Find(blelib.NewCharacteristic(uuid)); u != nil {
|
2016-12-29 01:29:46 +08:00
|
|
|
err = b.client.WriteCharacteristic(u.(*blelib.Characteristic), data, true)
|
2016-07-04 23:00:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-07-05 04:53:54 +08:00
|
|
|
// Subscribe subscribes to notifications from the BLE device for the
|
2016-07-05 01:46:42 +08:00
|
|
|
// requested service and characteristic
|
2016-12-29 00:53:41 +08:00
|
|
|
func (b *ClientAdaptor) Subscribe(cUUID string, f func([]byte, error)) (err error) {
|
2016-07-05 01:46:42 +08:00
|
|
|
if !b.connected {
|
|
|
|
log.Fatalf("Cannot subscribe to BLE device until connected")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-01-24 20:36:16 +08:00
|
|
|
// bleMutex.Lock()
|
|
|
|
// defer bleMutex.Unlock()
|
|
|
|
|
2016-12-29 00:53:41 +08:00
|
|
|
uuid, _ := blelib.Parse(cUUID)
|
2016-07-05 01:46:42 +08:00
|
|
|
|
2016-12-29 00:53:41 +08:00
|
|
|
if u := b.profile.Find(blelib.NewCharacteristic(uuid)); u != nil {
|
|
|
|
h := func(req []byte) { f(req, nil) }
|
|
|
|
err = b.client.Subscribe(u.(*blelib.Characteristic), false, h)
|
2016-07-03 18:11:34 +08:00
|
|
|
if err != nil {
|
2016-12-29 00:53:41 +08:00
|
|
|
return err
|
2016-03-03 14:00:05 +08:00
|
|
|
}
|
2016-07-08 17:44:45 +08:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-12-29 00:53:41 +08:00
|
|
|
return
|
2016-03-03 14:00:05 +08:00
|
|
|
}
|
|
|
|
|
2017-04-05 17:47:28 +08:00
|
|
|
// getBLEDevice is singleton for blelib HCI device connection
|
|
|
|
func getBLEDevice(impl string) (d *blelib.Device, err error) {
|
|
|
|
if currentDevice != nil {
|
|
|
|
return currentDevice, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
dev, e := defaultDevice(impl)
|
|
|
|
if e != nil {
|
|
|
|
return nil, errors.Wrap(e, "can't get device")
|
|
|
|
}
|
|
|
|
blelib.SetDefaultDevice(dev)
|
|
|
|
|
|
|
|
currentDevice = &dev
|
|
|
|
d = &dev
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-12-29 00:53:41 +08:00
|
|
|
func filter(name string) blelib.AdvFilter {
|
|
|
|
return func(a blelib.Advertisement) bool {
|
|
|
|
return strings.ToLower(a.LocalName()) == strings.ToLower(name) ||
|
|
|
|
a.Address().String() == strings.ToLower(name)
|
2016-03-03 14:00:05 +08:00
|
|
|
}
|
|
|
|
}
|