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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d78009f7808d20ac2554c8c370a00ecd36b75e2e
4
- data.tar.gz: 1f1f238adbe3cfcc939f349e9e2c57636505b292
3
+ metadata.gz: 5ae0be9271595f436430f8494d6f0cb9710e8083
4
+ data.tar.gz: 7afac292c5b16a158134c0461ca483a07e506b3a
5
5
  SHA512:
6
- metadata.gz: 146ee276d74af008154a018ce72b0f410df1a294a1cf1f6383466cc06bbc2a37fa90e84272ceb9db70d9085f0491af86493091c7fb80d857dc44c31ca25e3912
7
- data.tar.gz: 9947d6aced47920d648f5f5390a1a693997bc24d2a3a35d04b9c1eb9d409b0b6b0797a2c1d948b21c2cac44e5f0b6975ca405e6e977e9ef6d80518c99a5f88ba
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
- @port = opts[:port] || Dir.glob("/dev/{cu.usbmodem,ttyACM}*")[0]
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
- clear_the_buffer
27
- open_port do |port|
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
- while true do
32
- byte = port.each_byte.next
33
- append_to_buffer(byte)
34
- if Time.now > cycle_end
35
- yield @beacons
36
- @beacons = []
37
- cycle_end = Time.now + @cycle_seconds
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 append_to_buffer(byte)
50
- if @buffer.size == 0 && (byte == 0x00 || byte == 0x80)
51
- @buffer << byte
52
- @packet_type = byte
53
- elsif @buffer.size == 1
54
- @buffer << byte
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
@@ -1,3 +1,3 @@
1
1
  module ScanBeacon
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
data/lib/scan_beacon.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require "scan_beacon/version"
2
2
  require "scan_beacon/beacon"
3
3
  require "scan_beacon/beacon_parser"
4
+ require "scan_beacon/ble112_device"
4
5
  require "scan_beacon/ble112_scanner"
5
6
 
6
7
  module ScanBeacon
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.1.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-22 00:00:00.000000000 Z
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