scan_beacon 0.5.2 → 0.5.3

Sign up to get free protection for your applications and to get access to all the features.
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