Skip to content

Commit a80141f

Browse files
author
Tzu-Jung Lee
committed
update a few things
1. Introduce ble.Device interface, so we can mock it, and have other platform independent implmentation such as grpc-based one. 2. Promote GATT stuff from ble/examples/lib/gatt to ble/ 3. Make API more synchronous: a. Remove StopAdvertising, StopScanning, .. b. Add context.Context in Scanning, AdvertiseXX API. So, user can easily have a blocking advertising/scanning with optionally set timeout. 4. Update current examples accordingly, and move them to lib/examples/basic/
1 parent 7a094ac commit a80141f

25 files changed

+742
-628
lines changed

adv.go

+4-12
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,10 @@
11
package ble
22

3-
// AdvHandler ...
4-
type AdvHandler interface {
5-
Handle(a Advertisement)
6-
}
7-
8-
// The AdvHandlerFunc type is an adapter to allow the use of ordinary functions as packet or event handlers.
9-
// If f is a function with the appropriate signature, HandlerFunc(f) is a Handler object that calls f.
10-
type AdvHandlerFunc func(a Advertisement)
3+
// AdvHandler handles advertisement.
4+
type AdvHandler func(a Advertisement)
115

12-
// Handle handles an advertisement.
13-
func (f AdvHandlerFunc) Handle(a Advertisement) {
14-
f(a)
15-
}
6+
// AdvFilter returns true if the advertisement matches specified condition.
7+
type AdvFilter func(a Advertisement) bool
168

179
// Advertisement ...
1810
type Advertisement interface {

darwin/conn.go

+7-5
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
package darwin
22

33
import (
4-
"context"
54
"sync"
65

6+
"golang.org/x/net/context"
7+
78
"github.com/currantlabs/ble"
89
"github.com/raff/goble/xpc"
910
)
@@ -52,7 +53,8 @@ func (c *conn) SetContext(ctx context.Context) {
5253
}
5354

5455
func (c *conn) LocalAddr() ble.Addr {
55-
return c.dev.Addr()
56+
// return c.dev.Address()
57+
return c.addr // FIXME
5658
}
5759

5860
func (c *conn) RemoteAddr() ble.Addr {
@@ -94,7 +96,7 @@ func (c *conn) subscribed(char *ble.Characteristic) {
9496
return
9597
}
9698
send := func(b []byte) (int, error) {
97-
c.dev.sendCmd(15, xpc.Dict{
99+
c.dev.sendCmd(c.dev.pm, 15, xpc.Dict{
98100
"kCBMsgArgUUIDs": [][]byte{},
99101
"kCBMsgArgAttributeID": h,
100102
"kCBMsgArgData": b,
@@ -116,11 +118,11 @@ func (c *conn) unsubscribed(char *ble.Characteristic) {
116118
}
117119

118120
func (c *conn) sendReq(id int, args xpc.Dict) msg {
119-
c.dev.sendCmd(id, args)
121+
c.dev.sendCmd(c.dev.cm, id, args)
120122
m := <-c.rspc
121123
return msg(m.args())
122124
}
123125

124126
func (c *conn) sendCmd(id int, args xpc.Dict) {
125-
c.dev.sendCmd(id, args)
127+
c.dev.sendCmd(c.dev.pm, id, args)
126128
}

darwin/device.go

+89-55
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@ import (
77
"log"
88
"time"
99

10-
"github.com/currantlabs/ble"
10+
"github.com/pkg/errors"
1111
"github.com/raff/goble/xpc"
12+
"golang.org/x/net/context"
13+
14+
"github.com/currantlabs/ble"
1215
)
1316

1417
const (
@@ -41,7 +44,9 @@ const (
4144

4245
// Device is either a Peripheral or Central device.
4346
type Device struct {
44-
xpc xpc.XPC
47+
pm xpc.XPC // peripheralManager
48+
cm xpc.XPC // centralManager
49+
4550
role int // 1: peripheralManager (server), 0: centralManager (client)
4651

4752
rspc chan msg
@@ -70,9 +75,10 @@ func NewDevice(opts ...Option) (*Device, error) {
7075
return nil, err
7176
}
7277

73-
d.xpc = xpc.XpcConnect("com.apple.blued", d)
78+
d.pm = xpc.XpcConnect("com.apple.blued", d)
79+
d.cm = xpc.XpcConnect("com.apple.blued", d)
7480

75-
return d, nil
81+
return d, errors.Wrap(d.Init(), "can't init")
7682
}
7783

7884
// Option sets the options specified.
@@ -86,86 +92,114 @@ func (d *Device) Option(opts ...Option) error {
8692

8793
// Init ...
8894
func (d *Device) Init() error {
89-
rsp := d.sendReq(1, xpc.Dict{
95+
rsp := d.sendReq(d.cm, 1, xpc.Dict{
96+
"kCBMsgArgName": fmt.Sprintf("gopher-%v", time.Now().Unix()),
97+
"kCBMsgArgOptions": xpc.Dict{
98+
"kCBInitOptionShowPowerAlert": 1,
99+
},
100+
"kCBMsgArgType": 0,
101+
})
102+
s := State(rsp.state())
103+
if s != StatePoweredOn {
104+
return fmt.Errorf("state: %s", s)
105+
}
106+
107+
rsp = d.sendReq(d.pm, 1, xpc.Dict{
90108
"kCBMsgArgName": fmt.Sprintf("gopher-%v", time.Now().Unix()),
91109
"kCBMsgArgOptions": xpc.Dict{
92110
"kCBInitOptionShowPowerAlert": 1,
93111
},
94-
"kCBMsgArgType": d.role,
112+
"kCBMsgArgType": 1,
95113
})
96-
if s := State(rsp.state()); s != StatePoweredOn {
114+
s = State(rsp.state())
115+
if s != StatePoweredOn {
97116
return fmt.Errorf("state: %s", s)
98117
}
99118
return nil
100119
}
101120

102121
// AdvertiseMfgData ...
103-
func (d *Device) AdvertiseMfgData(b []byte) error {
104-
return d.sendReq(8, xpc.Dict{
122+
func (d *Device) AdvertiseMfgData(ctx context.Context, b []byte) error {
123+
if err := d.sendReq(d.pm, 8, xpc.Dict{
105124
"kCBAdvDataAppleMfgData": b,
106-
}).err()
125+
}).err(); err != nil {
126+
return errors.Wrap(err, "can't advertise")
127+
}
128+
<-ctx.Done()
129+
return ctx.Err()
107130
}
108131

109132
// AdvertiseNameAndServices advertises name and specifid service UUIDs.
110-
func (d *Device) AdvertiseNameAndServices(name string, ss ...ble.UUID) error {
111-
return d.sendReq(8, xpc.Dict{
133+
func (d *Device) AdvertiseNameAndServices(ctx context.Context, name string, ss ...ble.UUID) error {
134+
if err := d.sendReq(d.pm, 8, xpc.Dict{
112135
"kCBAdvDataLocalName": name,
113136
"kCBAdvDataServiceUUIDs": uuidSlice(ss)},
114-
).err()
137+
).err(); err != nil {
138+
return err
139+
}
140+
<-ctx.Done()
141+
d.stopAdvertising()
142+
return ctx.Err()
115143
}
116144

117145
// AdvertiseIBeaconData advertises iBeacon packet with specified manufacturer data.
118-
func (d *Device) AdvertiseIBeaconData(md []byte) error {
146+
func (d *Device) AdvertiseIBeaconData(ctx context.Context, md []byte) error {
119147
var utsname xpc.Utsname
120148
xpc.Uname(&utsname)
121149

122150
if utsname.Release >= "14." {
123151
l := len(md)
124152
b := []byte{byte(l + 5), 0xFF, 0x4C, 0x00, 0x02, byte(l)}
125-
return d.AdvertiseMfgData(append(b, md...))
153+
return d.AdvertiseMfgData(ctx, append(b, md...))
126154
}
127-
return d.sendReq(8, xpc.Dict{"kCBAdvDataAppleBeaconKey": md}).err()
155+
if err := d.sendReq(d.pm, 8, xpc.Dict{"kCBAdvDataAppleBeaconKey": md}).err(); err != nil {
156+
return err
157+
}
158+
<-ctx.Done()
159+
return d.stopAdvertising()
128160
}
129161

130162
// AdvertiseIBeacon advertises iBeacon packet.
131-
func (d *Device) AdvertiseIBeacon(u ble.UUID, major, minor uint16, pwr int8) error {
163+
func (d *Device) AdvertiseIBeacon(ctx context.Context, u ble.UUID, major, minor uint16, pwr int8) error {
132164
b := make([]byte, 21)
133165
copy(b, ble.Reverse(u)) // Big endian
134166
binary.BigEndian.PutUint16(b[16:], major) // Big endian
135167
binary.BigEndian.PutUint16(b[18:], minor) // Big endian
136168
b[20] = uint8(pwr) // Measured Tx Power
137-
return d.AdvertiseIBeaconData(b)
138-
}
139-
140-
// StopAdvertising stops advertising.
141-
func (d *Device) StopAdvertising() error {
142-
return d.sendReq(9, nil).err()
169+
return d.AdvertiseIBeaconData(ctx, b)
143170
}
144171

145-
// SetAdvHandler ...
146-
func (d *Device) SetAdvHandler(ah ble.AdvHandler) error {
147-
d.advHandler = ah
148-
return nil
172+
// stopAdvertising stops advertising.
173+
func (d *Device) stopAdvertising() error {
174+
return errors.Wrap(d.sendReq(d.pm, 9, nil).err(), "can't stop advertising")
149175
}
150176

151177
// Scan ...
152-
func (d *Device) Scan(allowDup bool) error {
153-
return d.sendCmd(29, xpc.Dict{
178+
func (d *Device) Scan(ctx context.Context, allowDup bool, h ble.AdvHandler) error {
179+
d.advHandler = h
180+
if err := d.sendCmd(d.cm, 29, xpc.Dict{
154181
// "kCBMsgArgUUIDs": uuidSlice(ss),
155182
"kCBMsgArgOptions": xpc.Dict{
156183
"kCBScanOptionAllowDuplicates": map[bool]int{true: 1, false: 0}[allowDup],
157184
},
158-
})
185+
}); err != nil {
186+
return err
187+
}
188+
<-ctx.Done()
189+
if err := d.stopScanning(); err != nil {
190+
return errors.Wrap(ctx.Err(), err.Error())
191+
}
192+
return ctx.Err()
159193
}
160194

161-
// StopScanning stops scanning
162-
func (d *Device) StopScanning() error {
163-
return d.sendCmd(30, nil)
195+
// stopAdvertising stops advertising.
196+
func (d *Device) stopScanning() error {
197+
return errors.Wrap(d.sendCmd(d.cm, 30, nil), "can't stop scanning")
164198
}
165199

166200
// RemoveAllServices removes all services of device's
167201
func (d *Device) RemoveAllServices() error {
168-
return d.sendCmd(12, nil)
202+
return d.sendCmd(d.pm, 12, nil)
169203
}
170204

171205
// AddService adds a service to device's database.
@@ -266,7 +300,7 @@ func (d *Device) AddService(s *ble.Service) error {
266300
}
267301
xs["kCBMsgArgCharacteristics"] = xcs
268302

269-
return d.sendReq(10, xs).err()
303+
return d.sendReq(d.pm, 10, xs).err()
270304
}
271305

272306
// SetServices ...
@@ -283,14 +317,24 @@ func (d *Device) SetServices(ss []*ble.Service) error {
283317
}
284318

285319
// Dial ...
286-
func (d *Device) Dial(a ble.Addr) (ble.Client, error) {
287-
d.sendCmd(31, xpc.Dict{
320+
func (d *Device) Dial(ctx context.Context, a ble.Addr) (ble.Client, error) {
321+
d.sendCmd(d.cm, 31, xpc.Dict{
288322
"kCBMsgArgDeviceUUID": xpc.MakeUUID(a.String()),
289323
"kCBMsgArgOptions": xpc.Dict{
290324
"kCBConnectOptionNotifyOnDisconnection": 1,
291325
},
292326
})
293-
return NewClient(<-d.chConn)
327+
select {
328+
case <-ctx.Done():
329+
return nil, ctx.Err()
330+
case c := <-d.chConn:
331+
return NewClient(c)
332+
}
333+
}
334+
335+
// Stop ...
336+
func (d *Device) Stop() error {
337+
return nil
294338
}
295339

296340
// HandleXpcEvent process Device events and asynchronous errors.
@@ -316,7 +360,7 @@ func (d *Device) HandleXpcEvent(event xpc.Dict, err error) {
316360
break
317361
}
318362
a := &adv{args: m.args(), ad: args.advertisementData()}
319-
go d.advHandler.Handle(a)
363+
go d.advHandler(a)
320364

321365
case evtConfirmation:
322366
// log.Printf("confirmed: %d", args.attributeID())
@@ -349,7 +393,7 @@ func (d *Device) HandleXpcEvent(event xpc.Dict, err error) {
349393
v = buf.Bytes()
350394
}
351395

352-
d.sendCmd(13, xpc.Dict{
396+
d.sendCmd(d.pm, 13, xpc.Dict{
353397
"kCBMsgArgAttributeID": aid,
354398
"kCBMsgArgData": v,
355399
"kCBMsgArgTransactionID": args.transactionID(),
@@ -366,7 +410,7 @@ func (d *Device) HandleXpcEvent(event xpc.Dict, err error) {
366410
if xw.ignoreResponse() == 1 {
367411
continue
368412
}
369-
d.sendCmd(13, xpc.Dict{
413+
d.sendCmd(d.pm, 13, xpc.Dict{
370414
"kCBMsgArgAttributeID": aid,
371415
"kCBMsgArgData": nil,
372416
"kCBMsgArgTransactionID": args.transactionID(),
@@ -422,16 +466,6 @@ func (d *Device) HandleXpcEvent(event xpc.Dict, err error) {
422466
}
423467
}
424468

425-
// Addr ...
426-
func (d *Device) Addr() ble.Addr {
427-
return nil
428-
}
429-
430-
// Stop ...
431-
func (d *Device) Stop() error {
432-
return nil
433-
}
434-
435469
func (d *Device) conn(m msg) *conn {
436470
// Convert xpc.UUID to ble.UUID.
437471
a := ble.MustParse(m.deviceUUID().String())
@@ -444,13 +478,13 @@ func (d *Device) conn(m msg) *conn {
444478
}
445479

446480
// sendReq sends a message and waits for its reply.
447-
func (d *Device) sendReq(id int, args xpc.Dict) msg {
448-
d.sendCmd(id, args)
481+
func (d *Device) sendReq(x xpc.XPC, id int, args xpc.Dict) msg {
482+
d.sendCmd(x, id, args)
449483
return <-d.rspc
450484
}
451485

452-
func (d *Device) sendCmd(id int, args xpc.Dict) error {
486+
func (d *Device) sendCmd(x xpc.XPC, id int, args xpc.Dict) error {
453487
logger.Info("send", "id", id, "args", fmt.Sprintf("%v", args))
454-
d.xpc.Send(xpc.Dict{"kCBMsgId": id, "kCBMsgArgs": args}, false)
488+
x.Send(xpc.Dict{"kCBMsgId": id, "kCBMsgArgs": args}, false)
455489
return nil
456490
}

dev.go

+35-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
package ble
22

33
import (
4-
"context"
54
"io"
5+
6+
"golang.org/x/net/context"
67
)
78

89
// Addr represents a network end point address.
@@ -38,3 +39,36 @@ type Conn interface {
3839
// SetTxMTU sets the ATT_MTU which the remote device is capable of accepting.
3940
SetTxMTU(mtu int)
4041
}
42+
43+
// Device ...
44+
type Device interface {
45+
// AddService adds a service to database.
46+
AddService(svc *Service) error
47+
48+
// RemoveAllServices removes all services that are currently in the database.
49+
RemoveAllServices() error
50+
51+
// SetServices set the specified service to the database.
52+
// It removes all currently added services, if any.
53+
SetServices(svcs []*Service) error
54+
55+
// Stop detatch the GATT server from a peripheral device.
56+
Stop() error
57+
58+
// AdvertiseNameAndServices advertises device name, and specified service UUIDs.
59+
// It tres to fit the UUIDs in the advertising packet as much as possi
60+
// If name doesn't fit in the advertising packet, it will be put in scan response.
61+
AdvertiseNameAndServices(ctx context.Context, name string, uuids ...UUID) error
62+
63+
// AdvertiseIBeaconData advertise iBeacon with given manufacturer data.
64+
AdvertiseIBeaconData(ctx context.Context, b []byte) error
65+
66+
// AdvertiseIBeacon advertises iBeacon with specified parameters.
67+
AdvertiseIBeacon(ctx context.Context, u UUID, major, minor uint16, pwr int8) error
68+
69+
// Scan starts scanning. Duplicated advertisements will be filtered out if allowDup is set to false.
70+
Scan(ctx context.Context, allowDup bool, h AdvHandler) error
71+
72+
// Dial ...
73+
Dial(ctx context.Context, a Addr) (Client, error)
74+
}

0 commit comments

Comments
 (0)