Class: ScanBeacon::BeaconParser
- Inherits:
-
Object
- Object
- ScanBeacon::BeaconParser
- Defined in:
- lib/scan_beacon/beacon_parser.rb
Constant Summary collapse
- DEFAULT_LAYOUTS =
{ altbeacon: "m:2-3=beac,i:4-19,i:20-21,i:22-23,p:24-24,d:25-25", eddystone_uid: "s:0-1=aafe,m:2-2=00,p:3-3:-41,i:4-13,i:14-19;d:20-21" }
- AD_TYPE_MFG =
0xff- AD_TYPE_SERVICE =
0x03- BT_EIR_SERVICE_DATA =
"\x16"
Instance Attribute Summary collapse
-
#beacon_type ⇒ Object
Returns the value of attribute beacon_type.
Class Method Summary collapse
Instance Method Summary collapse
- #generate_ad(beacon) ⇒ Object
- #generate_field(field, value) ⇒ Object
-
#initialize(beacon_type, layout) ⇒ BeaconParser
constructor
A new instance of BeaconParser.
- #inspect ⇒ Object
- #matches?(data) ⇒ Boolean
- #parse(data, ad_type = AD_TYPE_MFG) ⇒ Object
- #parse_data_fields(data) ⇒ Object
- #parse_elems(elems, data) ⇒ Object
- #parse_ids(data) ⇒ Object
- #parse_mfg_or_service_id(data) ⇒ Object
- #parse_power(data) ⇒ Object
Constructor Details
#initialize(beacon_type, layout) ⇒ BeaconParser
Returns a new instance of BeaconParser.
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
# File 'lib/scan_beacon/beacon_parser.rb', line 16 def initialize(beacon_type, layout) @beacon_type = beacon_type @layout = layout.split(",") if layout.include?("s") @ad_type = AD_TYPE_SERVICE else @ad_type = AD_TYPE_MFG end @matchers = @layout.find_all {|item| ["m", "s"].include? item[0]}.map {|matcher| _, range_start, range_end, expected = matcher.split(/:|=|-/) {start: range_start.to_i, end: range_end.to_i, expected: expected} } @ids = @layout.find_all {|item| item[0] == "i"}.map {|id| _, range_start, range_end = id.split(/:|-/) {start: range_start.to_i, end: range_end.to_i} } @data_fields = @layout.find_all {|item| item[0] == "d"}.map {|field| _, range_start, range_end = field.split(/:|-/) {start: range_start.to_i, end: range_end.to_i} } _, power_start, power_end = @layout.find {|item| item[0] == "p"}.split(/:|-/) @power = {start: power_start.to_i, end: power_end.to_i} end |
Instance Attribute Details
#beacon_type ⇒ Object
Returns the value of attribute beacon_type.
10 11 12 |
# File 'lib/scan_beacon/beacon_parser.rb', line 10 def beacon_type @beacon_type end |
Class Method Details
.default_parsers ⇒ Object
12 13 14 |
# File 'lib/scan_beacon/beacon_parser.rb', line 12 def self.default_parsers DEFAULT_LAYOUTS.map {|name, layout| BeaconParser.new name, layout } end |
Instance Method Details
#generate_ad(beacon) ⇒ Object
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
# File 'lib/scan_beacon/beacon_parser.rb', line 94 def generate_ad(beacon) length = [@matchers, @ids, @power, @data_fields].flatten.map {|elem| elem[:end] }.max + 1 ad = "\x00" * length @matchers.each do |matcher| ad[matcher[:start]..matcher[:end]] = [matcher[:expected]].pack("H*") end @ids.each_with_index do |id, index| ad[id[:start]..id[:end]] = generate_field(id, beacon.ids[index]) end @data_fields.each_with_index do |field, index| ad[field[:start]..field[:end]] = generate_field(field, beacon.data[index]) unless beacon.data[index].nil? end ad[@power[:start]..@power[:end]] = [beacon.power].pack('c') if @ad_type == AD_TYPE_SERVICE "\x03\x03" + [beacon.service_uuid].pack("S<") + [length+1].pack('C') + BT_EIR_SERVICE_DATA + ad elsif @ad_type == AD_TYPE_MFG ad[0..1] = [beacon.mfg_id].pack("S<") [length+1].pack('C') + [AD_TYPE_MFG].pack('C') + ad end end |
#generate_field(field, value) ⇒ Object
115 116 117 118 119 120 121 122 123 124 125 126 127 |
# File 'lib/scan_beacon/beacon_parser.rb', line 115 def generate_field(field, value) field_length = field[:end] - field[:start] + 1 case field_length when 1 [value].pack("c") when 2 [value].pack("S>") when 6 [value].pack("Q>")[2..-1] else [value].pack("H*")[0..field_length-1] end end |
#inspect ⇒ Object
129 130 131 |
# File 'lib/scan_beacon/beacon_parser.rb', line 129 def inspect "<BeaconParser type=\"#{@beacon_type}\", layout=\"#{@layout.join(",")}\">" end |
#matches?(data) ⇒ Boolean
40 41 42 43 44 45 |
# File 'lib/scan_beacon/beacon_parser.rb', line 40 def matches?(data) @matchers.each do |matcher| return false unless data[matcher[:start]..matcher[:end]].unpack("H*").join == matcher[:expected] end return true end |
#parse(data, ad_type = AD_TYPE_MFG) ⇒ Object
47 48 49 50 51 52 53 54 55 56 |
# File 'lib/scan_beacon/beacon_parser.rb', line 47 def parse(data, ad_type = AD_TYPE_MFG) return nil if ad_type != @ad_type || !matches?(data) if @ad_type == AD_TYPE_MFG Beacon.new(ids: parse_ids(data), power: parse_power(data), beacon_type: @beacon_type, data: parse_data_fields(data), mfg_id: parse_mfg_or_service_id(data)) else Beacon.new(ids: parse_ids(data), power: parse_power(data), beacon_type: @beacon_type, data: parse_data_fields(data), service_uuid: parse_mfg_or_service_id(data)) end end |
#parse_data_fields(data) ⇒ Object
62 63 64 |
# File 'lib/scan_beacon/beacon_parser.rb', line 62 def parse_data_fields(data) parse_elems(@data_fields, data) end |
#parse_elems(elems, data) ⇒ Object
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
# File 'lib/scan_beacon/beacon_parser.rb', line 66 def parse_elems(elems, data) elems.map {|elem| elem_str = data[elem[:start]..elem[:end]] elem_length = elem_str.size case elem_length when 1 elem_str.unpack('C')[0] when 2 # two bytes, so treat it as a short (big endian) elem_str.unpack('S>')[0] when 6 # 6 bytes, treat it is an eddystone instance id ("\x00\x00"+elem_str).unpack('Q>')[0] else # not two bytes, so treat it as a hex string elem_str.unpack('H*').join end } end |
#parse_ids(data) ⇒ Object
58 59 60 |
# File 'lib/scan_beacon/beacon_parser.rb', line 58 def parse_ids(data) parse_elems(@ids, data) end |
#parse_mfg_or_service_id(data) ⇒ Object
86 87 88 |
# File 'lib/scan_beacon/beacon_parser.rb', line 86 def parse_mfg_or_service_id(data) data[0..1].unpack('S>')[0] end |
#parse_power(data) ⇒ Object
90 91 92 |
# File 'lib/scan_beacon/beacon_parser.rb', line 90 def parse_power(data) data[@power[:start]..@power[:end]].unpack('c')[0] end |