I was able to get the UART example working on my Argon yesterday with it printing down the serial. Now I need to now make it usable to communicate between a mobile app and the Argon. The BLE Wiring API had a scope that included a BLESerial library. Since that doesn’t seem to exist yet, is there an alternative library I can migrate in easily?
For the device side, you need to implement either the BLE Central or BLE Peripheral roles, probably the Peripheral role if you are going to control it from a mobile app.
https://docs.particle.io/tutorials/device-os/bluetooth-le/#uart-peripheral
There is no example for the mobile app side, and it will vary significantly between iOS and Android. But its not Particle-specific. The BLE UART protocol was designed by Nordic and is supported by a number of apps.
The Adafruit Bluefruit mobile app for iOS and Android supports the BLE UART protocol, and the app is open source. That’s the easiest way to see how to implement it on a mobile device.
Thanks @rickkas7 for the response. That example is the one I am talking about.
I have the BLE Peripheral UART example firmware built on the device. The example provided does not have an example of a good technique to parse the received data, though I think we have that figured out. We took the incoming data, used sprintf() to write it to a char array, then casted it to a String for comparison.
Now I’m looking for a good technique to transmit data back out the bluetooth.
Our first goal is to communicate with the app the available SSID’s and receive the password from the app to login.
An example of how it is currently performed with the Particle app would be perfect. I’m more interested in the firmware side programming than the mobile app side at this time.
Is there a timeline of when the BLESerial library may be released?
I just wrote a library to make it easier to use BLE UART Peripheral mode.
BleSerialPeripheralRK
Library to simplify using BLE UART peripheral mode on Gen 3 devices
Introduction
Particle Gen 3 devices (Argon, Boron, Xenon) running Device OS 1.3.0-rc.1 and later have support for BLE (Bluetooth Low Energy) in central and peripheral roles.
Nordic Semiconductor created a UART peripheral protocol to allow central devices (like mobile phones) to connect to a BLE device and read UART-like data streams. This is supported not only by the nRF Toolbox app, but also some other apps like the Adafruit Bluefruit app.
There is a code example in the docs, however this class encapsulates the BLE stuff and makes a Serial-like interface to it. Among the benefits:
- Reading is easy using standard functions like read(), readUntil(), readString(), etc. like you can from Serial, Serial1, etc…
- Writing is easy and buffered, allowing not only write() to write a byte, but also all of the variations of print(), println(), printf(), printlnf(), etc. that are available when using Serial, etc…
- All of the BLE stuff is encapsulated so you don’t have to worry about it.
Documentation can be found at: https://rickkas7.github.io/BleSerialPeripheralRK/index.html
Github repository: https://github.com/rickkas7/BleSerialPeripheralRK
License: MIT
Example
There is one example in 1-simple-BleSerialPeripheralRK:
#include "BleSerialPeripheralRK.h"
SerialLogHandler logHandler;
SYSTEM_THREAD(ENABLED);
// First parameter is the transmit buffer size, second parameter is the receive buffer size
BleSerialPeripheralStatic<32, 256> bleSerial;
const unsigned long TRANSMIT_PERIOD_MS = 2000;
unsigned long lastTransmit = 0;
int counter = 0;
void setup() {
Serial.begin();
// This must be called from setup()!
bleSerial.setup();
// If you don't have any other services to advertise, just call advertise().
// Otherwise, call getServiceUuid() to get the serial service UUID and add that to your
// custom advertising data payload and call BLE.advertise() yourself with all of your necessary
// services added.
bleSerial.advertise();
}
void loop() {
// This must be called from loop() on every call to loop.
bleSerial.loop();
// Print out anything we receive
if(bleSerial.available()) {
String s = bleSerial.readString();
Log.info("received: %s", s.c_str());
}
if (millis() - lastTransmit >= TRANSMIT_PERIOD_MS) {
lastTransmit = millis();
// Every two seconds, send something to the other side
bleSerial.printlnf("testing %d", ++counter);
Log.info("counter=%d", counter);
}
}
Among the important things:
You normally instantiate a BleSerialPeripheralStatic object as a global variable. The first number in the <> is the transmit buffer size and the second is the receive buffer size.
// First parameter is the transmit buffer size, second parameter is the receive buffer size
BleSerialPeripheralStatic<256, 256> bleSerial;
Because the data is buffered and only sent from loop() the transmit buffer must be larger than the amount of data you intend to sent at once, or the maximum data that will accumulate between calls to loop.
Likewise, since data is read from loop but received asynchronously by BLE, you must have a receive buffer that is large enough to hold any data between times you will be processing it from your loop function.
If there is a data overflow situation, the data is discarded.
Be sure to call this from your setup() function.
bleSerial.setup();
If you are only using BLE UART you can call:
bleSerial.advertise();
If you are advertising multiple services, instead call bleSerial.getServiceUuid()
to get the UART serial UUID and add it with your own services:
BleAdvertisingData data;
data.appendServiceUUID(bleSerial.getServiceUuid());
// append your own service UUIDs here
BLE.advertise(&data);
Be sure to call this from loop, as often as possible.
bleSerial.loop();
In this example we used bleSerial.readString()
but there are many method of the Stream class to read. Beware of blocking, however. If you are waiting to read a string, you won’t be calling bleSerial.loop()
and data won’t be transmitted during that time.
Finally, you can print to BLE serial using all of the standard Stream methods. The example uses bleSerial.printlnf()
to print a line using sprintf
formatting.
It’s in the Particle libraries as BleSerialPeripheralRK and at the Github link above.
Thank you for making this happen Rick! This is exactly what I was looking for.
What are the limitations on the buffer sizes?
The minimum is as described. The maximum is the amount of free memory you have. That’s normally about 60K, but you need to leave room for other things. Several kilobytes should not a problem. There are different settings for tx and rx since you probably can set the tx buffer smaller than the rx buffer.
Thanks @rickkas7 for making things easier for us less advanced Particle users
Im getting an error when using your code …Any ideas ??
src/BleSerialPeripheralRK.h:60:61: error: 'BlePeerDevice' does not name a type
void onDataReceived(const uint8_t* data, size_t len, const BlePeerDevice& peer);
^
src/BleSerialPeripheralRK.h:63:74: error: 'BlePeerDevice' does not name a type
static void onDataReceivedStatic(const uint8_t* data, size_t len, const BlePeerDevice& peer, void* context);
^
src/BleSerialPeripheralRK.h:68:2: error: 'BleCharacteristic' does not name a type
BleCharacteristic txCharacteristic;
^
src/BleSerialPeripheralRK.h:69:2: error: 'BleCharacteristic' does not name a type
BleCharacteristic rxCharacteristic;
^
../build/module.mk:277: recipe for target '../build/target/user/platform-14-msrc/particle_mesh_switch.o' failed
make[2]: Leaving directory '/firmware/user'
make[2]: *** [../build/target/user/platform-14-msrc/particle_mesh_switch.o] Error 1
../../../build/recurse.mk:11: recipe for target 'user' failed
make[1]: Leaving directory '/firmware/modules/xenon/user-part'
make[1]: *** [user] Error 2
../build/recurse.mk:11: recipe for target 'modules/xenon/user-part' failed
make: *** [modules/xenon/user-part] Error 2
src/BleSerialPeripheralRK.h:60:61: error: ‘BlePeerDevice’ does not name a type
My guess is that your build is not targeting 1.3.0-rc.1. Since that is not a default Device OS release, make sure you've manually set that as your build target Device OS version.
Yes I read that after I posted this ,I am going to update my device today to 1.3.0…And see If I can get this BLE thing to work …thanks
Ricks new library works for me when using Workbench.
When I cloud compile and cloud flash I don’t see the UART option when I connect using the Bluefruit App but it does show up when I local compile and flash via DFU using Workbench which seemed alittle weird to me. Maybe you could try this also to confirm.
@rickkas7 The Library is working for me. It’s allowing me to easily do what I was wanting to do so THANK YOU!
@rickkas7 Using your new BLE Serial example library is there a way to change the name that currently shows up in the Bluefruit app?
Now it shows up as Argon-VFR527 - Is there an easy way to change that to something different?
How can I make it show up as “Rickkas-Is-Da-Best” ?
You can set the local name in the advertising data
https://docs.particle.io/reference/device-os/firmware/argon/#appendlocalname-
@rickkas7 I’ve been testing your new BLE serial library linked below.
One thing I have noticed from testing is that the Argon will reset and the counter will start back at 1 if the connected device loses the BLE connection and the Bluefruit Application shows that the connection has been lost warning.
Everything works fine until the connection is lost and the Argon resets almost every time.
I’m guessing the Argon’s code freaks out when it’s in the middle of processing incoming or outgoing data over serial and it just hard faults or resets for some reason.
I just wanted to throw this out there in case you wanted to test it somewhere the connected device is on the fringe of losing the connection and see what happens to your Gen3 device once the connection is lost.
I’m not sure if we can prevent the resets via code or if it’s a deeper nRF52 chip issue?
Let me know what your thoughts are because your library is very helpful!
I’ve tried the example code you put together for us @rickkas7 without any luck of using it as a wireless serial device. I made sure to flash the OS and bootloader to 1.3.0. I have tried it on two different Argons. I tried SYSTEM_MODE() in MANUAL, SEMI-AUTOMATIC and AUTOMATIC. I’ve tried it with credentials and connected to the cloud and without. I’ve added the BleSerialPeripheralRK library to the project, cleaned the OS and flashed the device OS and application.
I appended the service UUID, like instructed, but I do not see UART capable in the advertising Argon. I’ve tried using Bluefruit, nRF Connect and BLE Scanner apps on an iPhone and an S8.
Reading through the code it looks like it should work, but doesn’t.
Any guidance on things that need to be added that are not stated or present in the example code?
Here is what I have in my .ino file:
#include "BleSerialPeripheralRK.h"
SerialLogHandler logHandler;
SYSTEM_THREAD(ENABLED);
SYSTEM_MODE(SEMI_AUTOMATIC);
// First parameter is the transmit buffer size, second parameter is the receive buffer size
BleSerialPeripheralStatic<256, 256> bleSerial;
const unsigned long TRANSMIT_PERIOD_MS = 2000;
unsigned long lastTransmit = 0;
int counter = 0;
void setup() {
Serial.begin();
// This must be called from setup()!
bleSerial.setup();
WiFi.setCredentials("MyNetowrk","ReallyBigPassword");
// If you don't have any other services to advertise, just call advertise().
// Otherwise, call getServiceUuid() to get the serial service UUID and add that to your
// custom advertising data payload and call BLE.advertise() yourself with all of your necessary
// services added.
BleAdvertisingData data;
data.appendServiceUUID(bleSerial.getServiceUuid());
// append your own service UUIDs here
BLE.advertise(&data);
bleSerial.advertise();
}
void loop() {
// This must be called from loop() on every call to loop.
bleSerial.loop();
// Print out anything we receive
if(bleSerial.available()) {
String s = bleSerial.readString();
Log.info("received: %s", s.c_str());
}
if (millis() - lastTransmit >= TRANSMIT_PERIOD_MS) {
lastTransmit = millis();
// Every two seconds, send something to the other side
bleSerial.printlnf("testing %d", ++counter);
Log.info("counter=%d", counter);
}
}
Are you sure it's not advertising?
I have a similar issue reported to Rick where I saw the service advertised but after connecting to the device there was no UART service exposed.
See here