radbeacon 0.1.1

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 25442163473a2a7ce504430cce4a4ecb803abbd2
4
+ data.tar.gz: 423b8b43f661d3927f48cab858bea9c6cf86272c
5
+ SHA512:
6
+ metadata.gz: 9b4504f24ec9ae91fff3e6de568d9603ce377e3b25c26e5bd92f4aa45789c182d6149be3a69b84688e2515db76285b91183b27e15612a77d970ed66c66a5fbfb
7
+ data.tar.gz: e3be66fd90ef611358c15e6fd879d4b8caaa43e50887173e7bd18b5afb4585166ad161b2575b2123136ff5b9b72c900720b5998ce682b558146059d56f4e7282
@@ -0,0 +1,5 @@
1
+ cprun.sh
2
+ yell.rb
3
+ yeller.rb
4
+ .DS_Store
5
+ pkg/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in radbeacon.gemspec
4
+ gemspec
@@ -0,0 +1,32 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ radbeacon (0.1.1)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ diff-lcs (1.2.5)
10
+ rake (10.1.0)
11
+ rspec (3.2.0)
12
+ rspec-core (~> 3.2.0)
13
+ rspec-expectations (~> 3.2.0)
14
+ rspec-mocks (~> 3.2.0)
15
+ rspec-core (3.2.2)
16
+ rspec-support (~> 3.2.0)
17
+ rspec-expectations (3.2.0)
18
+ diff-lcs (>= 1.2.0, < 2.0)
19
+ rspec-support (~> 3.2.0)
20
+ rspec-mocks (3.2.1)
21
+ diff-lcs (>= 1.2.0, < 2.0)
22
+ rspec-support (~> 3.2.0)
23
+ rspec-support (3.2.2)
24
+
25
+ PLATFORMS
26
+ ruby
27
+
28
+ DEPENDENCIES
29
+ bundler (~> 1.9)
30
+ radbeacon!
31
+ rake (~> 10.0)
32
+ rspec (~> 3.2)
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 TODO: Write your name
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.
@@ -0,0 +1,97 @@
1
+ <img src="http://i.imgur.com/gI6paLj.jpg" style="float:left" height="200">
2
+
3
+ # What's all this then?
4
+
5
+ A ruby gem that provides RadBeacon scanning and configuring capabilities on a linux machine. Currently the only supported beacon type is RadBeacon USB.
6
+
7
+ # Scanning
8
+
9
+ The `Radbeacon::Scanner` class has a `scan` method that returns an array of configurable RadBeacons (in the form of `Radbeacon::Usb` objects). The duration of a scan (default = 5 seconds) is an attribute that can be set during initialization.
10
+
11
+ ```
12
+ scanner = Radbeacon::Scanner.new(10)
13
+ radbeacons = scanner.scan
14
+ ```
15
+
16
+ There is also a `fetch` method that returns a `Radbeacon::Usb` object for a given MAC address.
17
+
18
+ ```
19
+ radbeacon = scanner.fetch("11:22:33:44:55:66")
20
+ ```
21
+
22
+ #### Scan Options
23
+
24
+ An (optional) `options` hash is available as an attribute on the `Scanner` class. Below are the following options that can be used:
25
+
26
+ - `:filter_mac` - Specify an array of MAC addresses that the scanner will filter for when doing a scan
27
+
28
+ ```
29
+ options[:filter_mac] = ["11:22:33:44:55:66", "55:66:77:88:99:00"]
30
+ ```
31
+
32
+ - `:enable_hcitool_duration` - Use the custom hcitool-duration binary that adds a duration option to the `hcitool lescan` command. To use this you must copy the new hcitool binary over the standard one (located at `/usr/bin/hcitool` on the ZBOX for example).
33
+
34
+ ```
35
+ options[:enable_hcitool_duration] = true
36
+ ```
37
+
38
+ #Configuring
39
+
40
+ All identifiers and other parameters are attributes on the `Radbeacon::Usb` class:
41
+
42
+ - `dev_name`
43
+ - `uuid`
44
+ - `major`
45
+ - `minor`
46
+ - `power`
47
+ - `tx_power`
48
+ - `adv_rate`
49
+ - `beacon_type`
50
+
51
+ To make a config change, simply assign one of these attributes to the desired value and call the `save()` method with the beacon's PIN (as a string).
52
+
53
+ ```
54
+ radbeacon.save(pin)
55
+ ```
56
+
57
+ For example:
58
+
59
+ ```
60
+ radbeacon.dev_name = "Test Beacon"
61
+ radbeacon.uuid = "2F234454-CF6D-4A0F-ADF2-F4911BA9ABCD"
62
+ radbeacon.major = 1
63
+ radbeacon.minor = 1
64
+ radbeacon.power = -66
65
+ radbeacon.tx_power = 3
66
+ radbeacon.adv_rate = 10
67
+ radbeacon.beacon_type = "dual"
68
+ radbeacon.save('0000')
69
+ ```
70
+
71
+ # Other Actions
72
+
73
+ All other RadBeacon actions are available as well
74
+
75
+ ##### Change PIN
76
+ ```
77
+ radbeacon.change_pin(new_pin, old_pin)
78
+ ```
79
+
80
+ ##### Factory Reset
81
+ ```
82
+ radbeacon.factory_reset(pin)
83
+ ```
84
+
85
+ ##### Boot to DFU
86
+ ```
87
+ radbeacon.boot_to_dfu(pin)
88
+ ```
89
+
90
+ ##### Lock
91
+ ```
92
+ radbeacon.lock(pin)
93
+ ```
94
+
95
+ # Dependencies
96
+
97
+ BlueZ (Linux Bluetooth stack) is required to scan for and communicate with RadBeacons via Bluetooth. Specifically, the `hcitool` and `gatttool` commands.
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,9 @@
1
+ require "radbeacon/version"
2
+ require "radbeacon/le_scanner"
3
+ require "radbeacon/bluetooth_le_device"
4
+ require "radbeacon/utils"
5
+ require "radbeacon/scanner"
6
+ require "radbeacon/usb"
7
+ module Radbeacon
8
+
9
+ end
@@ -0,0 +1,135 @@
1
+ require 'pty'
2
+ require 'expect'
3
+
4
+ module Radbeacon
5
+ class BluetoothLeDevice
6
+ attr_reader :mac_address, :name, :is_connectable, :characteristics, :values, :errors
7
+
8
+ TIMEOUT = 0.5
9
+
10
+ def initialize(mac_address, name)
11
+ @errors = []
12
+ @mac_address = mac_address
13
+ @name = name
14
+ @is_connectable = false
15
+ @characteristics = Array.new
16
+ @values = Hash.new
17
+ end
18
+
19
+ def display
20
+ puts "MAC Address: " + @mac_address + " Name: " + @name + " Can connect: " + @is_connectable.to_s
21
+ end
22
+
23
+ def fetch_characteristics
24
+ result = false
25
+ if self.can_connect?
26
+ @is_connectable = true
27
+ if self.discover_characteristics
28
+ if self.char_values
29
+ result = true
30
+ end
31
+ end
32
+ end
33
+ result
34
+ end
35
+
36
+ def can_connect?
37
+ @errors = []
38
+ result = false
39
+ cmd = "gatttool -b #{@mac_address} --interactive"
40
+ PTY.spawn(cmd) do |output, input, pid|
41
+ output.expect(/\[LE\]>/)
42
+ input.puts "connect"
43
+ if output.expect(/Connection successful/, TIMEOUT)
44
+ @is_connectable = true
45
+ result = true
46
+ else
47
+ @errors << "Connection failed"
48
+ end
49
+ input.puts "quit"
50
+ end
51
+ result
52
+ end
53
+
54
+ def characteristics_command
55
+ success = true
56
+ output = nil
57
+ rout, wout = IO.pipe
58
+ rerr, werr = IO.pipe
59
+ characteristics_command_str = "gatttool -b #{@mac_address} --characteristics"
60
+ pid = Process.spawn(characteristics_command_str, :out => wout, :err => werr)
61
+ begin
62
+ Timeout.timeout(5) do
63
+ Process.wait(pid)
64
+ end
65
+ rescue Timeout::Error
66
+ Process.kill('TERM', pid)
67
+ success = false
68
+ end
69
+ wout.close
70
+ werr.close
71
+ stdout = rout.readlines.join("")
72
+ stderr = rerr.readlines.join("")
73
+ rout.close
74
+ rerr.close
75
+ if success
76
+ output = [stdout, stderr].join("")
77
+ end
78
+ output
79
+ end
80
+
81
+ def discover_characteristics
82
+ @errors = []
83
+ result = false
84
+ output = characteristics_command
85
+ if output && output.strip != "Discover all characteristics failed: Internal application error: I/O"
86
+ @characteristics = []
87
+ output.each_line do |line|
88
+ result = line.scan(/^handle = (0x[a-f0-9]{4}), char properties = (0x[a-f0-9]{2}), char value handle = (0x[a-f0-9]{4}), uuid = ([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})$/)
89
+ if !result.empty?
90
+ characteristic = {"handle" => result[0][0], "properties" => result[0][1], "value_handle" => result[0][2], "uuid" => result[0][3]}
91
+ @characteristics << characteristic
92
+ end
93
+ end
94
+ result = true
95
+ else
96
+ @errors << "Discover characteristics failed"
97
+ end
98
+ result
99
+ end
100
+
101
+ def char_values
102
+ @errors = []
103
+ result = false
104
+ cmd = "gatttool -b #{@mac_address} --interactive"
105
+ if @characteristics != []
106
+ PTY.spawn(cmd) do |output, input, pid|
107
+ output.expect(/\[LE\]>/)
108
+ input.puts "connect"
109
+ if output.expect(/Connection successful/, TIMEOUT)
110
+ @characteristics.each do |char|
111
+ input.puts "char-read-hnd #{char['value_handle']}"
112
+ if output.expect(/Characteristic value\/descriptor: /, TIMEOUT)
113
+ if value = output.expect(/^[0-9a-f\s]+\n/, TIMEOUT)
114
+ @values[char['value_handle']] = value.first.strip
115
+ end
116
+ end
117
+ end
118
+ result = true
119
+ else
120
+ @errors << "Fetch characteristic values failed"
121
+ end
122
+ input.puts "quit"
123
+ end
124
+ else
125
+ @errors << "No characteristics present"
126
+ end
127
+ result
128
+ end
129
+
130
+ private
131
+
132
+ attr_writer :mac_address, :name, :is_connectable, :characteristics, :values, :errors
133
+
134
+ end
135
+ end
@@ -0,0 +1,65 @@
1
+ module Radbeacon
2
+
3
+ class LeScanner
4
+ attr_accessor :duration, :options
5
+
6
+ def initialize(duration = 5)
7
+ @duration = duration
8
+ @options = {}
9
+ end
10
+
11
+ def scan_command
12
+ rout, wout = IO.pipe
13
+ scan_command_str = "sudo hcitool lescan"
14
+ pid = Process.spawn(scan_command_str, :out => wout)
15
+ begin
16
+ Timeout.timeout(@duration) do
17
+ Process.wait(pid)
18
+ end
19
+ rescue Timeout::Error
20
+ Process.kill('TERM', pid)
21
+ end
22
+ wout.close
23
+ scan_output = rout.readlines.join("")
24
+ rout.close
25
+ scan_output
26
+ end
27
+
28
+ def scan_command_duration
29
+ `sudo hcitool lescan --duration #{@duration}`
30
+ end
31
+
32
+ def passive_scan
33
+ devices = Array.new
34
+ if @options[:enable_hcitool_duration] == true
35
+ scan_output = self.scan_command_duration
36
+ else
37
+ scan_output = self.scan_command
38
+ end
39
+ scan_output.each_line do |line|
40
+ result = line.scan(/^([A-F0-9:]{15}[A-F0-9]{2}) (.*)$/)
41
+ if !result.empty?
42
+ mac_address = result[0][0]
43
+ name = result[0][1]
44
+ if !devices.find {|s| s.mac_address == mac_address}
45
+ filter_mac = @options[:filter_mac]
46
+ if !filter_mac or (filter_mac.include?(mac_address) if filter_mac.is_a?(Array))
47
+ device = BluetoothLeDevice.new(mac_address, name)
48
+ devices << device
49
+ end
50
+ end
51
+ end
52
+ end
53
+ devices
54
+ end
55
+
56
+ def scan
57
+ devices = self.passive_scan
58
+ devices.each do |dev|
59
+ dev.fetch_characteristics
60
+ end
61
+ devices
62
+ end
63
+
64
+ end
65
+ end
@@ -0,0 +1,28 @@
1
+ module Radbeacon
2
+ class Scanner < LeScanner
3
+
4
+ C_DEVICE_NAME = "0x0003"
5
+ RADBEACON_USB = "52 61 64 42 65 61 63 6f 6e 20 55 53 42"
6
+
7
+ def scan
8
+ devices = super
9
+ radbeacons = devices.map { |dev| radbeacon_check(dev) }
10
+ radbeacons.compact
11
+ end
12
+
13
+ def fetch(mac_address)
14
+ dev = BluetoothLeDevice.new(mac_address, nil)
15
+ radbeacon_check(dev) if dev.fetch_characteristics
16
+ end
17
+
18
+ def radbeacon_check(device)
19
+ radbeacon = nil
20
+ case device.values[C_DEVICE_NAME]
21
+ when RADBEACON_USB
22
+ radbeacon = Usb.create_if_valid(device)
23
+ end
24
+ radbeacon
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,196 @@
1
+ module Radbeacon
2
+ class Usb < BluetoothLeDevice
3
+ include Utils
4
+
5
+ ## Define GATT characteristic constants
6
+ # Generic Access Profile
7
+ C_DEVICE_NAME = "0x0003"
8
+ C_APPEARANCE = "0x0006"
9
+ # Device Information
10
+ C_MANUFACTURER_NAME = "0x000a"
11
+ C_MODEL_NUMBER = "0x000d"
12
+ C_SERIAL_STRING = "0x0010"
13
+ C_FIRMWARE_STRING = "0x0013"
14
+ # Configuration
15
+ GATT_DEV_MODEL = "0x0017"
16
+ GATT_DEV_ID = "0x001a"
17
+ GATT_DEV_NAME = "0x001d"
18
+ GATT_UUID = "0x0020"
19
+ GATT_MAJOR = "0x0023"
20
+ GATT_MINOR = "0x0026"
21
+ GATT_POWER = "0x0029"
22
+ GATT_TXPOWER = "0x002c"
23
+ GATT_INTERVAL = "0x002f"
24
+ GATT_RESULT = "0x0032"
25
+ GATT_NEW_PIN = "0x0035"
26
+ GATT_ACTION = "0x0038"
27
+ GATT_PIN = "0x003b"
28
+ GATT_BCTYPE = "0x003e"
29
+ GATT_FWVERSION = "0x0041"
30
+ GATT_CONN_TIMEOUT = "0x0044"
31
+ GATT_BEACON_SWITCH = "0x0047"
32
+
33
+ ## Define GATT action/result constants
34
+ # Actions
35
+ GATT_ACTION_DONOTHING = "00000000"
36
+ GATT_ACTION_UPDATE_ADV = "00000001"
37
+ GATT_ACTION_UPDATE_PIN = "00000002"
38
+ GATT_ACTION_FACTORY_RESET = "00000003"
39
+ GATT_ACTION_DFU = "00000004"
40
+ GATT_ACTION_LOCK = "00000005"
41
+ GATT_ACTION_CONNECTABLE_TIME = "00000006"
42
+ # Results
43
+ GATT_SUCCES = "00000000"
44
+ GATT_INVALID_PIN = "00000001"
45
+ GATT_ERROR = "00000002" #not used
46
+
47
+ ## Transmit power and advertisement frequency values
48
+ TRANSMIT_POWER_VALUES = {-23 => "00", -21 => "01", -18 => "03", -14 => "05",
49
+ -11 => "07", -7 => "09", -4 => "0b", 0 => "0d", 3 => "0f"}
50
+ DEFAULT_MEASURED_POWER_VALUES = [-94, -92, -90, -86, -84, -79, -74, -72, -66]
51
+ ADVERTISING_RATE_VALUES = {0 => "0000", 1 => "2006", 2 => "0003", 3 => "f501", 4 => "7001",
52
+ 5 => "2001", 6 => "ea00", 7 => "c400", 8 => "a800", 9 => "9100", 10 => "8000"}
53
+ BEACON_TYPES = {"ibeacon" => "01", "altbeacon" => "02", "dual" => "03"}
54
+
55
+ ## Timeout length for GATT commands
56
+ TIMEOUT = 0.5
57
+
58
+ ## Valid UUID pattern
59
+ VALID_UUID = /^[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}$/
60
+
61
+ attr_reader :mac_address, :errors, :dev_model, :dev_id, :dev_version
62
+ attr_accessor :dev_name, :uuid, :major, :minor, :power, :tx_power, :adv_rate, :beacon_type
63
+
64
+ def self.create_if_valid(device)
65
+ # Check everything
66
+ required_attrs = [GATT_DEV_MODEL, GATT_DEV_ID, GATT_FWVERSION, GATT_DEV_NAME,
67
+ GATT_UUID, GATT_MAJOR, GATT_MINOR, GATT_POWER, GATT_TXPOWER, GATT_INTERVAL, GATT_BCTYPE]
68
+ if required_attrs.all? { |key| device.values[key] }
69
+ self.new(device)
70
+ end
71
+ end
72
+
73
+ def initialize(device)
74
+ @errors = []
75
+ @mac_address = device.mac_address
76
+ @dev_model = bytes_to_text(device.values[GATT_DEV_MODEL])
77
+ @dev_id = bytes_to_text(device.values[GATT_DEV_ID])
78
+ @dev_version = bytes_to_text(device.values[GATT_FWVERSION])
79
+ @dev_name = bytes_to_text(device.values[GATT_DEV_NAME])
80
+ @uuid = bytes_to_uuid(device.values[GATT_UUID])
81
+ @major = bytes_to_major_minor(device.values[GATT_MAJOR])
82
+ @minor = bytes_to_major_minor(device.values[GATT_MINOR])
83
+ @power = bytes_to_power(device.values[GATT_POWER])
84
+ @tx_power = TRANSMIT_POWER_VALUES.key(device.values[GATT_TXPOWER])
85
+ @adv_rate = ADVERTISING_RATE_VALUES.key(device.values[GATT_INTERVAL].delete(' '))
86
+ @beacon_type = BEACON_TYPES.key(device.values[GATT_BCTYPE])
87
+ end
88
+
89
+ def valid?
90
+ @errors = []
91
+ @errors << "Invalid device name" unless @dev_name.length <= 20
92
+ @errors << "Invalid UUID" unless @uuid.match(VALID_UUID)
93
+ @errors << "Invalid major value" unless @major.to_i.between?(0, 65535)
94
+ @errors << "Invalid minor value" unless @minor.to_i.between?(0, 65535)
95
+ @errors << "Invalid measured power value" unless @power.to_i.between?(-127, -1)
96
+ @errors << "Invalid transmit power" unless TRANSMIT_POWER_VALUES.has_key?(@tx_power)
97
+ @errors << "Invalid advertising rate" unless ADVERTISING_RATE_VALUES.has_key?(@adv_rate)
98
+ @errors << "Invalid beacon type" unless BEACON_TYPES.has_key?(@beacon_type)
99
+ if @errors.empty?
100
+ true
101
+ else
102
+ false
103
+ end
104
+ end
105
+
106
+ def save(pin)
107
+ if self.valid?
108
+ update_params_commands = ["#{GATT_DEV_NAME} #{text_to_bytes(@dev_name)}",
109
+ "#{GATT_UUID} #{uuid_to_bytes(@uuid)}", "#{GATT_MAJOR} #{major_minor_to_bytes(@major)}",
110
+ "#{GATT_MINOR} #{major_minor_to_bytes(@minor)}", "#{GATT_POWER} #{power_to_bytes(@power)}",
111
+ "#{GATT_TXPOWER} #{TRANSMIT_POWER_VALUES[@tx_power]}", "#{GATT_INTERVAL} #{ADVERTISING_RATE_VALUES[@adv_rate]}",
112
+ "#{GATT_BCTYPE} #{BEACON_TYPES[@beacon_type]}", "#{GATT_ACTION} #{GATT_ACTION_UPDATE_ADV}", "#{GATT_PIN} #{pin_to_bytes(pin)}"]
113
+ con(update_params_commands)
114
+ else
115
+ false
116
+ end
117
+ end
118
+
119
+ def change_pin(new_pin, old_pin)
120
+ update_pin_commands = ["#{GATT_NEW_PIN} #{pin_to_bytes(new_pin)}", "#{GATT_ACTION} #{GATT_ACTION_UPDATE_PIN}",
121
+ "#{GATT_PIN} #{pin_to_bytes(old_pin)}"]
122
+ con(update_pin_commands)
123
+ end
124
+
125
+ def factory_reset(pin)
126
+ reset_commands = ["#{GATT_ACTION} #{GATT_ACTION_FACTORY_RESET}", "#{GATT_PIN} #{pin_to_bytes(pin)}"]
127
+ con(reset_commands) && defaults
128
+ end
129
+
130
+ def boot_to_dfu(pin)
131
+ dfu_commands = ["#{GATT_ACTION} #{GATT_ACTION_DFU}", "#{GATT_PIN} #{pin_to_bytes(pin)}"]
132
+ con(dfu_commands)
133
+ end
134
+
135
+ def lock(pin)
136
+ lock_commands = ["#{GATT_ACTION} #{GATT_ACTION_LOCK}", "#{GATT_PIN} #{pin_to_bytes(pin)}"]
137
+ con(lock_commands)
138
+ end
139
+
140
+ private
141
+ attr_writer :mac_address, :errors, :dev_model, :dev_id, :dev_version
142
+
143
+ def defaults
144
+ @dev_name = "RadBeacon USB"
145
+ @uuid = "2F234454-CF6D-4A0F-ADF2-F4911BA9FFA6"
146
+ @major = 1
147
+ @minor = 1
148
+ @power = -66
149
+ @tx_power = 3
150
+ @adv_rate = 10
151
+ @beacon_type = "dual"
152
+ end
153
+
154
+ def con(commands)
155
+ @errors = []
156
+ result = false
157
+ cmd = "gatttool -b #{@mac_address} --interactive"
158
+ PTY.spawn(cmd) do |output, input, pid|
159
+ output.expect(/\[LE\]>/)
160
+ input.puts "connect"
161
+ if output.expect(/Connection successful/, TIMEOUT)
162
+ commands.each do |cmd|
163
+ cmd = "char-write-req #{cmd}"
164
+ input.puts cmd
165
+ if output.expect(/Characteristic value was written successfully/, TIMEOUT)
166
+ result = true
167
+ else
168
+ @errors << "Action failed: #{cmd}"
169
+ result = false
170
+ break
171
+ end
172
+ end
173
+ if result
174
+ input.puts "char-read-hnd #{GATT_RESULT}"
175
+ if output.expect(/Characteristic value\/descriptor: 00 00 00 00/, TIMEOUT)
176
+ result = true
177
+ else
178
+ @errors << "Invalid PIN"
179
+ result = false
180
+ end
181
+ end
182
+ else
183
+ @errors << "Connection failed"
184
+ end
185
+ input.puts "quit"
186
+ _, status = Process.waitpid2(pid)
187
+ if !status.success?
188
+ result = false
189
+ @errors << "Process failed to exit properly"
190
+ end
191
+ end
192
+ result
193
+ end
194
+
195
+ end
196
+ end
@@ -0,0 +1,41 @@
1
+ module Radbeacon
2
+ module Utils
3
+
4
+ def text_to_bytes(text)
5
+ text.unpack('H*')[0]
6
+ end
7
+
8
+ def bytes_to_text(bytes)
9
+ [bytes.delete(' ')].pack('H*').gsub(/\x00/,'')
10
+ end
11
+
12
+ def uuid_to_bytes(uuid)
13
+ uuid.gsub(/-/, '')
14
+ end
15
+
16
+ def bytes_to_uuid(bytes)
17
+ bytes.delete(' ').sub(/([a-fA-F0-9]{8})([a-fA-F0-9]{4})([a-fA-F0-9]{4})([a-fA-F0-9]{4})([a-fA-F0-9]{12})/, '\1-\2-\3-\4-\5').upcase
18
+ end
19
+
20
+ def major_minor_to_bytes(value)
21
+ sprintf("%04x", value.to_i)
22
+ end
23
+
24
+ def bytes_to_major_minor(bytes)
25
+ bytes.delete(' ').to_i(16)
26
+ end
27
+
28
+ def power_to_bytes(power)
29
+ sprintf("%x", power.to_i + 256)
30
+ end
31
+
32
+ def bytes_to_power(bytes)
33
+ bytes.to_i(16) - 256
34
+ end
35
+
36
+ def pin_to_bytes(pin)
37
+ pin.unpack('H*')[0]
38
+ end
39
+
40
+ end
41
+ end
@@ -0,0 +1,3 @@
1
+ module Radbeacon
2
+ VERSION = "0.1.1"
3
+ end
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'radbeacon/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "radbeacon"
8
+ spec.version = Radbeacon::VERSION
9
+ spec.authors = ["Radius Networks"]
10
+ spec.email = ["support@radiusnetworks.com"]
11
+
12
+ spec.summary = %q{Provides RadBeacon (BLE Proximity Beacon) scanning and configuring capabilities on a linux machine.}
13
+ spec.homepage = "http://www.radiusnetworks.com"
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
+
25
+ end
metadata ADDED
@@ -0,0 +1,102 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: radbeacon
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Radius Networks
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2015-05-19 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
+ - ".rspec"
64
+ - Gemfile
65
+ - Gemfile.lock
66
+ - LICENSE.txt
67
+ - README.md
68
+ - Rakefile
69
+ - lib/radbeacon.rb
70
+ - lib/radbeacon/bluetooth_le_device.rb
71
+ - lib/radbeacon/le_scanner.rb
72
+ - lib/radbeacon/scanner.rb
73
+ - lib/radbeacon/usb.rb
74
+ - lib/radbeacon/utils.rb
75
+ - lib/radbeacon/version.rb
76
+ - radbeacon.gemspec
77
+ homepage: http://www.radiusnetworks.com
78
+ licenses:
79
+ - MIT
80
+ metadata: {}
81
+ post_install_message:
82
+ rdoc_options: []
83
+ require_paths:
84
+ - lib
85
+ required_ruby_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ requirements: []
96
+ rubyforge_project:
97
+ rubygems_version: 2.4.3
98
+ signing_key:
99
+ specification_version: 4
100
+ summary: Provides RadBeacon (BLE Proximity Beacon) scanning and configuring capabilities
101
+ on a linux machine.
102
+ test_files: []