2015-06-07 12:38:19 -07:00
|
|
|
package ble
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2016-07-03 10:51:20 +02:00
|
|
|
"github.com/currantlabs/gatt"
|
2016-07-03 12:11:34 +02:00
|
|
|
"github.com/hybridgroup/gobot"
|
2016-03-02 22:00:05 -08:00
|
|
|
"log"
|
|
|
|
"strings"
|
2015-06-07 12:38:19 -07:00
|
|
|
)
|
|
|
|
|
2016-07-08 20:36:53 +02:00
|
|
|
var _ gobot.Adaptor = (*BLEClientAdaptor)(nil)
|
2015-06-07 12:38:19 -07:00
|
|
|
|
2016-07-08 20:36:53 +02:00
|
|
|
// Represents a Client Connection to a BLE Peripheral
|
|
|
|
type BLEClientAdaptor struct {
|
2016-07-03 12:11:34 +02:00
|
|
|
name string
|
|
|
|
uuid string
|
|
|
|
device gatt.Device
|
|
|
|
peripheral gatt.Peripheral
|
|
|
|
services map[string]*BLEService
|
|
|
|
connected bool
|
|
|
|
ready chan struct{}
|
2015-06-07 12:38:19 -07:00
|
|
|
}
|
|
|
|
|
2016-07-08 20:36:53 +02:00
|
|
|
// NewBLEClientAdaptor returns a new BLEClientAdaptor given a name and uuid
|
|
|
|
func NewBLEClientAdaptor(name string, uuid string) *BLEClientAdaptor {
|
|
|
|
return &BLEClientAdaptor{
|
2016-03-02 22:00:05 -08:00
|
|
|
name: name,
|
|
|
|
uuid: uuid,
|
2015-06-29 09:47:18 -07:00
|
|
|
connected: false,
|
2016-07-03 12:11:34 +02:00
|
|
|
ready: make(chan struct{}),
|
|
|
|
services: make(map[string]*BLEService),
|
2015-06-07 12:38:19 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-08 20:36:53 +02:00
|
|
|
func (b *BLEClientAdaptor) Name() string { return b.name }
|
|
|
|
func (b *BLEClientAdaptor) UUID() string { return b.uuid }
|
|
|
|
func (b *BLEClientAdaptor) Peripheral() gatt.Peripheral { return b.peripheral }
|
2015-06-07 12:38:19 -07:00
|
|
|
|
|
|
|
// Connect initiates a connection to the BLE peripheral. Returns true on successful connection.
|
2016-07-08 20:36:53 +02:00
|
|
|
func (b *BLEClientAdaptor) Connect() (errs []error) {
|
2015-06-07 12:38:19 -07:00
|
|
|
device, err := gatt.NewDevice(DefaultClientOptions...)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Failed to open BLE device, err: %s\n", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
b.device = device
|
|
|
|
|
|
|
|
// Register handlers.
|
|
|
|
device.Handle(
|
2016-07-03 10:51:20 +02:00
|
|
|
gatt.PeripheralDiscovered(b.DiscoveryHandler),
|
|
|
|
gatt.PeripheralConnected(b.ConnectHandler),
|
|
|
|
gatt.PeripheralDisconnected(b.DisconnectHandler),
|
2015-06-07 12:38:19 -07:00
|
|
|
)
|
|
|
|
|
2016-07-03 10:51:20 +02:00
|
|
|
device.Init(b.StateChangeHandler)
|
2016-03-02 22:00:05 -08:00
|
|
|
<-b.ready
|
2015-06-07 12:38:19 -07:00
|
|
|
// TODO: make sure peripheral currently exists for this UUID before returning
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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-07-08 20:36:53 +02:00
|
|
|
func (b *BLEClientAdaptor) Reconnect() (errs []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-07-08 20:36:53 +02:00
|
|
|
func (b *BLEClientAdaptor) Disconnect() (errs []error) {
|
2016-03-02 22:43:41 -08:00
|
|
|
b.peripheral.Device().CancelConnection(b.peripheral)
|
|
|
|
|
2015-06-07 12:38:19 -07:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Finalize finalizes the BLEAdaptor
|
2016-07-08 20:36:53 +02:00
|
|
|
func (b *BLEClientAdaptor) Finalize() (errs []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
|
2015-06-07 12:38:19 -07:00
|
|
|
// requested service and characteristic
|
2016-07-08 20:36:53 +02:00
|
|
|
func (b *BLEClientAdaptor) ReadCharacteristic(sUUID string, 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
|
|
|
|
2016-07-08 11:44:45 +02:00
|
|
|
characteristic := b.lookupCharacteristic(sUUID, cUUID)
|
|
|
|
if characteristic == nil {
|
2016-07-08 12:02:15 +02:00
|
|
|
log.Println("Cannot read from unknown characteristic")
|
2016-07-08 11:44:45 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2015-06-29 09:47:18 -07:00
|
|
|
val, err := b.peripheral.ReadCharacteristic(characteristic)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("Failed to read characteristic, err: %s\n", err)
|
2016-07-03 12:11:34 +02:00
|
|
|
return nil, err
|
2015-06-29 15:25:59 -07:00
|
|
|
}
|
|
|
|
|
2016-07-03 12:11:34 +02:00
|
|
|
return val, nil
|
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-07-08 20:36:53 +02:00
|
|
|
func (b *BLEClientAdaptor) WriteCharacteristic(sUUID string, cUUID string, data []byte) (err error) {
|
2016-07-04 17:00:36 +02:00
|
|
|
if !b.connected {
|
|
|
|
log.Fatalf("Cannot write to BLE device until connected")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-07-08 11:44:45 +02:00
|
|
|
characteristic := b.lookupCharacteristic(sUUID, cUUID)
|
|
|
|
if characteristic == nil {
|
2016-07-08 12:02:15 +02:00
|
|
|
log.Println("Cannot write to unknown characteristic")
|
2016-07-08 11:44:45 +02:00
|
|
|
return
|
|
|
|
}
|
2016-07-04 19:46:42 +02:00
|
|
|
|
|
|
|
err = b.peripheral.WriteCharacteristic(characteristic, data, true)
|
2016-07-04 17:00:36 +02:00
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("Failed to write characteristic, err: %s\n", err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
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-07-08 20:36:53 +02:00
|
|
|
func (b *BLEClientAdaptor) Subscribe(sUUID string, 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
|
|
|
|
}
|
|
|
|
|
2016-07-08 11:44:45 +02:00
|
|
|
characteristic := b.lookupCharacteristic(sUUID, cUUID)
|
|
|
|
if characteristic == nil {
|
2016-07-08 12:02:15 +02:00
|
|
|
log.Println("Cannot subscribe to unknown characteristic")
|
2016-07-08 11:44:45 +02:00
|
|
|
return
|
|
|
|
}
|
2016-07-04 19:46:42 +02:00
|
|
|
|
|
|
|
fn := func(c *gatt.Characteristic, b []byte, err error) {
|
|
|
|
f(b, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
err = b.peripheral.SetNotifyValue(characteristic, fn)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("Failed to subscribe to characteristic, err: %s\n", err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-07-08 20:36:53 +02:00
|
|
|
func (b *BLEClientAdaptor) StateChangeHandler(d gatt.Device, s gatt.State) {
|
2015-06-07 12:38:19 -07:00
|
|
|
fmt.Println("State:", s)
|
|
|
|
switch s {
|
|
|
|
case gatt.StatePoweredOn:
|
|
|
|
fmt.Println("scanning...")
|
|
|
|
d.Scan([]gatt.UUID{}, false)
|
|
|
|
return
|
|
|
|
default:
|
|
|
|
d.StopScanning()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-08 20:36:53 +02:00
|
|
|
func (b *BLEClientAdaptor) DiscoveryHandler(p gatt.Peripheral, a *gatt.Advertisement, rssi int) {
|
2016-07-05 12:35:37 +02:00
|
|
|
// try looking by local name
|
|
|
|
if a.LocalName == b.UUID() {
|
|
|
|
b.uuid = p.ID()
|
|
|
|
} else {
|
|
|
|
// try looking by ID
|
|
|
|
id := strings.ToUpper(b.UUID())
|
|
|
|
if strings.ToUpper(p.ID()) != id {
|
|
|
|
return
|
|
|
|
}
|
2015-06-07 12:38:19 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Stop scanning once we've got the peripheral we're looking for.
|
|
|
|
p.Device().StopScanning()
|
|
|
|
|
2016-03-02 22:00:05 -08:00
|
|
|
// and connect to it
|
|
|
|
p.Device().Connect(p)
|
2015-06-07 12:38:19 -07:00
|
|
|
}
|
|
|
|
|
2016-07-08 20:36:53 +02:00
|
|
|
func (b *BLEClientAdaptor) ConnectHandler(p gatt.Peripheral, err error) {
|
2016-07-03 12:11:34 +02:00
|
|
|
fmt.Printf("\nConnected Peripheral ID:%s, NAME:(%s)\n", p.ID(), p.Name())
|
2016-03-02 22:00:05 -08:00
|
|
|
|
2016-07-03 12:11:34 +02:00
|
|
|
b.peripheral = p
|
2016-03-02 22:00:05 -08:00
|
|
|
|
2016-07-09 11:52:48 -06:00
|
|
|
if err := p.SetMTU(250); err != nil {
|
2016-07-03 12:11:34 +02:00
|
|
|
fmt.Printf("Failed to set MTU, err: %s\n", err)
|
|
|
|
}
|
2016-03-02 22:00:05 -08:00
|
|
|
|
2016-07-03 12:11:34 +02:00
|
|
|
ss, err := p.DiscoverServices(nil)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("Failed to discover services, err: %s\n", err)
|
|
|
|
return
|
|
|
|
}
|
2016-03-02 22:00:05 -08:00
|
|
|
|
2016-07-03 12:11:34 +02:00
|
|
|
for _, s := range ss {
|
|
|
|
b.services[s.UUID().String()] = NewBLEService(s.UUID().String(), s)
|
2016-03-02 22:00:05 -08:00
|
|
|
|
2016-07-03 12:11:34 +02:00
|
|
|
cs, err := p.DiscoverCharacteristics(nil, s)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("Failed to discover characteristics, err: %s\n", err)
|
|
|
|
continue
|
|
|
|
}
|
2016-03-02 22:00:05 -08:00
|
|
|
|
2016-07-03 12:11:34 +02:00
|
|
|
for _, c := range cs {
|
|
|
|
b.services[s.UUID().String()].characteristics[c.UUID().String()] = c
|
2016-03-02 22:00:05 -08:00
|
|
|
}
|
2016-07-03 12:11:34 +02:00
|
|
|
}
|
2016-03-02 22:00:05 -08:00
|
|
|
|
|
|
|
b.connected = true
|
|
|
|
close(b.ready)
|
2015-06-07 12:38:19 -07:00
|
|
|
}
|
|
|
|
|
2016-07-08 20:36:53 +02:00
|
|
|
func (b *BLEClientAdaptor) DisconnectHandler(p gatt.Peripheral, err error) {
|
2015-06-07 12:38:19 -07:00
|
|
|
fmt.Println("Disconnected")
|
|
|
|
}
|
|
|
|
|
2016-07-08 11:44:45 +02:00
|
|
|
// Finalize finalizes the BLEAdaptor
|
2016-07-08 20:36:53 +02:00
|
|
|
func (b *BLEClientAdaptor) lookupCharacteristic(sUUID string, cUUID string) *gatt.Characteristic {
|
2016-07-08 11:44:45 +02:00
|
|
|
service := b.services[sUUID]
|
|
|
|
if service == nil {
|
2016-07-08 12:02:15 +02:00
|
|
|
log.Printf("Unknown service ID: %s\n", sUUID)
|
2016-07-08 11:44:45 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
characteristic := service.characteristics[cUUID]
|
|
|
|
if characteristic == nil {
|
2016-07-08 12:02:15 +02:00
|
|
|
log.Printf("Unknown characteristic ID: %s\n", cUUID)
|
2016-07-08 11:44:45 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return characteristic
|
|
|
|
}
|
|
|
|
|
2016-03-02 22:00:05 -08:00
|
|
|
// Represents a BLE Peripheral's Service
|
|
|
|
type BLEService struct {
|
2016-07-03 12:11:34 +02:00
|
|
|
uuid string
|
|
|
|
service *gatt.Service
|
|
|
|
characteristics map[string]*gatt.Characteristic
|
2016-03-02 22:00:05 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewBLEAdaptor returns a new BLEService given a uuid
|
|
|
|
func NewBLEService(sUuid string, service *gatt.Service) *BLEService {
|
|
|
|
return &BLEService{
|
2016-07-03 12:11:34 +02:00
|
|
|
uuid: sUuid,
|
|
|
|
service: service,
|
2016-03-02 22:00:05 -08:00
|
|
|
characteristics: make(map[string]*gatt.Characteristic),
|
|
|
|
}
|
|
|
|
}
|