scan_beacon 0.3.6 → 0.5.0

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: 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