//combined: the API + extract data from API + + WiFi setup (connect to hotspot)
//newly added: advertise BLE service + remote control
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
const char* ssid = "AdvancedNetwork";
const char* password = "qwertyuiop";
const char* apiKey = "nZoy03ORSwlTguK0/T7Rgg==IGRMtICtVhM2aVtC";
const char* apiEndpoint = "https://worldtimeapi.org/api/ip";
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include <BLEScan.h>//
#include <BLEAdvertisedDevice.h>//
#include <pfodEEPROM.h>//
#include <pfodDwgControls.h>//installed pfodDwgControls(some feature like 'Drawing control on pfod'is not needed)
#include <BLE2902.h>
#define DEBUG
// download the libraries from http://www.forward.com.au/pfod/pfodParserLibraries/index.html
// pfodParser.zip V3.52+ contains pfodParser, pfodSecurity, pfodDelay, pfodBLEBufferedSerial, pfodSMS and pfodRadio
#include <pfodParser.h>
#include <pfodBLEBufferedSerial.h> // used to prevent flooding bluetooth sends
/////////////////////end of library////////////////////////////////
int swap01(int); // method prototype for slider end swaps
// =========== pfodBLESerial definitions
const char* localName = "ESP32 BLE"; // <<<<<< change this string to customize the adverised name of your board
class pfodBLESerial : public Stream, public BLEServerCallbacks, public BLECharacteristicCallbacks {
public:
pfodBLESerial(); void begin(); void poll(); size_t write(uint8_t); size_t write(const uint8_t*, size_t); int read();
int available(); void flush(); int peek(); void close(); bool isConnected();
static void addReceiveBytes(const uint8_t* bytes, size_t len);
const static uint8_t pfodEOF[1]; const static char* pfodCloseConnection;
volatile static bool connected;
void onConnect(BLEServer* serverPtr);
void onDisconnect(BLEServer* serverPtr);
void onWrite(BLECharacteristic *pCharacteristic);
private:
static const int BLE_MAX_LENGTH = 20;
static const int BLE_RX_MAX_LENGTH = 256; static volatile size_t rxHead; static volatile size_t rxTail;
volatile static uint8_t rxBuffer[BLE_RX_MAX_LENGTH];
size_t txIdx; uint8_t txBuffer[BLE_MAX_LENGTH];
};
volatile size_t pfodBLESerial::rxHead = 0; volatile size_t pfodBLESerial::rxTail = 0;
volatile uint8_t pfodBLESerial::rxBuffer[BLE_RX_MAX_LENGTH]; const uint8_t pfodBLESerial::pfodEOF[1] = {(uint8_t) - 1};
const char* pfodBLESerial::pfodCloseConnection = "{!}"; volatile bool pfodBLESerial::connected = false;
#define SERVICE_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E" // UART service UUID
#define CHARACTERISTIC_UUID_RX "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
#define CHARACTERISTIC_UUID_TX "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"
BLEServer *serverPtr = NULL;
BLECharacteristic * characteristicTXPtr;
// =========== end pfodBLESerial definitions
pfodParser parser("V2"); // create a parser with menu version string to handle the pfod messages
// create a parser to handle the pfod messages
pfodBLESerial bleSerial; // create a BLE serial connection
pfodBLEBufferedSerial bleBufferedSerial; // create a BLE serial connection
unsigned long plot_msOffset = 0; // set by {@} response
bool clearPlot = false; // set by the {@} response code
/////////////////////start of setup/////////////////////////////
void setup() {
#ifdef DEBUG
Serial.begin(115200);
Serial.println();
#endif
Serial.begin(115200);
connectToWiFi();
// Create the BLE Device
BLEDevice::init("BLE ESP32");
// Create the BLE Server
serverPtr = BLEDevice::createServer();
serverPtr->setCallbacks(&bleSerial);
// Create the BLE Service
BLEService *servicePtr = serverPtr->createService(SERVICE_UUID);
// Create a BLE Characteristic
characteristicTXPtr = servicePtr->createCharacteristic(CHARACTERISTIC_UUID_TX, BLECharacteristic::PROPERTY_NOTIFY);
characteristicTXPtr->addDescriptor(new BLE2902());
BLECharacteristic * characteristicRXPtr = servicePtr->createCharacteristic(CHARACTERISTIC_UUID_RX, BLECharacteristic::PROPERTY_WRITE);
characteristicRXPtr->setCallbacks(&bleSerial);
serverPtr->getAdvertising()->addServiceUUID(BLEUUID(SERVICE_UUID));
// Start the service
servicePtr->start();
// Start advertising
serverPtr->getAdvertising()->start();
#ifdef DEBUG
Serial.println("BLE Server and Advertising started");
#endif
bleSerial.begin();
// connect parser
parser.connect(bleBufferedSerial.connect(&bleSerial)); // connect the parser to the i/o stream via buffer
//end
/* For DEBUG only
// Initialize BLE
BLEDevice::init("Tyler's ESP32");
BLEServer *pServer = BLEDevice::createServer();
BLEService *pService = pServer->createService(SERVICE_UUID);
BLECharacteristic *pCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID,
BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_WRITE
);
pCharacteristic->setValue("Hello World!"); // Initial value
pService->start();
// Configure advertising
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->setScanResponse(true);
pAdvertising->setMinPreferred(0x06);
pAdvertising->setMinPreferred(0x12);
BLEDevice::startAdvertising();
// Set up callbacks
pServer->setCallbacks(new MyServerCallbacks());
Serial.println("Characteristic defined! Now you can read it in your phone!");
}
class MyServerCallbacks : public BLEServerCallbacks {
void onConnect(BLEServer* pServer) {
Serial.println("You are connected");
}
}
*/
void loop() {
//API send request
if (Serial.available() > 0) {
String input = Serial.readString();
if (input.equals("send")) {
bool success = sendGETRequest();
if (success) {
Serial.println("GET request sent successfully");
} else {
Serial.println("Failed to send GET request");
}
}
}
delay(100);
//remote control button detection
uint8_t cmd = parser.parse(); // parse incoming data from connection
// parser returns non-zero when a pfod command is fully parsed
if (cmd != 0) { // have parsed a complete msg { to }
uint8_t* pfodFirstArg = parser.getFirstArg(); // may point to \0 if no arguments in this msg.
pfod_MAYBE_UNUSED(pfodFirstArg); // may not be used, just suppress warning
long pfodLongRtn; // used for parsing long return arguments, if any
pfod_MAYBE_UNUSED(pfodLongRtn); // may not be used, just suppress warning
if ('.' == cmd) {
// pfodApp has connected and sent {.} , it is asking for the main menu
if (!parser.isRefresh()) {
sendMainMenu(); // send back the menu designed
} else {
sendMainMenuUpdate(); // menu is cached just send update
}
// handle {@} request
} else if('@'==cmd) { // pfodApp requested 'current' time
plot_msOffset = millis(); // capture current millis as offset rawdata timestamps
clearPlot = true; // clear plot on reconnect as have new plot_msOffset
parser.print(F("{@`0}")); // return `0 as 'current' raw data milliseconds
// now handle commands returned from button/sliders
} else if('A'==cmd) { // user pressed -- 'Motion guard Alert'
// in the main Menu of Flip Clock
// << add your action code here for this button
parser.print(F("{}")); // change this return as needed.
// always send back a pfod msg otherwise pfodApp will disconnect.
} else if('B'==cmd) { // user pressed -- 'WiFi connect Pro'
// in the main Menu of Flip Clock
// << add your action code here for this button
parser.print(F("{}")); // change this return as needed.
// always send back a pfod msg otherwise pfodApp will disconnect.
} else if('C'==cmd) { // user pressed -- 'PrecisionSync'
// in the main Menu of Flip Clock
// << add your action code here for this button
parser.print(F("{}")); // change this return as needed.
// always send back a pfod msg otherwise pfodApp will disconnect.
} else if('D'==cmd) { // user pressed -- 'WeatherForecast'
// in the main Menu of Flip Clock
// << add your action code here for this button
parser.print(F("{}")); // change this return as needed.
// always send back a pfod msg otherwise pfodApp will disconnect.
} else if('a'==cmd) { // user pressed -- 'Connect to ESP32'
// in the main Menu of Flip Clock
// << add your action code here for this button
parser.print(F("{}")); // change this return as needed.
// always send back a pfod msg otherwise pfodApp will disconnect.
} else if ('!' == cmd) {
// CloseConnection command
closeConnection(parser.getPfodAppStream());
} else {
// unknown command
parser.print(F("{}")); // always send back a pfod msg otherwise pfodApp will disconnect.
}
}
}
void connectToWiFi() {
Serial.println("Connecting to WiFi...");
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting...");
}
Serial.println("Connected to WiFi");
}
//////// send get request message to API service host
bool sendGETRequest() {
HTTPClient http;
// Replace API_URL with the actual API endpoint URL
String API_URL = "https://api.api-ninjas.com/v1/worldtime?city=Leeds";
http.begin(API_URL);
http.addHeader("X-Api-Key", apiKey);
int httpResponseCode = http.GET();
if (httpResponseCode > 0) {
if (httpResponseCode == HTTP_CODE_OK) {
String payload = http.getString();
Serial.println("Response:");
Serial.println(payload);
// Parse JSON data
DynamicJsonDocument doc(1024);
DeserializationError error = deserializeJson(doc, payload);
if (error) {
Serial.print("deserializeJson() failed: ");
Serial.println(error.c_str());
return false;
}
// Example DATA Extraction from JSON (Time as example)
const char* datetime = doc["datetime"];
Serial.print("Time: ");
Serial.println(datetime);
return true;
} else {
Serial.print("HTTP request failed with error code: ");
Serial.println(httpResponseCode);
return false;
}
} else {
Serial.print("HTTP request failed: ");
Serial.println(http.errorToString(httpResponseCode).c_str());
return false;
}
http.end();
}
void closeConnection(Stream *io) {
//nothing special
}
void sendMainMenu() {
// !! Remember to change the parser version string
// every time you edit this method
parser.print(F("{,")); // start a Menu screen pfod message
// send menu background, format, prompt, refresh and version
parser.print(F("~<prompt not set>`0"));
parser.sendVersion(); // send the menu version
// send menu items
parser.print(F("|A<b>"));
parser.print(F("~Motion guard Alert"));
parser.print(F("|B"));
parser.print(F("~WiFi connect Pro"));
parser.print(F("|C"));
parser.print(F("~PrecisionSync"));
parser.print(F("|D"));
parser.print(F("~WeatherForecast"));
parser.print(F("|a<b><+4>"));
parser.print(F("~Connect to ESP32"));
parser.print(F("}")); // close pfod message
}
void sendMainMenuUpdate() {
parser.print(F("{;")); // start an Update Menu pfod message
// send menu items
parser.print(F("|A"));
parser.print(F("|B"));
parser.print(F("|C"));
parser.print(F("|D"));
parser.print(F("|a"));
parser.print(F("}")); // close pfod message
// ============ end of menu ===========
}
// ========== pfodBLESerial methods
pfodBLESerial::pfodBLESerial() {}
bool pfodBLESerial::isConnected() {
return (connected);
}
void pfodBLESerial::begin() {}
void pfodBLESerial::close() {}
void pfodBLESerial::poll() {}
size_t pfodBLESerial::write(const uint8_t* bytes, size_t len) {
for (size_t i = 0; i < len; i++) { write(bytes[i]); }
return len; // just assume it is all written
}
size_t pfodBLESerial::write(uint8_t b) {
if (!isConnected()) { return 1; }
txBuffer[txIdx++] = b;
if ((txIdx == sizeof(txBuffer)) || (b == ((uint8_t)'\n')) || (b == ((uint8_t)'}')) ) {
flush(); // send this buffer if full or end of msg or rawdata newline
}
return 1;
}
int pfodBLESerial::read() {
if (rxTail == rxHead) { return -1; }
// note increment rxHead befor writing
// so need to increment rxTail befor reading
rxTail = (rxTail + 1) % sizeof(rxBuffer);
uint8_t b = rxBuffer[rxTail];
return b;
}
// called as part of parser.parse() so will poll() each loop()
int pfodBLESerial::available() {
flush(); // send any pending data now. This happens at the top of each loop()
int rtn = ((rxHead + sizeof(rxBuffer)) - rxTail ) % sizeof(rxBuffer);
return rtn;
}
void pfodBLESerial::flush() {
if (txIdx == 0) { return; }
characteristicTXPtr->setValue((uint8_t*)txBuffer, txIdx);
txIdx = 0;
characteristicTXPtr->notify();
}
int pfodBLESerial::peek() {
if (rxTail == rxHead) { return -1; }
size_t nextIdx = (rxTail + 1) % sizeof(rxBuffer);
uint8_t byte = rxBuffer[nextIdx];
return byte;
}
void pfodBLESerial::addReceiveBytes(const uint8_t* bytes, size_t len) {
// note increment rxHead befor writing
// so need to increment rxTail befor reading
for (size_t i = 0; i < len; i++) {
rxHead = (rxHead + 1) % sizeof(rxBuffer);
rxBuffer[rxHead] = bytes[i];
}
}
//=========== ESP32 BLE callback methods
void pfodBLESerial:: onConnect(BLEServer* serverPtr) {
// clear parser with -1 in case partial message left, should not be one
addReceiveBytes(bleSerial.pfodEOF, sizeof(pfodEOF));
connected = true;
}
void pfodBLESerial::onDisconnect(BLEServer* serverPtr) {
// clear parser with -1 and insert {!} incase connection just lost
addReceiveBytes(bleSerial.pfodEOF, sizeof(pfodEOF));
addReceiveBytes((const uint8_t*)pfodCloseConnection, sizeof(pfodCloseConnection));
serverPtr->getAdvertising()->start();
connected = false;
}
void pfodBLESerial::onWrite(BLECharacteristic *pCharacteristic) {
std::string rxValue = pCharacteristic->getValue();
uint8_t *data = (uint8_t*)rxValue.data();
size_t len = rxValue.length();
addReceiveBytes((const uint8_t*)data, len);
}