Class: Tgios::BeaconManager

Inherits:
BindingBase show all
Defined in:
lib/tgios/beacon_manager.rb

Constant Summary collapse

ALGORITHMS =
[:continue, :timeout]
BeaconFoundKey =
'Tgios::BeaconManager::BeaconFound'
BeaconsFoundKey =
'Tgios::BeaconManager::BeaconsFound'
EnterRegionKey =
'Tgios::BeaconManager::EnterRegion'
ExitRegionKey =
'Tgios::BeaconManager::ExitRegion'

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from BindingBase

#hook, #prepareForRelease, #unhook

Constructor Details

#initialize(options) ⇒ BeaconManager

range_method: :rssi or :accuracy range_limit: around -70 for :rssi / around 1.5 (meters) for :accuracy background: range beacons when device is in background algorithm: :continue or :timeout tolerance: for algorithm continue: number of times a beacon is ranged continuously required to trigger beacon changed event, for algorithm timeout: time in seconds for a beacon marked as changed



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/tgios/beacon_manager.rb', line 43

def initialize(options)
  @events = {}
  @previous_beacons = []
  @beacons_last_seen_at = {}
  @background = options[:background]

  self.algorithm = (options[:algorithm] || :continue)
  @tolerance = (options[:tolerance] || 5)

  @uuid = NSUUID.alloc.initWithUUIDString(options[:uuid])
  @major = options[:major]
  @minor = options[:minor]
  @range_method = (options[:range_method] || :rssi)
  @range_limit = (options[:range_limit] || -70)

  @region = if @major && @minor
              CLBeaconRegion.alloc.initWithProximityUUID(@uuid, major: @major, minor: @minor, identifier: identifier)
            elsif @major
              CLBeaconRegion.alloc.initWithProximityUUID(@uuid, major: @major, identifier: identifier)
            else
              CLBeaconRegion.alloc.initWithProximityUUID(@uuid, identifier: identifier)
            end
  @region.notifyOnEntry = true
  @region.notifyOnExit = true
  @region.notifyEntryStateOnDisplay = true

  start_monitor

  UIApplicationWillEnterForegroundNotification.add_observer(self, 'on_enter_foreground:')
  UIApplicationDidEnterBackgroundNotification.add_observer(self, 'on_enter_background:')

end

Instance Attribute Details

#backgroundObject

Returns the value of attribute background.



19
20
21
# File 'lib/tgios/beacon_manager.rb', line 19

def background
  @background
end

#current_beaconObject

Returns the value of attribute current_beacon.



19
20
21
# File 'lib/tgios/beacon_manager.rb', line 19

def current_beacon
  @current_beacon
end

#range_limitObject

Returns the value of attribute range_limit.



19
20
21
# File 'lib/tgios/beacon_manager.rb', line 19

def range_limit
  @range_limit
end

#range_methodObject

Returns the value of attribute range_method.



19
20
21
# File 'lib/tgios/beacon_manager.rb', line 19

def range_method
  @range_method
end

#toleranceObject

Returns the value of attribute tolerance.



19
20
21
# File 'lib/tgios/beacon_manager.rb', line 19

def tolerance
  @tolerance
end

Class Method Details

.beacon_eqs(beacon1, beacon2) ⇒ Object



267
268
269
270
# File 'lib/tgios/beacon_manager.rb', line 267

def self.beacon_eqs(beacon1, beacon2)
  return beacon1 == beacon2 if beacon1.nil? || beacon2.nil?
  beacon1.minor == beacon2.minor && beacon1.major == beacon2.major && beacon1.proximityUUID == beacon2.proximityUUID
end

.beacon_key(beacon) ⇒ Object



272
273
274
# File 'lib/tgios/beacon_manager.rb', line 272

def self.beacon_key(beacon)
  [beacon.try(:proximityUUIDbeacon), beacon.try(:major), beacon.try(:minor)].join('/')
end

.defaultObject



31
32
33
# File 'lib/tgios/beacon_manager.rb', line 31

def self.default
  @default
end

.default=(val) ⇒ Object



27
28
29
# File 'lib/tgios/beacon_manager.rb', line 27

def self.default=(val)
  @default = val
end

.supportedObject



280
281
282
# File 'lib/tgios/beacon_manager.rb', line 280

def self.supported
  CLLocationManager.isRangingAvailable
end

Instance Method Details

#algorithm=(val) ⇒ Object

Raises:

  • (ArgumentError)


85
86
87
88
89
90
91
92
93
94
# File 'lib/tgios/beacon_manager.rb', line 85

def algorithm=(val)
  alg = val.to_s.to_sym
  return unless alg.present?
  if @algorithm != alg
    @previous_beacons.clear
  end
  raise ArgumentError.new("Algorithm not found, valid algorithm are: [#{ALGORITHMS.join(', ')}]") unless ALGORITHMS.include?(alg)

  @algorithm = alg
end

#beacon_eqs(beacon1, beacon2) ⇒ Object



263
264
265
# File 'lib/tgios/beacon_manager.rb', line 263

def beacon_eqs(beacon1, beacon2)
  self.class.beacon_eqs(beacon1, beacon2)
end

#deallocObject



294
295
296
297
# File 'lib/tgios/beacon_manager.rb', line 294

def dealloc
  onPrepareForRelease
  super
end

#did_enter_region(region) ⇒ Object



200
201
202
203
204
205
206
207
208
# File 'lib/tgios/beacon_manager.rb', line 200

def did_enter_region(region)
  if region.isKindOfClass(CLBeaconRegion)
    location_manager.startRangingBeaconsInRegion(region)
    if has_event(:enter_region)
      @events[:enter_region].call(region)
    end
    EnterRegionKey.post_notification(self, {region: region})
  end
end

#did_exit_region(region) ⇒ Object



210
211
212
213
214
215
216
217
218
# File 'lib/tgios/beacon_manager.rb', line 210

def did_exit_region(region)
  if region.isKindOfClass(CLBeaconRegion)
    location_manager.stopRangingBeaconsInRegion(region)
    if has_event(:exit_region)
      @events[:exit_region].call(region)
    end
    ExitRegionKey.post_notification(self, {region: region})
  end
end

#has_event(event) ⇒ Object



220
221
222
# File 'lib/tgios/beacon_manager.rb', line 220

def has_event(event)
  @events.has_key?(event)
end

#identifierObject



76
77
78
# File 'lib/tgios/beacon_manager.rb', line 76

def identifier
  [@uuid.UUIDString, @major, @minor].join('/')
end

#location_managerObject



148
149
150
151
152
153
154
155
156
# File 'lib/tgios/beacon_manager.rb', line 148

def location_manager
  @location_manager ||=
      begin
        manager = CLLocationManager.alloc.init
        manager.delegate = self
        request_authorization(manager)
        manager
      end
end

#locationManager(manager, rangingBeaconsDidFailForRegion: region, withError: error) ⇒ Object



96
97
98
99
100
101
102
103
104
# File 'lib/tgios/beacon_manager.rb', line 96

def locationManager(manager, didDetermineState: state, forRegion: region)
  NSLog "didDetermineState #{state}"
  if state == CLRegionStateInside
    manager.startRangingBeaconsInRegion(region)
    did_enter_region(region)
  elsif state == CLRegionStateOutside
    did_exit_region(region)
  end
end

#new_fake_beacon(options) ⇒ Object



276
277
278
# File 'lib/tgios/beacon_manager.rb', line 276

def new_fake_beacon(options)
  FakeBeacon.new({uuid: @uuid}.merge(options))
end

#on(event_key, &block) ⇒ Object



80
81
82
83
# File 'lib/tgios/beacon_manager.rb', line 80

def on(event_key,&block)
  @events[event_key] = block.weak!
  self
end

#on_enter_background(noti) ⇒ Object



195
196
197
198
# File 'lib/tgios/beacon_manager.rb', line 195

def on_enter_background(noti)
  NSLog 'on_enter_background'
  stop_monitor unless @background
end

#on_enter_foreground(noti) ⇒ Object



190
191
192
193
# File 'lib/tgios/beacon_manager.rb', line 190

def on_enter_foreground(noti)
  NSLog 'on_enter_foreground'
  self.performSelector('start_monitor', withObject: nil, afterDelay:1)
end

#onPrepareForReleaseObject



284
285
286
287
288
289
290
291
292
# File 'lib/tgios/beacon_manager.rb', line 284

def onPrepareForRelease
  UIApplicationWillEnterForegroundNotification.remove_observer(self)
  UIApplicationDidEnterBackgroundNotification.remove_observer(self)
  stop_monitor
  @location_manager = nil
  @events = nil
  @current_beacon = nil
  @previous_beacons = nil
end

#push_beacon(beacon) ⇒ Object



224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
# File 'lib/tgios/beacon_manager.rb', line 224

def push_beacon(beacon)
  case @algorithm
    when :continue
      if beacon_eqs(beacon, @current_beacon)
        @current_beacon = beacon
      else
        if @previous_beacons.find { |b| !beacon_eqs(beacon, b) }.blank? # all previous beacons is the new beacon
          @current_beacon = beacon
        else
          @current_beacon = nil if @previous_beacons.find{ |b| beacon_eqs(@current_beacon, b)}.blank? # all previous beacons is not the current beacon
        end
      end
      @previous_beacons << beacon
      @previous_beacons.delete_at(0) if @previous_beacons.length > @tolerance

    when :timeout
      time_now = Time.now
      beacon_hash = {beacon: beacon, time: time_now}
      @beacons_last_seen_at[self.class.beacon_key(beacon)] = beacon_hash

      while @previous_beacons.present? && time_now - @previous_beacons.first[:time] > @tolerance do
        @previous_beacons.delete_at(0)
      end
      @previous_beacons << beacon_hash

      current_beacon_hash = @beacons_last_seen_at[self.class.beacon_key(@current_beacon)]
      if current_beacon_hash

      end
      if !current_beacon_hash || time_now - current_beacon_hash[:time] < @tolerance
         # current beacon not change
      else
        count_hash = @previous_beacons.each_with_object(Hash.new(0)) {|e, h| h[self.class.beacon_key(e[:beacon])] += @tolerance - (time_now - e[:time])} # beacon has more weighting if time is later
        max_beacon_key = count_hash.max_by(&:last).first
        @current_beacon = @beacons_last_seen_at[max_beacon_key][:beacon]
      end
  end
end

#request_authorization(manager) ⇒ Object



158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/tgios/beacon_manager.rb', line 158

def request_authorization(manager)
  if manager.respond_to?(:requestAlwaysAuthorization)
    status = CLLocationManager.authorizationStatus
    if status == KCLAuthorizationStatusAuthorizedWhenInUse || status == KCLAuthorizationStatusDenied
      denied_title = 'Tgios::BeaconManager.request_alert.title.denied'._
      denied_title = 'Location services are off' if denied_title == 'Tgios::BeaconManager.request_alert.title.denied'

      disabled_title = 'Tgios::BeaconManager.request_alert.title.disabled'._
      disabled_title = 'Background location is not enabled' if disabled_title == 'Tgios::BeaconManager.request_alert.title.disabled'

      message = 'Tgios::BeaconManager.request_alert.message'._
      message = "To use background location you must turn on 'Always' in the Location Services Settings" if message == 'Tgios::BeaconManager.request_alert.message'

      title = (status == KCLAuthorizationStatusDenied) ? denied_title : disabled_title
      UIAlertView.alert(title, message: message)
    else
      manager.requestAlwaysAuthorization
    end
  end

end

#start_monitorObject



180
181
182
183
# File 'lib/tgios/beacon_manager.rb', line 180

def start_monitor
  location_manager.startMonitoringForRegion(@region)
  location_manager.requestStateForRegion(@region)
end

#stop_monitorObject



185
186
187
188
# File 'lib/tgios/beacon_manager.rb', line 185

def stop_monitor
  location_manager.stopRangingBeaconsInRegion(@region)
  location_manager.stopMonitoringForRegion(@region)
end