scan_beacon 0.1.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: d78009f7808d20ac2554c8c370a00ecd36b75e2e
4
+ data.tar.gz: 1f1f238adbe3cfcc939f349e9e2c57636505b292
5
+ SHA512:
6
+ metadata.gz: 146ee276d74af008154a018ce72b0f410df1a294a1cf1f6383466cc06bbc2a37fa90e84272ceb9db70d9085f0491af86493091c7fb80d857dc44c31ca25e3912
7
+ data.tar.gz: 9947d6aced47920d648f5f5390a1a693997bc24d2a3a35d04b9c1eb9d409b0b6b0797a2c1d948b21c2cac44e5f0b6975ca405e6e977e9ef6d80518c99a5f88ba
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ cprun.sh
2
+ yell.rb
3
+ yeller.rb
4
+ .DS_Store
5
+ pkg/
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Radius Networks
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,35 @@
1
+ # ScanBeacon gem
2
+
3
+ A ruby gem that allows you to scan for beacon advertisements using a BlueGiga BLE112 device.
4
+
5
+ # Example Usage
6
+
7
+ ## Install the gem
8
+ ```
9
+ gem install scan_beacon
10
+ ```
11
+
12
+ ## Start a scan
13
+ ``` ruby
14
+ require 'scan_beacon'
15
+ scanner = ScanBeacon::BLE112Scanner.new
16
+ scanner.scan do |beacons|
17
+ beacons.each do |beacon|
18
+ puts beacon.inspect
19
+ end
20
+ end
21
+ ```
22
+
23
+ ## Set a specific scan cycle period
24
+ ``` ruby
25
+ require 'scan_beacon'
26
+ scanner = ScanBeacon::BLE112Scanner.new cycle_seconds: 2
27
+ scanner.scan do |beacons|
28
+ beacons.each do |beacon|
29
+ puts beacon.inspect
30
+ end
31
+ end
32
+ ```
33
+
34
+ # Dependencies
35
+ You must have a BLE112 device plugged in to a USB port.
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,7 @@
1
+ require "scan_beacon/version"
2
+ require "scan_beacon/beacon"
3
+ require "scan_beacon/beacon_parser"
4
+ require "scan_beacon/ble112_scanner"
5
+
6
+ module ScanBeacon
7
+ end
@@ -0,0 +1,47 @@
1
+ require 'set'
2
+
3
+ module ScanBeacon
4
+ class Beacon
5
+
6
+ attr_accessor :mac, :ids, :power, :beacon_types
7
+
8
+ def initialize(opts={})
9
+ @ids = opts[:ids]
10
+ @power = opts[:power]
11
+ @beacon_types = Set.new [opts[:beacon_type]]
12
+ @rssis = []
13
+ end
14
+
15
+ def ==(obj)
16
+ obj.is_a?(Beacon) && obj.mac == @mac && obj.ids == @ids
17
+ end
18
+
19
+ def add_rssi(val)
20
+ @rssis << val
21
+ end
22
+
23
+ def add_type(val)
24
+ @beacon_types << val
25
+ end
26
+
27
+ def rssi
28
+ @rssis.inject(0) {|sum, el| sum += el} / @rssis.size.to_f
29
+ end
30
+
31
+ def uuid
32
+ "#{ids[0][0..7]}-#{ids[0][8..11]}-#{ids[0][12..15]}-#{ids[0][16..19]}-#{ids[0][20..-1]}".upcase
33
+ end
34
+
35
+ def major
36
+ ids[1]
37
+ end
38
+
39
+ def minor
40
+ ids[2]
41
+ end
42
+
43
+ def inspect
44
+ "<Beacon ids=#{@ids.join(",")} rssi=#{rssi}, scans=#{@rssis.size}, power=#{@power}, type=\"#{@beacon_types.to_a.join(",")}\">"
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,53 @@
1
+ module ScanBeacon
2
+ class BeaconParser
3
+
4
+ attr_accessor :beacon_type
5
+
6
+ def initialize(beacon_type, layout)
7
+ @beacon_type = beacon_type
8
+ @layout = layout.split(",")
9
+ @matchers = @layout.find_all {|item| item[0] == "m"}.map {|matcher|
10
+ _, range_start, range_end, expected = matcher.split(/:|=|-/)
11
+ {start: range_start.to_i, end: range_end.to_i, expected: expected}
12
+ }
13
+ @ids = @layout.find_all {|item| item[0] == "i"}.map {|id|
14
+ _, range_start, range_end = id.split(/:|-/)
15
+ {start: range_start.to_i, end: range_end.to_i}
16
+ }
17
+ _, power_start, power_end = @layout.find {|item| item[0] == "p"}.split(/:|-/)
18
+ @power = {start: power_start.to_i, end: power_end.to_i}
19
+ end
20
+
21
+ def matches?(data)
22
+ @matchers.each do |matcher|
23
+ return false unless data[matcher[:start]..matcher[:end]].unpack("H*").join == matcher[:expected]
24
+ end
25
+ return true
26
+ end
27
+
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)
31
+ end
32
+
33
+ def parse_ids(data)
34
+ @ids.map {|id|
35
+ if id[:end] - id[:start] == 1
36
+ # two bytes, so treat it as a short (big endian)
37
+ data[id[:start]..id[:end]].unpack('S>')[0]
38
+ else
39
+ # not two bytes, so treat it as a hex string
40
+ data[id[:start]..id[:end]].unpack('H*').join
41
+ end
42
+ }
43
+ end
44
+
45
+ def parse_power(data)
46
+ data[@power[:start]..@power[:end]].unpack('c')[0]
47
+ end
48
+
49
+ def inspect
50
+ "<BeaconParser type=\"#{@beacon_type}\", layout=\"#{@layout.join(",")}\">"
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,122 @@
1
+ require 'timeout'
2
+
3
+ module ScanBeacon
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
+
10
+ DEFAULT_LAYOUTS = {altbeacon: "m:2-3=beac,i:4-19,i:20-21,i:22-23,p:24-24,d:25-25"}
11
+
12
+ attr_reader :beacons
13
+
14
+ def initialize(opts = {})
15
+ @port = opts[:port] || Dir.glob("/dev/{cu.usbmodem,ttyACM}*")[0]
16
+ @cycle_seconds = opts[:cycle_seconds] || 1
17
+ @parsers = DEFAULT_LAYOUTS.map {|name, layout| BeaconParser.new name, layout }
18
+ @beacons = []
19
+ end
20
+
21
+ def add_parser(parser)
22
+ @parsers << parser
23
+ end
24
+
25
+ def scan
26
+ clear_the_buffer
27
+ open_port do |port|
28
+ send_scan_command(port)
29
+ cycle_end = Time.now + @cycle_seconds
30
+
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
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ def open_port
44
+ File.open(@port, 'r+b') do |port|
45
+ yield port
46
+ end
47
+ end
48
+
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
76
+ end
77
+ clear_the_buffer
78
+ end
79
+ end
80
+
81
+ def add_beacon(beacon, rssi)
82
+ if idx = @beacons.find_index(beacon)
83
+ @beacons[idx].add_type beacon.beacon_types.first
84
+ beacon = @beacons[idx]
85
+ else
86
+ @beacons << beacon
87
+ end
88
+ beacon.add_rssi(rssi)
89
+ end
90
+
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
+ end
122
+ end
@@ -0,0 +1,3 @@
1
+ module ScanBeacon
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'scan_beacon/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "scan_beacon"
8
+ spec.version = ScanBeacon::VERSION
9
+ spec.authors = ["Radius Networks"]
10
+ spec.email = ["support@radiusnetworks.com"]
11
+
12
+ spec.summary = %q{Provides Beacon scanning functionality}
13
+ spec.homepage = "https://github.com/RadiusNetworks/scanbeacon-gem"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = "exe"
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.9"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+ spec.add_development_dependency "rspec", "~> 3.2"
24
+ end
metadata ADDED
@@ -0,0 +1,96 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: scan_beacon
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Radius Networks
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2015-06-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.9'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.9'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.2'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.2'
55
+ description:
56
+ email:
57
+ - support@radiusnetworks.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".gitignore"
63
+ - LICENSE.txt
64
+ - README.md
65
+ - Rakefile
66
+ - lib/scan_beacon.rb
67
+ - lib/scan_beacon/beacon.rb
68
+ - lib/scan_beacon/beacon_parser.rb
69
+ - lib/scan_beacon/ble112_scanner.rb
70
+ - lib/scan_beacon/version.rb
71
+ - scan_beacon.gemspec
72
+ homepage: https://github.com/RadiusNetworks/scanbeacon-gem
73
+ licenses:
74
+ - MIT
75
+ metadata: {}
76
+ post_install_message:
77
+ rdoc_options: []
78
+ require_paths:
79
+ - lib
80
+ required_ruby_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ requirements: []
91
+ rubyforge_project:
92
+ rubygems_version: 2.2.2
93
+ signing_key:
94
+ specification_version: 4
95
+ summary: Provides Beacon scanning functionality
96
+ test_files: []