Proper way to find the closest beacon

2019-05-19 01:02发布

问题:

When the didRangeBeacons method is called, is the closest beacon beacons.first? Or is it the beacon in the list of beacons that has the lowest accuracy value? Are both correct? Thanks

回答1:

While the list of beacons in the didRangeBeacons:inRegion: callback is sorted by the accuracy field (which actually measures distance in meters), there are a number of problems with relying on this:

  • Sometimes beacons have an accuracy value of -1 if no RSSI (signal strength) samples were available in the past second. This gives you incorrect results if you relay on the automatic sorting.

  • Sometimes beacons drop out of the list if they aren't detected in the last second. This is a common problem in noisy bluetooth environments and with infrequently transmitting beacons.

A more robust way to know the closest beacon is to implement an algorithm yourself that ignores accuracy values of -1 and is tolerant of temporary dropouts (say 5 seconds) in beacon detections.

Or course, this is just the beginning. You can get even fancier by adding all kinds of other filters to tune it to your exact use case. Without over complicating things, here's some Swift code I use to the basics:

let expirationTimeSecs = 5.0
public var closestBeacon: CLBeacon? = nil
var trackedBeacons: Dictionary<String, CLBeacon>
var trackedBeaconTimes: Dictionary<String, NSDate>

override init() {
  trackedBeacons = Dictionary<String, CLBeacon>()
  trackedBeaconTimes = Dictionary<String, NSDate>()
}

public func locationManager(manager: CLLocationManager, didRangeBeacons beacons: [CLBeacon], inRegion region: CLBeaconRegion) {
  let now = NSDate()
  for beacon in beacons {
    let key = keyForBeacon(beacon)
    if beacon.accuracy < 0 {
      NSLog("Ignoring beacon with negative distance")
    }
    else {
      trackedBeacons[key] = beacon
      if (trackedBeaconTimes[key] != nil) {
        trackedBeaconTimes[key] = now
      }
      else {
        trackedBeaconTimes[key] = now
      }
    }
  }
  purgeExpiredBeacons()
  calculateClosestBeacon()
}

func calculateClosestBeacon() {
  var changed = false
    // Initialize cloestBeaconCandidate to the latest tracked instance of current closest beacon
    var closestBeaconCandidate: CLBeacon?
    if closestBeacon != nil {
      let closestBeaconKey = keyForBeacon(closestBeacon!)
      for key in trackedBeacons.keys {
        if key == closestBeaconKey {
          closestBeaconCandidate = trackedBeacons[key]
        }
      }
    }

    for key in trackedBeacons.keys {
      var closer = false
      let beacon = trackedBeacons[key]
      if (beacon != closestBeaconCandidate) {
        if beacon!.accuracy > 0 {
          if closestBeaconCandidate == nil {
            closer = true
          }
          else if beacon!.accuracy < closestBeaconCandidate!.accuracy {
            closer = true
          }
        }
        if closer {
          closestBeaconCandidate = beacon
          changed = true
        }
      }
    }
    if (changed) {
      closestBeacon = closestBeaconCandidate
    }
}

func keyForBeacon(beacon: CLBeacon) -> String {
  return "\(beacon.proximityUUID.UUIDString) \(beacon.major) \(beacon.minor)"
}

func purgeExpiredBeacons() {
  let now = NSDate()
  var changed = false
  var newTrackedBeacons = Dictionary<String, CLBeacon>()
  var newTrackedBeaconTimes = Dictionary<String, NSDate>()
  for key in trackedBeacons.keys {
    let beacon = trackedBeacons[key]
    let lastSeenTime = trackedBeaconTimes[key]!
    if now.timeIntervalSinceDate(lastSeenTime) > expirationTimeSecs {
      NSLog("******* Expired seeing beacon: \(key) time interval is \(now.timeIntervalSinceDate(lastSeenTime))")
      changed = true
    }
    else {
      newTrackedBeacons[key] = beacon!
      newTrackedBeaconTimes[key] = lastSeenTime
    }
  }
  if changed {
    trackedBeacons = newTrackedBeacons
    trackedBeaconTimes = newTrackedBeaconTimes
  }
}


回答2:

Once you detect beacons, in order to see which is closest, you need to iterate through the beacons array in the locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray *)beacons inRegion:(CLBeaconRegion *)region callback, and compare the accuracy property of each beacon. The beacon with the lowest accuracy value is the closest.