Advertising data is in readable format for iPhone, but it is in decimal format for android. How to read and interpret the advertising packet for android.
this.scanner = this.ble.scan(["00EDSE-0000-00AE-9VVQ-9125475145125"], 1).subscribe((response) => {
console.log("success scan.." + JSON.stringify(response));
this.ble.connect(response.id).subscribe((response) => {
this.toast.show("Successfully paired", '2000', 'bottom').subscribe((toast) => {
console.log(toast);
this.spinnerDialog.hide();
});
Android advertising data is received as an ArrayBuffer. You need to convert it to a human readable string using javascripts String.fromCharCode method:
let stringResult = String.fromCharCode.apply(null, new Uint8Array(buffer));
Where buffer is the advertising property of you response object. You can find the ionic-native docs for BLE here.
Related
Trying to send esp32 chip wifi credentials from android app (built with ionic). Using Ionic capacitor/bluetooth-le plug in to write to esp32, using the write function:
await BleClient.write(device.deviceId, GATT Service ID, Characteristic ID, textToDataView('wifi_ssid,wifi_password'));
Code for BleClient.write:
async write(deviceId: string, service: string, characteristic: string, value: DataView): Promise<void> {
service = validateUUID(service);
characteristic = validateUUID(characteristic);
return this.queue(async () => {
if (!value?.buffer) {
throw new Error('Invalid data.');
}
let writeValue: DataView | string = value;
if (Capacitor.getPlatform() !== 'web') {
// on native we can only write strings
writeValue = dataViewToHexString(value);
}
await BluetoothLe.write({
deviceId,
service,
characteristic,
value: writeValue,
});
});
}
How to pass wifi credentials as value argument to the write function so it's correctly received by ESP32?
On the ESP32 side, I'm using the wifi_prov_mgr example code, which in turn uses google protocol buffer (I'm very new to protobuf and don't really understand how it works). ESP uses wifi_config.c (Espressif wifi_provisioning component). When I send wifi credentials from the app using BleClient.write, it shows up in wifi_config.c as inbuf with value wifi_ssid,wifi_password:��Z�?�Z�?
Here's the relevant code for wifi_config.c:
esp_err_t wifi_prov_config_data_handler(uint32_t session_id, const uint8_t *inbuf, ssize_t inlen, uint8_t **outbuf, ssize_t *outlen, void *priv_data)
{
ESP_LOGI(TAG ,"Wifi config payload inbuf value: %s", inbuf);
WiFiConfigPayload *req;
WiFiConfigPayload resp;
esp_err_t ret;
req = wi_fi_config_payload__unpack(NULL, inlen, inbuf);
if (!req) {
ESP_LOGE(TAG, "Unable to unpack config data");
return ESP_ERR_INVALID_ARG;
}
I'm having a very hard time understanding how wi_fi_config_payload_unpack processes the inbuf argument. This is where the code gets into protobuf code generated by the proto files. The proto file for wifi config data looks like this:
message CmdSetConfig {
bytes ssid = 1;
bytes passphrase = 2;
bytes bssid = 3;
int32 channel = 4;
}
message WiFiConfigPayload {
WiFiConfigMsgType msg = 1;
oneof payload {
CmdGetStatus cmd_get_status = 10;
RespGetStatus resp_get_status = 11;
CmdSetConfig cmd_set_config = 12;
RespSetConfig resp_set_config = 13;
CmdApplyConfig cmd_apply_config = 14;
RespApplyConfig resp_apply_config = 15;
}
}
So my question is - how do I pass the wifi credentials in BleClient.write so it is correctly recognized by wifi_config.c on the ESP32 side?
I thought about using the Android app developed by Espressif to pass wifi credentials to the ESP32 chip, but then I don't know how to integrate native Android code with ionic code, since I need my app to do more than just credential the ESP32.
Resolved this issue by using Google's protocol buffer code for javascript. Here's code that worked for me:
import * as goog from 'google-protobuf';
export class SetupPage implements OnInit {
messages = require('../../assets/proto-js/wifi_config_pb.js');
connectedDevice: BleDevice;
bleScan: any;
wifiSSID: Promise<any>;
scanResults: Promise<[]>;
wifiCredentials: string;
uint8String: Uint8Array;
buffer: Uint8Array;
ngOnInit() {
async getBLE(){
await BleClient.initialize();
var cmdSetMessage = new this.messages.CmdSetConfig();
var wifiConfigPayloadMessage = new this.messages.WiFiConfigPayload;
message.setSsid('wifiid');
message.setPassphrase('password');
wifiConfigPayloadMessage.setCmdSetConfig(cmdSetMessage);
wifiConfigPayloadMessage.setMsg(2); //this is ESP32 BLE specific
let bytesOfStuff = await wifiConfigPayloadMessage.serializeBinary();
this.connectedDevice = await BleClient.requestDevice();
await BleClient.connect(this.connectedDevice.deviceId);
BleClient.writeWithoutResponse(this.connectedDevice.deviceId, '021a9004-0382-
4aea-bff4-6b3f1c5adfb4', '021aff52-0382-4aea-bff4-6b3f1c5adfb4',
bytesOfStuff)
//After this, you need to send wifiConfigPayloadMessage.setMsg(4) to ESP32 to apply wifi credentials and connect to the selected wifi
}
}
Before this code can function, you need to install google-protobuf from npm or other package installer and protoc or another compiler for the proto files in order to generate pb.js files (since I'm working with javascript) for each proto file. In this example, the proto file is called wifi_config.proto, and the corresponding pb.js file (created by running protoc) is called wifi_config_pb.js.
Functions used to create the data to be transferred are defined in the pb.js files and correspond to objects defined in the proto files. The following references will help a lot if you don't have a good understanding of how this mechanism works:
https://developers.google.com/protocol-buffers/docs/proto3 (google protocol-buffer tutorial)
Protocol Buffers in Ionic (stack question)
I have an Android App build in Apache Cordova, it works with Bluetooth 4.0(BLE) the plugin GitHub repostory indicates this shape for send information to a ble device (in my case a HM10 module):
this function writes data to a characteristic.
ble.write(device_id, service_uuid, characteristic_uuid, data, success, failure);
Description
Function write writes data to a characteristic.
Parameters:
device_id: UUID or MAC address of the peripheral
service_uuid: UUID of the BLE service
characteristic_uuid: UUID of the BLE characteristic
data: binary data, use an ArrayBuffer
success: Success callback function that is invoked when the connection is successful. [optional]
failure: Error callback function, invoked when error occurs. [optional]
I have tried to send data for this way but failure function is called immediately
function startBed() {
var UiidS="FFE0"; //UUID SERVICE: AT+UUID? => 0xFFE0
var UiidC="FFE1"; //UUID CHARACTERISTICS AT+CHAR? => 0xFFE1
var data = new Uint8Array(1);
data[0] = 1;
setTimeout(ble.write(hMac,UiidS,UiidC,data.buffer,success,failure),2000);
function success() {
document.getElementById("btstate1").innerHTML = "Dato enviado!!!";
}
function failure() {
document.getElementById("btstate1").innerHTML = "Dato NO enviado!!!";
var time=2000;
navigator.vibrate(time);
}
}
if I take the text box value and send it directly, the HM10 receives an unrecognized characters like Ó.
var UiidS="FFE0";
var UiidC="FFE1";
var dato =document.getElementById("btstate6").value;
setTimeout(ble.write(hMac,UiidS,UiidC,dato,success,failure),2000);
In conclution
I don't know why the module not receive the data correctly or in other cases the data isn't send. Maybe is for something of UUID.
I'm still trying to solve my problem, I confirmed that the UUID is correct, but I have the doubt about whether I am sending the data well as ArrayBuffer
Using a different plugin (https://github.com/randdusing/cordova-plugin-bluetoothle)
I used the following
var encodedString = bluetoothle.bytesToEncodedString([145,0,14,0,255]);
bluetoothle.write(this.writeSuccess.bind(this), this.writeError.bind(this), {
"value": encodedString,
"service": this.serviceUuid,
"characteristic": this.serviceCharacteristic,
"type": "noResponse",
"address": this.currentConnectedDevice
});
Where
// From plugin source:
encodedStringToBytes: function(string) {
var data = atob(string);
var bytes = new Uint8Array(data.length);
for (var i = 0; i < bytes.length; i++)
{
bytes[i] = data.charCodeAt(i);
}
return bytes;
},
bytesToEncodedString: function(bytes) {
return btoa(String.fromCharCode.apply(null, bytes));
},
I hope this helps
We are currently trying to implement the transmission of images from a mobile device (in this case an IPhone) to a desktop application. We tried already the Bluetooth Serial plugin which works fine for Android but does not list any devices when scanning for our desktop application.
To cover iOS support (AFAIK iOS only supports BluetoothLE), we reimplemented our desktop application to use BluetoothLE and behave like a peripheral. Also we altered our Ionic application to use BLE plugin.
Now BluetoothLE only supports the transmission of packages with the size of 20 Byte whilst our image is about 500kb big. So we could obviously split our image into chunks and transmit it with the following function (taken from this gist):
function writeLargeData(buffer) {
console.log('writeLargeData', buffer.byteLength, 'bytes in',MAX_DATA_SEND_SIZE, 'byte chunks.');
var chunkCount = Math.ceil(buffer.byteLength / MAX_DATA_SEND_SIZE);
var chunkTotal = chunkCount;
var index = 0;
var startTime = new Date();
var transferComplete = function () {
console.log("Transfer Complete");
}
var sendChunk = function () {
if (!chunkCount) {
transferComplete();
return; // so we don't send an empty buffer
}
console.log('Sending data chunk', chunkCount + '.');
var chunk = buffer.slice(index, index + MAX_DATA_SEND_SIZE);
index += MAX_DATA_SEND_SIZE;
chunkCount--;
ble.write(
device_id,
service_uuid,
characteristic_uuid,
chunk,
sendChunk, // success callback - call sendChunk() (recursive)
function(reason) { // error callback
console.log('Write failed ' + reason);
}
)
}
// send the first chunk
sendChunk();
}
Still this would mean for us that we would have to launch about 25k transmissions which I assume will take a long time to complete. Now I wonder why is that the data transmission via Bluetooth is that handicapped.
If you want to try out L2CAP your could modify your Central desktop app somehow like this:
private let characteristicUUID = CBUUID(string: CBUUIDL2CAPPSMCharacteristicString)
...
Then advertize and publish a L2CAP channel:
let service = CBMutableService(type: peripheralUUID, primary: true)
let properties: CBCharacteristicProperties = [.read, .indicate]
let permissions: CBAttributePermissions = [.readable]
let characteristic = CBMutableCharacteristic(type: characteristicUUID, properties: properties, value: nil, permissions: permissions)
self.characteristic = characteristic
service.characteristics = [characteristic]
self.manager.add(service)
self.manager.publishL2CAPChannel(withEncryption: false)
let data = [CBAdvertisementDataLocalNameKey : "Peripherial-42", CBAdvertisementDataServiceUUIDsKey: [peripheralUUID]] as [String : Any]
self.manager.startAdvertising(data)
In your
func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didSubscribeTo characteristic: CBCharacteristic) {
respective your
func peripheralManager(_ peripheral: CBPeripheralManager, didPublishL2CAPChannel PSM: CBL2CAPPSM, error: Error?) {
offer the PSM value (= kind of socket handle (UInt16), for Bluetooth stream connections):
let data = withUnsafeBytes(of: PSM) { Data($0) }
if let characteristic = self.characteristic {
characteristic.value = data
self.manager.updateValue(data, for: characteristic, onSubscribedCentrals: self.subscribedCentrals)
}
finally in
func peripheralManager(_ peripheral: CBPeripheralManager, didOpen channel: CBL2CAPChannel?, error: Error?)
open an input stream:
channel.inputStream.delegate = self
channel.inputStream.schedule(in: RunLoop.current, forMode: .default)
channel.inputStream.open()
where the delegate could look something like this:
func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
switch eventCode {
case Stream.Event.hasBytesAvailable:
if let stream = aStream as? InputStream {
...
//buffer is some UnsafeMutablePointer<UInt8>
let read = stream.read(buffer, maxLength: capacity)
print("\(read) bytes read")
}
case ...
}
iOS app with Central Role
Assuming you have something like that in your iOS code:
func sendImage(imageData: Data) {
self.manager = CBCentralManager(delegate: self, queue: nil)
self.imageData = imageData
self.bytesToWrite = imageData.count
NSLog("start")
}
then you can modify your peripheral on your iOS client to work with the L2Cap channel like this:
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
...
if let characteristicValue = characteristic.value {
let psm = characteristicValue.withUnsafeBytes {
$0.load(as: UInt16.self)
}
print("using psm \(psm) for l2cap channel!")
peripheral.openL2CAPChannel(psm)
}
}
and as soon as you are notified of the opened channel, open the output stream on it:
func peripheral(_ peripheral: CBPeripheral, didOpen channel: CBL2CAPChannel?, error: Error?)
...
channel.outputStream.delegate = self.streamDelegate
channel.outputStream.schedule(in: RunLoop.current, forMode: .default)
channel.outputStream.open()
Your supplied stream delegate might look like this:
func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
switch eventCode {
case Stream.Event.hasSpaceAvailable:
if let stream = aStream as? OutputStream, let imageData = self.imageData {
if self.bytesToWrite > 0 {
let bytesWritten = imageData.withUnsafeBytes {
stream.write(
$0.advanced(by: totalBytes),
maxLength: self.bytesToWrite
)
}
self.bytesToWrite -= bytesWritten
self.totalBytes += bytesWritten
print("\(bytesWritten) bytes written, \(bytesToWrite) remain")
} else {
NSLog("finished")
}
}
case ...
There is a cool WWDC video from 2017, What's New in Core Bluetooth, see here https://developer.apple.com/videos/play/wwdc2017/712/
At around 14:45 it starts to discuss how L2Cap channels are working.
At 28:47, the Get the Most out of Core Bluetooth topic starts, in which performance-related things are discussed in detail. That's probably exactly what you're interested in.
Finally, at 37:59 you will see various possible throughputs in kbps.
Based on the data shown on the slide, the maximum possible speed with L2CAP + EDL (Extended Data Length) + 15ms interval is 394 kbps.
Please have a look at this comment
The following snippet is taken from there
ble.requestMtu(yourDeviceId, 512, () => {
console.log('MTU Size ok.');
}, error => {
console.log('MTU Size failed.');
});
It is suggesting that you need to request the Mtu after connection and then I think you can break your message into chunks of 512 bytes rather than 20 bytes.
They have done this for android specific issue
First I should say that there are already tons of blog posts and Q&As on the exact same topic, so please read them first.
If you run iPhone 7, you have the LE Data Length Extension. The default MTU is also 185 bytes, which means you can send notifications or write without response commands with 182 bytes of payload. And please make sure you absolutely not use Write With Response or Indications since that will almost stall the transfer. When you run iOS in central mode you are restricted to 30 ms connection interval. Using a shorter connection interval can have benefits, so I would suggest you to run iOS in peripheral mode instead so you from the central side can set a connection interval of something short, say 12 ms. Since iPhone X and iPhone 8, you can also switch to the 2MBit/s PHY to get increased transfer speed. So to answer your actual question why BLE data transfer is handicapped: it's not, at least if you follow best practice.
You also haven't told anything about the system that runs your desktop application. If it supports 2 MBit/s PHY, LE Data Length Extension and a MTU of at least 185, then you should be happy and make sure your connections use all those features. If not, you should still get higher performance if you enable at least one of them.
Advertising data is in readable format for iPhone, but it is in decimal format for android. How to read and interpret the advertising packet for android.
this.scanner = this.ble.scan(["00EDSE-0000-00AE-9VVQ-9125475145125"], 1).subscribe((response) => {
console.log("success scan.." + JSON.stringify(response));
this.ble.connect(response.id).subscribe((response) => {
this.toast.show("Successfully paired", '2000', 'bottom').subscribe((toast) => {
console.log(toast);
this.spinnerDialog.hide();
});
I'm using Cordova/Phonegap plugin Ble Central to connect to BLE devices using Android Phone.
As per the documentation on github code is :
onConnect = function(device) {
ble.isConnected(deviceId,
function() {
console.log("Connected");
},
function() {
console.error("Disconnected");
}
);
ble.readRSSI(deviceId, function(rssi) {
console.log('read RSSI',rssi,'with device', deviceId);
});
};
ble.connect(deviceId, onConnect, app.onError);
Even though ble.isConnected() fucntion returns true, RSSI value never returns.
What could be the issue here ?? Has anyone tried using cordova-plugin-ble-central to retrieve RSSI ?
Try retrieving rssi from Peripheral data.
onConnect = function(device) {
console.log('rssi' + device.rssi);
};
See if it gives a proper value.