forked from hybridgroup/gobot
/
ble_client_adaptor.go
205 lines (166 loc) · 5.11 KB
/
ble_client_adaptor.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
package ble
import (
"context"
"log"
"strings"
"sync"
"gobot.io/x/gobot"
blelib "github.com/go-ble/ble"
"github.com/pkg/errors"
)
var currentDevice *blelib.Device
var bleMutex sync.Mutex
var bleCtx context.Context
// BLEConnector is the interface that a BLE ClientAdaptor must implement
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
WithoutReponses(bool)
}
// ClientAdaptor represents a Client Connection to a BLE Peripheral
type ClientAdaptor struct {
name string
address string
DeviceName string
addr blelib.Addr
device *blelib.Device
client blelib.Client
profile *blelib.Profile
connected bool
ready chan struct{}
withoutReponses bool
}
// NewClientAdaptor returns a new ClientAdaptor given an address or peripheral name
func NewClientAdaptor(address string) *ClientAdaptor {
return &ClientAdaptor{
name: gobot.DefaultName("BLEClient"),
address: address,
DeviceName: "default",
connected: false,
withoutReponses: false,
}
}
// Name returns the name for the adaptor
func (b *ClientAdaptor) Name() string { return b.name }
// SetName sets the name for the adaptor
func (b *ClientAdaptor) SetName(n string) { b.name = n }
// Address returns the Bluetooth LE address for the adaptor
func (b *ClientAdaptor) Address() string { return b.address }
// WithoutReponses sets if the adaptor should expect responses after
// writing characteristics for this device
func (b *ClientAdaptor) WithoutReponses(use bool) { b.withoutReponses = use }
// Connect initiates a connection to the BLE peripheral. Returns true on successful connection.
func (b *ClientAdaptor) Connect() (err error) {
bleMutex.Lock()
defer bleMutex.Unlock()
b.device, err = getBLEDevice(b.DeviceName)
if err != nil {
return errors.Wrap(err, "can't connect to device "+b.DeviceName)
}
var cln blelib.Client
cln, err = blelib.Connect(context.Background(), filter(b.Address()))
if err != nil {
return errors.Wrap(err, "can't connect to peripheral "+b.Address())
}
b.addr = cln.Addr()
b.address = cln.Addr().String()
b.SetName(cln.Name())
b.client = cln
p, err := b.client.DiscoverProfile(true)
if err != nil {
return errors.Wrap(err, "can't discover profile")
}
b.profile = p
b.connected = true
return
}
// 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
func (b *ClientAdaptor) Reconnect() (err error) {
if b.connected {
b.Disconnect()
}
return b.Connect()
}
// Disconnect terminates the connection to the BLE peripheral. Returns true on successful disconnect.
func (b *ClientAdaptor) Disconnect() (err error) {
b.client.CancelConnection()
return
}
// Finalize finalizes the BLEAdaptor
func (b *ClientAdaptor) Finalize() (err error) {
return b.Disconnect()
}
// ReadCharacteristic returns bytes from the BLE device for the
// requested characteristic uuid
func (b *ClientAdaptor) ReadCharacteristic(cUUID string) (data []byte, err error) {
if !b.connected {
log.Fatalf("Cannot read from BLE device until connected")
return
}
uuid, _ := blelib.Parse(cUUID)
if u := b.profile.Find(blelib.NewCharacteristic(uuid)); u != nil {
data, err = b.client.ReadCharacteristic(u.(*blelib.Characteristic))
}
return
}
// WriteCharacteristic writes bytes to the BLE device for the
// requested service and characteristic
func (b *ClientAdaptor) WriteCharacteristic(cUUID string, data []byte) (err error) {
if !b.connected {
log.Println("Cannot write to BLE device until connected")
return
}
uuid, _ := blelib.Parse(cUUID)
if u := b.profile.Find(blelib.NewCharacteristic(uuid)); u != nil {
err = b.client.WriteCharacteristic(u.(*blelib.Characteristic), data, b.withoutReponses)
}
return
}
// Subscribe subscribes to notifications from the BLE device for the
// requested service and characteristic
func (b *ClientAdaptor) Subscribe(cUUID string, f func([]byte, error)) (err error) {
if !b.connected {
log.Fatalf("Cannot subscribe to BLE device until connected")
return
}
uuid, _ := blelib.Parse(cUUID)
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)
if err != nil {
return err
}
return nil
}
return
}
// 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
}
func filter(name string) blelib.AdvFilter {
return func(a blelib.Advertisement) bool {
return strings.ToLower(a.LocalName()) == strings.ToLower(name) ||
a.Addr().String() == strings.ToLower(name)
}
}