/************************************************************************************************************
*
* Copyright(C) 2017 Timo Sariwating
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, If not, see .
*
************************************************************************************************************
DESCRIPTION:
This sketch gets Heart Rate from an BLE HRM monitor and display it on an OLED.
- It uses an ESP32 with i2c OLED Display
/************************************************************************************************************/
#include
#include
#include "BLEDevice.h"
#include "SSD1306.h"
#include
const String sketchName = "ESP32 HRM BLE Client";
// BLE
// The remote HRM service we wish to connect to.
static BLEUUID serviceUUID(BLEUUID((uint16_t)0x180D));
// The HRM characteristic of the remote service we are interested in.
static BLEUUID charUUID(BLEUUID((uint16_t)0x2A37));
static BLEAddress *pServerAddress;
static boolean doConnect = false;
static boolean connected = false;
static boolean notification = false;
static BLERemoteCharacteristic* pRemoteCharacteristic;
// Wifi
WiFiClient wifi_client;
const char* ssid = "SSID";
const char* password = "PASSWORD";
// MQTT
PubSubClient mqtt(wifi_client);
const char* mqtt_server = "192.168.60.1";
const String base_topic = "iot/sariwating/";
long lastReconnectAttempt = 0;
// Oled Display
SSD1306 display(0x3c, 5, 4);
unsigned long screen_update, stats_update;
// TypeDef
typedef struct {
char ID[20];
uint16_t HRM;
}HRM;
HRM hrm;
//--------------------------------------------------------------------------------------------
// Setup the Serial Port and output Sketch name and compile date
//--------------------------------------------------------------------------------------------
void startSerial(uint32_t baud) {
// Setup Serial Port aut 115200 Baud
Serial.begin(baud);
delay(10);
Serial.println();
Serial.print(sketchName);
Serial.print(F(" | Compiled: "));
Serial.print(__DATE__);
Serial.print(F("/"));
Serial.println(__TIME__);
} // End of startSerial
//--------------------------------------------------------------------------------------------
// Draw OLED
//--------------------------------------------------------------------------------------------
void drawOLED(){
display.clear();
display.setColor(WHITE);
display.setTextAlignment(TEXT_ALIGN_CENTER);
display.setFont(ArialMT_Plain_24);
display.drawString( 64, 20, String(hrm.HRM) + " bpm");
display.display();
} //drawOLED()
//--------------------------------------------------------------------------------------------
// MQTT callback routine
//--------------------------------------------------------------------------------------------
void callback(char* topic, byte* payload, unsigned int length) {
// handle message arrived
} // End of callback
//--------------------------------------------------------------------------------------------
// MQTT send update
//--------------------------------------------------------------------------------------------
void sendMqttStats() {
mqtt.publish( (base_topic + "esp32" + F("/$name")).c_str(), sketchName.c_str(), true);
mqtt.publish( (base_topic + "esp32" + F("/$localip")).c_str(), WiFi.localIP().toString().c_str(), true);
mqtt.publish( (base_topic + "esp32" + F("/$mac")).c_str(), WiFi.macAddress().c_str(), true);
mqtt.publish( (base_topic + "esp32" + F("/$arch")).c_str(), "xtensa-esp32", true);
mqtt.publish( (base_topic + "esp32" + F("/$sdk")).c_str(), String(ARDUINO).c_str(), true);
mqtt.publish( (base_topic + "esp32" + F("/$fwversion")).c_str(), (String(__DATE__) + "/" + String(__TIME__)).c_str(), true);
mqtt.publish( (base_topic + "esp32" + F("/$online")).c_str(), "true", true);
} // End of sendMqttStats()
//--------------------------------------------------------------------------------------------
// MQTT reconnect
//--------------------------------------------------------------------------------------------
boolean reconnect() {
if (mqtt.connect("ESP32Client", (base_topic + "esp32" + F("/$online")).c_str(), 1, true, "false")) {
Serial.println(F("MQTT Connected"));
// Once connected, publish an announcement...
sendMqttStats();
// ... and resubscribe
}
return mqtt.connected();
} // End of reconnect
//--------------------------------------------------------------------------------------------
// Publish JSON to MQTT
//--------------------------------------------------------------------------------------------
void mqttPublish(const char* topic, JsonObject& sendJson) {
if (mqtt.connected()) {
char buffer[sendJson.measureLength() + 1];
sendJson.printTo(buffer, sizeof(buffer));
mqtt.publish(topic, buffer, true);
}
} // End of mqttPublish
//--------------------------------------------------------------------------------------------
// Send HRM stats to MQTT
//--------------------------------------------------------------------------------------------
void sendHRMData() {
DynamicJsonBuffer dataBuffer;
JsonObject& dataJson = dataBuffer.createObject();
dataJson[F("HRM")] = hrm.HRM;
mqttPublish( (base_topic + "esp32" + F("/json")).c_str(), dataJson);
} // End of sendHRMData
//--------------------------------------------------------------------------------------------
// BLE notifyCallback
//--------------------------------------------------------------------------------------------
static void notifyCallback( BLERemoteCharacteristic* pBLERemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify) {
if (bitRead(pData[0], 0) == 1) {
Serial.println(F("16bit HeartRate Detected"));
} else {
Serial.println(F("8bit HeartRate Detected"));
}
if (length == 2) {
hrm.HRM = pData[1];
Serial.print("Heart Rate ");
Serial.print(hrm.HRM, DEC);
Serial.println("bpm");
sendHRMData();
}
}
//--------------------------------------------------------------------------------------------
// Connect to BLE HRM
//--------------------------------------------------------------------------------------------
bool connectToServer(BLEAddress pAddress) {
Serial.print(F("Forming a connection to "));
Serial.println(pAddress.toString().c_str());
BLEClient* pClient = BLEDevice::createClient();
Serial.println(F(" - Created client"));
// Connect to the HRM BLE Server.
pClient->connect(pAddress);
Serial.println(F(" - 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(F("Failed to find our service UUID: "));
Serial.println(serviceUUID.toString().c_str());
return false;
}
Serial.println(F(" - 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(F("Failed to find our characteristic UUID: "));
Serial.println(charUUID.toString().c_str());
return false;
}
Serial.println(F(" - Found our characteristic"));
// Register for Notify
pRemoteCharacteristic->registerForNotify(notifyCallback);
}
//--------------------------------------------------------------------------------------------
// 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(F("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.getServiceUUID().equals(serviceUUID)) {
//
Serial.print(F("Found our device! address: "));
advertisedDevice.getScan()->stop();
pServerAddress = new BLEAddress(advertisedDevice.getAddress());
doConnect = true;
} // Found our server
} // onResult
}; // MyAdvertisedDeviceCallbacks
//--------------------------------------------------------------------------------------------
// Setup Routine
//--------------------------------------------------------------------------------------------
void setup() {
// Setup Serial Port aut 115200 Baud
startSerial(115200);
// Start the OLED Display
display.init();
display.setFont(ArialMT_Plain_24);
display.flipScreenVertically(); // this is to flip the screen 180 degrees
display.setColor(WHITE);
display.setTextAlignment(TEXT_ALIGN_CENTER);
display.drawString(64, 20, "YouTube");
display.display();
// We will connect to the WiFi network
Serial.print(F("Connecting to "));
Serial.println(ssid);
/* Explicitly set the ESP32 to be a WiFi-client, otherwise, it by default,
would try to act as both a client and an access-point and could cause
network-issues with your other WiFi-devices on your WiFi-network. */
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(F("."));
}
Serial.println("");
Serial.println(F("WiFi connected"));
Serial.print(F("IP address: "));
Serial.println(WiFi.localIP());
// MQTT
mqtt.setServer(mqtt_server, 1883);
mqtt.setCallback(callback);
lastReconnectAttempt = 0;
// Start BLE
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 30 seconds.
BLEScan* pBLEScan = BLEDevice::getScan();
pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
pBLEScan->setActiveScan(true);
pBLEScan->start(30);
} // End of setup.
//--------------------------------------------------------------------------------------------
// Main Program Loop
//--------------------------------------------------------------------------------------------
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(*pServerAddress)) {
Serial.println(F("We are now connected to the BLE HRM"));
connected = true;
} else {
Serial.println(F("We have failed to connect to the HRM; there is nothin more we will do."));
}
doConnect = false;
}
// Turn notification on
if (connected) {
if (notification == false) {
Serial.println(F("Turning Notifocation On"));
const uint8_t onPacket[] = {0x1, 0x0};
pRemoteCharacteristic->getDescriptor(BLEUUID((uint16_t)0x2902))->writeValue((uint8_t*)onPacket, 2, true);
notification = true;
}
}
if (!mqtt.connected()) {
long now = millis();
if (now - lastReconnectAttempt > 5000) {
lastReconnectAttempt = now;
// Attempt to reconnect
if (reconnect()) {
lastReconnectAttempt = 0;
}
}
} else {
// Client connected
mqtt.loop();
}
//-------------------------------------------------------------------------------------------
// Timed Code
//-------------------------------------------------------------------------------------------
// Every 100 Milliseconds
if ((millis()-screen_update)>100) { // 100 Milliseconds
drawOLED();
screen_update = millis();
}
// Every 10 Seconds
if ((millis()-stats_update)>10000) { // 10 Seconds
stats_update = millis();
sendMqttStats();
}
} // End of loop