scan_beacon 0.3.6 → 0.5.0

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: 97af646b756254104fb434abe2feba314cfd29c5
4
- data.tar.gz: a59a971d4561c0ce78c6d9d8f048b4ad7633888d
3
+ metadata.gz: 986f14b044864f3f4e4403ae4cf4babab759035c
4
+ data.tar.gz: 39fe84b3c5617b4b4ba965af4fc22c7084b5e978
5
5
  SHA512:
6
- metadata.gz: 4a8a190d612e264c8334f1d92d9ab893fb8dc0f6ccc73507cde1e3c6e0ca8a7e4c94d3b5f5ecfd0dc9584e747c85538285ba070fa13ce5d36e458aef1a659935
7
- data.tar.gz: a3ce4f396c8476231c7508bd4d100be5db12f035f1a623d6231cd617be628e47a04e91bac2ea77d97cd5c9bec3a58c714a0a36caa0c7a5c68d43e059ad164d06
6
+ metadata.gz: 6d9c4da68dac5591b0719c44587258aef4ecc632138d0878185341ac20174bbb3106e113b9709f3c1cd5f24812ca15c336b719736cbf404d7ec36811d6da35f7
7
+ data.tar.gz: 50ff7678d58cb831273184acbde2c64b3c7301c1a869348b5003e3900ae8f596916a22cd1bec805f2bbf85c63952484f252c8389e5c3d664b3182164329e5563
data/.gitignore CHANGED
@@ -3,6 +3,8 @@ yell.rb
3
3
  yeller.rb
4
4
  .DS_Store
5
5
  pkg/
6
-
6
+ tmp/
7
+ *.bundle
8
+ *.so
7
9
  # Since this is a gem not an app, ignore the lock file
8
10
  Gemfile.lock
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # ScanBeacon gem
2
2
 
3
- A ruby gem that allows you to scan for beacon advertisements using a BlueGiga BLE112 device.
3
+ A ruby gem that allows you to scan for beacon advertisements using CoreBluetooth (on Mac OS X) or a BlueGiga BLE112 device (on mac or linux)
4
4
 
5
5
  # Example Usage
6
6
 
@@ -9,10 +9,19 @@ A ruby gem that allows you to scan for beacon advertisements using a BlueGiga BL
9
9
  gem install scan_beacon
10
10
  ```
11
11
 
12
- ## Start a scan
12
+ ## Create your scanner
13
13
  ``` ruby
14
14
  require 'scan_beacon'
15
+ # to scan using CoreBluetooth on a mac
16
+ scanner = ScanBeacon::CoreBluetoothScanner.new
17
+ # to scan using a BLE112 device
15
18
  scanner = ScanBeacon::BLE112Scanner.new
19
+ # to scan using BlueZ on Linux (make sure you have privileges)
20
+ scanner = ScanBeacon::BlueZScanner.new
21
+ ```
22
+
23
+ ## Start a scan, yield beacons in a loop
24
+ ``` ruby
16
25
  scanner.scan do |beacons|
17
26
  beacons.each do |beacon|
18
27
  puts beacon.inspect
@@ -23,7 +32,7 @@ end
23
32
  ## Set a specific scan cycle period
24
33
  ``` ruby
25
34
  require 'scan_beacon'
26
- scanner = ScanBeacon::BLE112Scanner.new cycle_seconds: 2
35
+ scanner = ScanBeacon::BLE112Scanner.new cycle_seconds: 5
27
36
  scanner.scan do |beacons|
28
37
  beacons.each do |beacon|
29
38
  puts beacon.inspect
@@ -31,6 +40,12 @@ scanner.scan do |beacons|
31
40
  end
32
41
  ```
33
42
 
43
+ ## Scan once for a set period and then return an array of beacons
44
+ ``` ruby
45
+ scanner = ScanBeacon::CoreBluetoothScanner.new cycle_seconds: 2
46
+ beacons = scanner.scan
47
+ ```
48
+
34
49
  ## Add a custom beacon layout
35
50
  By default, this gem supports AltBeacon advertisements. But you can add a beacon parser to support other major beacon formats as well.
36
51
 
@@ -41,5 +56,37 @@ scanner.add_parser( ScanBeacon::BeaconParser.new(:mybeacon, "m:2-3=0000,i:4-19,i
41
56
  ...
42
57
  ```
43
58
 
59
+ ## Advertise as a beacon on Linux using BlueZ
60
+ Example:
61
+ ``` ruby
62
+ # altbeacon
63
+ beacon = ScanBeacon::Beacon.new(
64
+ ids: ["2F234454CF6D4A0FADF2F4911BA9FFA6", 11,11],
65
+ power: -59,
66
+ mfg_id: 0x0118,
67
+ beacon_type: :altbeacon
68
+ )
69
+ advertiser = ScanBeacon::BlueZAdvertiser.new(beacon: beacon)
70
+ advertiser.start
71
+ ...
72
+ advertiser.stop
73
+
74
+ # Eddystone UID
75
+ beacon = ScanBeacon::Beacon.new(
76
+ ids: ["2F234454F4911BA9FFA6", 3],
77
+ power: -20,
78
+ service_uuid: 0xFEAA,
79
+ beacon_type: :eddystone_uid
80
+ )
81
+ advertiser = ScanBeacon::BlueZAdvertiser.new(beacon: beacon)
82
+ advertiser.start
83
+ ...
84
+ advertiser.stop
85
+ ```
86
+
87
+
44
88
  # Dependencies
45
- You must have a BLE112 device plugged in to a USB port.
89
+ To scan for beacons, you must have a Linux machine with BlueZ installed, or a Mac, or a BLE112 device plugged in to a USB port (on Mac or Linux).
90
+
91
+ To advertise as a beacon, you must have a Linux machine with BlueZ installed.
92
+
data/Rakefile CHANGED
@@ -1 +1,10 @@
1
1
  require "bundler/gem_tasks"
2
+ require "rake/extensiontask"
3
+
4
+ Rake::ExtensionTask.new "core_bluetooth" do |ext|
5
+ ext.lib_dir = "lib/scan_beacon"
6
+ end
7
+
8
+ Rake::ExtensionTask.new "bluez" do |ext|
9
+ ext.lib_dir = "lib/scan_beacon"
10
+ end
@@ -0,0 +1,125 @@
1
+ #ifdef linux
2
+ #include "ruby.h"
3
+ #include <stdio.h>
4
+ #include <errno.h>
5
+ #include <unistd.h>
6
+ #include <sys/ioctl.h>
7
+ #include <sys/socket.h>
8
+ #include <bluetooth/bluetooth.h>
9
+ #include <bluetooth/hci.h>
10
+ #include <bluetooth/hci_lib.h>
11
+ #pragma pack(1)
12
+
13
+
14
+ #define FLAGS_NOT_CONNECTABLE 0x1A
15
+ #define FLAGS_CONNECTABLE 0x18
16
+
17
+ typedef struct {
18
+ uint8_t len;
19
+ struct {
20
+ uint8_t len;
21
+ uint8_t type;
22
+ uint8_t data;
23
+ } flags;
24
+ uint8_t ad_data[28];
25
+ } Advertisement;
26
+
27
+ Advertisement advertisement;
28
+ int advertising;
29
+
30
+ VALUE method_start_advertising();
31
+
32
+ void init_advertisement()
33
+ {
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
+ }
40
+
41
+ VALUE method_set_connectable(VALUE self, VALUE connectable)
42
+ {
43
+ if ( RTEST(connectable) )
44
+ {
45
+ advertisement.flags.data = FLAGS_CONNECTABLE;
46
+ } else {
47
+ advertisement.flags.data = FLAGS_NOT_CONNECTABLE;
48
+ };
49
+ if (advertising) {
50
+ method_start_advertising();
51
+ }
52
+ return connectable;
53
+ }
54
+
55
+ VALUE method_set_advertisement_bytes(VALUE self, VALUE bytes)
56
+ {
57
+ uint8_t len = RSTRING_LEN(bytes);
58
+ if (len > 28) {
59
+ len = 28;
60
+ }
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
+ }
66
+ return bytes;
67
+ }
68
+
69
+ VALUE method_start_advertising()
70
+ {
71
+ struct hci_request rq;
72
+ le_set_advertising_parameters_cp adv_params_cp;
73
+ uint8_t status;
74
+
75
+ // open connection to the device
76
+ int device_id = hci_get_route(NULL);
77
+ int device_handle = hci_open_dev(device_id);
78
+
79
+ // set advertising data
80
+ memset(&rq, 0, sizeof(rq));
81
+ rq.ogf = OGF_LE_CTL;
82
+ rq.ocf = OCF_LE_SET_ADVERTISING_DATA;
83
+ rq.cparam = &advertisement;
84
+ rq.clen = sizeof(advertisement);
85
+ rq.rparam = &status;
86
+ rq.rlen = 1;
87
+ hci_send_req(device_handle, &rq, 1000);
88
+
89
+ // set advertising params
90
+ memset(&adv_params_cp, 0, sizeof(adv_params_cp));
91
+ uint16_t interval_100ms = htobs(0x00A0); // 0xA0 * 0.625ms = 100ms
92
+ adv_params_cp.min_interval = interval_100ms;
93
+ adv_params_cp.max_interval = interval_100ms;
94
+ adv_params_cp.advtype = 0x03; // non-connectable undirected advertising
95
+ adv_params_cp.chan_map = 0x07;// all 3 channels
96
+ memset(&rq, 0, sizeof(rq));
97
+ rq.ogf = OGF_LE_CTL;
98
+ rq.ocf = OCF_LE_SET_ADVERTISING_PARAMETERS;
99
+ rq.cparam = &adv_params_cp;
100
+ rq.clen = LE_SET_ADVERTISING_PARAMETERS_CP_SIZE;
101
+ rq.rparam = &status;
102
+ rq.rlen = 1;
103
+ hci_send_req(device_handle, &rq, 1000);
104
+
105
+ // turn on advertising
106
+ hci_le_set_advertise_enable(device_handle, 0x01, 1000);
107
+
108
+ // and close the connection
109
+ hci_close_dev(device_handle);
110
+ advertising = 1;
111
+ return Qnil;
112
+ }
113
+
114
+ VALUE method_stop_advertising()
115
+ {
116
+ int device_id = hci_get_route(NULL);
117
+ int device_handle = hci_open_dev(device_id);
118
+ hci_le_set_advertise_enable(device_handle, 0x00, 1000);
119
+ hci_close_dev(device_handle);
120
+ advertising = 0;
121
+ return Qnil;
122
+ }
123
+
124
+
125
+ #endif // linux
data/ext/bluez/bluez.c ADDED
@@ -0,0 +1,43 @@
1
+ #ifdef linux
2
+ #include "ruby.h"
3
+ #include <stdio.h>
4
+ #include <errno.h>
5
+ #include <unistd.h>
6
+ #include <sys/ioctl.h>
7
+ #include <sys/socket.h>
8
+ #include <bluetooth/bluetooth.h>
9
+ #include <bluetooth/hci.h>
10
+ #include <bluetooth/hci_lib.h>
11
+
12
+ VALUE bluez_module = Qnil;
13
+
14
+ void Init_bluez();
15
+ VALUE method_device_up(VALUE self, VALUE device_id);
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();
21
+ VALUE method_scan(int argc, VALUE *argv, VALUE klass);
22
+ VALUE method_devices();
23
+ void init_advertisement();
24
+
25
+ void Init_bluez()
26
+ {
27
+ VALUE scan_beacon_module = rb_const_get(rb_cObject, rb_intern("ScanBeacon"));
28
+ bluez_module = rb_define_module_under(scan_beacon_module, "BlueZ");
29
+ rb_define_singleton_method(bluez_module, "device_up", method_device_up, 1);
30
+ 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);
35
+ rb_define_singleton_method(bluez_module, "scan", method_scan, -1);
36
+ rb_define_singleton_method(bluez_module, "devices", method_devices, 0);
37
+
38
+ // initialize the advertisement
39
+ init_advertisement();
40
+ }
41
+
42
+
43
+ #endif // linux
@@ -0,0 +1,101 @@
1
+ #ifdef linux
2
+ #include "ruby.h"
3
+ #include <stdio.h>
4
+ #include <errno.h>
5
+ #include <unistd.h>
6
+ #include <sys/ioctl.h>
7
+ #include <sys/socket.h>
8
+ #include <bluetooth/bluetooth.h>
9
+ #include <bluetooth/hci.h>
10
+ #include <bluetooth/hci_lib.h>
11
+
12
+ #include "utils.h"
13
+
14
+ VALUE method_device_up(VALUE self, VALUE device_id)
15
+ {
16
+ VALUE success;
17
+ int ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
18
+ int hdev = NUM2INT(device_id);
19
+ /* Start HCI device */
20
+ if (ioctl(ctl, HCIDEVUP, hdev) < 0) {
21
+ if (errno == EALREADY) {
22
+ success = Qtrue;
23
+ } else {
24
+ rb_sys_fail("Can't init device");
25
+ success = Qfalse;
26
+ }
27
+ } else {
28
+ success = Qtrue;
29
+ }
30
+ close(ctl);
31
+ return success;
32
+ }
33
+
34
+ VALUE method_device_down(VALUE self, VALUE device_id)
35
+ {
36
+ VALUE success;
37
+ int ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
38
+ int hdev = NUM2INT(device_id);
39
+ /* Stop HCI device */
40
+ if (ioctl(ctl, HCIDEVDOWN, hdev) < 0) {
41
+ rb_sys_fail("Can't init device");
42
+ success = Qfalse;
43
+ } else {
44
+ success = Qtrue;
45
+ }
46
+ close(ctl);
47
+ return success;
48
+ }
49
+
50
+ VALUE di_to_hash(struct hci_dev_info *di)
51
+ {
52
+ VALUE hash = rb_hash_new();
53
+ rb_hash_aset(hash, ID2SYM(rb_intern("device_id")), INT2FIX(di->dev_id));
54
+ rb_hash_aset(hash, ID2SYM(rb_intern("name")), rb_str_new2(di->name));
55
+ rb_hash_aset(hash, ID2SYM(rb_intern("addr")), ba2value(&di->bdaddr));
56
+ if (hci_test_bit(HCI_UP, &di->flags)) {
57
+ rb_hash_aset(hash, ID2SYM(rb_intern("up")), Qtrue);
58
+ } else {
59
+ rb_hash_aset(hash, ID2SYM(rb_intern("up")), Qfalse);
60
+ }
61
+ return hash;
62
+ }
63
+
64
+ VALUE method_devices()
65
+ {
66
+ struct hci_dev_list_req *dl;
67
+ struct hci_dev_req *dr;
68
+ struct hci_dev_info di;
69
+ int i;
70
+
71
+ if (!(dl = malloc(HCI_MAX_DEV * sizeof(struct hci_dev_req) + sizeof(uint16_t)))) {
72
+ rb_raise(rb_eException, "Can't allocate memory");
73
+ return Qnil;
74
+ }
75
+ dl->dev_num = HCI_MAX_DEV;
76
+ dr = dl->dev_req;
77
+
78
+ int ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
79
+ if (ioctl(ctl, HCIGETDEVLIST, (void *) dl) < 0) {
80
+ rb_raise(rb_eException, "Can't get device list");
81
+ return Qnil;
82
+ }
83
+
84
+ VALUE devices = rb_ary_new();
85
+
86
+ for (i = 0; i< dl->dev_num; i++) {
87
+ di.dev_id = (dr+i)->dev_id;
88
+ if (ioctl(ctl, HCIGETDEVINFO, (void *) &di) < 0)
89
+ continue;
90
+ if (hci_test_bit(HCI_RAW, &di.flags) &&
91
+ !bacmp(&di.bdaddr, BDADDR_ANY)) {
92
+ int dd = hci_open_dev(di.dev_id);
93
+ hci_read_bd_addr(dd, &di.bdaddr, 1000);
94
+ hci_close_dev(dd);
95
+ }
96
+ rb_ary_push(devices, di_to_hash(&di));
97
+ }
98
+ return devices;
99
+ }
100
+
101
+ #endif // linux
@@ -0,0 +1,14 @@
1
+ # Loads mkmf which is used to make makefiles for Ruby extensions
2
+ require 'mkmf'
3
+
4
+ # Give it a name
5
+ extension_name = 'scan_beacon/bluez'
6
+
7
+ # The destination
8
+ dir_config(extension_name)
9
+
10
+ if RUBY_PLATFORM =~ /linux/
11
+ abort 'could not find bluetooth library (libbluetooth-dev)' unless have_library("bluetooth")
12
+ end
13
+
14
+ create_makefile(extension_name)
@@ -0,0 +1,144 @@
1
+ #ifdef linux
2
+ #include "ruby.h"
3
+ #include <stdio.h>
4
+ #include <errno.h>
5
+ #include <unistd.h>
6
+ #include <sys/ioctl.h>
7
+ #include <sys/socket.h>
8
+ #include <time.h>
9
+ #include <bluetooth/bluetooth.h>
10
+ #include <bluetooth/hci.h>
11
+ #include <bluetooth/hci_lib.h>
12
+
13
+ #include "utils.h"
14
+
15
+ VALUE method_scan();
16
+
17
+
18
+ VALUE stop_scan(VALUE device_id);
19
+ VALUE perform_scan(VALUE device_id);
20
+
21
+ struct hci_filter stored_filters[10];
22
+ int device_handles[10];
23
+
24
+ VALUE method_scan(int argc, VALUE *argv, VALUE klass)
25
+ {
26
+ VALUE rb_device_id;
27
+ int device_id;
28
+ int device_handle;
29
+ uint8_t scan_type = 0x01; //passive
30
+ uint8_t own_type = 0x00; // I think this specifies not to use a random MAC
31
+ uint8_t filter_dups = 0x00;
32
+ uint8_t filter_policy = 0x00; // ?
33
+ uint16_t interval = htobs(0x0005);
34
+ uint16_t window = htobs(0x0005);
35
+
36
+ struct hci_filter new_filter;
37
+
38
+ // which device was specified?
39
+ rb_scan_args(argc, argv, "01", &rb_device_id);
40
+ if (rb_device_id == Qnil) {
41
+ device_id = hci_get_route(NULL);
42
+ } else {
43
+ device_id = NUM2INT(rb_device_id);
44
+ }
45
+ // open the device
46
+ if ( (device_handle = hci_open_dev(device_id)) < 0) {
47
+ rb_raise(rb_eException, "Could not open device");
48
+ }
49
+ device_handles[device_id] = device_handle;
50
+
51
+ // save the old filter so we can restore it later
52
+ socklen_t filter_size = sizeof(stored_filters[0]);
53
+ if (getsockopt(device_handle, SOL_HCI, HCI_FILTER, &stored_filters[device_id], &filter_size) < 0) {
54
+ rb_raise(rb_eException, "Could not get socket options");
55
+ }
56
+
57
+ // new filter to only look for event packets
58
+ hci_filter_clear(&new_filter);
59
+ hci_filter_set_ptype(HCI_EVENT_PKT, &new_filter);
60
+ hci_filter_set_event(EVT_LE_META_EVENT, &new_filter);
61
+ if (setsockopt(device_handle, SOL_HCI, HCI_FILTER, &new_filter, sizeof(new_filter)) < 0) {
62
+ rb_raise(rb_eException, "Could not set socket options");
63
+ }
64
+
65
+ // set the params
66
+ hci_le_set_scan_parameters(device_handle, scan_type, interval, window, own_type, filter_policy, 1000);
67
+ hci_le_set_scan_enable(device_handle, 0x01, filter_dups, 1000);
68
+
69
+ // perform the scan and make sure device gets put back into a proper state
70
+ // even in the case of being interrupted by a ruby exception
71
+ rb_ensure(perform_scan, INT2FIX(device_id), stop_scan, INT2FIX(device_id));
72
+ return Qnil;
73
+ }
74
+
75
+ VALUE perform_scan(VALUE device_id_in)
76
+ {
77
+ int device_id = FIX2INT(device_id_in);
78
+ int device_handle = device_handles[device_id];
79
+ unsigned char buf[HCI_MAX_EVENT_SIZE], *ptr;
80
+ int len;
81
+ int keep_scanning = 1;
82
+ while (keep_scanning) {
83
+ evt_le_meta_event *meta;
84
+ le_advertising_info *info;
85
+
86
+ // wait for data with a timeout
87
+ fd_set set;
88
+ FD_ZERO(&set);
89
+ FD_SET(device_handle, &set);
90
+ struct timeval timeout;
91
+ timeout.tv_sec = 0;
92
+ timeout.tv_usec = 200000; // 200ms
93
+ int ret = select(device_handle + 1, &set, NULL, NULL, &timeout);
94
+ if (ret < 0) {
95
+ rb_raise(rb_eException, "Error waiting for data");
96
+ } else if (ret == 0) {
97
+ // timeout. yield nil to give ruby a chance to stop the scan.
98
+ keep_scanning = rb_yield(Qnil) != Qfalse;
99
+ continue;
100
+ }
101
+
102
+ // keep trying to read until we get something
103
+ while ((len = read(device_handle, buf, sizeof(buf))) < 0) {
104
+ if (errno == EAGAIN || errno == EINTR) {
105
+ continue;
106
+ }
107
+ keep_scanning = 0;
108
+ break;
109
+ }
110
+
111
+ if (len > 0) {
112
+ ptr = buf + (1 + HCI_EVENT_HDR_SIZE);
113
+ len -= (1 + HCI_EVENT_HDR_SIZE);
114
+ meta = (void *) ptr;
115
+ // check if this event is an advertisement
116
+ if (meta->subevent != EVT_LE_ADVERTISING_REPORT) {
117
+ continue;
118
+ }
119
+ // parse out the ad data, the mac, and the rssi
120
+ info = (le_advertising_info *) (meta->data + 1);
121
+ VALUE rssi = INT2FIX( (int8_t)info->data[info->length] );
122
+ VALUE ad_data = rb_str_new((void *)info->data, info->length);
123
+ VALUE addr = ba2value(&info->bdaddr);
124
+ keep_scanning = rb_yield_values(3, addr, ad_data, rssi) != Qfalse;
125
+ }
126
+ }
127
+ return Qnil;
128
+ }
129
+
130
+ VALUE stop_scan(VALUE device_id_in)
131
+ {
132
+ int device_id = FIX2INT(device_id_in);
133
+ int device_handle = device_handles[device_id];
134
+
135
+ // put back the old filter
136
+ setsockopt(device_handle, SOL_HCI, HCI_FILTER, &stored_filters[device_id], sizeof(stored_filters[0]));
137
+
138
+ // stop scanning
139
+ hci_le_set_scan_enable(device_handle, 0x00, 0x00, 1000);
140
+ hci_close_dev(device_handle);
141
+ return Qnil;
142
+ }
143
+
144
+ #endif // linux
data/ext/bluez/utils.c ADDED
@@ -0,0 +1,21 @@
1
+ #ifdef linux
2
+ #include "ruby.h"
3
+ #include <stdio.h>
4
+ #include <errno.h>
5
+ #include <unistd.h>
6
+ #include <sys/ioctl.h>
7
+ #include <sys/socket.h>
8
+ #include <bluetooth/bluetooth.h>
9
+ #include <bluetooth/hci.h>
10
+ #include <bluetooth/hci_lib.h>
11
+
12
+ #include "utils.h"
13
+
14
+ VALUE ba2value(bdaddr_t *bdaddr)
15
+ {
16
+ char addr[18];
17
+ ba2str(bdaddr, addr);
18
+ return rb_str_new2(addr);
19
+ }
20
+
21
+ #endif // linux
data/ext/bluez/utils.h ADDED
@@ -0,0 +1,2 @@
1
+
2
+ VALUE ba2value(bdaddr_t *bdaddr);
@@ -0,0 +1,150 @@
1
+ #ifdef __APPLE__
2
+
3
+ // Include the Ruby headers and goodies
4
+ #include "ruby.h"
5
+ #import <Foundation/Foundation.h>
6
+ #import <CoreBluetooth/CoreBluetooth.h>
7
+
8
+ // Defining a space for information and references about the module to be stored internally
9
+ VALUE cb_module = Qnil;
10
+
11
+ // Prototype for the initialization method - Ruby calls this, not you
12
+ void Init_core_bluetooth();
13
+ VALUE method_scan();
14
+ VALUE method_new_adverts();
15
+ VALUE new_scan_hash(NSString* device, NSData *data, NSNumber *rssi, NSData *service_uuid);
16
+
17
+ @interface BLEDelegate : NSObject <CBCentralManagerDelegate>
18
+ - (NSArray *)scans;
19
+ @end
20
+
21
+ @implementation BLEDelegate {
22
+ NSMutableArray *_scans;
23
+ }
24
+
25
+ - (id)init
26
+ {
27
+ self = [super init];
28
+ _scans = [[NSMutableArray alloc] init];
29
+ return self;
30
+ }
31
+
32
+ - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral
33
+ advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
34
+ {
35
+ NSData *mfgData = advertisementData[@"kCBAdvDataManufacturerData"];
36
+ NSDictionary *serviceData = advertisementData[@"kCBAdvDataServiceData"];
37
+ if (mfgData) {
38
+ NSDictionary *scan = @{@"device": peripheral.identifier.UUIDString,
39
+ @"data": mfgData,
40
+ @"rssi": RSSI
41
+ };
42
+ @synchronized(_scans) {
43
+ [_scans addObject: scan];
44
+ }
45
+ } else if (serviceData) {
46
+ NSData *svcData = [serviceData allValues][0];
47
+ CBUUID *uuid = serviceData.allKeys[0];
48
+ NSDictionary *scan = @{@"device": peripheral.identifier.UUIDString,
49
+ @"data": svcData,
50
+ @"rssi": RSSI,
51
+ @"service_uuid": uuid.data
52
+ };
53
+ @synchronized(_scans) {
54
+ [_scans addObject: scan];
55
+ }
56
+ }
57
+ }
58
+
59
+ - (void)centralManagerDidUpdateState:(CBCentralManager *)central
60
+ {
61
+ [central scanForPeripheralsWithServices:nil options:@{CBCentralManagerScanOptionAllowDuplicatesKey : @(YES)}];
62
+ }
63
+
64
+ - (NSArray *) scans
65
+ {
66
+ NSArray *scanCopy;
67
+ @synchronized(_scans) {
68
+ scanCopy = _scans;
69
+ _scans = [[NSMutableArray alloc] init];
70
+ }
71
+ return scanCopy;
72
+ }
73
+
74
+ @end
75
+
76
+ VALUE sym_device = Qnil;
77
+ VALUE sym_data = Qnil;
78
+ VALUE sym_rssi = Qnil;
79
+ VALUE sym_service_uuid = Qnil;
80
+ BLEDelegate *bleDelegate;
81
+ CBCentralManager *centralManager;
82
+
83
+ // initialize our module here
84
+ void Init_core_bluetooth()
85
+ {
86
+ VALUE scan_beacon_module = rb_const_get(rb_cObject, rb_intern("ScanBeacon"));
87
+ cb_module = rb_define_module_under(scan_beacon_module, "CoreBluetooth");
88
+ rb_define_singleton_method(cb_module, "scan", method_scan, 0);
89
+ rb_define_singleton_method(cb_module, "new_adverts", method_new_adverts, 0);
90
+
91
+ sym_device = ID2SYM(rb_intern("device"));
92
+ sym_data = ID2SYM(rb_intern("data"));
93
+ sym_rssi = ID2SYM(rb_intern("rssi"));
94
+ sym_service_uuid = ID2SYM(rb_intern("service_uuid"));
95
+ }
96
+
97
+ // create a ruby hash to yield back to ruby,
98
+ // of the form {device: "xxxx", data: "yyyy", rssi: -99}
99
+ VALUE new_scan_hash(NSString* device, NSData *data, NSNumber *rssi, NSData *service_uuid)
100
+ {
101
+ VALUE hash = rb_hash_new();
102
+ rb_hash_aset(hash, sym_device, rb_str_new_cstr(device.UTF8String));
103
+ rb_hash_aset(hash, sym_data, rb_str_new(data.bytes, data.length));
104
+ rb_hash_aset(hash, sym_rssi, INT2FIX( rssi.integerValue ));
105
+ if (service_uuid) {
106
+ uint16_t uuid = *((uint16_t *)service_uuid.bytes);
107
+ uuid = NSSwapShort(uuid);
108
+ rb_hash_aset(hash, sym_service_uuid, rb_str_new( (void*)&uuid, 2));
109
+ }
110
+ return hash;
111
+ }
112
+
113
+ VALUE method_new_adverts()
114
+ {
115
+ @autoreleasepool {
116
+ VALUE ary = rb_ary_new();
117
+ NSArray *scans = bleDelegate.scans;
118
+ for (NSDictionary *scan in scans) {
119
+ VALUE hash = new_scan_hash( scan[@"device"], scan[@"data"], scan[@"rssi"], scan[@"service_uuid"] );
120
+ rb_ary_push(ary, hash);
121
+ }
122
+ [scans release];
123
+ return ary;
124
+ }
125
+ }
126
+
127
+ VALUE method_scan()
128
+ {
129
+ VALUE scans = rb_ary_new();
130
+
131
+ @autoreleasepool {
132
+ @try {
133
+ bleDelegate = [[BLEDelegate alloc] init];
134
+ dispatch_queue_t scanQueue;
135
+ scanQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
136
+ centralManager = [[CBCentralManager alloc] initWithDelegate:bleDelegate queue:scanQueue];
137
+ BOOL exit = NO;
138
+ while (!exit) {
139
+ VALUE ret = rb_yield( Qnil );
140
+ exit = (ret == Qfalse);
141
+ }
142
+ }
143
+ @finally {
144
+ [centralManager stopScan];
145
+ }
146
+ }
147
+ return Qnil;
148
+ }
149
+
150
+ #endif // TARGET_OS_MAC
@@ -0,0 +1,23 @@
1
+ # Loads mkmf which is used to make makefiles for Ruby extensions
2
+ require 'mkmf'
3
+
4
+ # Give it a name
5
+ extension_name = 'scan_beacon/core_bluetooth'
6
+
7
+ # The destination
8
+ dir_config(extension_name)
9
+
10
+ $DLDFLAGS << " -framework Foundation"
11
+ $DLDFLAGS << " -framework CoreBluetooth"
12
+
13
+ unless RUBY_PLATFORM =~ /darwin/
14
+ # don't compile the code on non-mac platforms because
15
+ # CoreBluetooth wont be there, and we may not even have
16
+ # the ability to compile ObjC code.
17
+ COMPILE_C = "echo"
18
+ # create a dummy .so file so RubyGems thinks everything
19
+ # was successful. We wont try to load it anyway.
20
+ LINK_SO = "touch $@"
21
+ end
22
+
23
+ create_makefile(extension_name)
@@ -3,11 +3,14 @@ require 'set'
3
3
  module ScanBeacon
4
4
  class Beacon
5
5
 
6
- attr_accessor :mac, :ids, :power, :beacon_types
6
+ attr_accessor :mac, :ids, :power, :beacon_types, :data, :mfg_id, :service_uuid
7
7
 
8
8
  def initialize(opts={})
9
- @ids = opts[:ids]
9
+ @ids = opts[:ids] || []
10
+ @data = opts[:data] || []
10
11
  @power = opts[:power]
12
+ @mfg_id = opts[:mfg_id]
13
+ @service_uuid = opts[:service_uuid]
11
14
  @beacon_types = Set.new [opts[:beacon_type]]
12
15
  @rssis = []
13
16
  end
@@ -1,12 +1,27 @@
1
1
  module ScanBeacon
2
2
  class BeaconParser
3
-
3
+ DEFAULT_LAYOUTS = {
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"
6
+ }
7
+ AD_TYPE_MFG = 0xff
8
+ AD_TYPE_SERVICE = 0x03
9
+ BT_EIR_SERVICE_DATA = "\x16"
4
10
  attr_accessor :beacon_type
5
11
 
12
+ def self.default_parsers
13
+ DEFAULT_LAYOUTS.map {|name, layout| BeaconParser.new name, layout }
14
+ end
15
+
6
16
  def initialize(beacon_type, layout)
7
17
  @beacon_type = beacon_type
8
18
  @layout = layout.split(",")
9
- @matchers = @layout.find_all {|item| item[0] == "m"}.map {|matcher|
19
+ if layout.include?("s")
20
+ @ad_type = AD_TYPE_SERVICE
21
+ else
22
+ @ad_type = AD_TYPE_MFG
23
+ end
24
+ @matchers = @layout.find_all {|item| ["m", "s"].include? item[0]}.map {|matcher|
10
25
  _, range_start, range_end, expected = matcher.split(/:|=|-/)
11
26
  {start: range_start.to_i, end: range_end.to_i, expected: expected}
12
27
  }
@@ -14,6 +29,10 @@ module ScanBeacon
14
29
  _, range_start, range_end = id.split(/:|-/)
15
30
  {start: range_start.to_i, end: range_end.to_i}
16
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
+ }
17
36
  _, power_start, power_end = @layout.find {|item| item[0] == "p"}.split(/:|-/)
18
37
  @power = {start: power_start.to_i, end: power_end.to_i}
19
38
  end
@@ -25,27 +44,88 @@ module ScanBeacon
25
44
  return true
26
45
  end
27
46
 
28
- def parse(data)
29
- return nil if !matches?(data)
30
- Beacon.new(ids: parse_ids(data), power: parse_power(data), beacon_type: @beacon_type)
47
+ def parse(data, ad_type = AD_TYPE_MFG)
48
+ return nil if ad_type != @ad_type || !matches?(data)
49
+ if @ad_type == AD_TYPE_MFG
50
+ Beacon.new(ids: parse_ids(data), power: parse_power(data), beacon_type: @beacon_type,
51
+ data: parse_data_fields(data), mfg_id: parse_mfg_or_service_id(data))
52
+ else
53
+ Beacon.new(ids: parse_ids(data), power: parse_power(data), beacon_type: @beacon_type,
54
+ data: parse_data_fields(data), service_uuid: parse_mfg_or_service_id(data))
55
+ end
31
56
  end
32
57
 
33
58
  def parse_ids(data)
34
- @ids.map {|id|
35
- if id[:end] - id[:start] == 1
59
+ parse_elems(@ids, data)
60
+ end
61
+
62
+ def parse_data_fields(data)
63
+ parse_elems(@data_fields, data)
64
+ end
65
+
66
+ def parse_elems(elems, data)
67
+ elems.map {|elem|
68
+ 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
36
74
  # two bytes, so treat it as a short (big endian)
37
- data[id[:start]..id[:end]].unpack('S>')[0]
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]
38
79
  else
39
80
  # not two bytes, so treat it as a hex string
40
- data[id[:start]..id[:end]].unpack('H*').join
81
+ elem_str.unpack('H*').join
41
82
  end
42
83
  }
43
84
  end
44
85
 
86
+ def parse_mfg_or_service_id(data)
87
+ data[0..1].unpack('S>')[0]
88
+ end
89
+
45
90
  def parse_power(data)
46
91
  data[@power[:start]..@power[:end]].unpack('c')[0]
47
92
  end
48
93
 
94
+ def generate_ad(beacon)
95
+ length = [@matchers, @ids, @power, @data_fields].flatten.map {|elem| elem[:end] }.max + 1
96
+ ad = "\x00" * length
97
+ @matchers.each do |matcher|
98
+ ad[matcher[:start]..matcher[:end]] = [matcher[:expected]].pack("H*")
99
+ end
100
+ @ids.each_with_index do |id, index|
101
+ ad[id[:start]..id[:end]] = generate_field(id, beacon.ids[index])
102
+ end
103
+ @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?
105
+ end
106
+ ad[@power[:start]..@power[:end]] = [beacon.power].pack('c')
107
+ if @ad_type == AD_TYPE_SERVICE
108
+ "\x03\x03" + [beacon.service_uuid].pack("S<") + [length+1].pack('C') + BT_EIR_SERVICE_DATA + ad
109
+ elsif @ad_type == AD_TYPE_MFG
110
+ ad[0..1] = [beacon.mfg_id].pack("S<")
111
+ [length+1].pack('C') + [AD_TYPE_MFG].pack('C') + ad
112
+ end
113
+ end
114
+
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
+
49
129
  def inspect
50
130
  "<BeaconParser type=\"#{@beacon_type}\", layout=\"#{@layout.join(",")}\">"
51
131
  end
@@ -1,62 +1,26 @@
1
- require 'timeout'
2
-
3
1
  module ScanBeacon
4
- class BLE112Scanner
5
-
6
- DEFAULT_LAYOUTS = {altbeacon: "m:2-3=beac,i:4-19,i:20-21,i:22-23,p:24-24,d:25-25"}
7
-
8
- attr_reader :beacons
2
+ class BLE112Scanner < GenericScanner
9
3
 
10
4
  def initialize(opts = {})
5
+ super
11
6
  @device = BLE112Device.new opts[:port]
12
- @cycle_seconds = opts[:cycle_seconds] || 1
13
- @parsers = DEFAULT_LAYOUTS.map {|name, layout| BeaconParser.new name, layout }
14
- @beacons = []
15
7
  end
16
8
 
17
- def add_parser(parser)
18
- @parsers << parser
19
- end
20
-
21
- def scan
9
+ def each_advertisement
22
10
  @device.open do |device|
23
11
  device.start_scan
24
- cycle_end = Time.now + @cycle_seconds
25
-
26
12
  begin
27
- while true do
28
- check_for_beacon( device.read )
29
- if Time.now > cycle_end
30
- yield @beacons
31
- @beacons = []
32
- cycle_end = Time.now + @cycle_seconds
13
+ keep_scanning = true
14
+ while keep_scanning do
15
+ response = device.read
16
+ if response.advertisement?
17
+ keep_scanning = false if yield(response.advertisement_data, response.mac, response.rssi) == false
33
18
  end
34
19
  end
35
20
  ensure
36
21
  device.stop_scan
37
22
  end
38
-
39
- end
40
- end
41
-
42
- def check_for_beacon(response)
43
- if response.advertisement?
44
- beacon = nil
45
- if @parsers.detect {|parser| beacon = parser.parse(response.advertisement_data) }
46
- beacon.mac = response.mac
47
- add_beacon(beacon, response.rssi)
48
- end
49
- end
50
- end
51
-
52
- def add_beacon(beacon, rssi)
53
- if idx = @beacons.find_index(beacon)
54
- @beacons[idx].add_type beacon.beacon_types.first
55
- beacon = @beacons[idx]
56
- else
57
- @beacons << beacon
58
23
  end
59
- beacon.add_rssi(rssi)
60
24
  end
61
25
 
62
26
  end
@@ -0,0 +1,45 @@
1
+ module ScanBeacon
2
+ class BlueZAdvertiser
3
+
4
+ attr_accessor :beacon, :parser, :ad
5
+
6
+ def initialize(opts = {})
7
+ @device_id = opts[:device_id]
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
+ end
12
+
13
+ def beacon=(value)
14
+ @beacon = value
15
+ update_ad
16
+ end
17
+
18
+ def parser=(value)
19
+ @parser = value
20
+ update_ad
21
+ end
22
+
23
+ def ad=(value)
24
+ @ad = value
25
+ BlueZ.advertisement_bytes = @ad
26
+ end
27
+
28
+ def start
29
+ BlueZ.start_advertising
30
+ end
31
+
32
+ def stop
33
+ BlueZ.stop_advertising
34
+ end
35
+
36
+ def inspect
37
+ "<BlueZAdvertiser ad=#{@ad.inspect}>"
38
+ end
39
+
40
+ def update_ad
41
+ self.ad = @parser.generate_ad(@beacon) if @parser && @beacon
42
+ end
43
+
44
+ end
45
+ end
@@ -0,0 +1,26 @@
1
+ module ScanBeacon
2
+ class BlueZScanner < GenericScanner
3
+
4
+ def initialize(opts = {})
5
+ super
6
+ @device_id = opts[:device_id] || BlueZ.devices[0][:device_id]
7
+ BlueZ.device_up @device_id
8
+ end
9
+
10
+ def each_advertisement
11
+ ScanBeacon::BlueZ.scan(@device_id) do |mac, ad_data, rssi|
12
+ if ad_data.nil?
13
+ yield(nil)
14
+ elsif ad_data.size > 4
15
+ ad_type = ad_data[4].unpack("C")[0]
16
+ if ad_type == 0xff
17
+ yield(ad_data[5..-1], mac, rssi, ad_type)
18
+ else
19
+ yield(ad_data[9..-1], mac, rssi, ad_type)
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,25 @@
1
+ module ScanBeacon
2
+ class CoreBluetoothScanner < GenericScanner
3
+
4
+ def each_advertisement
5
+ keep_scanning = true
6
+ CoreBluetooth::scan do
7
+ sleep 0.2
8
+ advertisements = CoreBluetooth::new_adverts
9
+ advertisements.each do |scan|
10
+ if scan[:service_uuid]
11
+ advert = scan[:service_uuid] + scan[:data]
12
+ keep_scanning = false if yield(advert, scan[:device], scan[:rssi], 0x03) == false
13
+ else
14
+ keep_scanning = false if yield(scan[:data], scan[:device], scan[:rssi], 0xff) == false
15
+ end
16
+ end
17
+ if advertisements.empty?
18
+ keep_scanning = false if yield(nil, nil, nil) == false
19
+ end
20
+ keep_scanning
21
+ end
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,59 @@
1
+ module ScanBeacon
2
+ class GenericScanner
3
+
4
+ attr_reader :beacons
5
+
6
+ def initialize(opts = {})
7
+ @cycle_seconds = opts[:cycle_seconds] || 1
8
+ @parsers = BeaconParser.default_parsers
9
+ @beacons = []
10
+ end
11
+
12
+ def add_parser(parser)
13
+ @parsers << parser
14
+ end
15
+
16
+ def scan
17
+ @beacons = []
18
+ next_cycle = Time.now + @cycle_seconds
19
+ keep_scanning = true
20
+ each_advertisement do |data, device, rssi, ad_type = BeaconParser::AD_TYPE_MFG|
21
+ detect_beacon(data, device, rssi, ad_type) unless data.nil?
22
+
23
+ if Time.now > next_cycle
24
+ if block_given?
25
+ next_cycle = Time.now + @cycle_seconds
26
+ yield @beacons
27
+ @beacons = []
28
+ else
29
+ keep_scanning = false
30
+ end
31
+ end
32
+ end
33
+ return @beacons unless block_given?
34
+ end
35
+
36
+ def each_advertisement
37
+ raise NotImplementedError
38
+ end
39
+
40
+ def detect_beacon(data, device, rssi, ad_type)
41
+ beacon = nil
42
+ if @parsers.detect {|parser| beacon = parser.parse(data, ad_type) }
43
+ beacon.mac = device
44
+ add_beacon(beacon, rssi)
45
+ end
46
+ end
47
+
48
+ def add_beacon(beacon, rssi)
49
+ if idx = @beacons.find_index(beacon)
50
+ @beacons[idx].add_type beacon.beacon_types.first
51
+ beacon = @beacons[idx]
52
+ else
53
+ @beacons << beacon
54
+ end
55
+ beacon.add_rssi(rssi)
56
+ end
57
+
58
+ end
59
+ end
@@ -1,3 +1,3 @@
1
1
  module ScanBeacon
2
- VERSION = "0.3.6"
2
+ VERSION = "0.5.0"
3
3
  end
data/lib/scan_beacon.rb CHANGED
@@ -1,8 +1,18 @@
1
1
  require "scan_beacon/version"
2
2
  require "scan_beacon/beacon"
3
3
  require "scan_beacon/beacon_parser"
4
+ require "scan_beacon/generic_scanner"
4
5
  require "scan_beacon/ble112_device"
5
6
  require "scan_beacon/ble112_scanner"
7
+ case RUBY_PLATFORM
8
+ when /darwin/
9
+ require "scan_beacon/core_bluetooth"
10
+ require "scan_beacon/core_bluetooth_scanner"
11
+ when /linux/
12
+ require "scan_beacon/bluez"
13
+ require "scan_beacon/bluez_scanner"
14
+ require "scan_beacon/bluez_advertiser"
15
+ end
6
16
 
7
17
  module ScanBeacon
8
18
  end
data/scan_beacon.gemspec CHANGED
@@ -16,9 +16,11 @@ Gem::Specification.new do |spec|
16
16
  spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
17
  spec.bindir = "exe"
18
18
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.extensions = %w[ext/core_bluetooth/extconf.rb ext/bluez/extconf.rb]
19
20
  spec.require_paths = ["lib"]
20
21
 
21
22
  spec.add_development_dependency "bundler", "~> 1.9"
22
23
  spec.add_development_dependency "rake", "~> 10.0"
23
24
  spec.add_development_dependency "rspec", "~> 3.2"
25
+ spec.add_development_dependency "rake-compile"
24
26
  end
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.3.6
4
+ version: 0.5.0
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-07-02 00:00:00.000000000 Z
11
+ date: 2015-08-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -52,11 +52,27 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '3.2'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake-compile
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
55
69
  description:
56
70
  email:
57
71
  - support@radiusnetworks.com
58
72
  executables: []
59
- extensions: []
73
+ extensions:
74
+ - ext/core_bluetooth/extconf.rb
75
+ - ext/bluez/extconf.rb
60
76
  extra_rdoc_files: []
61
77
  files:
62
78
  - ".gitignore"
@@ -64,11 +80,24 @@ files:
64
80
  - LICENSE.txt
65
81
  - README.md
66
82
  - Rakefile
83
+ - ext/bluez/advertising.c
84
+ - ext/bluez/bluez.c
85
+ - ext/bluez/devices.c
86
+ - ext/bluez/extconf.rb
87
+ - ext/bluez/scanning.c
88
+ - ext/bluez/utils.c
89
+ - ext/bluez/utils.h
90
+ - ext/core_bluetooth/core_bluetooth.m
91
+ - ext/core_bluetooth/extconf.rb
67
92
  - lib/scan_beacon.rb
68
93
  - lib/scan_beacon/beacon.rb
69
94
  - lib/scan_beacon/beacon_parser.rb
70
95
  - lib/scan_beacon/ble112_device.rb
71
96
  - lib/scan_beacon/ble112_scanner.rb
97
+ - lib/scan_beacon/bluez_advertiser.rb
98
+ - lib/scan_beacon/bluez_scanner.rb
99
+ - lib/scan_beacon/core_bluetooth_scanner.rb
100
+ - lib/scan_beacon/generic_scanner.rb
72
101
  - lib/scan_beacon/version.rb
73
102
  - scan_beacon.gemspec
74
103
  homepage: https://github.com/RadiusNetworks/scanbeacon-gem