forked from go-ble/ble
-
Notifications
You must be signed in to change notification settings - Fork 0
/
device.go
191 lines (163 loc) · 5.11 KB
/
device.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
package linux
import (
"context"
"io"
"log"
"github.com/cinello/ble"
"github.com/cinello/ble/linux/att"
"github.com/cinello/ble/linux/gatt"
"github.com/cinello/ble/linux/hci"
"github.com/pkg/errors"
)
// NewDevice returns the default HCI device.
func NewDevice(opts ...hci.Option) (*Device, error) {
return NewDeviceWithName("Gopher", opts...)
}
// NewDeviceWithName returns the default HCI device.
func NewDeviceWithName(name string, opts ...hci.Option) (*Device, error) {
return NewDeviceWithNameAndHandler(name, nil, opts...)
}
func NewDeviceWithNameAndHandler(name string, handler ble.NotifyHandler, opts ...hci.Option) (*Device, error) {
dev, err := hci.NewHCI(opts...)
if err != nil {
return nil, errors.Wrap(err, "can't create hci")
}
if err = dev.Init(); err != nil {
return nil, errors.Wrap(err, "can't init hci")
}
srv, err := gatt.NewServerWithNameAndHandler(name, handler)
if err != nil {
return nil, errors.Wrap(err, "can't create server")
}
// mtu := ble.DefaultMTU
mtu := ble.MaxMTU // TODO: get this from user using Option.
if mtu > ble.MaxMTU {
return nil, errors.Wrapf(err, "maximum ATT_MTU is %d", ble.MaxMTU)
}
go loop(dev, srv, mtu)
return &Device{HCI: dev, Server: srv}, nil
}
func loop(dev *hci.HCI, s *gatt.Server, mtu int) {
for {
l2c, err := dev.Accept()
if err != nil {
// An EOF error indicates that the HCI socket was closed during
// the read. Don't report this as an error.
if err != io.EOF {
log.Printf("can't accept: %s", err)
}
return
}
// Initialize the per-connection cccd values.
l2c.SetContext(context.WithValue(l2c.Context(), ble.ContextKeyCCC, make(map[uint16]uint16)))
l2c.SetRxMTU(mtu)
s.Lock()
as, err := att.NewServer(s.DB(), l2c)
s.Unlock()
if err != nil {
log.Printf("can't create ATT server: %s", err)
continue
}
go as.Loop()
}
}
// Device ...
type Device struct {
HCI *hci.HCI
Server *gatt.Server
}
// AddService adds a service to database.
func (d *Device) AddService(svc *ble.Service) error {
return d.Server.AddService(svc)
}
// RemoveAllServices removes all services that are currently in the database.
func (d *Device) RemoveAllServices() error {
return d.Server.RemoveAllServices()
}
// SetServices set the specified service to the database.
// It removes all currently added services, if any.
func (d *Device) SetServices(svcs []*ble.Service) error {
return d.Server.SetServices(svcs)
}
// Stop stops gatt server.
func (d *Device) Stop() error {
return d.HCI.Close()
}
func (d *Device) Advertise(ctx context.Context, adv ble.Advertisement) error {
if err := d.HCI.AdvertiseAdv(adv); err != nil {
return err
}
<-ctx.Done()
d.HCI.StopAdvertising()
return ctx.Err()
}
// AdvertiseNameAndServices advertises device name, and specified service UUIDs.
// It tres to fit the UUIDs in the advertising packet as much as possible.
// If name doesn't fit in the advertising packet, it will be put in scan response.
func (d *Device) AdvertiseNameAndServices(ctx context.Context, name string, uuids ...ble.UUID) error {
if err := d.HCI.AdvertiseNameAndServices(name, uuids...); err != nil {
return err
}
<-ctx.Done()
d.HCI.StopAdvertising()
return ctx.Err()
}
// AdvertiseMfgData avertises the given manufacturer data.
func (d *Device) AdvertiseMfgData(ctx context.Context, id uint16, b []byte) error {
if err := d.HCI.AdvertiseMfgData(id, b); err != nil {
return err
}
<-ctx.Done()
d.HCI.StopAdvertising()
return ctx.Err()
}
// AdvertiseServiceData16 advertises data associated with a 16bit service uuid
func (d *Device) AdvertiseServiceData16(ctx context.Context, id uint16, b []byte) error {
if err := d.HCI.AdvertiseServiceData16(id, b); err != nil {
return err
}
<-ctx.Done()
d.HCI.StopAdvertising()
return ctx.Err()
}
// AdvertiseIBeaconData advertise iBeacon with given manufacturer data.
func (d *Device) AdvertiseIBeaconData(ctx context.Context, b []byte) error {
if err := d.HCI.AdvertiseIBeaconData(b); err != nil {
return err
}
<-ctx.Done()
d.HCI.StopAdvertising()
return ctx.Err()
}
// AdvertiseIBeacon advertises iBeacon with specified parameters.
func (d *Device) AdvertiseIBeacon(ctx context.Context, u ble.UUID, major, minor uint16, pwr int8) error {
if err := d.HCI.AdvertiseIBeacon(u, major, minor, pwr); err != nil {
return err
}
<-ctx.Done()
d.HCI.StopAdvertising()
return ctx.Err()
}
// Scan starts scanning. Duplicated advertisements will be filtered out if allowDup is set to false.
func (d *Device) Scan(ctx context.Context, allowDup bool, h ble.AdvHandler) error {
if err := d.HCI.SetAdvHandler(h); err != nil {
return err
}
if err := d.HCI.Scan(allowDup); err != nil {
return err
}
<-ctx.Done()
d.HCI.StopScanning()
return ctx.Err()
}
// Dial ...
func (d *Device) Dial(ctx context.Context, a ble.Addr) (ble.Client, error) {
// d.HCI.Dial is a blocking call, although most of time it should return immediately.
// But in case passing wrong device address or the device went non-connectable, it blocks.
cln, err := d.HCI.Dial(ctx, a)
return cln, errors.Wrap(err, "can't dial")
}
// Address returns the listener's device address.
func (d *Device) Address() ble.Addr {
return d.HCI.Addr()
}