-
Notifications
You must be signed in to change notification settings - Fork 19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
🤖🛜 Android BLE Integration #219
🤖🛜 Android BLE Integration #219
Conversation
Android Integration UpdateFor this BLE integration, a key component is going to be a library called altbeacon, which will allow us to seamlessly interact with beacons that transmit the iBeacon protocol. The plugin we used for the BLE scanner used this library for Android and it seemed to work great. The library is also actively maintained and very well documented which is a great plus. With that in mind, I have added altbeacon as a dependency to the Android side of This is honestly great news as it seems like integrating BLE on Android won't be all the hassle we originally thought it was. Plan Going ForwardEverything I have done so far has been a proof of concept, making sure we could 1) download the dependency and 2) use it to scan for a beacon. I think next steps are roughly as follows:
|
I think if we move forward with the plan to use one UUID and just differentiate beacons by major/minor (described in e-mission/e-mission-docs#1062), this implementation should switch from the "Monitoring" API to the "Ranging" API (examples of both in https://altbeacon.github.io/android-beacon-library/samples.html) |
My understanding is that Monitoring will tell us if you enter or leave a region (and regions are differentiated by UUID). But if there are multiple beacons in one region (ie sharing a UUID), we need Ranging to know when the individual beacons leave or enter. And, we will also get a granular measurement of each beacon's distance. |
Per the iOS documentation, we should really be using a two-stage process. First use See the preferred example here: I anticipate it will be similar on android. After we get the basic functionality to work, we should experiment with power drain/efficiency by potentially turning off the Ranging after we have determined the matching beacon. |
@louisg1337 @JGreenlee before we pivot to the new data model, we should verify that this holds for iOS as well. I have merged e-mission/e-mission-phone#1140 and created a new staging release (1.7.2). @JGreenlee and @louisg1337 can you please update your iPhones and verify that it behaves the same way (don't use the fake callback!) |
Great! just out of curiosity, I note that one of the beacons had |
The 'proximity' seems really sensitive to small changes. I can get ProximityImmediate when it's ~6 inches away. ProximityNear from ~1-3 feet away. I get ProximityFar anywhere from ~4-25 feet until it drops off. It also seems to be significantly affected by some object blocking the direct path from the phone to the beacon (for example, when I put my hand over the beacon). |
I think we can change the signal strength if we need to |
Interesting, we should experiment with these in cars and figure out the appropriate combo of signal strength and Proximity to use so that we detect the beacon in the current car, but not the beacon in a car parked nearby. But that is tuning for next month after we have the basic implementation working |
FSM Integration UpdateSo the past few days I've been wrestling with how to implement this and have been facing lots of setbacks that I will document here. The goal that I am trying to achieve is when we receive the In terms of the specifics, I wanted to make it so that we get the intent, start the scanner, let it scan for 2-3 seconds to ensure we pickup any beacons in the area, and then determine next steps. I wanted to do the 2-3 second wait as I have run into an issue a few times where the scanner does not pick up my beacon on the first or second scan, but does on the third. The little bit of delay would ensure that we will definitely pickup if there is a beacon nearby. With that in mind, I figured this code could go into
I would definitely appreciate any ideas or thoughts on a better way to implement this. I feel like there definitely is a clean way to accomplish all of this, I just don't know it yet. |
@louisg1337 couple of high level comments/suggestions:
Now that you see it visually, you can also see the holes in the plan. We want to spend most of our time in "Waiting for trip start"; once we have moved to "Checking for beacon", how do we get back to "Waiting for trip start" if it is not a fleet trip. The answer is obvious (timeout), but I think it would be helpful for you to draw out what the FSM looks like for both the location-triggered and beacon-triggered cases. You can see the existing services (e.g.
|
…ng if it has detected a beacon in the region. Also started setting up adding a new state in the FSM.
BLE Scanner With Timeout UpdateThank you @shankari for the help with all of this, I think its put me back on the right path. I think the reminder that we are in a FSM, so do things one step at a time, not all jumbled together concurrently, made a big difference. In terms of my update, I've made some progress on getting the scanning side of things to work so that we can scan and return if we saw a beacon. As mentioned above, I've been trying to take a more sequential approach to this, so now, instead of waiting X seconds in one thread while a different thread scans, we will be scanning X times, and then checking our results. This will give us a few chances to range any beacons in the area, while giving us the ability to exit out of the Although I did use ranging here, I wanted to acknowledge Shankari's comment above.
I agree with how we should be using monitoring instead, but for some reason I just could not get the monitoring to work. Even when I stripped back to bare bones and attempted to scan straight from the altbeacon tutorial, the monitoring would only ever return once. I could've sworn it worked a few days ago when I tried it last, but it just didn't like me today. I spent a good few hours today trying to figure out why, but I am a bit stumped. We are a bit strapped for time on this project, and we are only ranging X times, so I figure that this can be something we change out down the line when we have more time to poke and prod around. FSM IntegrationI have been messing around with this a bit as well, but it is not included in my most recent push as I didn't finish it. I think my general game plan for this is as follows.
I think this flow is nice, but I do have a few questions. I understand that we should be adding a new state to the FSM, but with the way that I thought of doing it feels a bit redundant. In steps 1 and 2 we would be sending another intent to the FSM from Another question I have is for 4. If we were to continue, what would that look like? While I was tracing what happens when I think I have the same question with what we should do if we choose to not continue? I am also concerned about sending the wrong intent and then the next geofence won't be created and the app breaks. I'll take some time in the next few days to dig into the thesis and FSM diagram some more to hopefully answer the questions above. |
Yes. You are close. High level comments:
Again, I think that understand the current FSM patterns and drawing out your new plan as an FSM will help you a lot |
…before starting a trip. Only works in the foreground right now.
FSM Integration UpdateAfter the post above and a brainstorming session with Katie, I think I have a better grasp on the FSM and what changes need to be done. I've taken some time to draw out a diagram, as well, which you can see below. On the left are all the states that the system can experience, and then on the right is all the functions that will get performed. I have also attempted to implement this, which can be seen in my most recent commit. I did have a few thoughts about my implementation that I figured I'd bring up. First of all, in The next thing I wanted to mention was that for the time being, to check if we are using a fleet config, I just did BluetoothService UpdateTwo important updates regarding this. First and foremost, in this current implementation, I am ranging the beacons instead of monitoring them. Even though I set a region with a specific UUID to range for, it seems like we are picking up any beacon in the area, not just ones with the specific UUID. Maybe that is just the intended way that ranging is supposed to work, if anything I'll do a bit more research to see why thats the case. For right now though, I just added an additional if statement to only save beacons with our desired UUID. The other update is that I realized I wasn't considering something important, which was running the Potential Solution I may need to refactor Additional NotesI just thought of this now and I wanted to get it out there so I don't forget about it, but we also need to implement the bluetooth scanning permissions for those with the fleet config. I think we already have the code setup when we did the BLE/Classic scanner, so hopefully it shouldn't be too hard. |
I thought that this shouldn't matter. As you may remember from our deep dive into the location tracking, we start a foreground service for the app, and when that is running, it should be equivalent to the service starting in the foreground. However, on reading up on foreground services, I see the following
I am not quite sure if the bluetooth service needs to be in the foreground or you just need to bind to the existing foreground service. I would recommend the second option so that we don't pollute the user's notification bar with too many foreground services. I would also suggest that you experiment with just starting foreground with location and |
…an run in the background.
I totally agree, I don't think starting another foreground service is the right thing to do, but thankfully though, it seems like we don't need to. I just pushed a commit that lets us scan beacons in the background, and all I am doing now is starting |
I did get around to testing this. Long story short – the config can be retrieved fairly easily by using the String app_config_key = ctxt.getString(R.string.key_usercache_app_config);
JSONObject config = (JSONObject) UserCacheFactory.getUserCache(ctxt).getDocument(app_config_key, false); (This depends on a new key that e-mission/cordova-usercache#50 adds. Until that is merged, you could just hardcode The way I tested this was by adding a new Cordova JS function DataCollectionPlugin.java ...
} else if (action.equals("getAppConfig")) {
try {
Log.d(cordova.getActivity(), TAG, "getting app config");
Context ctxt = cordova.getActivity();
String app_config_key = ctxt.getString(R.string.key_usercache_app_config);
JSONObject config = (JSONObject) UserCacheFactory.getUserCache(ctxt).getDocument(app_config_key, false);
callbackContext.success(config);
return true;
} catch (Exception e) {
Log.e(cordova.getActivity(), TAG, "Error getting app config");
callbackContext.error(e.getMessage());
return false;
}
}
... datacollection.js...
getAppConfig: function() {
return new Promise(function(resolve, reject) {
exec(resolve, reject, "DataCollection", "getAppConfig", []);
})
}
... Then a new row in the Profile tab to display the retrieved config: ProfileSettings.tsx...
<SettingRow
textKey="Read app config from native"
iconName="application"
action={() => {
window['cordova'].plugins.BEMDataCollection.getAppConfig().then((result) => {
const cfg = JSON.stringify(result, null, 2);
logInfo('appConfig = ' + cfg);
window.alert('appConfig = ' + cfg);
});
}}
/>
... It works! |
…luetoothMonitoringService for future constant monitoring, and added in some permission checks
Newest Commit UpdateI just pushed a new commit that has a few new changes that I'll explain below. Config integration Permissions Bluetooth Monitoring Testing |
…permissions so we ask for bluetooth permissions when the app opens for the time being.
Conflict was due to the newly added transitions Per e-mission/e-mission-docs#1062 (comment) resolved conflict by removing transitions from this PR, and changing `beacon_found` to `ble_beacon_found`
Consistent with e-mission/e-mission-docs#1062 (comment) - removed the newly added transitions - made them consistent with the plumbed through values - simplified the tracking logic in `TripDiaryStateMachineService` - minor refactoring to pull the isFleet check into an instance variable that we can reuse without having to read it every time Testing done: Non-fleet case - Start sending location - Not seeing any locations - Manually exit geofence ``` 04-13 22:51:45.962 23297 23297 I TripDiaryStateMachineRcvr: TripDiaryStateMachineReciever onReceive(android.app.ReceiverRestrictedContext@a7c085e, Intent { act=local.transition.exited_geofence flg=0x10 pkg=edu.berkeley.eecs.emission cmp=edu.berkeley.eecs.emission/.cordova.tracker.location.TripDiaryStateMachineReceiver }) called 04-13 22:51:46.118 23297 23297 D TripDiaryStateMachineService: handleAction(local.state.waiting_for_trip_start, local.transition.exited_geofence) called 04-13 22:51:46.160 23297 23297 D TripDiaryStateMachineService: Geofence exit in non-fleet mode, starting location tracking ``` - Stop sending locations - Manually end trip ``` 04-13 22:54:41.792 23297 23297 D TripDiaryStateMachineService: handleAction(local.state.ongoing_trip, local.transition.stopped_moving) completed, waiting for async operations to complete ``` Fleet case - Start sending location - Not seeing any locations ``` $ grep LocationChangeIntentService /tmp/logcat.log | tail 04-13 22:54:32.555 23297 32018 D LocationChangeIntentService: FINALLY! Got location update, intent is Intent { cmp=edu.berkeley.eecs.emission/.cordova.tracker.location.LocationChangeIntentService (has extras) } ``` - Manually exit geofence ``` 04-13 23:07:58.760 23297 23297 D TripDiaryStateMachineService: handleAction(local.state.waiting_for_trip_start, local.transition.exited_geofence) called 04-13 23:07:58.798 23297 23297 D TripDiaryStateMachineService: Geofence exit in fleet mode, checking for beacons before starting location tracking 04-13 23:07:58.807 23297 23297 D TripDiaryStateMachineService: handleAction(local.state.waiting_for_trip_start, local.transition.exited_geofence) completed, waiting for async operations to complete $ grep LocationChangeIntentService /tmp/logcat.log | tail 04-13 22:54:32.555 23297 32018 D LocationChangeIntentService: FINALLY! Got location update, intent is Intent { cmp=edu.berkeley.eecs.emission/.cordova.tracker.location.LocationChangeIntentService (has extras) } ``` - Generate BLE event ``` 04-13 23:11:26.244 23297 23297 D TripDiaryStateMachineService: handleAction(local.state.waiting_for_trip_start, local.transition.ble_beacon_found) called 04-13 23:11:26.293 23297 23297 D TripDiaryStateMachineService: TripDiaryStateMachineReceiver handleTripStart(local.transition.ble_beacon_found) called 04-13 23:11:26.319 23297 23297 D TripDiaryStateMachineService: Found beacon in fleet mode, starting location tracking 04-13 23:11:26.387 23297 23297 D ActivityRecognitionActions: Starting activity recognition with interval = 30000 04-13 23:11:26.490 23297 23297 D TripDiaryStateMachineService: handleAction(local.state.waiting_for_trip_start, local.transition.ble_beacon_found) completed, waiting for async operations to complete 04-13 23:11:26.568 23297 23297 D TripDiaryStateMachineService: newState after handling action is local.state.ongoing_trip $ grep LocationChangeIntentService /tmp/logcat.log | tail 04-13 23:20:58.573 23297 14437 D LocationChangeIntentService: FINALLY! Got location update, intent is Intent { cmp=edu.berkeley.eecs.emission/.cordova.tracker.location.LocationChangeIntentService (has extras) } $ grep LocationChangeIntentService /tmp/logcat.log | tail 04-13 23:22:28.651 23297 15241 D LocationChangeIntentService: FINALLY! Got location update, intent is Intent { cmp=edu.berkeley.eecs.emission/.cordova.tracker.location.LocationChangeIntentService (has extras) } ``` - Stop sending locations - Manually end trip ``` 04-13 23:23:24.649 23297 23297 D TripDiaryStateMachineService: handleAction(local.state.ongoing_trip, local.transition.stopped_moving) called 04-13 23:23:24.921 23297 23297 D TripDiaryStateMachineService: newState after handling action is local.state.waiting_for_trip_start 04-13 23:23:24.958 23297 23297 D TripDiaryStateMachineService: newState saved in prefManager is local.state.waiting_for_trip_start 04-13 23:22:54.173 23297 23297 D LocationChangeIntentService: onDestroy called 04-13 23:22:54.173 23297 23297 D LocationChangeIntentService: onDestroy called 04-13 23:22:54.173 23297 23297 D LocationChangeIntentService: onDestroy called ```
This will allow us to know *which* beacons we were seeing during the trip, and thus, to match up the car with the trip. Testing done: compiled ``` BUILD SUCCESSFUL in 7s 52 actionable tasks: 2 executed, 50 up-to-date ``` I am not able to test it since I don't currently have a BLE beacon, but should be able to push it to staging so that we can test it. Also bump up the plugin version to reflect the change
This thread will relate to all changes being made to implement BLE functionality into the Android side of
e-mission-datacollection
.This is a continuation of the work done in these following PRs where we created a Bluetooth Classic and BLE scanner.
The core Issue this relates to is e-mission/e-mission-docs#1046