← ESP32 PLC RTC Sync from a Smartphone over BLE
Hydraulic moving floor (BLE app)ESP32 PLC 38RBLEInfrastructure
ESP32 PLC RTC Sync from a Smartphone over BLE — full example
Set the ESP32 PLC clock from a smartphone over Bluetooth LE — no NTP or internet needed. Arduino example with a timestamp frame and settimeofday.
Complete, runnable program for the ESP32 PLC 38R (rtc-sync-over-ble-38r.ino): wiring header, requirements and integration notes included.
Download the full project pack — freeThis example + the related ones + bill of materials
Read-only preview.
/*
* COMPLETE EXAMPLE — RTC synchronization from the mobile app (BLE)
*
* Hardware: ESP32 PLC 38R (Industrial Shields)
* Based on: hydraulic moving floor project, AppLink.cpp ([D] command)
*
* Wiring: none — BLE only. The PLC advertises a Nordic UART service.
*
* Logic:
* - The equipment works without internet (mobile machine): NTP is not
* an option.
* - The smartphone DOES have the correct time, so the app sends it on
* connect with the [D]timestamp_ms* frame (Unix epoch in milliseconds).
* - The PLC sets its clock with settimeofday(); from then on events
* and logs carry the real date/time.
* - To test without the app: nRF Connect -> write "[D]1765532800000*"
* to the RX characteristic.
*
* Integration with other catalog modules:
* - Same BLE UART server as the equipment control
* (see ejemplos/piso-movil/ble-mobile-app-control-38r.ino).
* - The synchronized time makes it possible to timestamp the counters
* persisted in NVS (see ejemplos/piso-movil/nvs-persistent-counters-38r.ino).
*/
#include
#include
#include
#include
#include
#define SERVICE_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"
#define CHAR_RX_UUID "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
#define CHAR_TX_UUID "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"
BLECharacteristic *txChar;
bool deviceConnected = false;
bool rtcSynced = false;
// Sets the ESP32 internal clock from an epoch in milliseconds
void setRTC(uint64_t epochMs) {
struct timeval tv;
tv.tv_sec = (time_t)(epochMs / 1000ULL);
tv.tv_usec = (suseconds_t)((epochMs % 1000ULL) * 1000ULL);
settimeofday(&tv, NULL);
rtcSynced = true;
Serial.println("RTC synchronized from the mobile app");
}
class ServerCB : public BLEServerCallbacks {
void onConnect(BLEServer *s) { deviceConnected = true; }
void onDisconnect(BLEServer *s) { deviceConnected = false; s->startAdvertising(); }
};
class RxCB : public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic *c) {
String cmd = String(c->getValue().c_str()); // e.g. "[D]1765532800000*"
if (cmd.startsWith("[D]")) {
int end = cmd.indexOf('*');
if (end > 3) {
// strtoull because the epoch in ms does not fit in a 32-bit int
uint64_t epochMs = strtoull(cmd.substring(3, end).c_str(), NULL, 10);
if (epochMs > 1600000000000ULL) { // sanity check: > year 2020
setRTC(epochMs);
}
}
}
}
};
void setup() {
Serial.begin(115200);
BLEDevice::init("PLC-HIDRAULICO");
BLEServer *server = BLEDevice::createServer();
server->setCallbacks(new ServerCB());
BLEService *svc = server->createService(SERVICE_UUID);
txChar = svc->createCharacteristic(CHAR_TX_UUID, BLECharacteristic::PROPERTY_NOTIFY);
txChar->addDescriptor(new BLE2902());
BLECharacteristic *rxChar =
svc->createCharacteristic(CHAR_RX_UUID, BLECharacteristic::PROPERTY_WRITE);
rxChar->setCallbacks(new RxCB());
svc->start();
server->getAdvertising()->start();
Serial.println("BLE advertising. Waiting for [D]timestamp_ms* frame ...");
}
void loop() {
// Every 5 s print the PLC time (and notify it if an app is connected)
static uint32_t tLast = 0;
if (millis() - tLast > 5000) {
tLast = millis();
time_t now = time(NULL);
struct tm *t = localtime(&now);
char buf[32];
strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", t);
String line = String(rtcSynced ? "[RTC OK] " : "[RTC NOT SYNCED] ") + buf;
Serial.println(line);
if (deviceConnected) {
txChar->setValue(line.c_str());
txChar->notify();
}
}
delay(50);
}
Download the full project pack — freeThis example + the related ones + bill of materials