All,
I'll give a brief overview of my project first, I'm needing a nudge in the right direction.
Project: Assault Air Runner (Manual treadmill) has bluetooth ability and works with Zwift with no issues. I want to connect to bluetooth and run in my own game. I've already created a prototype game, endless runner style. To start out I just need the speed output; but will include distance, calories and maybe pace.
Experience: Strong background in mechanical and 3d software. Complete beginner in programming.
Hardware: ESP32, Arduino and HC05, Leonardo on the way.
Project so far: I'm pretty proud to have gotten the BLE Client example to work and talk with the treadmill. I've gotten the characteristics and made sense of what I wanted, I think. Image is just me trying to make sense of numbers.
Question: I got the BLE client to work but when it reads back it lists 4 characters always with a square in the second position. I've tried to read the GATT paperwork but feel it is over my head. Could you guys point me in the right direction to include the BLE client and to indicate/notify the speed numbers? I want to build on the project as I go. If I can get the correct output, then I can work on pushing that to a keystroke pressed.
Been doing some more research and found that the value is stored in little endian format by packet. Does anyone know a sketch example were you can grab only a certain packet?
To grab the characters in excel I know I'd use the MID formula like the following example:
A1: 73-58-12-EB-05-F4-01-16-03-DB-07-00-6C-00-31-00
Formula =MID(A1,43,2)
Result Speed = 31
I'm using the basic ESP32 BLE_Client and just changing the service and characteristic UUID that I got from the NRF Connect app. I see where it calls for the characteristic and writes a string. It's just way over my head to figure out why it only writes 4 characters and where it's pulling it from. Code snippet I think controls the output.
// Obtain a reference to the characteristic in the service of the remote BLE server.
pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID);
if (pRemoteCharacteristic == nullptr) {
Serial.print("Failed to find our characteristic UUID: ");
Serial.println(charUUID.toString().c_str());
pClient->disconnect();
return false;
}
Serial.println(" - Found our characteristic");
// Read the value of the characteristic.
if(pRemoteCharacteristic->canRead()) {
std::string value = pRemoteCharacteristic->readValue();
Serial.print("The characteristic value was: ");
Serial.println(value.c_str());
}
Full code below:
/**
* A BLE client example that is rich in capabilities.
* There is a lot new capabilities implemented.
* author unknown
* updated by chegewara
*/
#include "BLEDevice.h"
//#include "BLEScan.h"
// The remote service we wish to connect to.
static BLEUUID serviceUUID("a026ee07-0a7d-4ab3-97fa-f1500f9feb8b");
// The characteristic of the remote service we are interested in.
static BLEUUID charUUID("a026e01d-0a7d-4ab3-97fa-f1500f9feb8b");
static boolean doConnect = false;
static boolean connected = false;
static boolean doScan = false;
static BLERemoteCharacteristic* pRemoteCharacteristic;
static BLEAdvertisedDevice* myDevice;
static void notifyCallback(
BLERemoteCharacteristic* pBLERemoteCharacteristic,
uint8_t* pData,
size_t length,
bool isNotify) {
Serial.print("Notify callback for characteristic ");
Serial.print(pBLERemoteCharacteristic->getUUID().toString().c_str());
Serial.print(" of data length ");
Serial.println(length);
Serial.print("data: ");
Serial.println((char*)pData);
}
class MyClientCallback : public BLEClientCallbacks {
void onConnect(BLEClient* pclient) {
}
void onDisconnect(BLEClient* pclient) {
connected = false;
Serial.println("onDisconnect");
}
};
bool connectToServer() {
Serial.print("Forming a connection to ");
Serial.println(myDevice->getAddress().toString().c_str());
BLEClient* pClient = BLEDevice::createClient();
Serial.println(" - Created client");
pClient->setClientCallbacks(new MyClientCallback());
// Connect to the remove BLE Server.
pClient->connect(myDevice); // if you pass BLEAdvertisedDevice instead of address, it will be recognized type of peer device address (public or private)
Serial.println(" - Connected to server");
// Obtain a reference to the service we are after in the remote BLE server.
BLERemoteService* pRemoteService = pClient->getService(serviceUUID);
if (pRemoteService == nullptr) {
Serial.print("Failed to find our service UUID: ");
Serial.println(serviceUUID.toString().c_str());
pClient->disconnect();
return false;
}
Serial.println(" - Found our service");
// Obtain a reference to the characteristic in the service of the remote BLE server.
pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID);
if (pRemoteCharacteristic == nullptr) {
Serial.print("Failed to find our characteristic UUID: ");
Serial.println(charUUID.toString().c_str());
pClient->disconnect();
return false;
}
Serial.println(" - Found our characteristic");
// Read the value of the characteristic.
if(pRemoteCharacteristic->canRead()) {
std::string value = pRemoteCharacteristic->readValue();
Serial.print("The characteristic value was: ");
Serial.println(value.c_str());
}
if(pRemoteCharacteristic->canNotify())
pRemoteCharacteristic->registerForNotify(notifyCallback);
connected = true;
return true;
}
/**
* Scan for BLE servers and find the first one that advertises the service we are looking for.
*/
class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
/**
* Called for each advertising BLE server.
*/
void onResult(BLEAdvertisedDevice advertisedDevice) {
Serial.print("BLE Advertised Device found: ");
Serial.println(advertisedDevice.toString().c_str());
// We have found a device, let us now see if it contains the service we are looking for.
if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID)) {
BLEDevice::getScan()->stop();
myDevice = new BLEAdvertisedDevice(advertisedDevice);
doConnect = true;
doScan = true;
} // Found our server
} // onResult
}; // MyAdvertisedDeviceCallbacks
void setup() {
Serial.begin(115200);
Serial.println("Starting Arduino BLE Client application...");
BLEDevice::init("");
// Retrieve a Scanner and set the callback we want to use to be informed when we
// have detected a new device. Specify that we want active scanning and start the
// scan to run for 5 seconds.
BLEScan* pBLEScan = BLEDevice::getScan();
pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
pBLEScan->setInterval(1349);
pBLEScan->setWindow(449);
pBLEScan->setActiveScan(true);
pBLEScan->start(5, false);
} // End of setup.
// This is the Arduino main loop function.
void loop() {
// If the flag "doConnect" is true then we have scanned for and found the desired
// BLE Server with which we wish to connect. Now we connect to it. Once we are
// connected we set the connected flag to be true.
if (doConnect == true) {
if (connectToServer()) {
Serial.println("We are now connected to the BLE Server.");
} else {
Serial.println("We have failed to connect to the server; there is nothin more we will do.");
}
doConnect = false;
}
// If we are connected to a peer BLE Server, update the characteristic each time we are reached
// with the current time since boot.
if (connected) {
String newValue = "Time since boot: " + String(millis()/1000);
Serial.println("Setting new characteristic value to \"" + newValue + "\"");
// Set the characteristic's value to be the array of bytes that is actually a string.
pRemoteCharacteristic->writeValue(newValue.c_str(), newValue.length());
}else if(doScan){
BLEDevice::getScan()->start(0); // this is just eample to start scan after disconnect, most likely there is better way to do it in arduino
}
delay(1000); // Delay a second between loops.
} // End of loop
This should be called every second in loop(). Do you see this print out
String newValue = "Time since boot: " + String(millis()/1000);
Serial.println("Setting new characteristic value to \"" + newValue + "\"");
why it only writes 4 characters
Are you saying that you do not see the complete message from the teadmill( server) which you are expecting printed anywhere in the library example you are running?
I'm currently away from treadmill but will run another test tonight. The NRF bluetooth app shows the correct string of numbers, but the Arduino serial monitor outputs code below. Code below is an older test.
19:14:14.301 -> Setting new characteristic value to "Time since boot: 48"
19:14:14.345 -> Notify callback for characteristic a026e024-0a7d-4ab3-97fa-f1500f9feb8b of data length 3
19:14:14.391 -> data: ⸮T
19:14:15.366 -> Setting new characteristic value to "Time since boot: 49"
19:14:15.460 -> Notify callback for characteristic a026e024-0a7d-4ab3-97fa-f1500f9feb8b of data length 3
19:14:15.460 -> data: ⸮T
While writing this I got the idea to paste the data in Notepad++, data shown is (If image doesn't show, I attached also) To me this looks like an encoding problem?
I'll have to do a test and see if I can connect NRF Bluetooth log and Arduino serial monitor at same time. I've also got an log with more characteristics that displayed before getting treadmill values. Might try to input the gatt.writeDescriptor
V 10:53:02.469 Enabling notifications for a026e01d-0a7d-4ab3-97fa-f1500f9feb8b
D 10:53:02.469 gatt.setCharacteristicNotification(a026e01d-0a7d-4ab3-97fa-f1500f9feb8b, true)
D 10:53:02.470 gatt.writeDescriptor(00002902-0000-1000-8000-00805f9b34fb, value=0x0100)
I 10:53:02.521 Data written to descr. 00002902-0000-1000-8000-00805f9b34fb, value: (0x) 01-00
A 10:53:02.521 "Notifications enabled" sent
V 10:53:02.525 Notifications enabled for a026e01d-0a7d-4ab3-97fa-f1500f9feb8b
I 10:53:02.791 Notification received from a026e01d-0a7d-4ab3-97fa-f1500f9feb8b, value: (0x) 73-58-12-37-0D-83-02-8A-07-54-19-00-19-02-3F-00
A 10:53:02.791 "(0x) 73-58-12-37-0D-83-02-8A-07-54-19-00-19-02-3F-00" received
I've discovered the correct Characteristic UUID and changed my baud rate in the code and monitor to 9600. The serial monitor states data length of 16 characters but data output is still nonsense.
I think the issue is in the below line of code. I've read up on using the HEX but can't get it to work without errors. In the example it shows serial.print (x, HEX). So I assumed you could take the below code and with another set of brackets add the comma Hex.
Serial.println((char*)pData);
static void notifyCallback(
BLERemoteCharacteristic* pBLERemoteCharacteristic,
uint8_t* pData,
size_t length,
bool isNotify) {
Serial.print("Notify callback for characteristic ");
Serial.print(pBLERemoteCharacteristic->getUUID().toString().c_str());
Serial.print(" of data length ");
Serial.println(length);
Serial.print("data: ");
Serial.println((char*)pData);
}
I was thinking about this all night, woke up and saw you genius's had posted. Replaced the pData with Cattledog's code and IT WORKED!!!!!
Notify callback for characteristic a026e01d-0a7d-4ab3-97fa-f1500f9feb8b of data length 16
data: 73-58-12-14-0-C2-1-46-5-10-0-0-1-0-1C-0Setting new characteristic value to "Time since boot: 19"
Notify callback for characteristic a026e01d-0a7d-4ab3-97fa-f1500f9feb8b of data length 16
data: 73-58-12-15-0-C2-1-50-5-10-0-0-1-0-1D-0Setting new characteristic value to "Time since boot: 20"
Notify callback for characteristic a026e01d-0a7d-4ab3-97fa-f1500f9feb8b of data length 16
data: 73-58-12-16-0-C2-1-46-5-10-0-0-1-0-1C-0Setting new characteristic value to "Time since boot: 21"
Notify callback for characteristic a026e01d-0a7d-4ab3-97fa-f1500f9feb8b of data length 16
Now I can build on the project. I'm going to dig deeper and with Sparky's comment, make me think I can grab individual values. Which would be the second to last couplet of hex numbers. Once I get that, hopefully I'll be able to use the Leonardo to output to "W" key. Again Thanks to you guys this project could see an end.
think I can grab individual values. Which would be the second to last couplet of hex numbers.
I'm not certain what values you want, but each value has an index. These index numbers for a 16 byte array run from 0 to 15. That is, the indexes are zero based.
The data at these indexes in memory is binary, and the hex is only there for display.
Referring to your original post, if you want distance which is in the two values at index 9 and index 10, and is presented little ended then
Cattledog - I did not think it would be that easy. I literally only placed[14] as that was the speed identifier I needed. It output with 16 characters of the speed data so I'll limit that to just 2 characters. I can now take that and use it as my character input. 0-31 is walking speed, 31-50 is a jog, and anything greater than 50 is a sprint. My plan now is to connect the esp32 and a Leonardo to pass the keystroke. I hope to get a video of it when it works.
for (byte i = 0 ; i < length; i++)
{
Serial.print(pData[14]);
if(i < length-1)Serial.print('-');
}
Sparky and TheMemberFormallyKnownAsAwol, you two are way to smart for me! I'm not sure if the code you are posting would help my project or just proving a point. I don't even know where to start inputting it. I appreciate the input though. The more I learn and understand the better I can optimize the code.
pic2230:
Sparky and TheMemberFormallyKnownAsAwol, you two are way to smart for me! I'm not sure if the code you are posting would help my project or just proving a point. I don't even know where to start inputting it. I appreciate the input though. The more I learn and understand the better I can optimize the code.
sp. "too smart"
@sparky961 almost got their example for printing hex values with leading zeroes correct (just a couple of noob boo-boos), but didn't have the experience to test and evaluate it correctly
The code is compiled for a Diecimilia with a ATMEGA168 processor (all I had available to test it on) , but will compile just as well for a 328, as on a Uno.
To test the different codes, simply delete the comments at the start of the lines that begin "#define METHOD" one by one (and reinstate the comment on the previous line!) and compile the code to see the amount of object code created.
You'll see, as commented, that METHOD1 makes a noticeable (waay more than "12 bytes") saving in flash memory
// Baseline (no defines) = 1442 + 185 bytes (~10%)
//#define METHOD1 /* 1568 + 185 bytes (~10%)
//#define METHOD2 /* 1700 + 185 bytes (~11%)
//#define METHOD3 /* 1708 + 185 bytes (~11%)
volatile uint8_t my_byte;
void printLeadingZeroHex (uint8_t myByte) //the function name is overly-long, to make a point
{
#if defined(METHOD1)
if(myByte < 16) { // this is different to @sparky961's incorrect attempt
Serial.print('0'); // this too
}
Serial.print(myByte, HEX);
#endif
#if defined(METHOD2)
Serial.print( (myByte >> 4) & 0x0F, HEX );
Serial.print( (myByte >> 0) & 0x0F, HEX );
#endif
#if defined(METHOD3)
Serial.print(myByte >> 4, HEX);
Serial.print(myByte, HEX);
#endif
}
void setup()
{
Serial.begin(9600);
printLeadingZeroHex (my_byte);
}
void loop()
{
}
If all you want is the data in pData[14] then there is not need to iterate through the entire array with the for() loop. Showing all the values was really only need to confirm that you had the right characteristic and the data returned to the Arduino was the same as you saw on the nrf tool.
You can extract what you need with the appropriate index.
You can replace this complete print out
for (byte i = 0 ; i < length; i++)
{
Serial.print(pData[14]);
if(i < length-1)Serial.print('-');
}
The entire discussion about the leading 0 in the HEX printouts of your data is irrelevant to your purposes. You were smart enough to recognize that 02 and 2 were the same value. Once you confirmed that you had the correct returned values, I don't believe that you have any need for hex value displays anywhere in your project.
I'd bet that you think of distances, speeds, calories in base 10 and time in hours/minutes/seconds.
Project Update. I attached an potentiometer to my Leonardo board to mimic the treadmill and used the base potentiometer sketch to add the key pressed commands. Opened my base Unity prototype game and tested. It worked and the character would run forward and backward. Next step is to connect the ESP32 and Leonardo.
Does anyone know a diagram for this. I've searched and searched and can't find one. I know I need to connect the RX/TX, and my understanding is I've got to step the power down to 3.3v for the ESP32. Seems to be no documents to connect ESP and Leonardo.
Code below for future reference.
* Tutorial page: https://arduinogetstarted.com/tutorials/arduino-potentiometer
*/
float floatMap(float x, float in_min, float in_max, float out_min, float out_max) {
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
int buttonState = 0;
// the setup routine runs once when you press reset:
void setup() {
// initialize serial communication at 9600 bits per second:
Serial.begin(9600);
Keyboard.begin();
}
// the loop routine runs over and over again forever:
void loop() {
// read the input on analog pin A0:
int analogValue = analogRead(A0);
// Rescale to potentiometer's voltage (from 0V to 5V):
float voltage = floatMap(analogValue, 0, 1023, 0, 5);
// print out the value you read:
Serial.print("Analog: ");
Serial.print(analogValue);
delay(1000);
if (analogValue == 0)
{Keyboard.releaseAll();
}
if (analogValue > 0 && analogValue < 260)
{Keyboard.press('w');
}
else
Keyboard.release('w');
if (analogValue > 260)
{Keyboard.press('s');
}
else
{
Keyboard.release('s');
}
}
Do you only need the ESP32 to send data to the Leonardo?
Do you have GPIO17 Tx2 broken out on your ESP32 module?
If so, I believe that you can directly connect the ESP32 Tx2 to the hardware Serial1 Rx on the Leonardo (pin0). The ESP should send 3.3v TTL, and it should be able to be received by the Leonardo. A level shifter on this line might help if the voltage levels are not quite right for the Leonardo.
If you want bidirectional communication you will need the level shifter or voltage divider between the Leonardo Serial1 Tx and the ESP 32 Rx2.
I'm not sure where you are going with all this, and how much time you have for your development. I can certainly understand you wanting to leverage what you currently have working, but there may be better solutions.
The ESP32 S2 can have USB HID output, and there is a library for it.
There is also an integrated BLE device with a 32U4 (used by Leonardo) processor in this product by Adafruit in the feather series. This should look like a Leonardo with BLE and you should be able to get it set up with the characteristics to receive from the treadmill.
At some point you may want to start a new thread (or at least retitle this one).
Thread renamed for better clarity. I've got a level converter and wanted to make sure I'm hooking everything up correctly so I don't fry anything. I did a quick sketch of what I think would work, I'd appreciate if someone could look it over. Goal is to only get power from USB to PC connection.