Skip to content

Instantly share code, notes, and snippets.

@UriShX
Created August 5, 2019 15:14
Show Gist options
  • Save UriShX/2b1f1c7b461b466a4b4ae336d52653dd to your computer and use it in GitHub Desktop.
Save UriShX/2b1f1c7b461b466a4b4ae336d52653dd to your computer and use it in GitHub Desktop.
Fading / breathing LED on a ESP32 dev board, controlled via BLE. Control web app at: https://urishx.github.io/ESP32_fader/
/**
* Fading / breathing LED on a ESP32 dev board, controlled via BLE.
* Control web app at: https://urishx.github.io/ESP32_fader/.
* Features auto ranging and sync via subscription.
* Also outputs data to serial monitor.
* Written by Uri Shani, May 2019.
* MIT licensed (at least my very own original contributions).
*
* Breathing LED from https://sean.voisen.org/blog/2011/10/breathing-led-with-arduino/
* also: https://arduinoelectronics.wordpress.com/
*
* esp32 LEDC tutorial: https://techtutorialsx.com/2017/06/15/esp32-arduino-led-pwm-fading/
*/
#include <BLEUtils.h>
#include <BLEServer.h>
#include <BLEDevice.h>
#include <BLEAdvertising.h>
#include <BLE2902.h> // BLE notify and indicate properties
/** freeRTOS task handles */
TaskHandle_t blinkDelayTask;
TaskHandle_t sendBLEdataTask;
/** Characteristic for digital output */
BLECharacteristic *pCharacteristicRx;
BLECharacteristic *pCharacteristicTx;
/** BLE Advertiser */
BLEAdvertising* pAdvertising;
/** BLE Service */
BLEService *pService;
/** BLE Server */
BLEServer *pServer;
bool deviceConnected = false;
bool oldDeviceConnected = false;
uint8_t txValue = 0;
// See the following for generating UUIDs:
// https://www.uuidgenerator.net/
#define SERVICE_UUID "48696828-8aba-4445-b1d2-9fe5c3e47382" // UART service UUID
#define CHARACTERISTIC_UUID_RX_TX "7dd57463-acc5-48eb-9b7f-3052779322de"
unsigned int selection = 7; // default blinking rate
const unsigned int _min = 2; // minimal blinking rate
const unsigned int _max = 15; // maximum blinking rate
// formatting mask for using up to 4 single byte values in 32 bit unsigned int
const unsigned int _mask = ((0xff << 24) | (0xff << 16) | (0xff << 8));
unsigned int sendVal = (_max << 24) | (_min << 16) | selection;
String inputString = ""; // a String to hold incoming data
boolean stringComplete = false; // whether the string is complete
void setup() {
Serial.begin(115200); // Start serial communication
// LED fading task, to run with little to no dependency on the timing of Arduino's loop()
xTaskCreatePinnedToCore(
blinkLED5,
"blinkDelayTask",
1024,
NULL,
1,
&blinkDelayTask,
1
);
delay(500);
// BLE communication task
xTaskCreatePinnedToCore(
sendBLEdata,
"sendBLEdataTask",
2048,
NULL,
1,
&sendBLEdataTask,
1
);
delay(500);
inputString.reserve(20); // malloc for serial input
initBLE(true);
Serial.println("\n\nType numbers in the range of 2 to 15 to set delay period. Initial period is 7.");
Serial.println(sendVal, BIN);
}
void loop() {
//receive from serial terminal
//should be handled in ISR, no simple implementation in Arduino IDE for ESP32
if (Serial.available() > 0) {
float i;
char inByte = (char)Serial.read();
inputString += inByte;
if (inByte == '\n') {
stringComplete = true;
}
}
/* Handle strings when newline arrives
Arduino terminal adds "\r\n" to each recieved string
' ' space
'\t' horizontal tab
'\n' newline
'\v' vertical tab
'\f' feed
'\r' carriage return
*/
if (stringComplete) {
if (inputString[0] >= 49 && inputString[0] <= 57) {
unsigned int oldSelection = selection;
selection = inputString.toInt();
if (selection > _max || selection < _min) {
Serial.println("Number out of range! Select numbers in the range of 2 to 15.");
selection = oldSelection;
} else {
Serial.printf("Delay period set to:\t%i\n", selection);
sendVal = (sendVal & _mask) ^ selection;
}
}
inputString = "";
stringComplete = false;
}
}
void blinkLED5(void * parameter) {
// init():
TickType_t xLastWakeTime;
TickType_t xPeriod = pdMS_TO_TICKS(selection);
// the number of the LED pin
const int ledPin = 5; // 5 corresponds to GPIO5, Lolin32 built-in LED
// setting PWM properties
const int freq = 5000;
const int ledChannel = 0;
const int resolution = 8;
unsigned int dutyCycle = 255;
unsigned int i = 300;
xLastWakeTime = xTaskGetTickCount();
// configure LED PWM functionalitites
ledcSetup(ledChannel, freq, resolution);
// attach the channel to the GPIO to be controlled
ledcAttachPin(ledPin, ledChannel);
// LED "breathing" loop
while(1) {
xPeriod = pdMS_TO_TICKS(selection);
dutyCycle = 255 - int((exp(sin(i/2000.0*PI*10)) - 0.36787944)*108.492);
ledcWrite(ledChannel, dutyCycle);
if (i < 699) i++;
else i = 300;
vTaskDelayUntil(&xLastWakeTime, xPeriod);
}
}
// BLE callback functions
class MyServerCallbacks: public BLEServerCallbacks {
void onConnect(BLEServer* pServer) {
deviceConnected = true;
};
void onDisconnect(BLEServer* pServer) {
deviceConnected = false;
pAdvertising->start();
}
};
// recieve breathing rate update over BLE
class MyCallbacks: public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic *pCharacteristic) {
std::string rxValue = pCharacteristic->getValue();
char rxNum = 0;
// get value, print it out
if (rxValue.length() > 0) {
Serial.println("*********");
Serial.print("Received Value: ");
for (int i = 0; i < rxValue.length(); i++) {
Serial.print(rxValue[i]);
rxNum += rxValue[i];
}
Serial.printf("\n%x\n", rxNum);
Serial.println("*********");
}
// check if value is in range
if (rxNum > _max || rxNum < _min) {
// if value out of range print rejection message to serial monitor
Serial.println("Number out of range! Select numbers in the range of 2 to 15.");
} else {
// if value is in range set global variable (selection) to recieved value
selection = rxNum;
// format the recieve value for BLE
sendVal = (sendVal & _mask) ^ selection;
// print acknowledgment message to serial monitor
Serial.printf("Delay period set to:\t%d\n", selection);
}
}
};
/**
initBLE
Initialize BLE service and characteristic
Start BLE server and service advertising
*/
void initBLE(bool onOff) {
Serial.printf("initBLE core %d\n ", xPortGetCoreID());
if (onOff) {
if (BLEDevice::getInitialized() == 0) {
// Initialize BLE and set output power
BLEDevice::init("ESP32 UART Service");
BLEDevice::setPower(ESP_PWR_LVL_P7);
// Create BLE Server
pServer = BLEDevice::createServer();
// Set server callbacks
pServer->setCallbacks(new MyServerCallbacks());
// Create BLE Service
pService = pServer->createService(BLEUUID(SERVICE_UUID), 20);
// Create BLE Characteristic for reading, writing, and notifying led fading rate
pCharacteristicRx = pService->createCharacteristic(
BLEUUID(CHARACTERISTIC_UUID_RX_TX),
BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_WRITE |
BLECharacteristic::PROPERTY_NOTIFY
);
pCharacteristicRx->setCallbacks(new MyCallbacks());
pCharacteristicRx->addDescriptor(new BLE2902()); // notify descriptor
}
// Start the service
pService->start();
// Start advertising
pAdvertising = pServer->getAdvertising();
pAdvertising->start();
} else if (!onOff) {
// this is a provision for starting and stopping ble services, not fully implemented here
pAdvertising->stop();
pService->stop();
BLEDevice::deinit(true);
}
}
// BLE notification task
void sendBLEdata(void * parameter) {
TickType_t xLastWakeTime;
TickType_t xPeriod = pdMS_TO_TICKS(500);
xLastWakeTime = xTaskGetTickCount();
while(1) {
// if the device is connected via BLE try to send notifications
if (deviceConnected) {
// format data to be sent
pCharacteristicRx->setValue(sendVal);
std::string asSet = pCharacteristicRx->getValue();
String setString = "";
Serial.print("set characteristic value as:\t");
for (byte i = 0; i < asSet.length(); i++) {
char stringChar = asSet[i];
setString += stringChar;
}
Serial.println(setString);
// test if notifications are enabled by client
byte testNotify = *pCharacteristicRx->getDescriptorByUUID((uint16_t)0x2902)->getValue();
// if enabled, send value over BLE
if (testNotify == 1) {
pCharacteristicRx->notify(); // Send the value to the app!
Serial.print("*** Sent Int: \t");
Serial.print(sendVal, DEC);
Serial.println(" ***");
} else {
// else print failure message to serial monitor
Serial.print("notify failed, value of 0x2902 descriptor:\t");
Serial.println(testNotify, HEX);//*pCharacteristicMeas->getDescriptorByUUID((uint16_t)0x2902)->getValue(), HEX);
}
}
// sleep task for 500 ms
vTaskDelayUntil(&xLastWakeTime, xPeriod);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment