scan_beacon 0.1.0 → 0.2.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 +4 -4
- data/README.md +10 -0
- data/lib/scan_beacon/ble112_device.rb +117 -0
- data/lib/scan_beacon/ble112_scanner.rb +19 -78
- data/lib/scan_beacon/version.rb +1 -1
- data/lib/scan_beacon.rb +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5ae0be9271595f436430f8494d6f0cb9710e8083
|
4
|
+
data.tar.gz: 7afac292c5b16a158134c0461ca483a07e506b3a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bf44f28c567d9c4c221802aa4fa98b3fbcd00f0459faf60b4a8cb662107de7a84f1e2f9ef59ade343efed26edc1328976e046bf11ad0ba00b3a602c9b8562199
|
7
|
+
data.tar.gz: 791bd526ce8efaddf2b5085317e5d88024cadf2d676f4bfd61c30965bc01caa2d31d1417cb851200ddfea31cb460844bf3dcef7f74f966d37e55f50d3505eb84
|
data/README.md
CHANGED
@@ -31,5 +31,15 @@ scanner.scan do |beacons|
|
|
31
31
|
end
|
32
32
|
```
|
33
33
|
|
34
|
+
## Add a custom beacon layout
|
35
|
+
By default, this gem supports AltBeacon advertisements. But you can add a beacon parser to support other major beacon formats as well.
|
36
|
+
|
37
|
+
Example:
|
38
|
+
``` ruby
|
39
|
+
scanner = ScanBeacon::BLE112Scanner.new
|
40
|
+
scanner.add_parser( ScanBeacon::BeaconParser.new(:mybeacon, "m:2-3=0000,i:4-19,i:20-21,i:22-23,p:24-24") )
|
41
|
+
...
|
42
|
+
```
|
43
|
+
|
34
44
|
# Dependencies
|
35
45
|
You must have a BLE112 device plugged in to a USB port.
|
@@ -0,0 +1,117 @@
|
|
1
|
+
module ScanBeacon
|
2
|
+
class BLE112Device
|
3
|
+
|
4
|
+
# define a bunch of constants
|
5
|
+
BG_COMMAND = 0
|
6
|
+
BG_EVENT = 0x80
|
7
|
+
# msg classes
|
8
|
+
BG_MSG_CLASS_CONNECTION = 3
|
9
|
+
BG_MSG_CLASS_GAP = 6
|
10
|
+
# messages
|
11
|
+
BG_DISCONNECT = 0
|
12
|
+
BG_SET_MODE = 1
|
13
|
+
BG_DISCOVER = 2
|
14
|
+
BG_DISCOVER_STOP = 4
|
15
|
+
BG_SCAN_PARAMS = 7
|
16
|
+
# constants/enums
|
17
|
+
BG_GAP_DISCOVER_ALL = 2
|
18
|
+
BG_GAP_NON_DISCOVERABLE = 0
|
19
|
+
BG_GAP_NON_CONNECTABLE = 0
|
20
|
+
|
21
|
+
def initialize(port=nil)
|
22
|
+
@port = port || Dir.glob("/dev/{cu.usbmodem,ttyACM}*")[0]
|
23
|
+
end
|
24
|
+
|
25
|
+
def open
|
26
|
+
File.open(@port, 'r+b') do |file|
|
27
|
+
@file = file
|
28
|
+
yield self
|
29
|
+
end
|
30
|
+
@file = nil
|
31
|
+
end
|
32
|
+
|
33
|
+
def start_scan
|
34
|
+
# disconnect any connections
|
35
|
+
bg_command(@file, BG_MSG_CLASS_CONNECTION, BG_DISCONNECT,0)
|
36
|
+
# turn off adverts
|
37
|
+
bg_command(@file, BG_MSG_CLASS_GAP, BG_SET_MODE, [BG_GAP_NON_DISCOVERABLE, BG_GAP_NON_CONNECTABLE])
|
38
|
+
# stop previous scan
|
39
|
+
bg_command(@file, BG_MSG_CLASS_GAP, BG_DISCOVER_STOP)
|
40
|
+
# write new scan params
|
41
|
+
bg_command(@file, BG_MSG_CLASS_GAP, BG_SCAN_PARAMS, [200,200, 0], "S<S<C")
|
42
|
+
# start new scan
|
43
|
+
bg_command(@file, BG_MSG_CLASS_GAP, BG_DISCOVER, BG_GAP_DISCOVER_ALL)
|
44
|
+
end
|
45
|
+
|
46
|
+
def stop_scan
|
47
|
+
bg_command(@file, BG_MSG_CLASS_GAP, BG_DISCOVER_STOP)
|
48
|
+
end
|
49
|
+
|
50
|
+
def read
|
51
|
+
BLE112Response.new( bg_read(@file) )
|
52
|
+
end
|
53
|
+
|
54
|
+
class BLE112Response
|
55
|
+
def initialize(data)
|
56
|
+
@data = data
|
57
|
+
end
|
58
|
+
|
59
|
+
def size
|
60
|
+
@data.size
|
61
|
+
end
|
62
|
+
|
63
|
+
def event?
|
64
|
+
@data[0].unpack('C')[0] == BG_EVENT
|
65
|
+
end
|
66
|
+
|
67
|
+
def gap_scan?
|
68
|
+
@data[2..3].unpack('CC') == [BG_MSG_CLASS_GAP, 0]
|
69
|
+
end
|
70
|
+
|
71
|
+
def manufacturer_ad?
|
72
|
+
size > 20 && advertisement_type == 0xFF
|
73
|
+
end
|
74
|
+
|
75
|
+
def advertisement?
|
76
|
+
event? && gap_scan? && manufacturer_ad?
|
77
|
+
end
|
78
|
+
|
79
|
+
def advertisement_type
|
80
|
+
@data[19].unpack('C')[0]
|
81
|
+
end
|
82
|
+
|
83
|
+
def advertisement_data
|
84
|
+
@advertisement_data ||= @data[20..-1]
|
85
|
+
end
|
86
|
+
|
87
|
+
def mac
|
88
|
+
@data[6..11].unpack('H2 H2 H2 H2 H2 H2').join(":")
|
89
|
+
end
|
90
|
+
|
91
|
+
def rssi
|
92
|
+
@data[4].unpack('c')[0]
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
def bg_command(port, msg_class, msg, data=nil, data_format=nil)
|
99
|
+
data = [data].compact unless data.is_a? Array
|
100
|
+
if data_format.nil?
|
101
|
+
data = data.pack('C*')
|
102
|
+
else
|
103
|
+
data = data.pack(data_format)
|
104
|
+
end
|
105
|
+
cmd = [0, data.size, msg_class, msg].flatten.pack('C*') + data
|
106
|
+
port.write(cmd)
|
107
|
+
bg_read(port)
|
108
|
+
end
|
109
|
+
|
110
|
+
def bg_read(port)
|
111
|
+
response = port.read(1) while response.nil? || ![0x00, 0x80].include?(response.unpack('C')[0])
|
112
|
+
response << port.read(3)
|
113
|
+
payload_length = response[1].unpack('C')[0]
|
114
|
+
response << port.read(payload_length)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -2,17 +2,13 @@ require 'timeout'
|
|
2
2
|
|
3
3
|
module ScanBeacon
|
4
4
|
class BLE112Scanner
|
5
|
-
SCAN_CMD = [0,1,6,2,2].pack('CCCCC')
|
6
|
-
SCAN_PARAMS = [0, 5, 6, 7, 200,200, 0].pack('CCCCS<S<C')
|
7
|
-
RESET_CMD = [0,1,9,0,0].pack('ccccc')
|
8
|
-
MANUFACTURER_AD = 0xFF
|
9
5
|
|
10
6
|
DEFAULT_LAYOUTS = {altbeacon: "m:2-3=beac,i:4-19,i:20-21,i:22-23,p:24-24,d:25-25"}
|
11
7
|
|
12
8
|
attr_reader :beacons
|
13
9
|
|
14
10
|
def initialize(opts = {})
|
15
|
-
@
|
11
|
+
@device = BLE112Device.new opts[:port]
|
16
12
|
@cycle_seconds = opts[:cycle_seconds] || 1
|
17
13
|
@parsers = DEFAULT_LAYOUTS.map {|name, layout| BeaconParser.new name, layout }
|
18
14
|
@beacons = []
|
@@ -23,58 +19,33 @@ module ScanBeacon
|
|
23
19
|
end
|
24
20
|
|
25
21
|
def scan
|
26
|
-
|
27
|
-
|
28
|
-
send_scan_command(port)
|
22
|
+
@device.open do |device|
|
23
|
+
device.start_scan
|
29
24
|
cycle_end = Time.now + @cycle_seconds
|
30
25
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
26
|
+
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
|
33
|
+
end
|
38
34
|
end
|
35
|
+
ensure
|
36
|
+
device.stop_scan
|
39
37
|
end
|
40
|
-
end
|
41
|
-
end
|
42
38
|
|
43
|
-
def open_port
|
44
|
-
File.open(@port, 'r+b') do |port|
|
45
|
-
yield port
|
46
39
|
end
|
47
40
|
end
|
48
41
|
|
49
|
-
def
|
50
|
-
if
|
51
|
-
|
52
|
-
@
|
53
|
-
|
54
|
-
|
55
|
-
@payload_length = byte
|
56
|
-
@expected_size = 4 + (@packet_type & 0x07) + @payload_length
|
57
|
-
elsif @buffer.size > 1
|
58
|
-
@buffer << byte
|
59
|
-
check_for_beacon
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
def check_for_beacon
|
64
|
-
if @expected_size && @buffer.size >= @expected_size
|
65
|
-
packet_class = @buffer[2]
|
66
|
-
packet_command = @buffer[3]
|
67
|
-
payload = @buffer[4..-1].pack('C*')
|
68
|
-
if (@packet_type & 0x80 != 0x00) && (packet_class == 0x06) &&
|
69
|
-
(packet_command == 0x00) && @buffer[19] == MANUFACTURER_AD
|
70
|
-
data = payload[16..-1]
|
71
|
-
beacon = nil
|
72
|
-
if @parsers.detect {|parser| beacon = parser.parse(data) }
|
73
|
-
beacon.mac = parse_mac(payload)
|
74
|
-
add_beacon(beacon, parse_rssi(payload))
|
75
|
-
end
|
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)
|
76
48
|
end
|
77
|
-
clear_the_buffer
|
78
49
|
end
|
79
50
|
end
|
80
51
|
|
@@ -88,35 +59,5 @@ module ScanBeacon
|
|
88
59
|
beacon.add_rssi(rssi)
|
89
60
|
end
|
90
61
|
|
91
|
-
def parse_mac(payload)
|
92
|
-
payload[2..7].unpack('H2 H2 H2 H2 H2 H2').join(":")
|
93
|
-
end
|
94
|
-
|
95
|
-
def parse_rssi(payload)
|
96
|
-
payload[0].unpack('c')[0]
|
97
|
-
end
|
98
|
-
|
99
|
-
def clear_the_buffer
|
100
|
-
@buffer = []
|
101
|
-
@expected_size = nil
|
102
|
-
end
|
103
|
-
|
104
|
-
def send_scan_command(port)
|
105
|
-
# disconnect any connections
|
106
|
-
port.write([0,1,3,0,0].pack('CCCCC'))
|
107
|
-
port.read(7)
|
108
|
-
# turn off adverts
|
109
|
-
port.write([0,2,6,1,0,0].pack('CCCCCC'))
|
110
|
-
port.read(6)
|
111
|
-
# stop previous scan
|
112
|
-
port.write([0,0,6,4].pack('CCCC'))
|
113
|
-
port.read(6)
|
114
|
-
# write new scan params
|
115
|
-
port.write(SCAN_PARAMS)
|
116
|
-
port.read(6)
|
117
|
-
# start new scan
|
118
|
-
port.write(SCAN_CMD)
|
119
|
-
port.read(6)
|
120
|
-
end
|
121
62
|
end
|
122
63
|
end
|
data/lib/scan_beacon/version.rb
CHANGED
data/lib/scan_beacon.rb
CHANGED
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.
|
4
|
+
version: 0.2.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-06-
|
11
|
+
date: 2015-06-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -66,6 +66,7 @@ files:
|
|
66
66
|
- lib/scan_beacon.rb
|
67
67
|
- lib/scan_beacon/beacon.rb
|
68
68
|
- lib/scan_beacon/beacon_parser.rb
|
69
|
+
- lib/scan_beacon/ble112_device.rb
|
69
70
|
- lib/scan_beacon/ble112_scanner.rb
|
70
71
|
- lib/scan_beacon/version.rb
|
71
72
|
- scan_beacon.gemspec
|