rubyeddystoneurl 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 286da3a462d1cd40888d6ddff5d93b264223be60
4
+ data.tar.gz: 50afcba5208938ec2ff1849eeffcadc14250ce6a
5
+ SHA512:
6
+ metadata.gz: 19c2f242e3171fe80ede5e68b974594d716a25560d09b455c174af7a3e7f48cdcdbbc7c8efa1087998e707d907794a4381a510c514d837e49b965d3bc9ff18da
7
+ data.tar.gz: 1db4459ee61eb9510c1ae127386b41b88c7ca028e8596ff6b95b7a7d0abb5d650c41de8b03c3b2dd49d4fef6598e29e37b1e083085701dce167ecd0ababe75a8
@@ -0,0 +1,10 @@
1
+ require 'mkmf'
2
+
3
+ #have_library 'stdc++'
4
+ extension_name = 'rubyeddystoneurl/rubyeddystoneurl'
5
+ dir_config extension_name
6
+
7
+ $CPPFLAGS << " -I/usr/include/bluetooth"
8
+ $LOCAL_LIBS << " -lbluetooth"
9
+
10
+ create_makefile extension_name
@@ -0,0 +1,171 @@
1
+ #include "ruby.h"
2
+ #include <errno.h>
3
+ #include <signal.h>
4
+ #include <stdio.h>
5
+ #include <stdlib.h>
6
+ #include <sys/ioctl.h>
7
+ #include <sys/prctl.h>
8
+ #include <unistd.h>
9
+
10
+ #include <bluetooth/bluetooth.h>
11
+ #include <bluetooth/hci.h>
12
+ #include <bluetooth/hci_lib.h>
13
+
14
+ VALUE RubyEddystoneURL = Qnil;
15
+ void Init_rubyeddystoneurl();
16
+ VALUE method_scan(VALUE, VALUE);
17
+
18
+ void Init_rubyeddystoneurl(){
19
+ VALUE RubyEddystoneURL = rb_define_module("RubyEddystoneURL");
20
+ rb_define_method(RubyEddystoneURL, "scan", method_scan,1);
21
+ }
22
+
23
+ VALUE method_scan(VALUE self, VALUE timeout) {
24
+ VALUE retArray = rb_ary_new();
25
+
26
+ int hciDeviceId = 0;
27
+ int hciSocket;
28
+ struct hci_dev_info hciDevInfo;
29
+
30
+ struct hci_filter oldHciFilter;
31
+ struct hci_filter newHciFilter;
32
+ socklen_t oldHciFilterLen;
33
+
34
+ int currentAdapterState;
35
+ int timeoutCounter = 0;
36
+
37
+ unsigned char hciEventBuf[HCI_MAX_EVENT_SIZE];
38
+ int hciEventLen;
39
+ evt_le_meta_event *leMetaEvent;
40
+ le_advertising_info *leAdvertisingInfo;
41
+ char btAddress[18];
42
+ int i;
43
+ int8_t rssi;
44
+ fd_set rfds;
45
+ struct timeval tv;
46
+ int selectRetval;
47
+ char btInfo[500];
48
+
49
+ if(TYPE(timeout) != T_FIXNUM) {
50
+ rb_raise(rb_eTypeError, "Invalid parameter");
51
+ return Qnil;
52
+ }
53
+
54
+ memset(&hciDevInfo, 0x00, sizeof(hciDevInfo));
55
+
56
+ //use first available device
57
+ hciDeviceId = hci_get_route(NULL);
58
+
59
+ if (hciDeviceId < 0) {
60
+ hciDeviceId = 0; // use device 0, if device id is invalid
61
+ }
62
+
63
+ // setup HCI socket
64
+ hciSocket = hci_open_dev(hciDeviceId);
65
+
66
+ if (hciSocket == -1) {
67
+ rb_raise(rb_eStandardError, "Unsupported");
68
+ return Qnil;
69
+ }
70
+ hciDevInfo.dev_id = hciDeviceId;
71
+
72
+ // get old HCI filter
73
+ oldHciFilterLen = sizeof(oldHciFilter);
74
+ getsockopt(hciSocket, SOL_HCI, HCI_FILTER, &oldHciFilter, &oldHciFilterLen);
75
+
76
+ // setup new HCI filter
77
+ hci_filter_clear(&newHciFilter);
78
+ hci_filter_set_ptype(HCI_EVENT_PKT, &newHciFilter);
79
+ hci_filter_set_event(EVT_LE_META_EVENT, &newHciFilter);
80
+ setsockopt(hciSocket, SOL_HCI, HCI_FILTER, &newHciFilter, sizeof(newHciFilter));
81
+
82
+ // disable scanning, it may have been left on, if so hci_le_set_scan_parameters will fail without this
83
+ hci_le_set_scan_enable(hciSocket, 0x00, 0, 1000);
84
+
85
+ // get HCI dev info for adapter state
86
+ ioctl(hciSocket, HCIGETDEVINFO, (void *)&hciDevInfo);
87
+ currentAdapterState = hci_test_bit(HCI_UP, &hciDevInfo.flags);
88
+
89
+ if (!currentAdapterState) {
90
+ //powered off
91
+ currentAdapterState = 0;
92
+ } else if (hci_le_set_scan_parameters(hciSocket, 0x01, htobs(0x0010), htobs(0x0010), 0x00, 0, 1000) < 0) {
93
+ if (EPERM == errno) {
94
+ //unauthorized
95
+ currentAdapterState = 1;
96
+ } else if (EIO == errno) {
97
+ //unsupported
98
+ currentAdapterState = 2;
99
+ } else {
100
+ //unknown
101
+ currentAdapterState = 3;
102
+ }
103
+ } else {
104
+ //powered on
105
+ currentAdapterState = 4;
106
+ }
107
+
108
+ if(currentAdapterState == 4) {
109
+ //scan devices
110
+ hci_le_set_scan_enable(hciSocket, 0x00, 1, 1000);
111
+ hci_le_set_scan_enable(hciSocket, 0x01, 1, 1000);
112
+
113
+ while(1) {
114
+ FD_ZERO(&rfds);
115
+ FD_SET(hciSocket, &rfds);
116
+
117
+ tv.tv_sec = 1;
118
+ tv.tv_usec = 0;
119
+
120
+ selectRetval = select(hciSocket + 1, &rfds, NULL, NULL, &tv);
121
+
122
+ if(selectRetval) {
123
+ //get scan devices
124
+ hciEventLen = read(hciSocket, hciEventBuf, sizeof(hciEventBuf));
125
+ leMetaEvent = (evt_le_meta_event *)(hciEventBuf + (1 + HCI_EVENT_HDR_SIZE));
126
+ hciEventLen -= (1 + HCI_EVENT_HDR_SIZE);
127
+
128
+ if (leMetaEvent->subevent == 0x02) {
129
+ leAdvertisingInfo = (le_advertising_info *)(leMetaEvent->data + 1);
130
+ ba2str(&leAdvertisingInfo->bdaddr, btAddress);
131
+
132
+ VALUE hash = rb_hash_new();
133
+ rb_hash_aset(hash, rb_str_new2("address"), rb_str_new2(btAddress));
134
+
135
+ rssi = *(leAdvertisingInfo->data + leAdvertisingInfo->length);
136
+
137
+ rb_hash_aset(hash, rb_str_new2("rssi"), rb_int_new((int)rssi));
138
+
139
+ btInfo[0] = '\0';
140
+
141
+ for (i = 0; i < leAdvertisingInfo->length; i++) {
142
+ sprintf(btInfo + strlen(btInfo), "%02x", leAdvertisingInfo->data[i]);
143
+ }
144
+
145
+ //printf("%s\n", btInfo);
146
+
147
+ rb_hash_aset(hash, rb_str_new2("info"), rb_str_new2(btInfo));
148
+ rb_ary_push(retArray, hash);
149
+ }
150
+ }
151
+
152
+ timeoutCounter += 1;
153
+ sleep(1);
154
+
155
+ if(timeoutCounter == FIX2INT(timeout)) {
156
+ //stop scan
157
+ hci_le_set_scan_enable(hciSocket, 0x00, 0, 1000);
158
+ break;
159
+ }
160
+
161
+ }
162
+ }
163
+
164
+ // restore original filter
165
+ setsockopt(hciSocket, SOL_HCI, HCI_FILTER, &oldHciFilter, sizeof(oldHciFilter));
166
+ // disable LE scan
167
+ hci_le_set_scan_enable(hciSocket, 0x00, 0, 1000);
168
+ close(hciSocket);
169
+
170
+ return retArray;
171
+ }
@@ -0,0 +1,113 @@
1
+ =begin
2
+ * 2018 http://ferdinandsilva.com
3
+ *
4
+ * Ruby C Extension To Scan Eddystone-URL Beacons
5
+ *
6
+ * Author:
7
+ * Ferdinand Silva <ferdinandsilva@ferdinandsilva.com>
8
+ *
9
+ =end
10
+ require 'rubyeddystoneurl/rubyeddystoneurl'
11
+ include RubyEddystoneURL
12
+
13
+ def discover(timeout)
14
+ ret = Hash.new
15
+
16
+ devices = scan(timeout)
17
+
18
+ devices.each do |dev|
19
+ if !ret.key?(dev['address'])
20
+ # add to return
21
+ ret[dev['address']] = {
22
+ "name" => "",
23
+ "url" => "",
24
+ "rssi" => dev['rssi']
25
+ }
26
+ end
27
+
28
+ if dev['info'].length > 28
29
+ # valid info
30
+ lineToRemove = dev['info'][0..25]
31
+ cleanLine = dev['info'].sub lineToRemove, ""
32
+
33
+ if cleanLine[/^(00|01|02|03)/]
34
+ #URL
35
+ justStarted = true
36
+ urlStr = ""
37
+
38
+ while cleanLine.length > 0
39
+ lineToRemove = cleanLine[0..1]
40
+
41
+ #decode URL
42
+ if justStarted
43
+ justStarted = false
44
+
45
+ if lineToRemove == "00"
46
+ urlStr = "http://www."
47
+ elsif lineToRemove == "01"
48
+ urlStr = "https://www."
49
+ elsif lineToRemove == "02"
50
+ urlStr = "http://"
51
+ else
52
+ # 03
53
+ urlStr = "https://"
54
+ end
55
+ cleanLine = cleanLine.sub lineToRemove, ""
56
+ next
57
+ end
58
+
59
+ if lineToRemove == "00"
60
+ urlStr += ".com/"
61
+ elsif lineToRemove == "01"
62
+ urlStr += ".org/"
63
+ elsif lineToRemove == "02"
64
+ urlStr += ".edu/"
65
+ elsif lineToRemove == "03"
66
+ urlStr += ".net/"
67
+ elsif lineToRemove == "04"
68
+ urlStr += ".info/"
69
+ elsif lineToRemove == "05"
70
+ urlStr += ".biz/"
71
+ elsif lineToRemove == "06"
72
+ urlStr += ".gov/"
73
+ elsif lineToRemove == "07"
74
+ urlStr += ".com"
75
+ elsif lineToRemove == "08"
76
+ urlStr += ".org"
77
+ elsif lineToRemove == "09"
78
+ urlStr += ".edu"
79
+ elsif lineToRemove == "0a"
80
+ urlStr += ".net"
81
+ elsif lineToRemove == "0b"
82
+ urlStr += ".info"
83
+ elsif lineToRemove == "0c"
84
+ urlStr += ".biz"
85
+ elsif lineToRemove == "0d"
86
+ urlStr += ".gov"
87
+ else
88
+ urlStr += [lineToRemove].pack 'H*'
89
+ end
90
+
91
+ cleanLine = cleanLine.sub lineToRemove, ""
92
+ end
93
+
94
+ ret[dev['address']]['url'] = urlStr
95
+ else
96
+ #name
97
+ lineToRemove = cleanLine[0..3]
98
+ cleanLine = cleanLine.sub lineToRemove, ""
99
+ ret[dev['address']]['name'] = [cleanLine].pack 'H*'
100
+ end
101
+ end
102
+ end
103
+
104
+ # clean up devices
105
+ # remove devices that doesn't have a url
106
+ ret.each do |k,v|
107
+ if ret[k]["url"] == ""
108
+ ret.delete k
109
+ end
110
+ end
111
+
112
+ return ret
113
+ end
metadata ADDED
@@ -0,0 +1,46 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rubyeddystoneurl
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Ferdinand E. Silva
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-12-11 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Ruby C Extension To Scan Eddystone-URL Bluetooth Low Energy Beacons
14
+ email: ferdinandsilva@ferdinandsilva.com
15
+ executables: []
16
+ extensions:
17
+ - ext/rubyeddystoneurl/extconf.rb
18
+ extra_rdoc_files: []
19
+ files:
20
+ - ext/rubyeddystoneurl/extconf.rb
21
+ - ext/rubyeddystoneurl/rubyeddystoneurl.c
22
+ - lib/rubyeddystoneurl.rb
23
+ homepage: http://ferdinandsilva.com
24
+ licenses: []
25
+ metadata: {}
26
+ post_install_message:
27
+ rdoc_options: []
28
+ require_paths:
29
+ - lib
30
+ required_ruby_version: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ required_rubygems_version: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ requirements: []
41
+ rubyforge_project:
42
+ rubygems_version: 2.5.2.1
43
+ signing_key:
44
+ specification_version: 4
45
+ summary: Ruby C Extension To Scan Eddystone-URL Bluetooth Low Energy Beacons
46
+ test_files: []