scan_beacon 0.5.2 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/ext/bluez/addr_rotation.c +387 -0
- data/ext/bluez/advertising.c +42 -22
- data/ext/bluez/bluez.c +10 -8
- data/lib/scan_beacon/beacon/field.rb +93 -0
- data/lib/scan_beacon/beacon.rb +4 -3
- data/lib/scan_beacon/beacon_parser.rb +48 -52
- data/lib/scan_beacon/ble112_advertiser.rb +63 -0
- data/lib/scan_beacon/ble112_device.rb +45 -2
- data/lib/scan_beacon/ble112_scanner.rb +5 -1
- data/lib/scan_beacon/bluez_advertiser.rb +21 -4
- data/lib/scan_beacon/version.rb +1 -1
- data/lib/scan_beacon.rb +2 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f4548af4b44814857639f741b16b0b350d30ebd0
|
4
|
+
data.tar.gz: 42ca418ee1c526ea7286906ecd62e454cb1b6c63
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: da7aa4e68e45f42a7ec38b7502bc3ec955e8503ad22d62f97fe5d5c2797f59375a8c2c8825f3274091ed0deb5b305f86ee5a7a4b71e7aff47dfeb970f80c383f
|
7
|
+
data.tar.gz: 06b74ba91b756f475503143049b205d897ad785358811ea8878863e764b6110fc361dee6a7c7e35a125cf4dbe1cc464b131221ceed45341b5659c4f950b8c095
|
@@ -0,0 +1,387 @@
|
|
1
|
+
// code modified from bluez/tools/bdaddr.c, so including the below notice.
|
2
|
+
/*
|
3
|
+
*
|
4
|
+
* BlueZ - Bluetooth protocol stack for Linux
|
5
|
+
*
|
6
|
+
* Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
|
7
|
+
*
|
8
|
+
*
|
9
|
+
* This program is free software; you can redistribute it and/or modify
|
10
|
+
* it under the terms of the GNU General Public License as published by
|
11
|
+
* the Free Software Foundation; either version 2 of the License, or
|
12
|
+
* (at your option) any later version.
|
13
|
+
*
|
14
|
+
* This program is distributed in the hope that it will be useful,
|
15
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
16
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
17
|
+
* GNU General Public License for more details.
|
18
|
+
*
|
19
|
+
* You should have received a copy of the GNU General Public License
|
20
|
+
* along with this program; if not, write to the Free Software
|
21
|
+
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
22
|
+
*
|
23
|
+
*/
|
24
|
+
|
25
|
+
#ifdef linux
|
26
|
+
#include "ruby.h"
|
27
|
+
#include <stdio.h>
|
28
|
+
#include <errno.h>
|
29
|
+
#include <unistd.h>
|
30
|
+
#include <sys/ioctl.h>
|
31
|
+
#include <sys/socket.h>
|
32
|
+
#include <bluetooth/bluetooth.h>
|
33
|
+
#include <bluetooth/hci.h>
|
34
|
+
#include <bluetooth/hci_lib.h>
|
35
|
+
|
36
|
+
#include "utils.h"
|
37
|
+
|
38
|
+
VALUE method_device_up(VALUE self, VALUE device_id);
|
39
|
+
VALUE method_device_down(VALUE self, VALUE device_id);
|
40
|
+
|
41
|
+
|
42
|
+
static int transient = 0;
|
43
|
+
|
44
|
+
static int generic_reset_device(int dd)
|
45
|
+
{
|
46
|
+
bdaddr_t bdaddr;
|
47
|
+
int err;
|
48
|
+
|
49
|
+
err = hci_send_cmd(dd, 0x03, 0x0003, 0, NULL);
|
50
|
+
if (err < 0)
|
51
|
+
return err;
|
52
|
+
|
53
|
+
return hci_read_bd_addr(dd, &bdaddr, 10000);
|
54
|
+
}
|
55
|
+
|
56
|
+
#define OCF_ERICSSON_WRITE_BD_ADDR 0x000d
|
57
|
+
typedef struct {
|
58
|
+
bdaddr_t bdaddr;
|
59
|
+
} __attribute__ ((packed)) ericsson_write_bd_addr_cp;
|
60
|
+
|
61
|
+
static int ericsson_write_bd_addr(int dd, bdaddr_t *bdaddr)
|
62
|
+
{
|
63
|
+
struct hci_request rq;
|
64
|
+
ericsson_write_bd_addr_cp cp;
|
65
|
+
|
66
|
+
memset(&cp, 0, sizeof(cp));
|
67
|
+
bacpy(&cp.bdaddr, bdaddr);
|
68
|
+
|
69
|
+
memset(&rq, 0, sizeof(rq));
|
70
|
+
rq.ogf = OGF_VENDOR_CMD;
|
71
|
+
rq.ocf = OCF_ERICSSON_WRITE_BD_ADDR;
|
72
|
+
rq.cparam = &cp;
|
73
|
+
rq.clen = sizeof(cp);
|
74
|
+
rq.rparam = NULL;
|
75
|
+
rq.rlen = 0;
|
76
|
+
|
77
|
+
if (hci_send_req(dd, &rq, 1000) < 0)
|
78
|
+
return -1;
|
79
|
+
|
80
|
+
return 0;
|
81
|
+
}
|
82
|
+
|
83
|
+
#define OCF_ERICSSON_STORE_IN_FLASH 0x0022
|
84
|
+
typedef struct {
|
85
|
+
uint8_t user_id;
|
86
|
+
uint8_t flash_length;
|
87
|
+
uint8_t flash_data[253];
|
88
|
+
} __attribute__ ((packed)) ericsson_store_in_flash_cp;
|
89
|
+
|
90
|
+
static int ericsson_store_in_flash(int dd, uint8_t user_id, uint8_t flash_length, uint8_t *flash_data)
|
91
|
+
{
|
92
|
+
struct hci_request rq;
|
93
|
+
ericsson_store_in_flash_cp cp;
|
94
|
+
|
95
|
+
memset(&cp, 0, sizeof(cp));
|
96
|
+
cp.user_id = user_id;
|
97
|
+
cp.flash_length = flash_length;
|
98
|
+
if (flash_length > 0)
|
99
|
+
memcpy(cp.flash_data, flash_data, flash_length);
|
100
|
+
|
101
|
+
memset(&rq, 0, sizeof(rq));
|
102
|
+
rq.ogf = OGF_VENDOR_CMD;
|
103
|
+
rq.ocf = OCF_ERICSSON_STORE_IN_FLASH;
|
104
|
+
rq.cparam = &cp;
|
105
|
+
rq.clen = sizeof(cp);
|
106
|
+
rq.rparam = NULL;
|
107
|
+
rq.rlen = 0;
|
108
|
+
|
109
|
+
if (hci_send_req(dd, &rq, 1000) < 0)
|
110
|
+
return -1;
|
111
|
+
|
112
|
+
return 0;
|
113
|
+
}
|
114
|
+
|
115
|
+
static int csr_write_bd_addr(int dd, bdaddr_t *bdaddr)
|
116
|
+
{
|
117
|
+
unsigned char cmd[] = { 0x02, 0x00, 0x0c, 0x00, 0x11, 0x47, 0x03, 0x70,
|
118
|
+
0x00, 0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00,
|
119
|
+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
120
|
+
|
121
|
+
unsigned char cp[254], rp[254];
|
122
|
+
struct hci_request rq;
|
123
|
+
|
124
|
+
if (transient)
|
125
|
+
cmd[14] = 0x08;
|
126
|
+
|
127
|
+
cmd[16] = bdaddr->b[2];
|
128
|
+
cmd[17] = 0x00;
|
129
|
+
cmd[18] = bdaddr->b[0];
|
130
|
+
cmd[19] = bdaddr->b[1];
|
131
|
+
cmd[20] = bdaddr->b[3];
|
132
|
+
cmd[21] = 0x00;
|
133
|
+
cmd[22] = bdaddr->b[4];
|
134
|
+
cmd[23] = bdaddr->b[5];
|
135
|
+
|
136
|
+
memset(&cp, 0, sizeof(cp));
|
137
|
+
cp[0] = 0xc2;
|
138
|
+
memcpy(cp + 1, cmd, sizeof(cmd));
|
139
|
+
|
140
|
+
memset(&rq, 0, sizeof(rq));
|
141
|
+
rq.ogf = OGF_VENDOR_CMD;
|
142
|
+
rq.ocf = 0x00;
|
143
|
+
rq.event = EVT_VENDOR;
|
144
|
+
rq.cparam = cp;
|
145
|
+
rq.clen = sizeof(cmd) + 1;
|
146
|
+
rq.rparam = rp;
|
147
|
+
rq.rlen = sizeof(rp);
|
148
|
+
|
149
|
+
if (hci_send_req(dd, &rq, 2000) < 0)
|
150
|
+
return -1;
|
151
|
+
|
152
|
+
if (rp[0] != 0xc2) {
|
153
|
+
errno = EIO;
|
154
|
+
return -1;
|
155
|
+
}
|
156
|
+
|
157
|
+
if ((rp[9] + (rp[10] << 8)) != 0) {
|
158
|
+
errno = ENXIO;
|
159
|
+
return -1;
|
160
|
+
}
|
161
|
+
|
162
|
+
return 0;
|
163
|
+
}
|
164
|
+
|
165
|
+
static int csr_reset_device(int dd)
|
166
|
+
{
|
167
|
+
unsigned char cmd[] = { 0x02, 0x00, 0x09, 0x00,
|
168
|
+
0x00, 0x00, 0x01, 0x40, 0x00, 0x00,
|
169
|
+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
170
|
+
|
171
|
+
unsigned char cp[254], rp[254];
|
172
|
+
struct hci_request rq;
|
173
|
+
|
174
|
+
if (transient)
|
175
|
+
cmd[6] = 0x02;
|
176
|
+
|
177
|
+
memset(&cp, 0, sizeof(cp));
|
178
|
+
cp[0] = 0xc2;
|
179
|
+
memcpy(cp + 1, cmd, sizeof(cmd));
|
180
|
+
|
181
|
+
memset(&rq, 0, sizeof(rq));
|
182
|
+
rq.ogf = OGF_VENDOR_CMD;
|
183
|
+
rq.ocf = 0x00;
|
184
|
+
rq.event = EVT_VENDOR;
|
185
|
+
rq.cparam = cp;
|
186
|
+
rq.clen = sizeof(cmd) + 1;
|
187
|
+
rq.rparam = rp;
|
188
|
+
rq.rlen = sizeof(rp);
|
189
|
+
|
190
|
+
if (hci_send_req(dd, &rq, 2000) < 0)
|
191
|
+
return -1;
|
192
|
+
|
193
|
+
return 0;
|
194
|
+
}
|
195
|
+
|
196
|
+
#define OCF_TI_WRITE_BD_ADDR 0x0006
|
197
|
+
typedef struct {
|
198
|
+
bdaddr_t bdaddr;
|
199
|
+
} __attribute__ ((packed)) ti_write_bd_addr_cp;
|
200
|
+
|
201
|
+
static int ti_write_bd_addr(int dd, bdaddr_t *bdaddr)
|
202
|
+
{
|
203
|
+
struct hci_request rq;
|
204
|
+
ti_write_bd_addr_cp cp;
|
205
|
+
|
206
|
+
memset(&cp, 0, sizeof(cp));
|
207
|
+
bacpy(&cp.bdaddr, bdaddr);
|
208
|
+
|
209
|
+
memset(&rq, 0, sizeof(rq));
|
210
|
+
rq.ogf = OGF_VENDOR_CMD;
|
211
|
+
rq.ocf = OCF_TI_WRITE_BD_ADDR;
|
212
|
+
rq.cparam = &cp;
|
213
|
+
rq.clen = sizeof(cp);
|
214
|
+
rq.rparam = NULL;
|
215
|
+
rq.rlen = 0;
|
216
|
+
|
217
|
+
if (hci_send_req(dd, &rq, 1000) < 0)
|
218
|
+
return -1;
|
219
|
+
|
220
|
+
return 0;
|
221
|
+
}
|
222
|
+
|
223
|
+
#define OCF_BCM_WRITE_BD_ADDR 0x0001
|
224
|
+
typedef struct {
|
225
|
+
bdaddr_t bdaddr;
|
226
|
+
} __attribute__ ((packed)) bcm_write_bd_addr_cp;
|
227
|
+
|
228
|
+
static int bcm_write_bd_addr(int dd, bdaddr_t *bdaddr)
|
229
|
+
{
|
230
|
+
struct hci_request rq;
|
231
|
+
bcm_write_bd_addr_cp cp;
|
232
|
+
|
233
|
+
memset(&cp, 0, sizeof(cp));
|
234
|
+
bacpy(&cp.bdaddr, bdaddr);
|
235
|
+
|
236
|
+
memset(&rq, 0, sizeof(rq));
|
237
|
+
rq.ogf = OGF_VENDOR_CMD;
|
238
|
+
rq.ocf = OCF_BCM_WRITE_BD_ADDR;
|
239
|
+
rq.cparam = &cp;
|
240
|
+
rq.clen = sizeof(cp);
|
241
|
+
rq.rparam = NULL;
|
242
|
+
rq.rlen = 0;
|
243
|
+
|
244
|
+
if (hci_send_req(dd, &rq, 1000) < 0)
|
245
|
+
return -1;
|
246
|
+
|
247
|
+
return 0;
|
248
|
+
}
|
249
|
+
|
250
|
+
#define OCF_ZEEVO_WRITE_BD_ADDR 0x0001
|
251
|
+
typedef struct {
|
252
|
+
bdaddr_t bdaddr;
|
253
|
+
} __attribute__ ((packed)) zeevo_write_bd_addr_cp;
|
254
|
+
|
255
|
+
static int zeevo_write_bd_addr(int dd, bdaddr_t *bdaddr)
|
256
|
+
{
|
257
|
+
struct hci_request rq;
|
258
|
+
zeevo_write_bd_addr_cp cp;
|
259
|
+
|
260
|
+
memset(&cp, 0, sizeof(cp));
|
261
|
+
bacpy(&cp.bdaddr, bdaddr);
|
262
|
+
|
263
|
+
memset(&rq, 0, sizeof(rq));
|
264
|
+
rq.ogf = OGF_VENDOR_CMD;
|
265
|
+
rq.ocf = OCF_ZEEVO_WRITE_BD_ADDR;
|
266
|
+
rq.cparam = &cp;
|
267
|
+
rq.clen = sizeof(cp);
|
268
|
+
rq.rparam = NULL;
|
269
|
+
rq.rlen = 0;
|
270
|
+
|
271
|
+
if (hci_send_req(dd, &rq, 1000) < 0)
|
272
|
+
return -1;
|
273
|
+
|
274
|
+
return 0;
|
275
|
+
}
|
276
|
+
|
277
|
+
#define OCF_MRVL_WRITE_BD_ADDR 0x0022
|
278
|
+
typedef struct {
|
279
|
+
uint8_t parameter_id;
|
280
|
+
uint8_t bdaddr_len;
|
281
|
+
bdaddr_t bdaddr;
|
282
|
+
} __attribute__ ((packed)) mrvl_write_bd_addr_cp;
|
283
|
+
|
284
|
+
static int mrvl_write_bd_addr(int dd, bdaddr_t *bdaddr)
|
285
|
+
{
|
286
|
+
mrvl_write_bd_addr_cp cp;
|
287
|
+
|
288
|
+
memset(&cp, 0, sizeof(cp));
|
289
|
+
cp.parameter_id = 0xFE;
|
290
|
+
cp.bdaddr_len = 6;
|
291
|
+
bacpy(&cp.bdaddr, bdaddr);
|
292
|
+
|
293
|
+
if (hci_send_cmd(dd, OGF_VENDOR_CMD, OCF_MRVL_WRITE_BD_ADDR,
|
294
|
+
sizeof(cp), &cp) < 0)
|
295
|
+
return -1;
|
296
|
+
|
297
|
+
sleep(1);
|
298
|
+
return 0;
|
299
|
+
}
|
300
|
+
|
301
|
+
static int st_write_bd_addr(int dd, bdaddr_t *bdaddr)
|
302
|
+
{
|
303
|
+
return ericsson_store_in_flash(dd, 0xfe, 6, (uint8_t *) bdaddr);
|
304
|
+
}
|
305
|
+
|
306
|
+
static struct {
|
307
|
+
uint16_t compid;
|
308
|
+
int (*write_bd_addr)(int dd, bdaddr_t *bdaddr);
|
309
|
+
int (*reset_device)(int dd);
|
310
|
+
} vendor[] = {
|
311
|
+
{ 0, ericsson_write_bd_addr, NULL },
|
312
|
+
{ 10, csr_write_bd_addr, csr_reset_device },
|
313
|
+
{ 13, ti_write_bd_addr, NULL },
|
314
|
+
{ 15, bcm_write_bd_addr, generic_reset_device },
|
315
|
+
{ 18, zeevo_write_bd_addr, NULL },
|
316
|
+
{ 48, st_write_bd_addr, generic_reset_device },
|
317
|
+
{ 57, ericsson_write_bd_addr, generic_reset_device },
|
318
|
+
{ 72, mrvl_write_bd_addr, generic_reset_device },
|
319
|
+
{ 65535, NULL, NULL },
|
320
|
+
};
|
321
|
+
|
322
|
+
|
323
|
+
VALUE method_set_addr(VALUE klass, VALUE rb_device_id, VALUE rb_str_addr)
|
324
|
+
{
|
325
|
+
struct hci_dev_info di;
|
326
|
+
struct hci_version ver;
|
327
|
+
bdaddr_t bdaddr;
|
328
|
+
int i, dd, dev = 0, reset = 0;
|
329
|
+
|
330
|
+
dev = FIX2INT(rb_device_id);
|
331
|
+
bacpy(&bdaddr, BDADDR_ANY);
|
332
|
+
|
333
|
+
dd = hci_open_dev(dev);
|
334
|
+
if (dd < 0) {
|
335
|
+
rb_raise(rb_eException, "Can't open device");
|
336
|
+
}
|
337
|
+
|
338
|
+
if (hci_devinfo(dev, &di) < 0) {
|
339
|
+
hci_close_dev(dd);
|
340
|
+
rb_raise(rb_eException, "Can't get device info");
|
341
|
+
}
|
342
|
+
|
343
|
+
if (hci_read_local_version(dd, &ver, 1000) < 0) {
|
344
|
+
hci_close_dev(dd);
|
345
|
+
rb_raise(rb_eException, "Can't read version info for device");
|
346
|
+
}
|
347
|
+
|
348
|
+
if (!bacmp(&di.bdaddr, BDADDR_ANY)) {
|
349
|
+
if (hci_read_bd_addr(dd, &bdaddr, 1000) < 0) {
|
350
|
+
hci_close_dev(dd);
|
351
|
+
rb_raise(rb_eException, "Can't read address for device");
|
352
|
+
}
|
353
|
+
} else {
|
354
|
+
bacpy(&bdaddr, &di.bdaddr);
|
355
|
+
}
|
356
|
+
|
357
|
+
str2ba(RSTRING_PTR(rb_str_addr), &bdaddr);
|
358
|
+
|
359
|
+
for (i = 0; vendor[i].compid != 65535; i++)
|
360
|
+
{
|
361
|
+
if (ver.manufacturer == vendor[i].compid) {
|
362
|
+
|
363
|
+
if (vendor[i].write_bd_addr(dd, &bdaddr) < 0) {
|
364
|
+
hci_close_dev(dd);
|
365
|
+
rb_raise(rb_eException, "Can't write new address");
|
366
|
+
}
|
367
|
+
|
368
|
+
if (reset && vendor[i].reset_device) {
|
369
|
+
if (vendor[i].reset_device(dd) < 0) {
|
370
|
+
rb_raise(rb_eException, "Can't reset device");
|
371
|
+
} else {
|
372
|
+
ioctl(dd, HCIDEVRESET, dev);
|
373
|
+
}
|
374
|
+
} else {
|
375
|
+
method_device_down(Qnil, rb_device_id);
|
376
|
+
method_device_up(Qnil, rb_device_id);
|
377
|
+
}
|
378
|
+
|
379
|
+
hci_close_dev(dd);
|
380
|
+
return rb_str_addr;
|
381
|
+
}
|
382
|
+
}
|
383
|
+
hci_close_dev(dd);
|
384
|
+
rb_raise(rb_eException, "Unsupported manufacturer");
|
385
|
+
}
|
386
|
+
|
387
|
+
#endif // linux
|
data/ext/bluez/advertising.c
CHANGED
@@ -24,20 +24,23 @@ typedef struct {
|
|
24
24
|
uint8_t ad_data[28];
|
25
25
|
} Advertisement;
|
26
26
|
|
27
|
-
Advertisement
|
28
|
-
int advertising;
|
27
|
+
Advertisement advertisements[10];
|
29
28
|
|
30
29
|
VALUE method_start_advertising();
|
31
30
|
|
32
31
|
void init_advertisement()
|
33
32
|
{
|
34
|
-
memset(&advertisement, 0, sizeof(advertisement));
|
35
|
-
advertisement.flags.len = 2;
|
36
|
-
advertisement.flags.type = 1;
|
37
|
-
advertisement.flags.data = FLAGS_NOT_CONNECTABLE;
|
38
|
-
advertising = 0;
|
39
33
|
}
|
40
34
|
|
35
|
+
void set_advertisement_flags(Advertisement *advertisement)
|
36
|
+
{
|
37
|
+
memset(advertisement, 0, sizeof(Advertisement));
|
38
|
+
advertisement->flags.len = 2;
|
39
|
+
advertisement->flags.type = 1;
|
40
|
+
advertisement->flags.data = FLAGS_NOT_CONNECTABLE;
|
41
|
+
}
|
42
|
+
|
43
|
+
/*
|
41
44
|
VALUE method_set_connectable(VALUE self, VALUE connectable)
|
42
45
|
{
|
43
46
|
if ( RTEST(connectable) )
|
@@ -51,29 +54,30 @@ VALUE method_set_connectable(VALUE self, VALUE connectable)
|
|
51
54
|
}
|
52
55
|
return connectable;
|
53
56
|
}
|
57
|
+
*/
|
54
58
|
|
55
|
-
VALUE method_set_advertisement_bytes(VALUE self, VALUE bytes)
|
59
|
+
VALUE method_set_advertisement_bytes(VALUE self, VALUE rb_device_id, VALUE bytes)
|
56
60
|
{
|
61
|
+
int device_id = FIX2INT(rb_device_id);
|
62
|
+
Advertisement *advertisement = &advertisements[device_id];
|
57
63
|
uint8_t len = RSTRING_LEN(bytes);
|
58
64
|
if (len > 28) {
|
59
65
|
len = 28;
|
60
66
|
}
|
61
|
-
advertisement
|
62
|
-
|
63
|
-
|
64
|
-
method_start_advertising();
|
65
|
-
}
|
67
|
+
set_advertisement_flags(advertisement);
|
68
|
+
advertisement->len = len + advertisement->flags.len + 1; // + 1 is to account for the flags length field
|
69
|
+
memcpy(advertisement->ad_data, RSTRING_PTR(bytes), len);
|
66
70
|
return bytes;
|
67
71
|
}
|
68
72
|
|
69
|
-
VALUE method_start_advertising()
|
73
|
+
VALUE method_start_advertising(VALUE klass, VALUE rb_device_id, VALUE random_address)
|
70
74
|
{
|
71
75
|
struct hci_request rq;
|
72
76
|
le_set_advertising_parameters_cp adv_params_cp;
|
73
77
|
uint8_t status;
|
74
|
-
|
78
|
+
|
75
79
|
// open connection to the device
|
76
|
-
int device_id =
|
80
|
+
int device_id = FIX2INT(rb_device_id);
|
77
81
|
if (device_id < 0) {
|
78
82
|
rb_raise(rb_eException, "Could not find device");
|
79
83
|
}
|
@@ -85,8 +89,8 @@ VALUE method_start_advertising()
|
|
85
89
|
memset(&rq, 0, sizeof(rq));
|
86
90
|
rq.ogf = OGF_LE_CTL;
|
87
91
|
rq.ocf = OCF_LE_SET_ADVERTISING_DATA;
|
88
|
-
rq.cparam = &
|
89
|
-
rq.clen = sizeof(
|
92
|
+
rq.cparam = &advertisements[device_id];
|
93
|
+
rq.clen = sizeof(Advertisement);
|
90
94
|
rq.rparam = &status;
|
91
95
|
rq.rlen = 1;
|
92
96
|
hci_send_req(device_handle, &rq, 1000);
|
@@ -98,6 +102,9 @@ VALUE method_start_advertising()
|
|
98
102
|
adv_params_cp.max_interval = interval_100ms;
|
99
103
|
adv_params_cp.advtype = 0x03; // non-connectable undirected advertising
|
100
104
|
adv_params_cp.chan_map = 0x07;// all 3 channels
|
105
|
+
if (random_address != Qnil) {
|
106
|
+
adv_params_cp.own_bdaddr_type = LE_RANDOM_ADDRESS;
|
107
|
+
}
|
101
108
|
memset(&rq, 0, sizeof(rq));
|
102
109
|
rq.ogf = OGF_LE_CTL;
|
103
110
|
rq.ocf = OCF_LE_SET_ADVERTISING_PARAMETERS;
|
@@ -107,22 +114,35 @@ VALUE method_start_advertising()
|
|
107
114
|
rq.rlen = 1;
|
108
115
|
hci_send_req(device_handle, &rq, 1000);
|
109
116
|
|
117
|
+
if (random_address != Qnil)
|
118
|
+
{
|
119
|
+
// set random address
|
120
|
+
le_set_random_address_cp random_addr_cp;
|
121
|
+
str2ba(StringValuePtr(random_address), &random_addr_cp.bdaddr);
|
122
|
+
memset(&rq, 0, sizeof(rq));
|
123
|
+
rq.ogf = OGF_LE_CTL;
|
124
|
+
rq.ocf = OCF_LE_SET_RANDOM_ADDRESS;
|
125
|
+
rq.cparam = &random_addr_cp;
|
126
|
+
rq.clen = LE_SET_RANDOM_ADDRESS_CP_SIZE;
|
127
|
+
rq.rparam = &status;
|
128
|
+
rq.rlen = 1;
|
129
|
+
hci_send_req(device_handle, &rq, 1000);
|
130
|
+
}
|
131
|
+
|
110
132
|
// turn on advertising
|
111
133
|
hci_le_set_advertise_enable(device_handle, 0x01, 1000);
|
112
134
|
|
113
135
|
// and close the connection
|
114
136
|
hci_close_dev(device_handle);
|
115
|
-
advertising = 1;
|
116
137
|
return Qnil;
|
117
138
|
}
|
118
139
|
|
119
|
-
VALUE method_stop_advertising()
|
140
|
+
VALUE method_stop_advertising(VALUE klass, VALUE rb_device_id)
|
120
141
|
{
|
121
|
-
int device_id =
|
142
|
+
int device_id = FIX2INT(rb_device_id);
|
122
143
|
int device_handle = hci_open_dev(device_id);
|
123
144
|
hci_le_set_advertise_enable(device_handle, 0x00, 1000);
|
124
145
|
hci_close_dev(device_handle);
|
125
|
-
advertising = 0;
|
126
146
|
return Qnil;
|
127
147
|
}
|
128
148
|
|
data/ext/bluez/bluez.c
CHANGED
@@ -14,12 +14,13 @@ VALUE bluez_module = Qnil;
|
|
14
14
|
void Init_bluez();
|
15
15
|
VALUE method_device_up(VALUE self, VALUE device_id);
|
16
16
|
VALUE method_device_down(VALUE self, VALUE device_id);
|
17
|
-
VALUE method_set_connectable(VALUE self, VALUE connectable);
|
18
|
-
VALUE method_set_advertisement_bytes(VALUE self, VALUE bytes);
|
19
|
-
VALUE method_start_advertising();
|
20
|
-
VALUE method_stop_advertising();
|
17
|
+
//VALUE method_set_connectable(VALUE self, VALUE connectable);
|
18
|
+
VALUE method_set_advertisement_bytes(VALUE self, VALUE rb_device_id, VALUE bytes);
|
19
|
+
VALUE method_start_advertising(VALUE klass, VALUE rb_device_id, VALUE random_address);
|
20
|
+
VALUE method_stop_advertising(VALUE klass, VALUE rb_device_id);
|
21
21
|
VALUE method_scan(int argc, VALUE *argv, VALUE klass);
|
22
22
|
VALUE method_devices();
|
23
|
+
VALUE method_set_addr(VALUE klass, VALUE rb_device_id, VALUE rb_str_addr);
|
23
24
|
void init_advertisement();
|
24
25
|
|
25
26
|
void Init_bluez()
|
@@ -28,12 +29,13 @@ void Init_bluez()
|
|
28
29
|
bluez_module = rb_define_module_under(scan_beacon_module, "BlueZ");
|
29
30
|
rb_define_singleton_method(bluez_module, "device_up", method_device_up, 1);
|
30
31
|
rb_define_singleton_method(bluez_module, "device_down", method_device_down, 1);
|
31
|
-
rb_define_singleton_method(bluez_module, "start_advertising", method_start_advertising,
|
32
|
-
rb_define_singleton_method(bluez_module, "stop_advertising", method_stop_advertising,
|
33
|
-
rb_define_singleton_method(bluez_module, "
|
34
|
-
rb_define_singleton_method(bluez_module, "connectable=", method_set_connectable, 1);
|
32
|
+
rb_define_singleton_method(bluez_module, "start_advertising", method_start_advertising, 2);
|
33
|
+
rb_define_singleton_method(bluez_module, "stop_advertising", method_stop_advertising, 1);
|
34
|
+
rb_define_singleton_method(bluez_module, "set_advertisement_bytes", method_set_advertisement_bytes, 2);
|
35
|
+
//rb_define_singleton_method(bluez_module, "connectable=", method_set_connectable, 1);
|
35
36
|
rb_define_singleton_method(bluez_module, "scan", method_scan, -1);
|
36
37
|
rb_define_singleton_method(bluez_module, "devices", method_devices, 0);
|
38
|
+
rb_define_singleton_method(bluez_module, "set_addr", method_set_addr, 2);
|
37
39
|
|
38
40
|
// initialize the advertisement
|
39
41
|
init_advertisement();
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module ScanBeacon
|
2
|
+
class Beacon
|
3
|
+
class Field
|
4
|
+
include Comparable
|
5
|
+
ENCODING = "ASCII-8BIT".freeze
|
6
|
+
NULL_BYTE = "\x00".force_encoding(ENCODING).freeze
|
7
|
+
|
8
|
+
def initialize(opts = {})
|
9
|
+
self.set_data(opts)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.field_with_length(id, length)
|
13
|
+
return id if id.is_a? self
|
14
|
+
if id.is_a? String
|
15
|
+
self.new hex: id, length: length
|
16
|
+
elsif id.is_a? Integer
|
17
|
+
self.new number: id, length: length
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def set_data(opts = {})
|
22
|
+
bytes = opts[:bytes]
|
23
|
+
hex = opts[:hex]
|
24
|
+
number = opts[:number]
|
25
|
+
length = opts[:length]
|
26
|
+
if bytes
|
27
|
+
@data = bytes.force_encoding(ENCODING)
|
28
|
+
elsif hex
|
29
|
+
# zero pad hex if needed
|
30
|
+
hex = "0"*(length*2-hex.size) + hex if length and hex.size < length*2
|
31
|
+
@data = [hex].pack("H*")
|
32
|
+
elsif number
|
33
|
+
raise ArgumentError.new("Must also give a field length when you give a number") if length.nil?
|
34
|
+
set_data(hex: number.to_s(16), length: length)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def value
|
39
|
+
if @data.size < 6
|
40
|
+
self.to_i
|
41
|
+
else
|
42
|
+
self.to_hex
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_s
|
47
|
+
value.to_s
|
48
|
+
end
|
49
|
+
|
50
|
+
def inspect
|
51
|
+
"<Beacon::Field value=#{self.value.inspect}>"
|
52
|
+
end
|
53
|
+
|
54
|
+
def bytes
|
55
|
+
@data
|
56
|
+
end
|
57
|
+
|
58
|
+
def to_i
|
59
|
+
size = @data.size
|
60
|
+
case size
|
61
|
+
when 0
|
62
|
+
nil
|
63
|
+
when 1
|
64
|
+
@data.unpack("C")[0]
|
65
|
+
when 2
|
66
|
+
@data.unpack("S>")[0]
|
67
|
+
when 3
|
68
|
+
(NULL_BYTE + @data).unpack("L>")[0]
|
69
|
+
when 4
|
70
|
+
@data.unpack("L>")[0]
|
71
|
+
when 5,6,7
|
72
|
+
(NULL_BYTE*(8-size) + @data).unpack("Q>")[0]
|
73
|
+
when 8
|
74
|
+
@data.unpack("Q>")[0]
|
75
|
+
else
|
76
|
+
@data[-8..-1].unpack("Q>")[0]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def to_hex
|
81
|
+
@data.unpack("H*")[0]
|
82
|
+
end
|
83
|
+
|
84
|
+
def <=> (other)
|
85
|
+
if other.is_a? self.class
|
86
|
+
self.bytes <=> other.bytes
|
87
|
+
else
|
88
|
+
self.value <=> other
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
data/lib/scan_beacon/beacon.rb
CHANGED
@@ -32,15 +32,16 @@ module ScanBeacon
|
|
32
32
|
end
|
33
33
|
|
34
34
|
def uuid
|
35
|
-
|
35
|
+
id0 = ids[0].to_s
|
36
|
+
"#{id0[0..7]}-#{id0[8..11]}-#{id0[12..15]}-#{id0[16..19]}-#{id0[20..-1]}".upcase
|
36
37
|
end
|
37
38
|
|
38
39
|
def major
|
39
|
-
ids[1]
|
40
|
+
ids[1].to_i
|
40
41
|
end
|
41
42
|
|
42
43
|
def minor
|
43
|
-
ids[2]
|
44
|
+
ids[2].to_i
|
44
45
|
end
|
45
46
|
|
46
47
|
def ad_count
|
@@ -2,11 +2,11 @@ module ScanBeacon
|
|
2
2
|
class BeaconParser
|
3
3
|
DEFAULT_LAYOUTS = {
|
4
4
|
altbeacon: "m:2-3=beac,i:4-19,i:20-21,i:22-23,p:24-24,d:25-25",
|
5
|
-
eddystone_uid: "s:0-1=
|
5
|
+
eddystone_uid: "s:0-1=feaa,m:2-2=00,p:3-3:-41,i:4-13,i:14-19;d:20-21"
|
6
6
|
}
|
7
7
|
AD_TYPE_MFG = 0xff
|
8
8
|
AD_TYPE_SERVICE = 0x03
|
9
|
-
BT_EIR_SERVICE_DATA = "\x16"
|
9
|
+
BT_EIR_SERVICE_DATA = "\x16".force_encoding("ASCII-8BIT")
|
10
10
|
attr_accessor :beacon_type
|
11
11
|
|
12
12
|
def self.default_parsers
|
@@ -15,31 +15,49 @@ module ScanBeacon
|
|
15
15
|
|
16
16
|
def initialize(beacon_type, layout)
|
17
17
|
@beacon_type = beacon_type
|
18
|
-
@layout = layout.split(",")
|
19
18
|
if layout.include?("s")
|
20
19
|
@ad_type = AD_TYPE_SERVICE
|
21
20
|
else
|
22
21
|
@ad_type = AD_TYPE_MFG
|
23
22
|
end
|
24
|
-
@
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
@
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
23
|
+
@layout = layout
|
24
|
+
parse_layout
|
25
|
+
end
|
26
|
+
|
27
|
+
def parse_layout
|
28
|
+
@matchers = []
|
29
|
+
@ids = []
|
30
|
+
@data_fields = []
|
31
|
+
@power = nil
|
32
|
+
@layout.split(",").each do |field|
|
33
|
+
field_type, range_start, range_end, expected = field.split(/:|=|-/)
|
34
|
+
field_params = {
|
35
|
+
start: range_start.to_i,
|
36
|
+
end: range_end.to_i,
|
37
|
+
length: range_end.to_i - range_start.to_i + 1,
|
38
|
+
}
|
39
|
+
field_params[:expected] = [expected].pack("H*") unless expected.nil?
|
40
|
+
case field_type
|
41
|
+
when 'm'
|
42
|
+
@matchers << field_params
|
43
|
+
when 's'
|
44
|
+
# swap byte order of service uuid
|
45
|
+
expected = field_params[:expected]
|
46
|
+
field_params[:expected] = expected[1] + expected[0]
|
47
|
+
@matchers << field_params
|
48
|
+
when 'i'
|
49
|
+
@ids << field_params
|
50
|
+
when 'd'
|
51
|
+
@data_fields << field_params
|
52
|
+
when 'p'
|
53
|
+
@power = field_params
|
54
|
+
end
|
55
|
+
end
|
38
56
|
end
|
39
57
|
|
40
58
|
def matches?(data)
|
41
59
|
@matchers.each do |matcher|
|
42
|
-
return false unless data[matcher[:start]..matcher[:end]]
|
60
|
+
return false unless data[matcher[:start]..matcher[:end]] == matcher[:expected]
|
43
61
|
end
|
44
62
|
return true
|
45
63
|
end
|
@@ -66,20 +84,7 @@ module ScanBeacon
|
|
66
84
|
def parse_elems(elems, data)
|
67
85
|
elems.map {|elem|
|
68
86
|
elem_str = data[elem[:start]..elem[:end]]
|
69
|
-
|
70
|
-
case elem_length
|
71
|
-
when 1
|
72
|
-
elem_str.unpack('C')[0]
|
73
|
-
when 2
|
74
|
-
# two bytes, so treat it as a short (big endian)
|
75
|
-
elem_str.unpack('S>')[0]
|
76
|
-
when 6
|
77
|
-
# 6 bytes, treat it is an eddystone instance id
|
78
|
-
("\x00\x00"+elem_str).unpack('Q>')[0]
|
79
|
-
else
|
80
|
-
# not two bytes, so treat it as a hex string
|
81
|
-
elem_str.unpack('H*').join
|
82
|
-
end
|
87
|
+
Beacon::Field.new(bytes: elem_str)
|
83
88
|
}
|
84
89
|
end
|
85
90
|
|
@@ -88,46 +93,37 @@ module ScanBeacon
|
|
88
93
|
end
|
89
94
|
|
90
95
|
def parse_power(data)
|
96
|
+
return nil if @power.nil?
|
91
97
|
data[@power[:start]..@power[:end]].unpack('c')[0]
|
92
98
|
end
|
93
99
|
|
94
100
|
def generate_ad(beacon)
|
95
101
|
length = [@matchers, @ids, @power, @data_fields].flatten.map {|elem| elem[:end] }.max + 1
|
96
|
-
ad = "\x00" * length
|
102
|
+
ad = ("\x00" * length).force_encoding("ASCII-8BIT")
|
97
103
|
@matchers.each do |matcher|
|
98
|
-
ad[matcher[:start]..matcher[:end]] =
|
104
|
+
ad[matcher[:start]..matcher[:end]] = matcher[:expected]
|
99
105
|
end
|
100
106
|
@ids.each_with_index do |id, index|
|
101
|
-
|
107
|
+
id_bytes = Beacon::Field.field_with_length(beacon.ids[index], id[:length]).bytes
|
108
|
+
ad[id[:start]..id[:end]] = id_bytes
|
102
109
|
end
|
103
110
|
@data_fields.each_with_index do |field, index|
|
104
|
-
|
111
|
+
unless beacon.data[index].nil?
|
112
|
+
field_bytes = Beacon::Field.field_with_length(beacon.data[index], field[:length]).bytes
|
113
|
+
ad[field[:start]..field[:end]] = field_bytes
|
114
|
+
end
|
105
115
|
end
|
106
116
|
ad[@power[:start]..@power[:end]] = [beacon.power].pack('c')
|
107
117
|
if @ad_type == AD_TYPE_SERVICE
|
108
|
-
"\x03\x03" + [beacon.service_uuid].pack("S<") + [length+1].pack('C') + BT_EIR_SERVICE_DATA + ad
|
118
|
+
"\x03\x03".force_encoding("ASCII-8BIT") + [beacon.service_uuid].pack("S<") + [length+1].pack('C') + BT_EIR_SERVICE_DATA + ad
|
109
119
|
elsif @ad_type == AD_TYPE_MFG
|
110
120
|
ad[0..1] = [beacon.mfg_id].pack("S<")
|
111
121
|
[length+1].pack('C') + [AD_TYPE_MFG].pack('C') + ad
|
112
122
|
end
|
113
123
|
end
|
114
124
|
|
115
|
-
def generate_field(field, value)
|
116
|
-
field_length = field[:end] - field[:start] + 1
|
117
|
-
case field_length
|
118
|
-
when 1
|
119
|
-
[value].pack("c")
|
120
|
-
when 2
|
121
|
-
[value].pack("S>")
|
122
|
-
when 6
|
123
|
-
[value].pack("Q>")[2..-1]
|
124
|
-
else
|
125
|
-
[value].pack("H*")[0..field_length-1]
|
126
|
-
end
|
127
|
-
end
|
128
|
-
|
129
125
|
def inspect
|
130
|
-
"<BeaconParser type=\"#{@beacon_type}\", layout=\"#{@layout
|
126
|
+
"<BeaconParser type=\"#{@beacon_type}\", layout=\"#{@layout}\">"
|
131
127
|
end
|
132
128
|
end
|
133
129
|
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module ScanBeacon
|
2
|
+
class BLE112Advertiser
|
3
|
+
|
4
|
+
attr_accessor :beacon, :parser, :ad
|
5
|
+
|
6
|
+
def initialize(opts = {})
|
7
|
+
@device = BLE112Device.new opts[:port]
|
8
|
+
self.beacon = opts[:beacon]
|
9
|
+
self.parser = opts[:parser]
|
10
|
+
self.parser ||= BeaconParser.default_parsers.find {|parser| parser.beacon_type == beacon.beacon_types.first}
|
11
|
+
@advertising = false
|
12
|
+
end
|
13
|
+
|
14
|
+
def beacon=(value)
|
15
|
+
@beacon = value
|
16
|
+
update_ad
|
17
|
+
end
|
18
|
+
|
19
|
+
def parser=(value)
|
20
|
+
@parser = value
|
21
|
+
update_ad
|
22
|
+
end
|
23
|
+
|
24
|
+
def ad=(value)
|
25
|
+
@ad = value
|
26
|
+
end
|
27
|
+
|
28
|
+
def start(with_rotation = false)
|
29
|
+
@device.open do
|
30
|
+
@device.start_advertising(@ad, with_rotation)
|
31
|
+
@advertising = true
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def stop
|
36
|
+
@device.open do
|
37
|
+
@device.stop_advertising
|
38
|
+
@advertising = false
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def inspect
|
43
|
+
"<BLE112Advertiser ad=#{@ad.inspect}>"
|
44
|
+
end
|
45
|
+
|
46
|
+
def update_ad
|
47
|
+
self.ad = @parser.generate_ad(@beacon) if @parser && @beacon
|
48
|
+
self.start if @advertising
|
49
|
+
end
|
50
|
+
|
51
|
+
def rotate_addr
|
52
|
+
@device.open do
|
53
|
+
@device.rotate_addr
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def rotate_addr_and_update_ad
|
58
|
+
self.update_ad
|
59
|
+
self.start(true)
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
@@ -12,6 +12,10 @@ module ScanBeacon
|
|
12
12
|
BG_RESET = 0
|
13
13
|
BG_DISCONNECT = 0
|
14
14
|
BG_SET_MODE = 1
|
15
|
+
BG_GAP_SET_PRIVACY_FLAGS = 0
|
16
|
+
BG_GAP_SET_ADV_PARAM = 8
|
17
|
+
BG_GAP_SET_ADV_DATA = 9
|
18
|
+
|
15
19
|
BG_DISCOVER = 2
|
16
20
|
BG_DISCOVER_STOP = 4
|
17
21
|
BG_SCAN_PARAMS = 7
|
@@ -19,6 +23,8 @@ module ScanBeacon
|
|
19
23
|
BG_GAP_DISCOVER_ALL = 2
|
20
24
|
BG_GAP_NON_DISCOVERABLE = 0
|
21
25
|
BG_GAP_NON_CONNECTABLE = 0
|
26
|
+
BG_GAP_USER_DATA = 4
|
27
|
+
BG_GAP_CONNECTABLE = 2
|
22
28
|
|
23
29
|
def initialize(port=nil)
|
24
30
|
@port = port || Dir.glob("/dev/{cu.usbmodem,ttyACM}*")[0]
|
@@ -56,6 +62,39 @@ module ScanBeacon
|
|
56
62
|
bg_command(@file, BG_MSG_CLASS_GAP, BG_DISCOVER_STOP)
|
57
63
|
end
|
58
64
|
|
65
|
+
def start_advertising(ad_data, privacy = false)
|
66
|
+
# disconnect any connections
|
67
|
+
bg_command(@file, BG_MSG_CLASS_CONNECTION, BG_DISCONNECT,0)
|
68
|
+
|
69
|
+
# set advertising interval 0x00A0 = 100 ms interval, 7 = all channels
|
70
|
+
bg_command(@file, BG_MSG_CLASS_GAP, BG_GAP_SET_ADV_PARAM, [0xA0, 0x00, 0xA0, 0x00, 7])
|
71
|
+
|
72
|
+
# set privacy mode (rotate bluetooth address)
|
73
|
+
if privacy
|
74
|
+
bg_command(@file, BG_MSG_CLASS_GAP, BG_GAP_SET_PRIVACY_FLAGS, [1, 0])
|
75
|
+
end
|
76
|
+
|
77
|
+
# add flags header
|
78
|
+
ad_data = "\x02\x01\x06" + ad_data
|
79
|
+
ad_data = [0,ad_data.size].pack("C*") + ad_data
|
80
|
+
|
81
|
+
stop_advertising
|
82
|
+
bg_command(@file, BG_MSG_CLASS_GAP, BG_GAP_SET_ADV_DATA, ad_data.unpack("C*"))
|
83
|
+
bg_command(@file, BG_MSG_CLASS_GAP, BG_SET_MODE, [BG_GAP_USER_DATA, BG_GAP_CONNECTABLE])
|
84
|
+
end
|
85
|
+
|
86
|
+
def rotate_addr
|
87
|
+
# set peripheral into private mode is not needed, as the mac is rotated every time gap_set_mode is called
|
88
|
+
bg_command(@file, BG_MSG_CLASS_GAP, BG_GAP_SET_PRIVACY_FLAGS, [1, 0])
|
89
|
+
|
90
|
+
# set gap mode
|
91
|
+
bg_command(@file, BG_MSG_CLASS_GAP, BG_SET_MODE, [BG_GAP_USER_DATA, BG_GAP_CONNECTABLE])
|
92
|
+
end
|
93
|
+
|
94
|
+
def stop_advertising
|
95
|
+
bg_command(@file, BG_MSG_CLASS_GAP, BG_SET_MODE, [BG_GAP_NON_DISCOVERABLE, BG_GAP_NON_CONNECTABLE])
|
96
|
+
end
|
97
|
+
|
59
98
|
def read
|
60
99
|
BLE112Response.new( bg_read(@file) )
|
61
100
|
end
|
@@ -71,7 +110,7 @@ module ScanBeacon
|
|
71
110
|
|
72
111
|
class BLE112Response
|
73
112
|
def initialize(data)
|
74
|
-
@data = data
|
113
|
+
@data = data.force_encoding("ASCII-8BIT")
|
75
114
|
end
|
76
115
|
|
77
116
|
def size
|
@@ -90,8 +129,12 @@ module ScanBeacon
|
|
90
129
|
size > 20 && advertisement_type == 0xFF
|
91
130
|
end
|
92
131
|
|
132
|
+
def service_ad?
|
133
|
+
size > 20 && advertisement_type ==0x03
|
134
|
+
end
|
135
|
+
|
93
136
|
def advertisement?
|
94
|
-
event? && gap_scan? && manufacturer_ad?
|
137
|
+
event? && gap_scan? && (manufacturer_ad? || service_ad?)
|
95
138
|
end
|
96
139
|
|
97
140
|
def advertisement_type
|
@@ -14,7 +14,11 @@ module ScanBeacon
|
|
14
14
|
while keep_scanning do
|
15
15
|
response = device.read
|
16
16
|
if response.advertisement?
|
17
|
-
|
17
|
+
if response.manufacturer_ad?
|
18
|
+
keep_scanning = false if yield(response.advertisement_data, response.mac, response.rssi) == false
|
19
|
+
else
|
20
|
+
keep_scanning = false if yield(response.advertisement_data[4..-1], response.mac, response.rssi, 0x03) == false
|
21
|
+
end
|
18
22
|
end
|
19
23
|
end
|
20
24
|
ensure
|
@@ -1,12 +1,13 @@
|
|
1
1
|
module ScanBeacon
|
2
2
|
class BlueZAdvertiser
|
3
3
|
|
4
|
-
attr_accessor :beacon, :parser, :ad
|
4
|
+
attr_accessor :beacon, :parser, :ad, :addr
|
5
5
|
|
6
6
|
def initialize(opts = {})
|
7
7
|
@device_id = opts[:device_id] || BlueZ.devices.map {|d| d[:device_id]}[0]
|
8
8
|
raise "No available devices" if @device_id.nil?
|
9
9
|
BlueZ.device_up @device_id
|
10
|
+
@addr = @initial_addr = BlueZ.devices.find {|d| d[:device_id] == @device_id}[:addr]
|
10
11
|
self.beacon = opts[:beacon]
|
11
12
|
self.parser = opts[:parser]
|
12
13
|
self.parser ||= BeaconParser.default_parsers.find {|parser| parser.beacon_type == beacon.beacon_types.first}
|
@@ -24,15 +25,20 @@ module ScanBeacon
|
|
24
25
|
|
25
26
|
def ad=(value)
|
26
27
|
@ad = value
|
27
|
-
BlueZ.
|
28
|
+
BlueZ.set_advertisement_bytes @device_id, @ad
|
28
29
|
end
|
29
30
|
|
30
31
|
def start
|
31
|
-
BlueZ.start_advertising
|
32
|
+
BlueZ.start_advertising @device_id, nil
|
33
|
+
end
|
34
|
+
|
35
|
+
def start_with_random_addr
|
36
|
+
addr = random_addr
|
37
|
+
BlueZ.start_advertising @device_id, addr
|
32
38
|
end
|
33
39
|
|
34
40
|
def stop
|
35
|
-
BlueZ.stop_advertising
|
41
|
+
BlueZ.stop_advertising @device_id
|
36
42
|
end
|
37
43
|
|
38
44
|
def inspect
|
@@ -43,5 +49,16 @@ module ScanBeacon
|
|
43
49
|
self.ad = @parser.generate_ad(@beacon) if @parser && @beacon
|
44
50
|
end
|
45
51
|
|
52
|
+
def rotate_addr_and_update_ad
|
53
|
+
self.update_ad
|
54
|
+
self.stop
|
55
|
+
self.start_with_random_addr
|
56
|
+
end
|
57
|
+
|
58
|
+
def random_addr
|
59
|
+
data = @initial_addr + Time.now.to_s
|
60
|
+
Digest::SHA256.digest(data)[0..5].unpack("H2:H2:H2:H2:H2:H2").join(":")
|
61
|
+
end
|
62
|
+
|
46
63
|
end
|
47
64
|
end
|
data/lib/scan_beacon/version.rb
CHANGED
data/lib/scan_beacon.rb
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
require "scan_beacon/version"
|
2
2
|
require "scan_beacon/beacon"
|
3
|
+
require "scan_beacon/beacon/field"
|
3
4
|
require "scan_beacon/beacon_parser"
|
4
5
|
require "scan_beacon/generic_scanner"
|
5
6
|
require "scan_beacon/ble112_device"
|
6
7
|
require "scan_beacon/ble112_scanner"
|
8
|
+
require "scan_beacon/ble112_advertiser"
|
7
9
|
case RUBY_PLATFORM
|
8
10
|
when /darwin/
|
9
11
|
require "scan_beacon/core_bluetooth"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: scan_beacon
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Radius Networks
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-08-
|
11
|
+
date: 2015-08-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -80,6 +80,7 @@ files:
|
|
80
80
|
- LICENSE.txt
|
81
81
|
- README.md
|
82
82
|
- Rakefile
|
83
|
+
- ext/bluez/addr_rotation.c
|
83
84
|
- ext/bluez/advertising.c
|
84
85
|
- ext/bluez/bluez.c
|
85
86
|
- ext/bluez/devices.c
|
@@ -91,7 +92,9 @@ files:
|
|
91
92
|
- ext/core_bluetooth/extconf.rb
|
92
93
|
- lib/scan_beacon.rb
|
93
94
|
- lib/scan_beacon/beacon.rb
|
95
|
+
- lib/scan_beacon/beacon/field.rb
|
94
96
|
- lib/scan_beacon/beacon_parser.rb
|
97
|
+
- lib/scan_beacon/ble112_advertiser.rb
|
95
98
|
- lib/scan_beacon/ble112_device.rb
|
96
99
|
- lib/scan_beacon/ble112_scanner.rb
|
97
100
|
- lib/scan_beacon/bluez_advertiser.rb
|