This commit is contained in:
DTTerastar 2021-09-15 12:11:32 -04:00
parent 5e1f327020
commit a4fe08d8ee
11 changed files with 102 additions and 83 deletions

View File

@ -1,11 +1,6 @@
#include "BleFingerprint.h" #include "BleFingerprint.h"
#include "util.h" #include "util.h"
BleFingerprint::~BleFingerprint()
{
Serial.printf("%d Del | MAC: %s, ID: %s\n", xPortGetCoreID(), SMacf(address).c_str(), id.c_str());
}
BleFingerprint::BleFingerprint(BLEAdvertisedDevice *advertisedDevice, float fcmin, float beta, float dcutoff) : oneEuro{one_euro_filter<double, unsigned long>(1, fcmin, beta, dcutoff)} BleFingerprint::BleFingerprint(BLEAdvertisedDevice *advertisedDevice, float fcmin, float beta, float dcutoff) : oneEuro{one_euro_filter<double, unsigned long>(1, fcmin, beta, dcutoff)}
{ {
if (advertisedDevice->getAddressType() == BLE_ADDR_PUBLIC) if (advertisedDevice->getAddressType() == BLE_ADDR_PUBLIC)
@ -15,53 +10,49 @@ BleFingerprint::BleFingerprint(BLEAdvertisedDevice *advertisedDevice, float fcmi
address = advertisedDevice->getAddress(); address = advertisedDevice->getAddress();
newest = recent = oldest = rssi = advertisedDevice->getRSSI(); newest = recent = oldest = rssi = advertisedDevice->getRSSI();
String mac_address = SMacf(address); calRssi = advertisedDevice->haveTXPower() ? (-advertisedDevice->getTXPower()) - 41 : 0;
Serial.printf("%d New | MAC: %s", xPortGetCoreID(), mac_address.c_str()); fingerprint(advertisedDevice);
if (calRssi > 0) calRssi = defaultTxPower;
}
void BleFingerprint::fingerprint(BLEAdvertisedDevice *advertisedDevice)
{
if (advertisedDevice->haveName()) if (advertisedDevice->haveName())
name = String(advertisedDevice->getName().c_str()); name = String(advertisedDevice->getName().c_str());
calRssi = advertisedDevice->haveTXPower() ? (-advertisedDevice->getTXPower()) - 41 : 0;
std::string strServiceData = advertisedDevice->getServiceData();
uint8_t cServiceData[100];
strServiceData.copy((char *)cServiceData, strServiceData.length(), 0);
if (advertisedDevice->haveServiceUUID()) if (advertisedDevice->haveServiceUUID())
{ {
if (advertisedDevice->getServiceDataUUID().equals(BLEUUID(tileUUID)) == true) if (advertisedDevice->getServiceDataUUID().equals(BLEUUID(tileUUID)) == true)
{ {
id = "tile:" + mac_address; pid = "tile:" + getMac();
Serial.printf(", ID: %s", id.c_str());
} }
else if (advertisedDevice->getServiceDataUUID().equals(BLEUUID(exposureUUID)) == true) else if (advertisedDevice->getServiceDataUUID().equals(BLEUUID(exposureUUID)) == true)
{ // found covid exposure tracker { // found covid exposure tracker
id = "exp:" + String(strServiceData.length()); std::string strServiceData = advertisedDevice->getServiceData(BLEUUID(exposureUUID));
Serial.printf(", ID: %s", id.c_str()); pid = "exp:" + String(strServiceData.length());
//char *sdHex = NimBLEUtils::buildHexData(nullptr, (uint8_t *)strServiceData.data(), strServiceData.length());
//doc["tek"] = String(sdHex).substring(4, 20);
//free(sdHex);
} }
else if (advertisedDevice->getServiceDataUUID().equals(BLEUUID(beaconUUID)) == true) else if (advertisedDevice->getServiceDataUUID().equals(BLEUUID(beaconUUID)) == true)
{ // found Eddystone UUID { // found Eddystone UUID
Serial.print(", Eddystone"); std::string strServiceData = advertisedDevice->getServiceData(BLEUUID(beaconUUID));
if (cServiceData[0] == 0x10) if (strServiceData[0] == EDDYSTONE_URL_FRAME_TYPE && strServiceData.length() <= 18)
{ {
BLEEddystoneURL oBeacon = BLEEddystoneURL(); BLEEddystoneURL oBeacon = BLEEddystoneURL();
oBeacon.setData(strServiceData); oBeacon.setData(strServiceData);
// Serial.printf("Eddystone Frame Type (Eddystone-URL) ");
url = String(oBeacon.getDecodedURL().c_str()); url = String(oBeacon.getDecodedURL().c_str());
Serial.print(" URL: ");
Serial.print(url.c_str());
calRssi = oBeacon.getPower(); calRssi = oBeacon.getPower();
} }
else if (cServiceData[0] == 0x20) else if (strServiceData[0] == EDDYSTONE_TLM_FRAME_TYPE)
{ {
BLEEddystoneTLM oBeacon = BLEEddystoneTLM(); BLEEddystoneTLM oBeacon = BLEEddystoneTLM();
oBeacon.setData(strServiceData); oBeacon.setData(strServiceData);
Serial.printf(" TLM: "); temp = oBeacon.getTemp();
Serial.printf(oBeacon.toString().c_str()); volts = oBeacon.getVolt();
#ifdef VERBOSE
Serial.println(oBeacon.toString().c_str());
#endif
} }
} }
else else
@ -70,12 +61,11 @@ BleFingerprint::BleFingerprint(BLEAdvertisedDevice *advertisedDevice, float fcmi
for (int i = 0; i < advertisedDevice->getServiceUUIDCount(); i++) for (int i = 0; i < advertisedDevice->getServiceUUIDCount(); i++)
{ {
std::string sid = advertisedDevice->getServiceUUID(i).toString(); std::string sid = advertisedDevice->getServiceUUID(i).toString();
Serial.printf(", sID: %s", sid.c_str());
fingerprint = fingerprint + String(sid.c_str()); fingerprint = fingerprint + String(sid.c_str());
} }
id = fingerprint;
if (advertisedDevice->haveTXPower()) if (advertisedDevice->haveTXPower())
fingerprint = fingerprint + String(-advertisedDevice->getTXPower()); fingerprint = fingerprint + String(-advertisedDevice->getTXPower());
sid = fingerprint;
} }
} }
else if (advertisedDevice->haveManufacturerData()) else if (advertisedDevice->haveManufacturerData())
@ -83,8 +73,7 @@ BleFingerprint::BleFingerprint(BLEAdvertisedDevice *advertisedDevice, float fcmi
std::string strManufacturerData = advertisedDevice->getManufacturerData(); std::string strManufacturerData = advertisedDevice->getManufacturerData();
if (strManufacturerData.length() > 2) if (strManufacturerData.length() > 2)
{ {
char *mdHex = NimBLEUtils::buildHexData(nullptr, (uint8_t *)strManufacturerData.data(), strManufacturerData.length()); String manuf = Sprintf("%02x%02x", strManufacturerData[1], strManufacturerData[0]);
String manuf = String(mdHex).substring(2, 4) + String(mdHex).substring(0, 2);
if (manuf == "004c") // Apple if (manuf == "004c") // Apple
{ {
@ -98,52 +87,42 @@ BleFingerprint::BleFingerprint(BLEAdvertisedDevice *advertisedDevice, float fcmi
int major = ENDIAN_CHANGE_U16(oBeacon.getMajor()); int major = ENDIAN_CHANGE_U16(oBeacon.getMajor());
int minor = ENDIAN_CHANGE_U16(oBeacon.getMinor()); int minor = ENDIAN_CHANGE_U16(oBeacon.getMinor());
id = "iBeacon:" + proximityUUID + "-" + major + "-" + minor; pid = "iBeacon:" + proximityUUID + "-" + major + "-" + minor;
Serial.printf(", ID: %s", id.c_str());
calRssi = oBeacon.getSignalPower(); calRssi = oBeacon.getSignalPower();
} }
else else
{ {
String fingerprint = "apple:" + String(mdHex).substring(4, 8) + ":" + String(strManufacturerData.length());
if (advertisedDevice->haveTXPower()) if (advertisedDevice->haveTXPower())
fingerprint = fingerprint + String(-advertisedDevice->getTXPower()); pid = Sprintf("apple:%02x%02x:%d%d", strManufacturerData[2], strManufacturerData[3], strManufacturerData.length(), -advertisedDevice->getTXPower());
else
id = fingerprint; pid = Sprintf("apple:%02x%02x:%d", strManufacturerData[2], strManufacturerData[3], strManufacturerData.length());
Serial.printf(", ID: %s", id.c_str());
} }
} }
else if (manuf == "05a7") //Sonos else if (manuf == "05a7") //Sonos
{ {
id = "sonos:" + mac_address; pid = "sonos:" + getMac();
Serial.printf(", ID: %s", id.c_str());
} }
else if (manuf == "0006" && strManufacturerData.length() == 29) //microsoft else if (manuf == "0006" && strManufacturerData.length() == 29) //microsoft
{ {
id = "microsoft:" + String(mdHex).substring(12, 59); pid = Sprintf("microsoft:%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
Serial.printf(", ID: %s", id.c_str()); strManufacturerData[6], strManufacturerData[7], strManufacturerData[8], strManufacturerData[9], strManufacturerData[10], strManufacturerData[11],
strManufacturerData[12], strManufacturerData[13], strManufacturerData[14], strManufacturerData[15], strManufacturerData[16], strManufacturerData[17],
strManufacturerData[18], strManufacturerData[19], strManufacturerData[20], strManufacturerData[21], strManufacturerData[22], strManufacturerData[23],
strManufacturerData[24], strManufacturerData[25], strManufacturerData[26], strManufacturerData[27], strManufacturerData[28]);
} }
else if (manuf == "0075") //samsung else if (manuf == "0075") //samsung
{ {
id = "samsung:" + mac_address; pid = "samsung:" + getMac();
Serial.printf(", ID: %s", id.c_str());
} }
else else
{ {
String fingerprint = "md:" + String(mdHex).substring(2, 4) + String(mdHex).substring(0, 2) + ":" + String(strManufacturerData.length()); String fingerprint = Sprintf("md:%s:%d", manuf.c_str(), strManufacturerData.length());
if (advertisedDevice->haveTXPower()) if (advertisedDevice->haveTXPower())
fingerprint = fingerprint + String(-advertisedDevice->getTXPower()); fingerprint = fingerprint + String(-advertisedDevice->getTXPower());
id = macPublic ? mac_address : fingerprint; sid = fingerprint;
Serial.printf(", ID: %s, MD: %s", id.c_str(), mdHex);
} }
free(mdHex);
} }
} }
if (calRssi > 0) calRssi = defaultTxPower;
if (id.isEmpty() && macPublic)
id = mac_address;
Serial.println();
} }
bool BleFingerprint::filter() bool BleFingerprint::filter()
@ -169,6 +148,7 @@ void BleFingerprint::seen(BLEAdvertisedDevice *advertisedDevice)
newest = advertisedDevice->getRSSI(); newest = advertisedDevice->getRSSI();
rssi = median_of_3(oldest, recent, newest); rssi = median_of_3(oldest, recent, newest);
fingerprint(advertisedDevice);
if (!calRssi) calRssi = defaultTxPower; if (!calRssi) calRssi = defaultTxPower;
float ratio = (calRssi - rssi) / 35.0f; float ratio = (calRssi - rssi) / 35.0f;
@ -190,7 +170,7 @@ void BleFingerprint::setInitial(int initalRssi, float initalDistance)
bool BleFingerprint::report(JsonDocument *doc, int maxDistance) bool BleFingerprint::report(JsonDocument *doc, int maxDistance)
{ {
if (id.isEmpty()) if (pid.isEmpty() && sid.isEmpty() && !macPublic)
return false; return false;
if (!hasValue) if (!hasValue)
@ -216,17 +196,17 @@ bool BleFingerprint::report(JsonDocument *doc, int maxDistance)
{ {
if (!close) if (!close)
{ {
Display.close(mac.c_str(), id.c_str()); //Display.close(mac.c_str(), id.c_str());
close = true; close = true;
} }
} }
else if (close && output.value.position > 1.5) else if (close && output.value.position > 1.5)
{ {
Display.left(mac.c_str(), id.c_str()); //Display.left(mac.c_str(), id.c_str());
close = false; close = false;
} }
if (!id.isEmpty()) (*doc)[F("id")] = id; (*doc)[F("id")] = getId();
if (!name.isEmpty()) (*doc)[F("name")] = name; if (!name.isEmpty()) (*doc)[F("name")] = name;
(*doc)[F("rssi@1m")] = calRssi; (*doc)[F("rssi@1m")] = calRssi;
@ -237,5 +217,8 @@ bool BleFingerprint::report(JsonDocument *doc, int maxDistance)
(*doc)[F("distance")] = round(output.value.position * 100.0f) / 100.0f; (*doc)[F("distance")] = round(output.value.position * 100.0f) / 100.0f;
(*doc)[F("speed")] = round(output.value.speed * 1e7f) / 10.0f; (*doc)[F("speed")] = round(output.value.speed * 1e7f) / 10.0f;
if (volts) (*doc)[F("volts")] = volts;
if (temp) (*doc)[F("temp")] = temp;
return true; return true;
} }

View File

@ -2,7 +2,6 @@
#define _BLEFINGERPRINT_ #define _BLEFINGERPRINT_
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <GUI.h>
#include <NimBLEAdvertisedDevice.h> #include <NimBLEAdvertisedDevice.h>
#include <NimBLEBeacon.h> #include <NimBLEBeacon.h>
#include <NimBLEDevice.h> #include <NimBLEDevice.h>
@ -10,17 +9,37 @@
#include <NimBLEEddystoneURL.h> #include <NimBLEEddystoneURL.h>
#include <SoftFilters.h> #include <SoftFilters.h>
#define Sprintf(f, ...) ( \
{ \
char *s; \
asprintf(&s, f, __VA_ARGS__); \
String r = s; \
free(s); \
r; \
})
#define SMacf(f) ( \
{ \
auto nativeAddress = f.getNative(); \
Sprintf("%02x%02x%02x%02x%02x%02x", nativeAddress[5], nativeAddress[4], nativeAddress[3], nativeAddress[2], nativeAddress[1], nativeAddress[0]); \
})
class BleFingerprint class BleFingerprint
{ {
public: public:
BleFingerprint(BLEAdvertisedDevice *advertisedDevice, float fcmin, float beta, float dcutoff); BleFingerprint(BLEAdvertisedDevice *advertisedDevice, float fcmin, float beta, float dcutoff);
~BleFingerprint();
void seen(BLEAdvertisedDevice *advertisedDevice); void seen(BLEAdvertisedDevice *advertisedDevice);
bool report(JsonDocument *doc, int maxDistance); bool report(JsonDocument *doc, int maxDistance);
String getId() { return id; } String getId()
{
if (!pid.isEmpty()) return pid;
if (macPublic) return getMac();
if (!sid.isEmpty()) return sid;
return getMac();
}
String getMac() { return SMacf(address); }
float getDistance() { return output.value.position; } float getDistance() { return output.value.position; }
int getRSSI() { return rssi; } int getRSSI() { return rssi; }
@ -31,15 +50,18 @@ public:
long getLastSeen() { return lastSeenMicros; }; long getLastSeen() { return lastSeenMicros; };
private: private:
void fingerprint(BLEAdvertisedDevice *advertisedDevice);
bool hasValue = false, close = false, reported = false, macPublic = false; bool hasValue = false, close = false, reported = false, macPublic = false;
NimBLEAddress address; NimBLEAddress address;
String id, name, url; String pid, sid, name, url;
int rssi = -100, calRssi = 0; int rssi = -100, calRssi = 0;
int newest = -100; int newest = -100;
int recent = -100; int recent = -100;
int oldest = -100; int oldest = -100;
float raw, lastReported = 0; float raw, lastReported, temp = 0;
long firstSeenMicros, lastSeenMicros = 0, lastReportedMicros = 0; long firstSeenMicros, lastSeenMicros = 0, lastReportedMicros = 0;
uint16_t volts;
Reading<Differential<float>> output; Reading<Differential<float>> output;

View File

@ -18,6 +18,7 @@ void BleFingerprintCollection::cleanupOldFingerprints()
} }
if (oldest == nullptr) return; if (oldest == nullptr) return;
Display.removed(oldest);
fingerprints.remove(oldest); fingerprints.remove(oldest);
delete oldest; delete oldest;
} }
@ -34,6 +35,7 @@ BleFingerprint *BleFingerprintCollection::getFingerprintInternal(BLEAdvertisedDe
} }
auto created = new BleFingerprint(advertisedDevice, ONE_EURO_FCMIN, ONE_EURO_BETA, ONE_EURO_DCUTOFF); auto created = new BleFingerprint(advertisedDevice, ONE_EURO_FCMIN, ONE_EURO_BETA, ONE_EURO_DCUTOFF);
Display.added(created);
auto it2 = std::find_if(fingerprints.begin(), fingerprints.end(), [created](BleFingerprint *f) auto it2 = std::find_if(fingerprints.begin(), fingerprints.end(), [created](BleFingerprint *f)
{ return f->getId() == created->getId(); }); { return f->getId() == created->getId(); });
if (it2 != fingerprints.end()) if (it2 != fingerprints.end())

View File

@ -2,11 +2,8 @@
#define _BLEFINGERPRINTCOLLECTION_ #define _BLEFINGERPRINTCOLLECTION_
#include "BleFingerprint.h" #include "BleFingerprint.h"
#include "GUI.h"
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <GUI.h>
#include <NimBLEAdvertisedDevice.h>
#include <NimBLEBeacon.h>
#include <NimBLEDevice.h>
#define ONE_EURO_FCMIN 0.01 #define ONE_EURO_FCMIN 0.01
#define ONE_EURO_BETA 0.005 #define ONE_EURO_BETA 0.005

View File

@ -17,16 +17,26 @@ void GUI::connected(bool wifi = false, bool mqtt = false)
status("Wifi: %s Mqtt: %s", (wifi ? "no" : "yes"), (wifi ? "no" : "yes")); status("Wifi: %s Mqtt: %s", (wifi ? "no" : "yes"), (wifi ? "no" : "yes"));
} }
void GUI::close(const char *mac, const char *id) void GUI::close(BleFingerprint *f)
{ {
Serial.printf("%d Close | MAC: %s, ID: %-50s\n", xPortGetCoreID(), mac, id); Serial.printf("%d Close | MAC: %s, ID: %-50s\n", xPortGetCoreID(), f->getMac().c_str(), f->getId().c_str());
status("C: %s", id); status("C: %s", f->getId());
} }
void GUI::left(const char *mac, const char *id) void GUI::added(BleFingerprint *f)
{ {
Serial.printf("%d Left | MAC: %s, ID: %-50s\n", xPortGetCoreID(), mac, id); Serial.printf("%d New | MAC: %s, ID: %-50s\n", xPortGetCoreID(), f->getMac().c_str(), f->getId().c_str());
status("L: %s", id); }
void GUI::removed(BleFingerprint *f)
{
Serial.printf("%d Del | MAC: %s, ID: %-50s\n", xPortGetCoreID(), f->getMac().c_str(), f->getId().c_str());
}
void GUI::left(BleFingerprint *f)
{
Serial.printf("%d Left | MAC: %s, ID: %-50s\n", xPortGetCoreID(), f->getMac().c_str(), f->getId().c_str());
status("L: %s", f->getId());
} }
void GUI::status(const char *format, ...) void GUI::status(const char *format, ...)

View File

@ -1,6 +1,7 @@
#ifndef GUI_h #ifndef GUI_h
#define GUI_h #define GUI_h
#include "BleFingerprint.h"
#include <Arduino.h> #include <Arduino.h>
#ifdef M5STICK #ifdef M5STICK
@ -48,12 +49,16 @@
class GUI class GUI
{ {
public: public:
void added(BleFingerprint *f);
void removed(BleFingerprint *f);
void close(BleFingerprint *f);
void left(BleFingerprint *f);
void seenStart(); void seenStart();
void seenEnd(); void seenEnd();
void updateProgress(unsigned int percent) { digitalWrite(LED_BUILTIN, percent % 2); } void updateProgress(unsigned int percent) { digitalWrite(LED_BUILTIN, percent % 2); }
void updateEnd() { digitalWrite(LED_BUILTIN, !LED_BUILTIN_ON); } void updateEnd() { digitalWrite(LED_BUILTIN, !LED_BUILTIN_ON); }
void close(const char *mac, const char *id);
void left(const char *mac, const char *id);
void status(const char *message, ...); void status(const char *message, ...);
void connected(bool wifi, bool mqtt); void connected(bool wifi, bool mqtt);
void update(); void update();

View File

@ -3,8 +3,6 @@
#include <NimBLEDevice.h> #include <NimBLEDevice.h>
#define ENDIAN_CHANGE_U16(x) ((((x)&0xFF00) >> 8) + (((x)&0xFF) << 8)) #define ENDIAN_CHANGE_U16(x) ((((x)&0xFF00) >> 8) + (((x)&0xFF) << 8))
#define Sprintf(f, ...) ({ char* s; asprintf(&s, f, __VA_ARGS__); String r = s; free(s); r; })
#define SMacf(f) ({ auto nativeAddress = f.getNative(); Sprintf("%02x%02x%02x%02x%02x%02x", nativeAddress[5], nativeAddress[4], nativeAddress[3], nativeAddress[2], nativeAddress[1], nativeAddress[0]); })
#ifdef TX_DEFAULT #ifdef TX_DEFAULT
static const int defaultTxPower = TX_DEFAULT; static const int defaultTxPower = TX_DEFAULT;

View File

@ -52,8 +52,10 @@ board = esp32dev
lib_deps = ${common_env_data.lib_deps_external} lib_deps = ${common_env_data.lib_deps_external}
board_build.partitions = partitions_singleapp.csv board_build.partitions = partitions_singleapp.csv
monitor_speed = 115200 monitor_speed = 115200
upload_speed = 1500000
monitor_filters = esp32_exception_decoder monitor_filters = esp32_exception_decoder
build_flags = build_flags =
-D CORE_DEBUG_LEVEL=2
-D FIRMWARE='"esp32-verbose"' -D FIRMWARE='"esp32-verbose"'
-D VERBOSE -D VERBOSE
-Wall -Wall

View File

@ -1,7 +1,7 @@
#define Sprintf(f, ...) ({ char* s; asprintf(&s, f, __VA_ARGS__); String r = s; free(s); r; }) #define Sprintf(f, ...) ({ char* s; asprintf(&s, f, __VA_ARGS__); String r = s; free(s); r; })
#define ESPMAC (Sprintf("%06" PRIx64, ESP.getEfuseMac() >> 24)) #define ESPMAC (Sprintf("%06" PRIx64, ESP.getEfuseMac() >> 24))
#if VERBOSE #ifdef VERBOSE
#define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE #define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE
#endif #endif

View File

@ -60,7 +60,7 @@ bool sendTelemetry(int totalSeen = -1, int totalReported = -1, int totalAdverts
} }
teleFails++; teleFails++;
Serial.printf("Error after 10 tries sending telemetry (%d times since boot)\n", teleFails); log_e("Error after 10 tries sending telemetry (%d times since boot)", teleFails);
return false; return false;
} }
@ -243,7 +243,7 @@ void setup()
Serial.begin(115200); Serial.begin(115200);
Serial.setDebugOutput(true); Serial.setDebugOutput(true);
#if VERBOSE #ifdef VERBOSE
esp_log_level_set("*", ESP_LOG_DEBUG); esp_log_level_set("*", ESP_LOG_DEBUG);
#endif #endif
spiffsInit(); spiffsInit();

View File

@ -5,7 +5,6 @@
#include <AsyncTCP.h> #include <AsyncTCP.h>
#include <HTTPClient.h> #include <HTTPClient.h>
#include <HTTPUpdate.h> #include <HTTPUpdate.h>
#include <NimBLEBeacon.h>
#include <NimBLEDevice.h> #include <NimBLEDevice.h>
#include <SPIFFS.h> #include <SPIFFS.h>
#include <WebServer.h> #include <WebServer.h>
@ -18,6 +17,7 @@
#include "BleFingerprint.h" #include "BleFingerprint.h"
#include "BleFingerprintCollection.h" #include "BleFingerprintCollection.h"
#include "GUI.h"
#include "Settings.h" #include "Settings.h"
AsyncMqttClient mqttClient; AsyncMqttClient mqttClient;
@ -103,7 +103,7 @@ void setClock()
struct tm timeinfo; struct tm timeinfo;
gmtime_r(&now, &timeinfo); gmtime_r(&now, &timeinfo);
log_i(F("NTP synced, current time: %s"), asctime(&timeinfo)); log_i("NTP synced, current time: %s", asctime(&timeinfo));
} }
void configureOTA() void configureOTA()