I've got a class that implementing CLLocationManagerDelegate and monitors a beacon region. It receives didEnterRegion and didExitRegion events, amongst other things.
Sometimes I get rapid switching between region entered and left states, even though I'm sat next to the beacon within 1m. The beacon advertising frequency is set to 500ms and TX power is set to -79 and the RSSI is around -55 when measured within 1m range.
The entry/exit switching looks like this on a timeline
...
12:01 - didEnterRegion
12:02 - didExitRegion
12:05 - didEnterRegion
...
AFAIK iOS should wait 30 seconds before confirming an exit in order to avoid false, but this isn't happening.
Do you have any tips for solve this problem? Or if there's a library to do it for me?
Potential Solution?
Note: My initial solution idea is to write a class that buffers region entry/exit events for 10 seconds and then detects these false positives and removes them from the event stream. It would republish the event without the fluxuations. E.g. for the previous example on one event would be published.
12:05 - didEnterRegion
If you like this approach, any ideas for a simple implementation in Swift?
Related
I am using the AltBeacon API to send beacons in an Android app. I am using the startRangingBeacon method with the deafult region as an argument -
region = Region("all-beacons", null, null, null).
I know that beacons are sent with the frequency of 1 second. Is it possible to change this frequency?
I tried to search for a frequency variable in the BeaconManager but could not find one.
Simply call:
beaconManager.scanPeriod = 2000L // set callback to 2000ms instead of the default 1100ms
Be aware that increasing the frequency of callbacks will sometimes mean you don’t get detections, as some beacons do not advertise more frequently than once per second. If no detections are made, the callback will have a beacon count of zero.
I read somewhere, that beacons on apple, work in a similar way as geo fences, and there too is a limit of 20 per phone.
Is this the case?
Our use case: We want to send push notifications to users when the app is not running and the phone is not awake (in the pocket). We are thinking of relying on beacons instead of geo fences in order to avoid hitting the limit of 20 fences per phone and having to deal with significant location change function and similar solutions (for now).
Beacons work similar as geofences. You will get notified when entering or exiting a beacon-region (in bluetooth range) and you can also range all beacons around to get their ids.
There is also for CLBeaconRegions the 20 regions limit. See:
https://developer.apple.com/reference/corelocation/cllocationmanager/1423656-startmonitoringforregion
With a combination of monitoring regions and ranging, you could be able to do your stuff even with one region. (Depends heavily on your use case)
If you want to send geo-based notifications, beacons could work for you.
Yes there is a limit of max 20 regions (both beacon and geofence combine).
There are ways you can monitor 1000s of beacons but it depends on the use case.
You can use different wildcards and monitor 1000s of beacons but with some limitations. You have to play with beacon configurations like beacon UDID, major and minor values.
If you are only interested in enter or exit of any of the beacons (and not interested in specific beacon). You can actually just set the same UDID of the all the beacons and only monitor one beacon region will solve your problem.
let region = CLBeaconRegion.init(proximityUUID: UUID.init(uuidString: "YOUR_BEACONS_UDID_32_DIGITS")!, identifier: "beacon_region1")
locationManager.startMonitoring(for: region)
You can also group the beacons with UDID and Major value.
let r1 = CLBeaconRegion.init(proximityUUID: UUID.init(uuidString: "YOUR_BEACONS_UDID_32_DIGITS")!, major: 1000, identifier: "region_1000")
So you can monitor 20 groups of beacons in background and each group can have as many beacons as you want.
Although CoreLocation APIs limit beacon region monitoring to 20 regions (combined with geofence regions), you can look for nearly 86 billion different beacon locations for the purpose of sending notifications to a user.
Yes, that's right. 86 billion -- 85,899,345,920 to be precise. Think that's enough?
The simple technique is to combine beacon monitoring APIs with beacon ranging APIs. Using monitoring, you can wake up your app in the background when any one of your beacons is detected. Then using ranging APIs you can read the exact beacon identifiers detected, and send a push notification to the user. This will all work with the phone still sitting in the user's pocket.
Because each beacon identifier includes a major (0-63355) and a minor (0-63335), you can monitor for the maximum 20 beacon regions with a different ProximityUUID, and get 20 x 65536 x 65536 = 85,899,345,920 different combinations.
let region = CLBeaconRegion.init(proximityUUID: UUID.init(uuidString: "2F234454-CF6D-4A0F-ADF2-F4911BA9FFA6")!, identifier: "beacon_region1")
locationManager.startMonitoring(for: region)
locationManager.startRangingBeacons(in: region)
// TODO: repeat the above for up to 19 more regions
...
public func locationManager(_ manager: CLLocationManager, didRangeBeacons beacons: [CLBeacon], in region: CLBeaconRegion) {
for beacon in beacons {
var uuid = beacon.proximityUUID
var major = beacon.major
var minor = beacon.minor
// TODO: Send a unique local notification to the user for this
// uuid/major/minor combination identifying a particular place
}
}
There are some limits to this approach. If your beacons are placed within overlapping radio range of each other (~ 50 meters) then a user dwelling by one beacon for awhile then walking to the one next door will not get a background wakeup unless the beacons are in two different regions. There are some games you can play to get around this limitation, but for many use cases you don't even need to bother.
I have a program in SWIFT, that detects when a beacon or multiple beacons are out of range and based on
that it will perform something like saving some data into database and etc. Everything works well, however I do get lots of
false positive in a way that "didExitRegion" getting fired and few secs later "didEnterRegion" getting fired while I haven't been moved or etc.
I know this has lot to do with the tuning of the beacons and their qualities, but at this time I have come up with an alternative solution.
So I decided to use NSTimer to see if I am really off range or is it just a false positive that I am getting?
so when didExitRegion is getting fired, I start a NSTimer for 60 secs. If the 60 secs is up and didEnterRegion didn't get fire, then I know
I am really out of range and perform whatever data saving I need to do.
Otherwise if didEnterRegion is called within that 60 secs, then I'll assume it was a false positive and invalidate the
nstimer and not doing anything.
Everything works well as long as I am working with one beacon. The problem I have with timer is when multiple beacons go out of range.
lets say first beacon is out of range, so didExitRegion is getting fire and start the NSTimer
Let say 20 secs later second beacon is off range and again didExitRegion is getting fired and that one starts the NSTimer again.
Now my NStimer is all out of synch and at that time, things are not working correct and the NSTimer continuously start itself when 60 secs
is up and etc.
So my question is what is the work around this solution? How can I keep my nstimer in synch when is called again before is invalidated?
Is there a better way to this solution? Again, I know a better quality beacons can help, but that is not an option for me at this time.
One solution is to keep a dictionary like this:
var pendingExits = Dictionary<CLBeaconRegion,NSDate>()
Each time you get a didExitRegion call:
Only start the NSTimer if the dictionary is empty -- otherwise, assume the timer is already running.
Add the region to the dictionary along with a new NSDate() to set the timestamp of when it was added.
When you get a didEnterRegion callback:
Look for the region in the dictionary. If it is there, remove it.
When the timer goes off:
Look for any entry with a timestamp 60 seconds or more old. For this, remove the region from the dictionary and fire your custom exit logic.
Find the newest remaining timestamp (if any) in the dictionary. Start the timer to go off at that time plus 60 seconds.
I am afraid I know the answer to this.
Apple's location manager docs for the startMonitoringForRegion method say that you can monitor a maximum of 20 regions at a time.
I have a client app that makes heavy use of both geofence regions and beacon regions.
I had assumed that there were separate 20 region limits for geofence regions and beacon regions, but I fear that the limit is actually 20 regions total for both types.
Can somebody confirm my fears based on actual experience?
Yes, the 20 region limit is the maximum that CoreLocation lets you monitor for both CLBeaconRegions and CLCircularRegions (geofences) combined. When iOS 7 added beacon support, beacon regions inherited this same limitation for geofences because of the ways the APIs were defined. And as you suspected, the limit applies to any type of region you want to monitor. So you can monitor 10 CLBeaconRegions and 10 CLCircularRegions but no more than 20 combined of each type.
The limit is 20 for each type of CLRegion. This means you can monitor 20 CLCircularRegion and 20 CLBeaconRegion.
This isn't documented but I did some tests and added more than 20 CLBeaconRegion and more than 20 CLCircularRegion. The results were that self.locationManager.monitoredRegions.count was 40 having 20 monitoredRegions of type CLCircularRegion and the other 20 of type CLBeaconRegion.
Once you reach the limit for each type of CLRegion and you try to monitor new regions (of that type) they get ignored and locationManager:monitoringDidFailForRegion:withError: is called with the kCLErrorRegionMonitoringFailure error code.
My iOS application relies heavily on GPS and I tried writing a method that helps conserve battery but I am having little success.
I created an NSTimer that fired every 15 seconds. Every time the method was called, it would increment an int time up by 1. If int time reached 20(5 minutes) it would turn off the location updates and set a bool isStopped to true. Every time the method ran and int time was above 20, it would increment another int, int time2, up by 1. If the method was ran and int time2 was equal to 4, it would start the location updates again and set time2 to 0.
Then in the didUpdateLocation: method for the location manager, I have an algorithm that would first check if the bool isStopped was true, if it was true then it would check the new location's horizontal accuracy and make sure it was under 10. Then it would check the newLocation with a location object named coords and check to see if they were greater than 9 meters apart. If they were not, it would stop location updates again and return. If they were, it would continue to another algorithm where it would check the new location object against some arguments. If it passed the coords location object would be set with the new location object, the time and time2 ints would be set to 0, the isStopped would be set to false, and the whole process would start all over again.
In short, after 5 minutes of no location changes, the location updates would be stopped and periodically checked every 1 minute to see if the user had moved at least 10 meters from the previous location that passed all requirements. When the user does move far enough, it starts the process all over again and the user has to not move 10 or more meters for 5 minutes before it starts the periodic checks. The thought behind this is to do only few second checks every minute when the user isn't moving much instead of constantly having the location services running.
Now here's the problem I run into, when the location updates stop. The NSTimer stops running while the app is in the background.
Could I somehow schedule a background task to run the loop between the 1 minute checks? Does anyone have any better ideas? Or any ideas on solutions to this?
A while ago I asked a question that features all the different ways that you can run GPS in the background.
CLLocationManager geo-fencing/startMonitoringForRegion: vs. startMonitoringForSignificantLocationChanges: vs. 10-minute startUpdating calls
You have to do specific things in order to maintain GPS in the background. A NSTimer would not suffice, since you need to register your app to be allowed to keep it running in the background after ten minutes. You minimally have to run the GPS every 10 minutes in the background to keep your app running.
Anyway, the methods in the above post should answer your question.