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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a5db65d671cd5e9211413474964dec5c321db9b9
4
- data.tar.gz: c216b5ca742bf2c66802ac4d85e7d09d60d9c9ce
3
+ metadata.gz: f4548af4b44814857639f741b16b0b350d30ebd0
4
+ data.tar.gz: 42ca418ee1c526ea7286906ecd62e454cb1b6c63
5
5
  SHA512:
6
- metadata.gz: af95dc0840d42696f139b48321a7cd972f472ea8964bd4953dca498c9219bf90d8c1bd8f510ba1118b5cf9b230b43564eeadc8c706010eba9dee1b3fb8911267
7
- data.tar.gz: a456aa87b04087fbbae387ba5a7463aba1f57a49c26f98a1649bead49f25728ff9bfa5fc45445f3833468f53ee87c88f554abb9901e625590add68f050e18287
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
@@ -24,20 +24,23 @@ typedef struct {
24
24
  uint8_t ad_data[28];
25
25
  } Advertisement;
26
26
 
27
- Advertisement 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.len = len + advertisement.flags.len + 1; // + 1 is to account for the flags length field
62
- memcpy(advertisement.ad_data, RSTRING_PTR(bytes), len);
63
- if (advertising) {
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 = hci_get_route(NULL);
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 = &advertisement;
89
- rq.clen = sizeof(advertisement);
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 = hci_get_route(NULL);
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, 0);
32
- rb_define_singleton_method(bluez_module, "stop_advertising", method_stop_advertising, 0);
33
- rb_define_singleton_method(bluez_module, "advertisement_bytes=", method_set_advertisement_bytes, 1);
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
@@ -32,15 +32,16 @@ module ScanBeacon
32
32
  end
33
33
 
34
34
  def uuid
35
- "#{ids[0][0..7]}-#{ids[0][8..11]}-#{ids[0][12..15]}-#{ids[0][16..19]}-#{ids[0][20..-1]}".upcase
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=aafe,m:2-2=00,p:3-3:-41,i:4-13,i:14-19;d:20-21"
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
- @matchers = @layout.find_all {|item| ["m", "s"].include? item[0]}.map {|matcher|
25
- _, range_start, range_end, expected = matcher.split(/:|=|-/)
26
- {start: range_start.to_i, end: range_end.to_i, expected: expected}
27
- }
28
- @ids = @layout.find_all {|item| item[0] == "i"}.map {|id|
29
- _, range_start, range_end = id.split(/:|-/)
30
- {start: range_start.to_i, end: range_end.to_i}
31
- }
32
- @data_fields = @layout.find_all {|item| item[0] == "d"}.map {|field|
33
- _, range_start, range_end = field.split(/:|-/)
34
- {start: range_start.to_i, end: range_end.to_i}
35
- }
36
- _, power_start, power_end = @layout.find {|item| item[0] == "p"}.split(/:|-/)
37
- @power = {start: power_start.to_i, end: power_end.to_i}
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]].unpack("H*").join == matcher[:expected]
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
- elem_length = elem_str.size
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]] = [matcher[:expected]].pack("H*")
104
+ ad[matcher[:start]..matcher[:end]] = matcher[:expected]
99
105
  end
100
106
  @ids.each_with_index do |id, index|
101
- ad[id[:start]..id[:end]] = generate_field(id, beacon.ids[index])
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
- ad[field[:start]..field[:end]] = generate_field(field, beacon.data[index]) unless beacon.data[index].nil?
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.join(",")}\">"
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
- keep_scanning = false if yield(response.advertisement_data, response.mac, response.rssi) == false
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.advertisement_bytes = @ad
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
@@ -1,3 +1,3 @@
1
1
  module ScanBeacon
2
- VERSION = "0.5.2"
2
+ VERSION = "0.5.3"
3
3
  end
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.2
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-24 00:00:00.000000000 Z
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