How to connect to Ble device without rescan and manual device selection - ionic-framework

I'm creating an Ionic react (TypeScript) app which uses the Community Bluetooth-le plugin.
When I try to connect to a device using requestDevice this shows the available devices and I can then pair/connect with that device and all is good.
await BleClient.initialize();
if (isAndroid) {
await BleClient.disconnect(devices.deviceId);
}
const device = await BleClient.requestDevice({
services: services ? services : [],
optionalServices: optionalServices ? optionalServices : [],
namePrefix: prefixFilter ? prefixFilter : "",
});
await BleClient.connect(device.deviceId, (deviceId) => onDisconnect(deviceId));
await BleClient.getServices(device.deviceId).then(
(services) => {
if (services[0]) {
//....
} else {
//....
}
}
)
However, if I save the device ID and then try to directly connect with that device using getDevices (rather than scanning and manually connecting) it always fails with the following console output:
Uncaught (in promise) Error: Device not found. Call "requestDevice", "requestLEScan" or "getDevices" first.
The code I use is this:
await BleClient.initialize();
if (isAndroid) {
await BleClient.disconnect(devices.deviceId);
}
await BleClient.getDevices(devices.deviceId);
await BleClient.connect(devices.deviceId, (deviceId) => onDisconnect(deviceId));
For clarification: I want to be able to search for available devices and connect to the device the first time the app is opened. Then, save the device ID and use this ID to connect to the device directly using getDevices from that point onwards. Likewise if the app is closed and re-opened I need to be able to take the stored device data and connect with that device directly without the whole scan and manual selection process.
I don't understand what I'm missing.

I assume Device-1 (app) is scanning and Device-2 is advertising. Try to bond the devices after first time connection. This allow you to connect it automatically without scanning next time.
Make sure the Device-2 is in connectable mode after getting disconnected from Device-1.
EDIT-1
For example I am using a generic app called nRF connect and a smart watch. In this app we can scan and connect with any BLE device. I am following below steps:
Scan
Connect
Bond (option available under 3 dots at top right corner, refer image)
After third step, device get bonded and later whenever the device is in vicinity you can connect with it, without advertising and scanning. PFA image.
This is the overview of process to be followed, in regards to code you can get many ready examples related to android or iOS. Hope this is helpful!!

Related

BLE iOS - Failed to encrypt the connection, the connection has timed out unexpectedly

I'm using #capacitor-community/bluetooth-le to connect my Ionic 6 APP to a specific BLE device.
In Android everything works fine.
In iOS, first time connects to BLE successfully but next times (after pairing) gives the following error when try to connects: "Failed to encrypt the connection, the connection has timed out unexpectedly."
I have been tried a lot of different approachs: connects after scan. Connects when is scanning. But nothing works. What is strange is in first time everything works fine (after pairing).
Any help?
Unfortunately this is kind of a known issue in iOS. What I think is happening is that because you have already paired/bonded with the device at the OS level, the connection is being re-established at the OS level when the remote BLE device is discoverable. When you are attempting to reconnect to the remote device from your app, a connection is already in place at the OS level which is why it is failing at the app level. Alternatively, maybe the stored keys from the bonding process is causing the rebonding/pairing process to clash. You can confirm the issue by doing the following:-
Go to the iOS settings, Bluetooth, then unpair the device if it exists.
Attempt to reconnect to the device from your iOS app.
Disconnect the device from your iOS app (do not unpair from the Bluetooth settings this time).
Try to connect to the remote device from your Android app (or any other device that you can use apart from the iOS device). If the connection doesn't succeed, it means that the iOS device is still connected to the remote device.
If the connection succeeds from the Android device, try to disconnect and reconnect from the iOS device. If the connection succeeds, you'll know that the issue is with the OS level connection, and if the connection doesn't succeed, you'll know that the issue is with the stored bonding/pairing keys clashing.
As for the solution, I don't think there's a simple and straight-forward one unfortunately. Below is one suggested solution which I found useful in the past (you may need to modify this for your ionic app):-
In some cases, like for HID devices, once a peripheral is bonded, iOS
will automatically connect to it whenever it sees the peripheral
advertising. This behavior occurs independently of any app, and a
peripheral can be connected to an iOS device, but not connected to the
app that originally established the bond. If a bonded peripheral
disconnects from an iOS device and then reconnects at the iOS level,
the app will need to retrieve the peripheral object
(retrieveConnectedPeripherals(with[Services/Identifiers]:) and
explicitly connect again through a CBCentralManager to establish an
app-level connection. To retrieve your device with this method, you
must specify either the Apple-assigned device identifier from the
previously-returned CBPeripheral object or at least one service it
contains.
iOS does not allow developer apps to clear a peripheral’s bonding
status from the cache. To clear a bond, the user must go to the
Bluetooth section of iOS Settings and explicitly “Forget” the
peripheral. It may be helpful to include this information in your
app’s UI if it’ll affect user experience, as most users will not know
this.
You can find more information about this in the links below:-
The ultimate guide to Apple's CoreBluetooth
CoreBluetooth iOS pairing issue
CoreBluetooth pairing/forgetting
Unable to reconnect after cancelling BLE peripheral
In my case after iPhone pairs with peripheral, never connects anymore. What is strange, in my iPhone 6s with iOS 15.5 everythings works fine.
The pugin has this code (it's possible to some is wrong?)
https://github.com/capacitor-community/bluetooth-le/blob/main/ios/Plugin/Plugin.swift
let CONNECTION_TIMEOUT: Double = 10
let DEFAULT_TIMEOUT: Double = 5
#objc func connect(_ call: CAPPluginCall) {
guard self.getDeviceManager(call) != nil else { return }
guard let device = self.getDevice(call, checkConnection: false) else { return }
let timeout = self.getTimeout(call, defaultTimeout: CONNECTION_TIMEOUT)
device.setOnConnected(timeout, {(success, message) -> Void in
if success {
// only resolve after service discovery
call.resolve()
} else {
call.reject(message)
}
})
self.deviceManager?.setOnDisconnected(device, {(_, _) -> Void in
let key = "disconnected|\(device.getId())"
self.notifyListeners(key, data: nil)
})
self.deviceManager?.connect(device, timeout, {(success, message) -> Void in
if success {
log("Connected to peripheral. Waiting for service discovery.")
} else {
call.reject(message)
}
})
}
I already tried another plugins, and the result is the same.

How to create a stream listener that runs in the background across all pages and generates a popup on an event?

I'm creating an app using FlutterBluePlus, but you don't need any knowledge about that to answer my question. It's a more general question about Flutter
In flutterBluePlus there is a class BluetoothDevice which has a state field of type Stream you can use to listen to the connection state of the device.
What I want to do is, right after I make a connection to a Bluetooth device, I want to start listening to the Bluetooth connection state:
_connectStateSub = state.listen((event) { //state is of type Stream<BluetoothDeviceState>
switch (event) {
case BluetoothDeviceState.connected:
break;
case BluetoothDeviceState.disconnected:
case BluetoothDeviceState.disconnecting:
// ----> DISPLAY POPUP SAYING "Lost connection. Trying to reconnect" <----
break;
default:
}
});
This should run right after I make a connection to a Bluetooth device, and it will warn the user about sudden disconnects (for example device out of range). The reconnection happens automatically by FlutterBluePlus. So after a connectingt to a Bluetooth device, initiated from the app, this streamListener should run in the background forever, until a disconnect happens intentionally from the app, then it's cancelled.
How can I make this popup appear anywhere in the app?
I can not do the following:
case BluetoothDeviceState.disconnected:
case BluetoothDeviceState.disconnecting:
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content:Text('Lost connection. Trying to reconnect.'),duration: Duration(seconds: 6),));
That is because the context will no longer be valid when you go to another screen.
Are there any state management solutions I could use to work around this issue?

ionic 4 local notifications when app closed

I need to call my local notifications at a predefined time. This is working when my app is not closed. but once I closed the app, the notification is not working.
have you resolved this issue?
What you can try:
check if your app autostart/notification permission is enable.
Sometimes, depending on your device model, the autostart permission is
disabled by default, so if the application is closed you can not
receive notifications.
I had the same issue before.
Go thru this thread, where one user commented
On Mi4, with or without the every, after the app is exited,
notifications not received.
On Samsung S5, with "every" notifications stopped after the app
exited. Seems to be fine if scheduled without "every".
As stated in katzer
It might be possible that the underlying framework like Ionic is not compatible with the launch process defined by cordova. With the result that the plugin fires the click event on app start before the app is able to listen for the events.
So, what has work for me is to set in app.component.ts:
window.skipLocalNotificationReady = true;
subscribeLocalNotifications() {
this.localNotifications.on('click').subscribe((notification) => {
});
this.localNotifications.on('trigger').subscribe((notification) => {
});
this.localNotifications.fireQueuedEvents();
}
First you have to make your app can run at the background when you close your app.
Second you have to schedule the local notification through the condition you want. E.x: When you don't have wifi connection , push the notification with user
Did you use cordova-plugin-local-notifications?

Open video call flutter

Is there a solution yet for opening the video call function on the native phone in flutter? I have looked at Agora and others and none of them work the way we need them to.
That was rather annoying to research and come up with, here it goes. This is the best I can come up with while keeping high complexity and paid SDK's outside the solution.
First of all, you have to differentiate between the two platforms (iOS/Android) before initiating the video call. Since there's no uniform solution for both platforms AFAIK.
import 'dart:io';
if (Platform.isAndroid) {
// Android Video Call
} else if (Platform.isIOS) {
// iOS Video Call
}
iOS
Install the infamous url_launcher pub.
You'll need to use FaceTime Links (see full iOS URL Scheme Reference here or here)
Text example: facetime:14085551234 this initiates FaceTime video call to 14085551234 (you use email instead of phone number too)
import 'package:url_launcher/url_launcher.dart';
final String url = 'facetime:$phoneNumber';
if (await canLaunch(url)) {
await launch(url);
} else {
throw 'Could not launch $url';
}
This works surprisingly well. In this case you can replace $phoneNumber variable with something like $userEmail variable.
Android
Install android_intent pub
Add CALL_PHONE permission and show its prompt to user if you're using android.intent.action.CALL, or just use android.intent.action.DIAL without the permission.
This is where the problem lies... I tried the following solution and it only worked for regular calls not video calls
import 'package:android_intent/android_intent.dart';
/// This acton calls the user directly via native phone app but requires `CALL_PHONE` permission in _AndroidManifest_.
final callIntentAction = 'android.intent.action.CALL';
/// This action displays native phone app with dial pad open showing the passed phone number intent's argument/extra. Does not require permissions as of Jan2020.
final dialIntentAction = 'android.intent.action.DIAL';
final intentAction = callIntentAction;
AndroidIntent intent = AndroidIntent(
action: intentAction,
data: Uri.encodeFull('tel:$phoneNumber'),
arguments: {
/// KEY: actual phone number to call [source](https://developer.android.com/reference/android/content/Intent.html#EXTRA_PHONE_NUMBER)
/// VALUE: phoneNumber
'android.intent.extra.PHONE_NUMBER': phoneNumber,
/// KEY: [START_CALL_WITH_VIDEO_STATE](https://developer.android.com/reference/android/telecom/TelecomManager.html#EXTRA_START_CALL_WITH_VIDEO_STATE)
/// VALUE: `3` implies [STATE_BIDIRECTIONAL](https://developer.android.com/reference/android/telecom/VideoProfile.html#STATE_BIDIRECTIONAL)
'android.telecom.extra.START_CALL_WITH_VIDEO_STATE': '3',
},
);
await intent.launch();
Error-handling side-note: unfortunately with android_intent pub there's no error handling or "canOpen" method like url_launcher.
Your problem still lies with Android as there's no native general-purpose video-call app.
You have a couple of options:
A. You can link with your app a video-calling SDK/capability either third-party or your own. (like flutter_webrtc, agora_flutter_webrtc, SightCall, quickblox). This has the downside that the callee has to be using the same software i.e. your app has to be installed on callee's device. This approach is more future-proof. Note I'm not affiliated with any of the libraries I mentioned.
B. You can make a platform method for Android to go over a defined set of intents and check the package name of known video-calling apps with the extra/arguments they require. You'd have to check the list of intents one by one and see which applies and resolves correctly. For apps like Google Duo, Whatsapp, Skype, etc.... This is EXTREMELY prone to errors. As explained here.

CastVideos-ios demo fails to connect (GCKError Code=20 "Application was not found")

I'm trying to get ChromeCast working in my swift app using google-cast-sdk. When I found it was not working, I tried the demo app (CastVideos-ios) and got the exact same results (ie. does not connect).
I changed the bundle identifier of the CastVideos-Swift app to my bundle ID so I could run it on my device. I double-checked my App ID in the iOS Dev portal has the WiFi capability enabled, and of course it is enabled in the Xcode project file in both the CastVideos-swift demo, and in my own app.
Additionally, I have verified many times that my iPhone is on the same WiFI network as the ChromeCast device connected to my TV. I have tested the ChromeCast device using the YouTube App - works perfectly. When I tap the ChromeCast button in the YouTube app, it just throws to my "Living Room TV" - doesn't even ask me, I guess because there is only 1 CC device.
When I run the CastVideos-swift demo app (or my app) and I tap the ChromeCast button, I get a popup asking me to choose, and there is ONLY one option "Nearby Device" - from what I read, this seems to indicate that there is already a problem, that the app is treating my CC device like a "Guest" as if it were on a different WiFi, but its not (and again, YouTube app works) - so, I tap Nearby Devices and the Connect fails so I put in the code. Nothing happens in the app or TV, the debugger shows this error:
[GCKCastDeviceController notifyDidFailToConnectToApplicationWithError:], message: Failed to connect to app with error: Error Domain=com.google.cast.GCKError Code=20 "Application was not found" UserInfo={NSLocalizedDescription=Application was not found}
Additionally, it may be important (or not) but I am seeing this message in the logger also, before the failed to connect.
[GCKNNetworkUtils getTwoLowerBytesFromNetworkAddress:], message: Invalid network address
Finally, I find it fascinating that when I connect with YouTube app and cast a video - and then switch to the CastVideos-Swift demo, when I tap the CC button, instead of saying "Nearby devices" it correctly says "Living Room TV" and below that in parenthesis it says "Youtube" - which appears to indicate that it is aware that the YouTube app is currently casting to this device) - and YET, when I tap the Living Room TV device, I get the exact same error (the YouTube streaming continues to play without interruption).
My best idea is that it has something to do with my App ID since i used the same bundle ID for the demo app as my own app. But, except for turning on the WiFi capability, I cant think of anything else which would make it fail that I have read so far.
Note that I read that the YouTube app is not a good test of discoverability (it has access to internal APIs?) - I tried another app which was ChromeCast enabled, and it worked perfectly, no guest mode login, just asked me to pick "Living Room TV" and it worked fine.
Maybe someone here has seen this before? Thanks in advance.
Sadly, it turned out to be a bogus google cast app ID I was given by the project owner.