How to quickly get GPS location update without googles fusedLocationClient - huawei-mobile-services

Can we use the GPS antenna in way that we can control the polling speed? like every 10 seconds and every 1 second. Assume the power usage is manageable. I just want to get accurate location asap.

We can use Android default LocationManager class which is not google API.
And also Huawei Mapkit LocationManager
val locationManager = HuaweiLocationManager(this) locationManager.registerLocationUpdates(onSuccess = { viewModel.updateCurrentLocation(it) })
and subscribe to update in different speed.
fun registerLocationUpdates(
interval: Long = 10000,
onSuccess: ((location: Location?) -> Unit)? = null,
onFail: ((locationAvailability: LocationAvailability?) -> Unit)? = null
) {
val mLocationRequest = LocationRequest()
// Set the location update interval (in milliseconds).
mLocationRequest.interval = interval
// Set the weight.
mLocationRequest.priority = LocationRequest.PRIORITY_HIGH_ACCURACY
// Create location callback
if (mLocationCallback == null)
mLocationCallback = createLocationCallback(onSuccess, onFail)
// Request location update
mFusedLocationProviderClient.requestLocationUpdates(
mLocationRequest,
mLocationCallback,
Looper.getMainLooper()
)
.addOnSuccessListener {
// Requesting location updates is started successfully.
LogUtils.d("LocationKit -> Start Location Updates Successfully")
}
.addOnFailureListener { exception ->
// Failed to start location updates.
LogUtils.d("LocationKit -> Start Location Updates Failed with exception: ${exception.localizedMessage}")
}
}

Related

AltBeacon - background scan configuration

Sorry for my English :)
I have no idea how configure scanner to work properly in background (using ScanJob). I noticed that if the ScanJob starts more than 15 minutes after the previous scan is finished, a passive scan occurs even though there are beacons nearby. The reason for this is that the max age of the region is set to 15 minutes and the region is not restored after ScanJob starts. For now, I do so that after the scanner returns the results, I check if the list from monitoring regions is not empty and if it is, I do
if(beaconManager.monitoredRegions.isEmpty()) {
beaconManager.startRangingBeacons (region)
beaconManager.startMonitoring (region)
}
to set the region again. If I do not do this, passive scan starts every ScanJob stops .
If I invoke
beaconManager.startRangingBeacons (region)
beaconManager.startMonitoring (region)
each time if application starts then ScanJob is canceled immediately.
I wonder if there is any pattern to the background scan setup?
Maybe just remove condition in MonitoringStatus class?
if (millisSinceLastMonitor> MAX_STATUS_PRESERVATION_FILE_AGE_TO_RESTORE_SECS * 1000) {
LogManager.d (TAG, "Not restoring monitoring state because it was recorded too many milliseconds ago:" + millisSinceLastMonitor);
}
The scan job strategy is the default way the Android Beacon Library operates. It uses Android scheduled jobs to schedule scans and requires no configuration:
val beaconManager = BeaconManager.getInstanceForApplication(this)
val region = Region("all-beacons-region", null, null, beaconManager.startMonitoring(region)
beaconManager.startRangingBeacons(region)
In the foreground, scanning is constant and ranging callbacks come every ~1 sec.
In the background, scans are scheduled only once every ~15 minutes due to Android 8+ job scheduling limitations, so ranging callbacks come at that frequency. It is not designed for constant background scanning.
For constant background scanning you may configure the library to use a foreground service or the intent scan strategy.
I've wrote an simple application.
App: Aplication looks like this:
class App: Application(), MonitorNotifier {
val beaconManager by lazy { BeaconManager.getInstanceForApplication(this) }
val parser: BeaconParser =
BeaconParser().setBeaconLayout("m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24")
override fun onCreate() {
BeaconManager.setDebug(true)
super.onCreate()
beaconManager.beaconParsers.add(parser)
beaconManager.addMonitorNotifier(this)
beaconManager.addRangeNotifier { mutableCollection: MutableCollection<Beacon>, region: Region ->
}
}
override fun didEnterRegion(region: Region?) {
}
override fun didExitRegion(region: Region?) {
}
override fun didDetermineStateForRegion(state: Int, region: Region?) {
}
}
MainActivity looks like this:
class MainActivity : AppCompatActivity() {
val region = Region("all-beacons-region", null, null, null)
val beaconManager by lazy { BeaconManager.getInstanceForApplication(this) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
override fun onResume() {
super.onResume()
beaconManager.startRangingBeacons(region)
beaconManager.startMonitoring(region)
}
}
in this app configuration, two cases are possible with the assumption that there are beacons nearby
case 1. ScanJob has been started up to 15 minutes after the last scan. After finishing work, you can see in the logs
ScanJob: Checking to see if we need to start a passive scan
ScanJob: We are inside a beacon region. We will not scan between cycles.
and that is correct behavior
case 2. ScanJob has been started more than 15 minutes after the last scan. After finishing work, you will see in the logs:
ScanJob: Checking to see if we need to start a passive scan
it means, the passive scan has started. After a while, StartupBroadcastReceiver is triggered with the results of the passive scan. ScanJob starts to process the passive scan results. After finishing work, you will see in the logs:
ScanJob: Checking to see if we need to start a passive scan
it means, the passive scan has been started again and after a while the StartupBroadcastRetuver will be triggered again with the results of the passive scan. And so it will be over and over again for a while
when you start MainActivity again and call
beaconManager.startRangingBeacons(region)
beaconManager.startMonitoring(region)
In my opinion it is caused by the fact that monitoring state is not restored after 15 minutes from the last scan in the MonitoringStatus class (condition below)
else if (millisSinceLastMonitor > MAX_STATUS_PRESERVATION_FILE_AGE_TO_RESTORE_SECS * 1000) {
LogManager.d(TAG, "Not restoring monitoring state because it was recorded too many milliseconds ago: "+millisSinceLastMonitor);
}
and after the scan is complete, the method is called
private void startPassiveScanIfNeeded() {
if (mScanState != null) {
LogManager.d(TAG, "Checking to see if we need to start a passive scan");
boolean insideAnyRegion = mScanState.getMonitoringStatus().insideAnyRegion();
if (insideAnyRegion) {
// TODO: Set up a scan filter for not detecting a beacon pattern
LogManager.i(TAG, "We are inside a beacon region. We will not scan between cycles.");
}
else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (mScanHelper != null) {
mScanHelper.startAndroidOBackgroundScan(mScanState.getBeaconParsers());
}
}
else {
LogManager.d(TAG, "This is not Android O. No scanning between cycles when using ScanJob");
}
}
}
}
and insideAnyRegion is false because the monitoring state has not been restored, so passive scanning is started even though the beacons are nearby.
To get this case up pretty quickly, I suggest you set MAX_STATUS_PRESERVATION_FILE_AGE_TO_RESTORE_SECS = 1, then when ScanJob start for the first time, you will see what I mean.
The operating system does not always start ScanJob after 15 minutes
protected void updateMonitoringStatusTime(long time) {
File file = mContext.getFileStreamPath(STATUS_PRESERVATION_FILE_NAME);
file.setLastModified(time);
restoreMonitoringStatusIfNeeded();
}
private void restoreMonitoringStatusIfNeeded() {
if(mRegionsStatesMap.isEmpty()){
restoreMonitoringStatus();
}
}

Invoke a method in flutter after restarting the device

I build a to-do list app that should show notifications to remind about tasks. In order to be able to schedule the notifications to the exact minute of the deadline, I pass the notifications data from flutter to kotlin, and showing the notification from a Broadcast receiver.
Here I am sending the notifications data to kotlin:
await platform.invokeMethod('setNextNotification', {
'tasksNames': tasksNames,
'notificationsTimeInMillis': notificationsTimeInMillis
});
This is how I get the data inside FlutterActivity:
private const val CHANNEL = "flutter.native/helper"
class MainActivity : FlutterActivity() {
companion object {
const val TASKS_NAMES_EXTRA = "tasksNames"
const val NOTIFICATIONS_TIME_IN_MILLIS_EXTRA = "notificationsTimeInMillis"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
GeneratedPluginRegistrant.registerWith(this)
// Init the AlarmManager.
val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
// We got here from the setNotifications() method in flutter...
MethodChannel(flutterView, CHANNEL).setMethodCallHandler { call, result ->
if (call.method == "setNextNotification") {
// Get the time till next notification
val notificationsTimeInMillis: ArrayList<Long> = call.argument(NOTIFICATIONS_TIME_IN_MILLIS_EXTRA)
?: ArrayList()
// Create a pending intent for the notifications
val pIntent: PendingIntent? = createPendingIntent(call.argument(TASKS_NAMES_EXTRA), call.argument(TIME_LEFT_TEXTS_EXTRA), notificationsTimeInMillis, this)
// Cancel all alarms
while (alarmManager.nextAlarmClock != null)
alarmManager.cancel(alarmManager.nextAlarmClock.showIntent)
// Set the alarm
setAlarm(notificationsTimeInMillis[0], pIntent, alarmManager)
}
}
}
private fun setAlarm(notificationTime: Long, pIntent: PendingIntent?, alarmManager: AlarmManager) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // The API is 23 or higher...
alarmManager.setAlarmClock(AlarmManager.AlarmClockInfo(notificationTime, pIntent), pIntent)
} else { // The API is 19 - 22...
// We want the alarm to go of on the exact time it scheduled for so we use the setExact method.
alarmManager.setExact(AlarmManager.RTC_WAKEUP, notificationTime, pIntent)
}
}
private fun createPendingIntent(tasksNames: ArrayList<String>?, timeTillNotificationsInMillis: ArrayList<Long>?,
context: Context): android.app.PendingIntent? {
return try {
val intent: android.content.Intent = android.content.Intent(context, AlarmManagerHelperWakeful::class.java)
intent.action = "notification"
intent.putStringArrayListExtra(TASKS_NAMES_EXTRA, tasksNames)
intent.putStringArrayListExtra(NOTIFICATIONS_TIME_IN_MILLIS_EXTRA, timeTillNotificationsInMillisAsString)
android.app.PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
} catch (e: java.lang.Exception) {
null
}
}
}
And this is how I show the notification on the BroadcastReceiver, and after that sets the next notification:
Class AlarmManagerHelperWakeful : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (intent != null && intent.action == "notification" && context != null) {
val tasksLabels: ArrayList<String> = intent.getStringArrayListExtra(MainActivity.TASKS_NAMES_EXTRA)
?: ArrayList()
val notificationsTimeInMillisAsString: ArrayList<String> = intent.getStringArrayListExtra(MainActivity.NOTIFICATIONS_TIME_IN_MILLIS_EXTRA)
?: ArrayList()
if (tasksLabels.size > 0) {
// Create a notification manager.
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
var builder = NotificationCompat.Builder(context) // The initialization is for api 25 or lower so it is deprecated.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { // This is API 26 or higher...
// Create a channel for API 26 or higher;
val channelId = "channel_01" // The id of the channel.
if (notificationManager.getNotificationChannel(channelId) == null) {
val channel = NotificationChannel(channelId,
context.getString(R.string.notification_channel_name),
NotificationManager.IMPORTANCE_DEFAULT)
notificationManager.createNotificationChannel(channel)
}
// Update the builder to a no deprecated one.
builder = NotificationCompat.Builder(context, channelId)
}
// Set the notification details.
builder.setSmallIcon(android.R.drawable.ic_notification_overlay)
builder.setContentTitle(tasksLabels[0])
builder.setContentText(someText)
builder.priority = NotificationCompat.PRIORITY_DEFAULT
notificationId = someUniqueId
// Show the notification.
notificationManager.notify(notificationId.toInt(), builder.build())
// Remove this notification from the notifications lists.
tasksLabels.removeAt(0)
notificationsTimeInMillisAsString.removeAt(0)
// There are more notifications...
if (tasksLabels.size > 0) {
// Init the AlarmManager.
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
// Cancel all alarms
while (alarmManager.nextAlarmClock != null)
alarmManager.cancel(alarmManager.nextAlarmClock.showIntent)
// Create a pending intent for the notifications
val pIntent: PendingIntent? = createPendingIntent(tasksLabels, cnotificationsTimeInMillisAsString, context)
// Set the alarm
setAlarm(notificationsTimeInMillisAsString[0].toLong(), pIntent, alarmManager)
}
}
} else {
if (intent == null) {
Log.d("Debug", "Checking: intent == null")
} else if ( intent.action != "notification") {
Log.d("Debug", "Checking: intent.action != notification")
val tasksLabels: ArrayList<String> = intent.getStringArrayListExtra(MainActivity.TASKS_NAMES_EXTRA)
?: ArrayList()
Log.d("Debug", "Checking: tasksNames.size inside else if" + tasksLabels.size)
}
}
}
}
Everything works perfectly fine unless I restart my device. Then the Broadcast receiver gets an intent without any data. For the BoradcastReceiver to get intent with notifications data, I need to invoke the method from the flutter code (the method that sends notifications data to the kotlin code), which means that for now, the user has to enter the app for that. Otherwise, user will not see notifications until entering my app and reinvoking the flutter code.
How can I overcome that issue?
You should use Push Notification instead of sending local notification to your broadcast receiver. There're lot of cases that make your app cannot send local notification. ex: user closed app (lot of users alway close app after using), OS closed app or clean memory, Dart method is crashing.
Firebase FCM is pretty simple, it's much more simple than you solution that's using broadcast receiver. Also totally free.
https://pub.dev/packages/firebase_messaging
Pushwoosh is good too, it has schedule notification
https://pub.dev/packages/pushwoosh
Using push notification also has other pros that you app will also work on iOS, doesn't need to keep your app running on background which is very bad idea if your app doesn't have any special features that need to run background (music player, geo location, VOIP)
In case you don't want to use Push Notification. Take a look at this lib:
https://pub.dev/packages/flutter_local_notifications
Right now you are sending data from dart to the native plugin. You might try it the other way around. This example shows how you can get the native android reboot event. Next, you can use this example to fetch the required data. After fetching the data you can set the notification.
You could also try to store the information of the last notification in SharedPreferences, fetch that on boot, and set the notification.

OPC UA Client capture the lost item values from the UA server after a disconnect/connection error?

I am building a OPC UA Client using OPC Foundation SDK. I am able to create a subscription containing some Monitoreditems.
On the OPC UA server these monitored items change value constantly (every second or so).
I want to disconnect the client (simulate a connection broken ), keep the subcription alive and wait for a while. Then I reconnect having my subscriptions back, but I also want all the monitored Item values queued up during the disconnect. Right now I only get the last server value on reconnect.
I am setting a queuesize:
monitoredItem.QueueSize = 100;
To kind of simulate a connection error I have set the "delete subscription" to false on ClosesSession:
m_session.CloseSession(new RequestHeader(), false);
My question is how to capture the content of the queue after a disconnect/connection error???
Should the ‘lost values’ be “new MonitoredItem_Notification” automatically when the client reconnect?
Should the SubscriptionId be the same as before the connection was broken?
Should the sessionId be the same or will a new SessionId let med keep the existing subscriptions? What is the best way to simulate a connection error?
Many questions :-)
A sample from the code where I create the subscription containing some MonitoredItems and the MonitoredItem_Notification event method.
Any OPC UA Guru out there??
if (node.Displayname == "node to monitor")
{
MonitoredItem mon = CreateMonitoredItem((NodeId)node.reference.NodeId, node.Displayname);
m_subscription.AddItem(mon);
m_subscription.ApplyChanges();
}
private MonitoredItem CreateMonitoredItem(NodeId nodeId, string displayName)
{
if (m_subscription == null)
{
m_subscription = new Subscription(m_session.DefaultSubscription);
m_subscription.PublishingEnabled = true;
m_subscription.PublishingInterval = 3000;//1000;
m_subscription.KeepAliveCount = 10;
m_subscription.LifetimeCount = 10;
m_subscription.MaxNotificationsPerPublish = 1000;
m_subscription.Priority = 100;
bool cache = m_subscription.DisableMonitoredItemCache;
m_session.AddSubscription(m_subscription);
m_subscription.Create();
}
// add the new monitored item.
MonitoredItem monitoredItem = new MonitoredItem(m_subscription.DefaultItem);
//Each time a monitored item is sampled, the server evaluates the sample using a filter defined for each monitoreditem.
//The server uses the filter to determine if the sample should be reported. The type of filter is dependent on the type of item.
//DataChangeFilter for Variable, Eventfilter when monitoring Events. etc
//MonitoringFilter f = new MonitoringFilter();
//DataChangeFilter f = new DataChangeFilter();
//f.DeadbandValue
monitoredItem.StartNodeId = nodeId;
monitoredItem.AttributeId = Attributes.Value;
monitoredItem.DisplayName = displayName;
//Disabled, Sampling, (Report (includes sampling))
monitoredItem.MonitoringMode = MonitoringMode.Reporting;
//How often the Client wish to check for new values on the server. Must be 0 if item is an event.
//If a negative number the SamplingInterval is set equal to the PublishingInterval (inherited)
//The Subscriptions KeepAliveCount should always be longer than the SamplingInterval/PublishingInterval
monitoredItem.SamplingInterval = 500;
//Number of samples stored on the server between each reporting
monitoredItem.QueueSize = 100;
monitoredItem.DiscardOldest = true;//Discard oldest values when full
monitoredItem.CacheQueueSize = 100;
monitoredItem.Notification += m_MonitoredItem_Notification;
if (ServiceResult.IsBad(monitoredItem.Status.Error))
{
return null;
}
return monitoredItem;
}
private void MonitoredItem_Notification(MonitoredItem monitoredItem, MonitoredItemNotificationEventArgs e)
{
if (this.InvokeRequired)
{
this.BeginInvoke(new MonitoredItemNotificationEventHandler(MonitoredItem_Notification), monitoredItem, e);
return;
}
try
{
if (m_session == null)
{
return;
}
MonitoredItemNotification notification = e.NotificationValue as MonitoredItemNotification;
if (notification == null)
{
return;
}
string sess = m_session.SessionId.Identifier.ToString();
string s = string.Format(" MonitoredItem: {0}\t Value: {1}\t Status: {2}\t SourceTimeStamp: {3}", monitoredItem.DisplayName, (notification.Value.WrappedValue.ToString().Length == 1) ? notification.Value.WrappedValue.ToString() : notification.Value.WrappedValue.ToString(), notification.Value.StatusCode.ToString(), notification.Value.SourceTimestamp.ToLocalTime().ToString("HH:mm:ss.fff"));
richTextBox1.AppendText(s + "SessionId: " + sess);
}
catch (Exception exception)
{
ClientUtils.HandleException(this.Text, exception);
}
}e here
I don't know how much of this, if any, the SDK you're using does for you, but the approach when reconnecting is generally:
try to resume (re-activate) your old session. If this is successful your subscriptions will already exist and all you need to do is send more PublishRequests. Since you're trying to test by closing the session this probably won't work.
create a new session and then call the TransferSubscription service to transfer the previous subscriptions to your new session. You can then start sending PublishRequests and you'll get the queued notifications.
Again, depending on the stack/SDK/toolkit you're using some or none of this may be handled for you.

How to process all events emitted by RX Java regardless of error?

I'm using vertx.io web framework to send a list of items to a downstream HTTP server.
records.records() emits 4 records and I have specifically set the web client to connect to the wrong I.P/port.
Processing... prints 4 times.
Exception outer! prints 3 times.
If I put back the proper I.P/port then Susbscribe outer! prints 4 times.
io.reactivex.Flowable
.fromIterable(records.records())
.flatMap(inRecord -> {
System.out.println("Processing...");
// Do stuff here....
Observable<Buffer> bodyBuffer = Observable.just(Buffer.buffer(...));
Single<HttpResponse<Buffer>> request = client
.post(..., ..., ...)
.rxSendStream(bodyBuffer);
return request.toFlowable();
})
.subscribe(record -> {
System.out.println("Subscribe outer!");
}, ex -> {
System.out.println("Exception outer! " + ex.getMessage());
});
UPDATE:
I now understand that on error RX stops right a way. Is there a way to continue and process all records regardless and get an error for each?
Given this article: https://medium.com/#jagsaund/5-not-so-obvious-things-about-rxjava-c388bd19efbc
I have come up with this... Unless you see something wrong with this?
io.reactivex.Flowable
.fromIterable(records.records())
.flatMap
(inRecord -> {
Observable<Buffer> bodyBuffer = Observable.just(Buffer.buffer(inRecord.toString()));
Single<HttpResponse<Buffer>> request = client
.post("xxxxxx", "xxxxxx", "xxxxxx")
.rxSendStream(bodyBuffer);
// So we can capture how long each request took.
final long startTime = System.currentTimeMillis();
return request.toFlowable()
.doOnNext(response -> {
// Capture total time and print it with the logs. Removed below for brevity.
long processTimeMs = System.currentTimeMillis() - startTime;
int status = response.statusCode();
if(status == 200)
logger.info("Success!");
else
logger.error("Failed!");
}).doOnError(ex -> {
long processTimeMs = System.currentTimeMillis() - startTime;
logger.error("Failed! Exception.", ex);
}).doOnTerminate(() -> {
// Do some extra stuff here...
}).onErrorResumeNext(Flowable.empty()); // This will allow us to continue.
}
).subscribe(); // Don't handle here. We subscribe to the inner events.
Is there a way to continue and process all records regardless and get
an error for each?
According to the doc, the observable should be terminated if it encounters an error. So you can't get each error in onError.
You can use onErrorReturn or onErrorResumeNext() to tell the upstream what to do if it encounters an error (e.g. emit null or Flowable.empty()).

Google Nest subscriptionWithMessageFoundHandler fires once?

I am trying to code around the fact that the messageLostHandler doesn't fire for many minutes after a device is out of range using Audio (or Earshot for Android).
I was hoping that every few secs a message would be received from another device. It fires once. Is this expected? Since I can't rely on the messageLost handler - how do I know when a device is truly out of range of the ultrasonic?
I coded up a timer after receiving the subscriptionWithMessageFoundHandler hoping another message coming in I could just invalidate or restart the timer. If the timer fired, I'd know that x seconds passed and that the other device must be out of range. No such luck.
UPDATE: Here is the code in question:
let strategy = GNSStrategy.init(paramsBlock: { (params: GNSStrategyParams!) -> Void in
params.discoveryMediums = .Audio
})
publication = messageMgr.publicationWithMessage(pubMessage, paramsBlock: { (pubParams: GNSPublicationParams!) in
pubParams.strategy = strategy
})
subscription = messageMgr.subscriptionWithMessageFoundHandler({[unowned self] (message: GNSMessage!) -> Void in
self.messageViewController.addMessage(String(data: message.content, encoding:NSUTF8StringEncoding))
// We only seem to get a 1x notification of a message. So this timer is folly.
print("PING") //Only 1x per discovery.
}, messageLostHandler: {[unowned self](message: GNSMessage!) -> Void in
self.messageViewController.removeMessage(String(data: message.content, encoding: NSUTF8StringEncoding))
}, paramsBlock: { (subParams: GNSSubscriptionParams!) -> Void in
subParams.strategy = strategy
})
Notice that the "PING" only prints once.
When a device goes out of range, Nearby waits for 2 minutes before flushing the other device's token from its cache. So if you wait for 2 minutes, the messageLost handler should be called. Can you verify this? Also, is it safe to assume that you'd like to have a timeout shorter than 2 minutes? This timeout has been a topic of discussion, and there's been some talk of adding a parameter so apps can choose a value that's more appropriate for its use case.