transistorsoft / react-native-background-geolocation

Sophisticated, battery-conscious background-geolocation with motion-detection
http://shop.transistorsoft.com/pages/react-native-background-geolocation
MIT License
2.58k stars 425 forks source link

Unpredictable onLocation event tracking #790

Closed Martyneris closed 4 years ago

Martyneris commented 4 years ago

Your Environment

// Location listener :

onLocation(location) { console.log('[location] -', location); this.setState({ latitude: location.coords.latitude, longitude: location.coords.longitude, activity: location.activity.type, isMoving: location.is_moving, }); if (location.is_moving && location.activity.type == 'in_vehicle') { this.setState({ recording: true, recordCount: this.state.recordCount + 1 }) GeoLocationService.storeLocation(location); } else if (!location.is_moving) { FileService.forceUpload(); this.setState({ recording: false }) } else { this.setState({ recording: false }) } }

// We are using the setState function to track the event changes live on UI, where a few elements are showing the received info when the events are triggered. // GeolocationService.handleLocation is our custom function where we , using the "react-native-fs" lib, append new line for each location point each time a location event is triggered and once a certain file size is reached, its being sent to back end.


## Expected Behavior
- Plugin should notice the movement of the device and be able to distinguish the difference between "walking" and "in_vehicle"
- Plugin should collect all location points using provided configuration in #ready state
- Plugin should stop the tracking once the device is stationary or moving, but not "in_vehicle"
- Plugin should be able to do all of the above from the background

## Actual Behavior
 - On rare occasions plugin works as expected, begins, finishes the tracking and collects location points as expected.
- Plugin reacts to the start of movement perfectly, recognizes the difference between "walking" and "in_vehicle" and collects all location points as expected. This scenario happens but extremely rarely. This well working scenario also sometimes starts and works through it all on background mode as well. But also very rarely.
- Plugin reacts to the start of movement, recognizes the "walking" occurence and doesnt ever change, even when driving in vehicle at high speeds, e.g  100km/h. In this case we cannot collect the location points, as we are only interested in collecting vehicle movement data.
- Plugin reacts to the start of movement, recognizes the "walking" occurence and successfully recognizes the change to "in_vehicle", shows "in_vehicle" events during the whole time of the trip, but when we check the collected location points, some of them or even a huge part are missing.
- Plugin doesn't react to the start of the movement and does not collect any location event points.
- These same situations were also observed when testing with minimised ant terminated app.
- These same situations were also observed on different iPhone models.

## Context
Our goal is to configure this plugin to achieve the following : 
Plugin would notice when the device is moving and distinguishes the difference between "walking" and "in_vehicle".
Then, on each onLocation event we would check if the motion type is "in_vehicle" and collect location points using "react-native-fs" lib with a custom function that is called on each onLocation event.
Plugin would notice the stationary state and stop tracking using the provided parameters to BackgroundGeolocation.ready() method

## Debug logs
<!-- include iOS / Android logs
- ios XCode logs,
- use #getLog #emailLog methods (@see docs)
- Android: $ adb logcat -s TSLocationManager
-->
<details><summary>Logs</summary>

``` <!-- Syntax highlighting:  DO NOT REMOVE -->
{
    "activityRecognitionInterval": 10000,
    "allowIdenticalLocations": false,
    "autoSync": true,
    "autoSyncThreshold": 0,
    "batchSync": false,
    "debug": false,
    "deferTime": 0,
    "desiredAccuracy": -1,
    "desiredOdometerAccuracy": 100,
    "disableElasticity": false,
    "disableLocationAuthorizationAlert": false,
    "disableStopDetection": false,
    "distanceFilter": 1,
    "elasticityMultiplier": 1,
    "enableHeadless": true,
    "enableTimestampMeta": false,
    "extras": {},
    "fastestLocationUpdateInterval": -1,
    "forceReloadOnBoot": false,
    "forceReloadOnGeofence": false,
    "forceReloadOnHeartbeat": false,
    "forceReloadOnLocationChange": false,
    "forceReloadOnMotionChange": false,
    "forceReloadOnSchedule": false,
    "foregroundService": true,
    "geofenceInitialTriggerEntry": true,
    "geofenceModeHighAccuracy": false,
    "geofenceProximityRadius": 1000,
    "geofenceTemplate": "",
    "headers": {
        "X-FOO": "bar"
    },
    "headlessJobService": "com.transistorsoft.rnbackgroundgeolocation.HeadlessTask",
    "heartbeatInterval": -1,
    "httpRootProperty": "location",
    "httpTimeout": 60000,
    "isMoving": false,
    "locationTemplate": "",
    "locationTimeout": 60,
    "locationUpdateInterval": 1000,
    "locationsOrderDirection": "ASC",
    "logLevel": 5,
    "logMaxDays": 3,
    "maxBatchSize": -1,
    "maxDaysToPersist": 1,
    "maxRecordsToPersist": -1,
    "method": "POST",
    "minimumActivityRecognitionConfidence": 75,
    "notification": {
        "layout": "",
        "title": "",
        "text": "Location Service activated",
        "color": "",
        "channelName": "TSLocationManager",
        "smallIcon": "",
        "largeIcon": "",
        "priority": 0,
        "strings": {},
        "actions": []
    },
    "params": {
        "auth_token": "maybe_your_server_authenticates_via_token_YES?"
    },
    "persist": true,
    "persistMode": 2,
    "schedule": [],
    "scheduleUseAlarmManager": false,
    "speedJumpFilter": 300,
    "startOnBoot": true,
    "stationaryRadius": 25,
    "stopAfterElapsedMinutes": 0,
    "stopOnStationary": false,
    "stopOnTerminate": false,
    "stopTimeout": 4,
    "triggerActivities": "in_vehicle, on_bicycle, on_foot, running, walking",
    "url": "http:\/\/yourserver.com\/locations",
    "useSignificantChangesOnly": false,
    "enabled": true,
    "schedulerEnabled": false,
    "trackingMode": 1,
    "odometer": 13905.810546875,
    "isFirstBoot": true
}

background-geolocation.log.gz

christocracy commented 4 years ago

No such Plugin version: 5.5.1

Martyneris commented 4 years ago

My bad, its 3.0.7

christocracy commented 4 years ago

It’s a bad idea to rely on JavaScript #onLocation event for uploading locations. Use the plugin’s built-in http service. Search docs “HTTP Guide”.

And if you insist on doing that, you’d want to execute that within a background-task (search docs #startBackgroundTask).

I field-test almost daily with the sample app on 11 Android devices running uninterrupted for weeks at a time with perfect tracking behavior. iOS is always perfect too.

Martyneris commented 4 years ago

I've checked out the HTTP guide, but its not clear how would I go on about collecting only "in_vehicle" location points as it seems that HTTP event returns only the status information.

Our custom function that is being fired upon each #onLocation event is kind of similar to your function addEvent in the app demo, that is also being used on each #onLocation event and is used for forming the info object about the location. Are you suggesting I would need to try using #startBackgroundTask in #onLocation event and put our custom logic inside #startBackgroundTask ?

christocracy commented 4 years ago

You might be interested in the Android-only option triggerActivities

Martyneris commented 4 years ago

Yeah, I have tried that one, using only the "in_vehicle" activity, but the problem was, that the plugin did not always catch it and even though we were driving during testing with different devices, the event was not fired and the trips were not recorded.

Could you maybe share your #BackgroundGeolocation.ready() . that you mentioned before, which works perfectly? I would then try and build our custom logic on it.

Martyneris commented 4 years ago

Also, one of the main problems that were noticed was that the plugin does not notice the change in stationary/moving and does not start recording the trip from the background. This is really important, as we do not want our users to have to open the app themselves. Rather, its needed that the app would be able to start tracking, record locations and stop recording by itself, from the background. How could we achieve this as precise as possible?

christocracy commented 4 years ago

The plugin relies upon “black box” device motion APIs provided by Google Play Services. This api aggregates multiple sensors, including accelerometer, gyroscope, magnetometer, to come up the suspected device motion activity (in_vehicle, still, walking, etc).

If your device is missing sensors or the device manufacturer uses low quality sensors, the device motion api will emit low quality data.

The plugin can do nothing to improve the quality of events emitted by the motion api.

I field-test almost daily on 11 Android devices which all perform well, accept for Huawei.

Martyneris commented 4 years ago

Aright, we also tested on Sony, Samsung and Huawei, all with unpredictable results. Could you please share the #BackgroundGeolocation.ready() configuration that you are using?

christocracy commented 4 years ago

There's nothing wrong with your config. There is no config option that would result in "unpredictable results".

Have observed your logs? Why are you providing an invalid url: 'http://yourserver.com/locations'?

  ⚠️  Response: 0, unexpected end of stream on Connection{yourserver.com:80, proxy=DIRECT hostAddress=yourserver.com/141.8.224.195:80 cipherSuite=none protocol=http/1.1}
Martyneris commented 4 years ago

I've just left it from the example provided in starting guide. Does it have anything to do with the situation when the plugin does not work from the background/terminated app ? In earlier builds we weren't using HTTP url and its params and the results were similar.

christocracy commented 4 years ago

I've just left it from the example provided in starting guide

Don't. As you can see in the logs, the plugin is attempting to execute HTTP requests, consuming power unnecessarily. However, it has nothing to do with your problem.

Have you tried building and installing the Sample App on your device?

Martyneris commented 4 years ago

Understood. Nope, will do it now and test it out.

Martyneris commented 4 years ago

@christocracy Hello,

So we've tested the HTTP requests as per your advice and it works pretty smoothly most of the time. Still, most of the time is not 100% perfect, we've noticed some weird behaviour while testing on iPhoneX a few days before, when a total 4 hours of tracking data was not collected and sent using the HTTP method. The data was simply lost. The movement took part in an downtown urban area, so there were a lot of stops near stop lights and signs, maybe this could've had an impact as to why the plugin stopped tracking?

Could you please take a look at my configuration and provide an advice how could we achieve a 100% tracking with no empty holes like in the described situation ? Maybe we should add a ios exclusive setting to the configuration?

BackgroundGeolocation.ready({
            reset: true,
            desiredAccuracy: BackgroundGeolocation.DESIRED_ACCURACY_HIGH,
            distanceFilter: 1,
            // stopTimeout: 4,
            logLevel: BackgroundGeolocation.LOG_LEVEL_VERBOSE,
            stopOnTerminate: false, 
            startOnBoot: true,
            // enableHeadless: true,
            // preventSuspend: true,
            heartbeatInterval: 60,
            foregroundService: true,
            batchSync: true,
            maxBatchSize: 500,
            autoSync: true,
            autoSyncThreshold: 500,
            url: 'ourserveraddress.com',
            extras: {  
                'uid': uid,
            }
        }, (state) => {
            console.log("- BackgroundGeolocation is configured and ready: ", state.enabled);
            BackgroundGeolocation.startBackgroundTask().then(() => {
                PhoneService.startService();
            });
            BackgroundGeolocation.start();
            if (!state.enabled) {
                ////
                // 3. Start tracking!
                //
                BackgroundGeolocation.start(function () {
                    console.log("- Start success");
                });
            }
        });

Also, if there's a situation that a user would drive without having an internet/wifi connection - what would happen in that case? Would the plugin collect the data and send it out automatically when the internet would come on?

christocracy commented 4 years ago

Your config is fine.

The data was simply lost

The plugin stores hundreds-of-thousands of kilobytes of logs in its internal log database. See Wiki Debugging to learn how to fetch the logs.

Would the plugin collect the data and send it out automatically when the internet would come on?

Yes.

The plugin was originally designed for tracking emergency workers in disaster-zones (hurricanes, earthquakes) where the cell network is likely destroyed. It was designed to automatically upload queued locations in its internal SQLite database as soon as a network connection is re-established.

Martyneris commented 4 years ago

Yesterday we performed a test with two different android phones in the same car and another test with an iPhone. The plugin's behaviour was different in Android phones and part of the trip was not recorded/collected on one of the phones. On Samsung Galaxy S8 the trip was recorded fully. On Huawei P20 there were missing parts of the trip data. I can provide the logs from both of these phones.

On iPhone the plugin did not record the first trip of the day, but recorded all other locations later during the day almost perfectly. In total there were ~14km of lost location points and there isn't an obvious reason for why it might be happening. The plugin did not start tracking the first ~10kms in the morning and later in the afternoon, when the car stopped near the traffic light, the plugin did not start recording again for a few kilometers, resulting in more lost location points. I can provide the log from this iPhone.

Also, there was an issue noticed about stopping near traffic lights (both on Android and iOS) - it seems that sometimes when the device is not moving for a short period of time (30s waiting for a green light, for example) it then takes a longer time/distance for the plugin to start tracking again (it sometimes takes the plugin a couple of minutes or 2-3 kilometers to start tracking again).

Is there a way, setting or a prop that we could use to make the plugin even more sensitive and collect 100% of the movement?

christocracy commented 4 years ago

On iPhone the plugin did not record the first trip of the day

I've been testing on iOS for over 5 years. I run the sample app untouched for weeks at time and it never fails to initiate tracking. I regularly monitor my results at http://tracker.transistorsoft.com. It's either a device settings issue (eg: ensure Wifi enabled) or environment issue (rural vs urban environment).

As for Android, I field-test almost daily on the following devices. Huawei P20 almost never works because the OS aggressively kills background processes, which it's not supposed to do.

when the car stopped near the traffic light,

I suggest you increase your stopTimeout accordingly and keep doing more tests.

christocracy commented 4 years ago

and see http://dontkillmyapp.com

Martyneris commented 4 years ago

do you think iOS issue could maybe be related to location permissions that user chooses? Is there a difference between choosing "always" and "only when app in use" for the plugin? Im asking because it seems that inconsistencies with iOS tracking are still happening in our tests - portions or even whole trips dont get recorded.

christocracy commented 4 years ago

If your users selects "When In Use", the plugin cannot automatically engage tracking in the background. "When In Use" is for something like a Jogging App where the UI presents [Start Workout] / [Stop Workout] buttons. When user clicks [Start Workout], you execute BackgroundGeolocation.changePace(true). When the app goes to background, iOS will show the "Blue Bar" and tracking will run in the background.

1602 commented 4 years ago

I've been experiencing similarly looking issue with plugin version 3.0.4.

My app uses onLocation event in the background task for offline-only mode (trying to get app version for users not willing to upload their location to the server). I noticed that onLocation stops reporting event after app not being opened for a while (a day of two).

At the same time http sync reports location fine, so plugin works, just not my attempt to use it offline. I've recently upgraded to current latest version (3.3.2) with hope that this behaviour will be gone. I field-tested it after 20 hours idle period and it works fine for now, so there's a chance onLocation being triggered correctly in this version. Try updating to latest version, I hope it helps.

And a related question to @christocracy: what is the right way of using plugin for offline-only mode, when I just want each location recorded in the background without it being reported to http service. Basically the same way as it works with http backend, but only locally on mobile device of a user.

christocracy commented 4 years ago

what is the right way of using plugin for offline-only mode

Just set the url: "". The plugin persists each recorded location in its SQLite database, accessible via BackgroundGeolocation.getLocations

stale[bot] commented 4 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. You may also mark this issue as a "discussion" and I will leave this open.

stale[bot] commented 4 years ago

Closing this issue after a prolonged period of inactivity. Fell free to reopen this issue, if this still affecting you.