From b298730daade3ee6d7de1ee053e3b5b42af41245 Mon Sep 17 00:00:00 2001
From: Darrell
Date: Mon, 18 Jul 2022 02:30:20 -0400
Subject: [PATCH] Enroll using Identity Resolving Keys (#553)
* Inital IRK
* Add Enroll button on device
---
lib/BleFingerprint/BleFingerprint.cpp | 126 +++++++--
lib/BleFingerprint/BleFingerprint.h | 17 +-
.../BleFingerprintCollection.cpp | 84 +++++-
lib/BleFingerprint/BleFingerprintCollection.h | 6 +-
lib/BleFingerprint/string_utils.cpp | 24 +-
lib/BleFingerprint/string_utils.h | 3 +-
platformio.ini | 6 -
src/Enrollment.cpp | 264 ++++++++++++++++++
src/Enrollment.h | 12 +
src/globals.h | 2 +-
src/main.cpp | 132 ++++-----
src/main.h | 1 +
src/mqtt.cpp | 11 +
src/mqtt.h | 2 +
14 files changed, 585 insertions(+), 105 deletions(-)
create mode 100644 src/Enrollment.cpp
create mode 100644 src/Enrollment.h
diff --git a/lib/BleFingerprint/BleFingerprint.cpp b/lib/BleFingerprint/BleFingerprint.cpp
index 2706649..19250a6 100644
--- a/lib/BleFingerprint/BleFingerprint.cpp
+++ b/lib/BleFingerprint/BleFingerprint.cpp
@@ -3,6 +3,7 @@
#include "rssi.h"
#include "string_utils.h"
#include "util.h"
+#include "mbedtls/aes.h"
class ClientCallbacks : public BLEClientCallbacks
{
@@ -42,9 +43,12 @@ bool BleFingerprint::setId(const String& newId, short newIdType, const String& n
}
countable = !ignore && !BleFingerprintCollection::countIds.isEmpty() && prefixExists(BleFingerprintCollection::countIds, newId);
- id = newId;
- idType = newIdType;
- if (!newName.isEmpty()) name = newName;
+ if ((idType != newIdType) || !id.equals(newId)) {
+ id = newId;
+ idType = newIdType;
+ added = false;
+ }
+ if (!newName.isEmpty() && !name.equals(newName)) name = newName;
return true;
}
@@ -60,25 +64,11 @@ BleFingerprint::BleFingerprint(const BleFingerprintCollection *parent, BLEAdvert
{
firstSeenMillis = millis();
address = NimBLEAddress(advertisedDevice->getAddress());
+ addressType = advertisedDevice->getAddressType();
newest = recent = oldest = rssi = advertisedDevice->getRSSI();
seenCount = 1;
- auto mac = getMac();
- if (!BleFingerprintCollection::knownMacs.isEmpty() && prefixExists(BleFingerprintCollection::knownMacs, mac))
- setId("known:" + mac, ID_TYPE_KNOWN_MAC);
- else
- {
- switch (advertisedDevice->getAddressType())
- {
- case BLE_ADDR_PUBLIC:
- case BLE_ADDR_PUBLIC_ID:
- setId(mac, ID_TYPE_PUBLIC_MAC);
- break;
- default:
- setId(mac, ID_TYPE_MAC);
- break;
- }
- }
+ fingerprintAddress();
}
void BleFingerprint::fingerprint(NimBLEAdvertisedDevice *advertisedDevice)
@@ -102,6 +92,104 @@ void BleFingerprint::fingerprint(NimBLEAdvertisedDevice *advertisedDevice)
if (advertisedDevice->haveManufacturerData()) fingerprintManufactureData(advertisedDevice, haveTxPower, txPower);
}
+int bt_encrypt_be(const uint8_t *key, const uint8_t *plaintext, uint8_t *enc_data)
+{
+ mbedtls_aes_context s = {0};
+ mbedtls_aes_init(&s);
+
+ if (mbedtls_aes_setkey_enc(&s, key, 128) != 0) {
+ mbedtls_aes_free(&s);
+ return BLE_HS_EUNKNOWN;
+ }
+
+ if (mbedtls_aes_crypt_ecb(&s, MBEDTLS_AES_ENCRYPT, plaintext, enc_data) != 0) {
+ mbedtls_aes_free(&s);
+ return BLE_HS_EUNKNOWN;
+ }
+
+ mbedtls_aes_free(&s);
+ return 0;
+}
+
+struct encryption_block
+{
+ uint8_t key[16];
+ uint8_t plain_text[16];
+ uint8_t cipher_text[16];
+};
+
+int ble_ll_resolv_rpa(const uint8_t *rpa, const uint8_t *irk)
+{
+ int rc;
+ struct encryption_block ecb;
+
+ auto irk32 = (const uint32_t *)irk;
+ auto key32 = (uint32_t *)&ecb.key[0];
+ auto pt32 = (uint32_t *)&ecb.plain_text[0];
+
+ key32[0] = irk32[0];
+ key32[1] = irk32[1];
+ key32[2] = irk32[2];
+ key32[3] = irk32[3];
+
+
+ pt32[0] = 0;
+ pt32[1] = 0;
+ pt32[2] = 0;
+ pt32[3] = 0;
+
+ ecb.plain_text[15] = rpa[3];
+ ecb.plain_text[14] = rpa[4];
+ ecb.plain_text[13] = rpa[5];
+
+ auto err = bt_encrypt_be(ecb.key, ecb.plain_text, ecb.cipher_text);
+
+ if ((ecb.cipher_text[15] == rpa[0]) && (ecb.cipher_text[14] == rpa[1]) &&
+ (ecb.cipher_text[13] == rpa[2])) {
+
+ rc = 1;
+ } else {
+ rc = 0;
+ }
+
+ //Serial.printf("RPA resolved %d %d %02x%02x%02x %02x%02x%02x\n", rc, err, rpa[0], rpa[1], rpa[2], ecb.cipher_text[15], ecb.cipher_text[14], ecb.cipher_text[13]);
+
+ return rc;
+}
+
+void BleFingerprint::fingerprintAddress()
+{
+ auto mac = getMac();
+ if (!BleFingerprintCollection::knownMacs.isEmpty() && prefixExists(BleFingerprintCollection::knownMacs, mac))
+ setId("known:" + mac, ID_TYPE_KNOWN_MAC);
+ else
+ {
+ switch (addressType)
+ {
+ case BLE_ADDR_PUBLIC:
+ case BLE_ADDR_PUBLIC_ID:
+ setId(mac, ID_TYPE_PUBLIC_MAC);
+ break;
+ case BLE_ADDR_RANDOM:
+ {
+ auto naddress = address.getNative();
+ auto irks = BleFingerprintCollection::irks;
+ auto it = std::find_if(irks.begin(), irks.end(), [naddress](std::pair &p) {
+ auto irk = std::get<0>(p);
+ return ble_ll_resolv_rpa(naddress, irk);
+ });
+ if (it != irks.end()) {
+ setId(std::get<1>(*it), ID_TYPE_KNOWN_IRK);
+ break;
+ }
+ }
+ default:
+ setId(mac, ID_TYPE_MAC);
+ break;
+ }
+ }
+}
+
void BleFingerprint::fingerprintServiceAdvertisements(NimBLEAdvertisedDevice *advertisedDevice, size_t serviceAdvCount, bool haveTxPower, int8_t txPower)
{
for (size_t i = 0; i < serviceAdvCount; i++)
diff --git a/lib/BleFingerprint/BleFingerprint.h b/lib/BleFingerprint/BleFingerprint.h
index 35b55ec..bb16a84 100644
--- a/lib/BleFingerprint/BleFingerprint.h
+++ b/lib/BleFingerprint/BleFingerprint.h
@@ -34,6 +34,7 @@
#define ID_TYPE_SMARTTAG short(121)
#define ID_TYPE_ITAG short(125)
#define ID_TYPE_ITRACK short(127)
+#define ID_TYPE_NUT short(128)
#define ID_TYPE_TRACKR short(130)
#define ID_TYPE_TILE short( 135)
#define ID_TYPE_MEATER short(140)
@@ -42,12 +43,13 @@
#define ID_TYPE_APPLE_NEARBY short(150)
#define ID_TYPE_QUERY_MODEL short(155)
#define ID_TYPE_QUERY_NAME short(160)
-#define ID_TYPE_EBEACON short(165)
-#define ID_TYPE_ABEACON short(170)
-#define ID_TYPE_IBEACON short(175)
-#define ID_TYPE_RM_ASST short(180)
-#define ID_TYPE_KNOWN_MAC short(185)
-#define ID_TYPE_NUT short(190)
+#define ID_TYPE_RM_ASST short(165)
+#define ID_TYPE_KNOWN_IRK short(200)
+#define ID_TYPE_KNOWN_MAC short(210)
+#define ID_TYPE_EBEACON short(220)
+#define ID_TYPE_ABEACON short(230)
+#define ID_TYPE_IBEACON short(240)
+
class BleFingerprintCollection;
@@ -103,6 +105,7 @@ public:
}
bool shouldCount();
+ void fingerprintAddress();
private:
@@ -118,7 +121,7 @@ private:
unsigned long firstSeenMillis, lastSeenMillis = 0, lastReportedMillis = 0, lastQryMillis = 0;
unsigned long seenCount = 1, lastSeenCount = 0;
uint16_t mv = 0;
- uint8_t battery = 0xFF;
+ uint8_t battery = 0xFF, addressType = 0xFF;
Reading> output;
diff --git a/lib/BleFingerprint/BleFingerprintCollection.cpp b/lib/BleFingerprint/BleFingerprintCollection.cpp
index a0eb649..50bff35 100644
--- a/lib/BleFingerprint/BleFingerprintCollection.cpp
+++ b/lib/BleFingerprint/BleFingerprintCollection.cpp
@@ -1,4 +1,85 @@
#include "BleFingerprintCollection.h"
+#include
+
+/* Static variables */
+String BleFingerprintCollection::include{}, BleFingerprintCollection::exclude{}, BleFingerprintCollection::query{}, BleFingerprintCollection::knownMacs{}, BleFingerprintCollection::knownIrks{}, BleFingerprintCollection::countIds{};
+float BleFingerprintCollection::skipDistance = 0.0f, BleFingerprintCollection::maxDistance = 0.0f, BleFingerprintCollection::absorption = 3.5f, BleFingerprintCollection::countEnter = 2, BleFingerprintCollection::countExit = 4;
+int BleFingerprintCollection::refRssi = 0, BleFingerprintCollection::forgetMs = 0, BleFingerprintCollection::skipMs = 0, BleFingerprintCollection::countMs = 10000;
+std::vector> BleFingerprintCollection::irks;
+
+bool BleFingerprintCollection::config(String& id, String& json) {
+ DynamicJsonDocument doc(1024);
+ deserializeJson(doc, json);
+ auto p = id.indexOf("irk:");
+ if (p == 0) {
+ auto irk_hex = id.substring(4);
+ uint8_t* irk = new uint8_t[16];
+ if (!hextostr(irk_hex, irk, 16))
+ return false;
+ irks.push_back({irk, doc["id"]});
+
+ for(auto it = std::begin(fingerprints); it != std::end(fingerprints); ++it)
+ (*it)->fingerprintAddress();
+ }
+ return true;
+}
+
+void BleFingerprintCollection::connectToWifi() {
+ std::istringstream iss(BleFingerprintCollection::knownIrks.c_str());
+ std::string irk_hex;
+ while (iss >> irk_hex) {
+ uint8_t* irk = new uint8_t[16];
+ if (!hextostr(irk_hex.c_str(), irk, 16))
+ continue;
+ irks.push_back({irk, String("irk:") + irk_hex.c_str()});
+ }
+}
+
+bool BleFingerprintCollection::command(String& command, String& pay) {
+
+ if (command == "max_distance")
+ {
+ BleFingerprintCollection::maxDistance = pay.toFloat();
+ spurt("/max_dist", pay);
+ }
+ else if (command == "absorption")
+ {
+ BleFingerprintCollection::absorption = pay.toFloat();
+ spurt("/absorption", pay);
+ }
+ else if (command == "query")
+ {
+ BleFingerprintCollection::query = pay;
+ spurt("/query", pay);
+ }
+ else if (command == "include")
+ {
+ BleFingerprintCollection::include = pay;
+ spurt("/include", pay);
+ }
+ else if (command == "exclude")
+ {
+ BleFingerprintCollection::exclude = pay;
+ spurt("/exclude", pay);
+ }
+ else if (command == "known_macs")
+ {
+ BleFingerprintCollection::knownMacs = pay;
+ spurt("/known_macs", pay);
+ }
+ else if (command == "known_irks")
+ {
+ BleFingerprintCollection::knownIrks = pay;
+ spurt("/known_irks", pay);
+ }
+ else if (command == "count_ids")
+ {
+ BleFingerprintCollection::countIds = pay;
+ spurt("/count_ids", pay);
+ } else
+ return false;
+ return true;
+}
void BleFingerprintCollection::cleanupOldFingerprints()
{
@@ -64,9 +145,6 @@ const std::list BleFingerprintCollection::getCopy()
log_e("Couldn't give semaphore!");
return std::move(copy);
}
-String BleFingerprintCollection::include{}, BleFingerprintCollection::exclude{}, BleFingerprintCollection::query{}, BleFingerprintCollection::knownMacs{}, BleFingerprintCollection::countIds{};
-float BleFingerprintCollection::skipDistance = 0.0f, BleFingerprintCollection::maxDistance = 0.0f, BleFingerprintCollection::absorption = 3.5f, BleFingerprintCollection::countEnter = 2, BleFingerprintCollection::countExit = 4;
-int BleFingerprintCollection::refRssi = 0, BleFingerprintCollection::forgetMs = 0, BleFingerprintCollection::skipMs = 0, BleFingerprintCollection::countMs = 10000;
const std::list* const BleFingerprintCollection::getNative()
{
diff --git a/lib/BleFingerprint/BleFingerprintCollection.h b/lib/BleFingerprint/BleFingerprintCollection.h
index 4c44d47..0b604a2 100644
--- a/lib/BleFingerprint/BleFingerprintCollection.h
+++ b/lib/BleFingerprint/BleFingerprintCollection.h
@@ -21,7 +21,11 @@ public:
const std::list* const getNative();
const std::list getCopy();
void setDisable(bool disable) { _disable = disable; }
- static String knownMacs, include, exclude, query;
+ void connectToWifi();
+ bool command(String& command, String& pay);
+ bool config(String& id, String& json);
+ static std::vector> irks;
+ static String knownMacs, knownIrks, include, exclude, query;
static float skipDistance, maxDistance, absorption;
static int refRssi, forgetMs, skipMs;
static String countIds;
diff --git a/lib/BleFingerprint/string_utils.cpp b/lib/BleFingerprint/string_utils.cpp
index 88fcdc1..867b7c1 100644
--- a/lib/BleFingerprint/string_utils.cpp
+++ b/lib/BleFingerprint/string_utils.cpp
@@ -2,6 +2,8 @@
#include
#include
+static constexpr char hexmap[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+
std::string ltrim(const std::string &s, char toTrim)
{
size_t start = s.find_first_not_of(toTrim);
@@ -46,8 +48,6 @@ String kebabify(const String &text)
std::string hexStr(const char *data, unsigned int len)
{
- constexpr char hexmap[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
-
std::string s(len * 2, ' ');
for (int i = 0; i < len; ++i)
{
@@ -69,8 +69,6 @@ std::string hexStr(const uint8_t *&s, unsigned int len)
std::string hexStrRev(const char *data, unsigned int len)
{
- constexpr char hexmap[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
-
std::string s(len * 2, ' ');
for (int i = 0; i < len; ++i)
{
@@ -90,6 +88,24 @@ std::string hexStrRev(const std::string &s)
return hexStrRev(s.c_str(), s.length());
}
+uint8_t hextob(char ch)
+{
+ if (ch >= '0' && ch <= '9') return ch - '0';
+ if (ch >= 'A' && ch <= 'F') return ch - 'A' + 10;
+ if (ch >= 'a' && ch <= 'f') return ch - 'a' + 10;
+ return 0;
+}
+
+bool hextostr(const String &hexStr, uint8_t* output, size_t len)
+{
+ if (len & 1) return false;
+ if (hexStr.length() < len*2) return false;
+ int k = 0;
+ for (size_t i = 0; i < len*2; i+=2)
+ output[k++] = (hextob(hexStr[i]) << 4) | hextob(hexStr[i+1]);
+ return true;
+}
+
bool prefixExists(const String &prefixes, const String &s)
{
unsigned int start = 0;
diff --git a/lib/BleFingerprint/string_utils.h b/lib/BleFingerprint/string_utils.h
index 6bc3ece..f96c2e5 100644
--- a/lib/BleFingerprint/string_utils.h
+++ b/lib/BleFingerprint/string_utils.h
@@ -24,5 +24,6 @@ std::string hexStr(const std::string& s);
std::string hexStrRev(const uint8_t *data, int len);
std::string hexStrRev(const char *data, int len);
std::string hexStrRev(const std::string &s);
-bool prefixExists(const String& prefixes, const String& s);
+bool hextostr(const String &hexStr, uint8_t* output, size_t len);
+bool prefixExists(const String &prefixes, const String &s);
bool spurt(const String &fn, const String &content);
diff --git a/platformio.ini b/platformio.ini
index dab94ef..d4bbfe0 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -17,13 +17,7 @@ extra_configs = platformio_override.ini
debug_build_flags = -O0 -ggdb3 -g3 -DDEBUG_TLS_MEM
build_flags =
-D MQTT_MIN_FREE_MEMORY=8128
- -D SECURE_CLIENT=SECURE_CLIENT_BEARSSL
- -D BEARSSL_SSL_BASIC
- -D CONFIG_BT_NIMBLE_MAX_BONDS=0
- -D CONFIG_BT_NIMBLE_MAX_CCCDS=0
-D CONFIG_BT_NIMBLE_TASK_STACK_SIZE=8128
- -D CONFIG_BT_NIMBLE_ROLE_PERIPHERAL_DISABLED
- -D CONFIG_BT_NIMBLE_ROLE_BROADCASTER_DISABLED
-D CONFIG_ASYNC_TCP_USE_WDT=0
-D CONFIG_BT_NIMBLE_MSYS1_BLOCK_COUNT=20
-D BLE_GATTC_UNRESPONSIVE_TIMEOUT_MS=2000
diff --git a/src/Enrollment.cpp b/src/Enrollment.cpp
new file mode 100644
index 0000000..d79d927
--- /dev/null
+++ b/src/Enrollment.cpp
@@ -0,0 +1,264 @@
+#include "Enrollment.h"
+#include "mqtt.h"
+
+#include
+#include
+#include
+#include
+#include
+
+namespace Enrollment
+{
+ static bool enrolling = false;
+ static NimBLEServer* pServer;
+ static String irk, id;
+
+ class ServerCallbacks : public NimBLEServerCallbacks {
+
+ void onConnect(NimBLEServer* pServer) {
+ Serial.println("Client connected");
+ Serial.println("Multi-connect support: start advertising");
+ NimBLEDevice::startAdvertising();
+ };
+
+ void onConnect(NimBLEServer* pServer, ble_gap_conn_desc* desc) {
+ Serial.print("Client address: ");
+ Serial.println(NimBLEAddress(desc->peer_ota_addr).toString().c_str());
+ NimBLEDevice::startSecurity(desc->conn_handle);
+ };
+
+ void onDisconnect(NimBLEServer* pServer) {
+ Serial.println("Client disconnected - start advertising");
+ NimBLEDevice::startAdvertising();
+ };
+
+ void onMTUChange(uint16_t MTU, ble_gap_conn_desc* desc) {
+ Serial.printf("MTU updated: %u for connection ID: %u\n", MTU, desc->conn_handle);
+ };
+
+ void onAuthenticationComplete(ble_gap_conn_desc* desc){
+ if(!desc->sec_state.encrypted) Serial.println("Encrypt connection failed!");
+ else Serial.println("Encrypt connection success!");
+ };
+ };
+
+ class CharacteristicCallbacks : public NimBLECharacteristicCallbacks {
+ void onRead(NimBLECharacteristic* pCharacteristic){
+ Serial.print(pCharacteristic->getUUID().toString().c_str());
+ Serial.print(": onRead(), value: ");
+ Serial.println(pCharacteristic->getValue().c_str());
+ };
+
+ void onWrite(NimBLECharacteristic* pCharacteristic) {
+ Serial.print(pCharacteristic->getUUID().toString().c_str());
+ Serial.print(": onWrite(), value: ");
+ Serial.println(pCharacteristic->getValue().c_str());
+ };
+ /** Called before notification or indication is sent,
+ * the value can be changed here before sending if desired.
+ */
+ void onNotify(NimBLECharacteristic* pCharacteristic) {
+ Serial.println("Sending notification to clients");
+ };
+
+
+ /** The status returned in status is defined in NimBLECharacteristic.h.
+ * The value returned in code is the NimBLE host return code.
+ */
+ void onStatus(NimBLECharacteristic* pCharacteristic, Status status, int code) {
+ String str = ("Notification/Indication status code: ");
+ str += status;
+ str += ", return code: ";
+ str += code;
+ str += ", ";
+ str += NimBLEUtils::returnCodeToString(code);
+ Serial.println(str);
+ };
+
+ void onSubscribe(NimBLECharacteristic* pCharacteristic, ble_gap_conn_desc* desc, uint16_t subValue) {
+ String str = "Client ID: ";
+ str += desc->conn_handle;
+ str += " Address: ";
+ str += std::string(NimBLEAddress(desc->peer_ota_addr)).c_str();
+ if(subValue == 0) {
+ str += " Unsubscribed to ";
+ }else if(subValue == 1) {
+ str += " Subscribed to notfications for ";
+ } else if(subValue == 2) {
+ str += " Subscribed to indications for ";
+ } else if(subValue == 3) {
+ str += " Subscribed to notifications and indications for ";
+ }
+ str += std::string(pCharacteristic->getUUID()).c_str();
+
+ Serial.println(str);
+ };
+ };
+
+ class DescriptorCallbacks : public NimBLEDescriptorCallbacks {
+ void onWrite(NimBLEDescriptor* pDescriptor) {
+ std::string dscVal = pDescriptor->getValue();
+ Serial.print("Descriptor witten value:");
+ Serial.println(dscVal.c_str());
+ };
+
+ void onRead(NimBLEDescriptor* pDescriptor) {
+ Serial.print(pDescriptor->getUUID().toString().c_str());
+ Serial.println(" Descriptor read");
+ };
+ };
+
+
+ static DescriptorCallbacks dscCallbacks;
+ static CharacteristicCallbacks chrCallbacks;
+
+ bool Busy() {
+ return enrolling;
+ }
+
+ void Setup() {
+ NimBLEDevice::setSecurityIOCap(BLE_HS_IO_DISPLAY_YESNO);
+ NimBLEDevice::setSecurityAuth(BLE_SM_PAIR_AUTHREQ_BOND | BLE_SM_PAIR_AUTHREQ_MITM | BLE_SM_PAIR_AUTHREQ_SC);
+ NimBLEDevice::setSecurityPasskey(123456);
+ }
+
+ static int iter_irk(int obj_type, union ble_store_value *val, void *cookie)
+ {
+ static const char hex_digits[] = "0123456789abcdef";
+ const struct ble_store_value_sec *sec;
+ int rc;
+
+ sec = &val->sec;
+ if (sec->irk_present) {
+ std::string output;
+ output.reserve(32);
+ for(int i = 0; i < 16; i++) {
+ auto c = sec->irk[15 - i];
+ output.push_back(hex_digits[c >> 4]);
+ output.push_back(hex_digits[c & 15]);
+ }
+ printf("IRK found: %s\n", output.c_str());
+ irk = output.c_str();
+ }
+
+ return 0;
+ }
+
+ bool Loop() {
+
+ if (!enrolling) {
+ return false;
+ }
+ irk = "";
+
+ NimBLEDevice::getScan()->stop();
+ NimBLEAdvertising* pAdvertising = NimBLEDevice::getAdvertising();
+
+ if (!pServer) {
+ pServer = NimBLEDevice::createServer();
+ pServer->setCallbacks(new ServerCallbacks());
+
+ NimBLEService* pDeadService = pServer->createService("DEAD");
+ NimBLECharacteristic* pBeefCharacteristic = pDeadService->createCharacteristic(
+ "BEEF",
+ NIMBLE_PROPERTY::READ |
+ NIMBLE_PROPERTY::WRITE |
+ NIMBLE_PROPERTY::READ_ENC | // only allow reading if paired / encrypted
+ NIMBLE_PROPERTY::WRITE_ENC // only allow writing if paired / encrypted
+ );
+
+ pBeefCharacteristic->setValue("Burger");
+ pBeefCharacteristic->setCallbacks(&chrCallbacks);
+
+ /** 2904 descriptors are a special case, when createDescriptor is called with
+ * 0x2904 a NimBLE2904 class is created with the correct properties and sizes.
+ * However we must cast the returned reference to the correct type as the method
+ * only returns a pointer to the base NimBLEDescriptor class.
+ */
+ NimBLE2904* pBeef2904 = (NimBLE2904*)pBeefCharacteristic->createDescriptor("2904");
+ pBeef2904->setFormat(NimBLE2904::FORMAT_UTF8);
+ pBeef2904->setCallbacks(&dscCallbacks);
+
+
+ NimBLEService* pBaadService = pServer->createService("BAAD");
+ NimBLECharacteristic* pFoodCharacteristic = pBaadService->createCharacteristic(
+ "F00D",
+ NIMBLE_PROPERTY::READ |
+ NIMBLE_PROPERTY::WRITE |
+ NIMBLE_PROPERTY::NOTIFY
+ );
+
+ pFoodCharacteristic->setValue("Fries");
+ pFoodCharacteristic->setCallbacks(&chrCallbacks);
+
+ /** Note a 0x2902 descriptor MUST NOT be created as NimBLE will create one automatically
+ * if notification or indication properties are assigned to a characteristic.
+ */
+
+ /** Custom descriptor: Arguments are UUID, Properties, max length in bytes of the value */
+ NimBLEDescriptor* pC01Ddsc = pFoodCharacteristic->createDescriptor(
+ "C01D",
+ NIMBLE_PROPERTY::READ |
+ NIMBLE_PROPERTY::WRITE|
+ NIMBLE_PROPERTY::WRITE_ENC, // only allow writing if paired / encrypted
+ 20
+ );
+ pC01Ddsc->setValue("Send it back!");
+ pC01Ddsc->setCallbacks(&dscCallbacks);
+
+ /** Start the services when finished creating all Characteristics and Descriptors */
+ pDeadService->start();
+ pBaadService->start();
+
+ pAdvertising->addServiceUUID(pDeadService->getUUID());
+ pAdvertising->addServiceUUID(pBaadService->getUUID());
+ }
+
+ pAdvertising->setScanResponse(true);
+ pAdvertising->start();
+
+ Serial.println("Advertising Started");
+ if(pServer->getConnectedCount()) {
+ NimBLEService* pSvc = pServer->getServiceByUUID("BAAD");
+ if(pSvc) {
+ NimBLECharacteristic* pChr = pSvc->getCharacteristic("F00D");
+ if(pChr) {
+ pChr->notify(true);
+ }
+ }
+ }
+
+ auto started = millis();
+ while (millis() - started < 60000)
+ {
+ auto rc = ble_store_iterate(BLE_STORE_OBJ_TYPE_PEER_SEC, iter_irk, NULL);
+ if (!irk.isEmpty()) {
+ alias(String("irk:") + irk, id.isEmpty() ? (String("irk:") + irk) : id);
+ NimBLEDevice::deleteAllBonds();
+ break;
+ }
+ printf("%d num bonds\n", NimBLEDevice::getNumBonds());
+ delay(1000);
+ }
+
+ pAdvertising->stop();
+ enrolling = false;
+ return true;
+ }
+
+ bool Command(String& command, String& pay)
+ {
+ if (command == "enroll")
+ {
+ id = pay.equals("PRESS") ? "" : pay;
+ enrolling = true;
+ return true;
+ }
+ return false;
+ }
+
+ bool SendDiscovery()
+ {
+ return sendButtonDiscovery("Enroll", EC_CONFIG);
+ }
+}
diff --git a/src/Enrollment.h b/src/Enrollment.h
new file mode 100644
index 0000000..2ff82fa
--- /dev/null
+++ b/src/Enrollment.h
@@ -0,0 +1,12 @@
+#pragma once
+
+#include "string_utils.h"
+
+namespace Enrollment
+{
+ bool Busy();
+ bool Loop();
+ void Setup();
+ bool Command(String& command, String& pay);
+ bool SendDiscovery();
+}
diff --git a/src/globals.h b/src/globals.h
index ebb8417..4453069 100644
--- a/src/globals.h
+++ b/src/globals.h
@@ -26,7 +26,7 @@ Setup variable declaration macros.
#endif
_DECL char buffer[2048];
-_DECL String room, id, statusTopic, teleTopic, roomsTopic, setTopic;
+_DECL String room, id, statusTopic, teleTopic, roomsTopic, setTopic, configTopic;
_DECL AsyncMqttClient mqttClient;
_DECL DynamicJsonDocument doc _INIT_N(((2048)));
_DECL String localIp;
diff --git a/src/main.cpp b/src/main.cpp
index ecf0bcd..b00b437 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -5,8 +5,21 @@ bool sendTelemetry(int totalSeen, int totalFpSeen, int totalFpQueried, int total
{
if (!online)
{
- if (pub(statusTopic.c_str(), 0, true, "online") && pub((roomsTopic + "/max_distance").c_str(), 0, true, String(BleFingerprintCollection::maxDistance).c_str()) && pub((roomsTopic + "/absorption").c_str(), 0, true, String(BleFingerprintCollection::absorption).c_str()) && pub((roomsTopic + "/query").c_str(), 0, true, BleFingerprintCollection::query.c_str()) && pub((roomsTopic + "/include").c_str(), 0, true, BleFingerprintCollection::include.c_str()) && pub((roomsTopic + "/exclude").c_str(), 0, true, BleFingerprintCollection::exclude.c_str()) && pub((roomsTopic + "/known_macs").c_str(), 0, true, BleFingerprintCollection::knownMacs.c_str()) && pub((roomsTopic + "/count_ids").c_str(), 0, true, BleFingerprintCollection::countIds.c_str()) && pub((roomsTopic + "/status_led").c_str(), 0, true, String(GUI::statusLed ? "ON" : "OFF").c_str()) && pub((roomsTopic + "/arduino_ota").c_str(), 0, true, String(arduinoOta ? "ON" : "OFF").c_str()) &&
- pub((roomsTopic + "/auto_update").c_str(), 0, true, String(autoUpdate ? "ON" : "OFF").c_str()) && pub((roomsTopic + "/prerelease").c_str(), 0, true, String(prerelease ? "ON" : "OFF").c_str()) && pub((roomsTopic + "/active_scan").c_str(), 0, true, String(activeScan ? "ON" : "OFF").c_str()) && Motion::SendOnline())
+ if (pub(statusTopic.c_str(), 0, true, "online")
+ && pub((roomsTopic + "/max_distance").c_str(), 0, true, String(BleFingerprintCollection::maxDistance).c_str())
+ && pub((roomsTopic + "/absorption").c_str(), 0, true, String(BleFingerprintCollection::absorption).c_str())
+ && pub((roomsTopic + "/query").c_str(), 0, true, BleFingerprintCollection::query.c_str())
+ && pub((roomsTopic + "/include").c_str(), 0, true, BleFingerprintCollection::include.c_str())
+ && pub((roomsTopic + "/exclude").c_str(), 0, true, BleFingerprintCollection::exclude.c_str())
+ && pub((roomsTopic + "/known_macs").c_str(), 0, true, BleFingerprintCollection::knownMacs.c_str())
+ && pub((roomsTopic + "/known_irks").c_str(), 0, true, BleFingerprintCollection::knownIrks.c_str())
+ && pub((roomsTopic + "/count_ids").c_str(), 0, true, BleFingerprintCollection::countIds.c_str())
+ && pub((roomsTopic + "/status_led").c_str(), 0, true, String(GUI::statusLed ? "ON" : "OFF").c_str())
+ && pub((roomsTopic + "/arduino_ota").c_str(), 0, true, String(arduinoOta ? "ON" : "OFF").c_str())
+ && pub((roomsTopic + "/auto_update").c_str(), 0, true, String(autoUpdate ? "ON" : "OFF").c_str())
+ && pub((roomsTopic + "/prerelease").c_str(), 0, true, String(prerelease ? "ON" : "OFF").c_str())
+ && pub((roomsTopic + "/active_scan").c_str(), 0, true, String(activeScan ? "ON" : "OFF").c_str())
+ && Motion::SendOnline())
{
online = true;
reconnectTries = 0;
@@ -32,6 +45,7 @@ bool sendTelemetry(int totalSeen, int totalFpSeen, int totalFpQueried, int total
&& sendSwitchDiscovery("Arduino OTA", EC_CONFIG)
&& sendSwitchDiscovery("Prerelease", EC_CONFIG)
&& Motion::SendDiscovery()
+ && Enrollment::SendDiscovery()
#ifdef MACCHINA_A0
&& sendTeleSensorDiscovery("Battery", EC_NONE, "{{ value_json.batt }}", "battery", "%")
&& sendTeleBinarySensorDiscovery("Charging", EC_NONE, "{{ value_json.charging }}", "battery_charging")
@@ -170,6 +184,7 @@ void setupNetwork()
WiFiSettings.heading("Scanning ℹ️", false);
activeScan = WiFiSettings.checkbox("active_scan", false, "Request scan results (usually not needed)");
BleFingerprintCollection::knownMacs = WiFiSettings.string("known_macs", "", "Known BLE mac addresses (no colons, space seperated)");
+ BleFingerprintCollection::knownIrks = WiFiSettings.string("known_irks", "", "Known BLE identity resolving keys, should be 32 hex chars space seperated");
BleFingerprintCollection::query = WiFiSettings.string("query", DEFAULT_QUERY, "Query device ids for characteristics (eg. apple:1005:9-26)");
WiFiSettings.heading("Counting ℹ️", false);
@@ -194,6 +209,7 @@ void setupNetwork()
WiFiSettings.heading("Misc ℹ️", false);
GUI::statusLed = WiFiSettings.checkbox("status_led", true, "Status LED");
+ fingerprints.connectToWifi();
Motion::ConnectToWifi();
#ifdef SENSORS
@@ -258,6 +274,7 @@ void setupNetwork()
statusTopic = roomsTopic + "/status";
teleTopic = roomsTopic + "/telemetry";
setTopic = roomsTopic + "/+/set";
+ configTopic = CHANNEL + "/settings/+/config";
}
void onMqttConnect(bool sessionPresent)
@@ -265,6 +282,7 @@ void onMqttConnect(bool sessionPresent)
xTimerStop(reconnectTimer, 0);
mqttClient.subscribe("espresense/rooms/*/+/set", 1);
mqttClient.subscribe(setTopic.c_str(), 1);
+ mqttClient.subscribe(configTopic.c_str(), 1);
GUI::connected(true, true);
}
@@ -276,63 +294,13 @@ void onMqttDisconnect(AsyncMqttClientDisconnectReason reason)
online = false;
}
-void onMqttMessage(char *topic, char *payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total)
-{
- char new_payload[len + 1];
- new_payload[len] = '\0';
- strncpy(new_payload, payload, len);
- Serial.printf("%s: %s\n", topic, new_payload);
+bool Command(String& command, String& pay) {
- String top = String(topic);
- String pay = String(new_payload);
-
- auto setPos = top.lastIndexOf("/set");
- if (setPos <= 1) return;
- auto commandPos = top.lastIndexOf("/", setPos - 1);
- if (commandPos < 0) return;
- auto command = top.substring(commandPos + 1, setPos);
-
- bool changed = true;
- if (command == "max_distance")
- {
- BleFingerprintCollection::maxDistance = pay.toFloat();
- spurt("/max_dist", pay);
- }
- else if (command == "absorption")
- {
- BleFingerprintCollection::absorption = pay.toFloat();
- spurt("/absorption", pay);
- }
- else if (command == "active_scan")
+ if (command == "active_scan")
{
activeScan = pay == "ON";
spurt("/active_scan", String(activeScan));
}
- else if (command == "query")
- {
- BleFingerprintCollection::query = pay;
- spurt("/query", pay);
- }
- else if (command == "include")
- {
- BleFingerprintCollection::include = pay;
- spurt("/include", pay);
- }
- else if (command == "exclude")
- {
- BleFingerprintCollection::exclude = pay;
- spurt("/exclude", pay);
- }
- else if (command == "known_macs")
- {
- BleFingerprintCollection::knownMacs = pay;
- spurt("/known_macs", pay);
- }
- else if (command == "count_ids")
- {
- BleFingerprintCollection::countIds = pay;
- spurt("/count_ids", pay);
- }
else if (command == "status_led")
{
GUI::statusLed = pay == "ON";
@@ -357,13 +325,49 @@ void onMqttMessage(char *topic, char *payload, AsyncMqttClientMessageProperties
{
ESP.restart();
}
- else if (command == "dump_memory")
- {
- heap_caps_dump_all();
+ else
+ return false;
+ return true;
+}
+
+void onMqttMessage(char *topic, char *payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total)
+{
+ char new_payload[len + 1];
+ new_payload[len] = '\0';
+ strncpy(new_payload, payload, len);
+
+ String top = String(topic);
+ String pay = String(new_payload);
+
+ auto setPos = top.lastIndexOf("/set");
+ auto configPos = top.lastIndexOf("/config");
+ if (configPos > 1) {
+ auto idPos = top.lastIndexOf("/", configPos - 1);
+ if (idPos < 0) goto skip;
+ auto id = top.substring(idPos + 1, configPos);
+ Serial.printf("%d MQTT | Config %s: %s\n", xPortGetCoreID(), id.c_str(), pay.c_str());
+ fingerprints.config(id, pay);
+ } else if (setPos > 1) {
+
+ auto commandPos = top.lastIndexOf("/", setPos - 1);
+ if (commandPos < 0) goto skip;
+ auto command = top.substring(commandPos + 1, setPos);
+ Serial.printf("%d MQTT | Set %s: %s\n", xPortGetCoreID(), command.c_str(), pay.c_str());
+
+ bool changed = false;
+ if (Command(command, pay))
+ changed = true;
+ else if (fingerprints.command(command, pay))
+ changed = true;
+ else if (Enrollment::Command(command, pay))
+ changed = true;
+ else if (Motion::Command(command, pay))
+ changed = true;
+ if (changed) online = false;
+ } else {
+skip:
+ Serial.printf("%d MQTT | Unknown: %s: %s\n", xPortGetCoreID(), topic, new_payload);
}
- else if (Motion::Command(command, pay)){}
- else changed = false;
- if (changed) online = false;
}
void reconnect(TimerHandle_t xTimer)
@@ -481,11 +485,10 @@ void reportTask(void *parameter)
void scanTask(void *parameter)
{
- NimBLEDevice::init("");
+ NimBLEDevice::init("ESPresense");
for (esp_ble_power_type_t i = ESP_BLE_PWR_TYPE_CONN_HDL0; i <= ESP_BLE_PWR_TYPE_CONN_HDL8; i = esp_ble_power_type_t((int)i + 1))
NimBLEDevice::setPower(ESP_PWR_LVL_P9, i);
- NimBLEDevice::setSecurityAuth(true, true, true);
- NimBLEDevice::setSecurityRespKey(BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID);
+ Enrollment::Setup();
NimBLEDevice::setMTU(23);
auto pBLEScan = NimBLEDevice::getScan();
@@ -504,6 +507,9 @@ void scanTask(void *parameter)
if (f->query())
totalFpQueried++;
+ while (Enrollment::Busy())
+ Enrollment::Loop();
+
if (updateInProgress())
{
fingerprints.setDisable(true);
diff --git a/src/main.h b/src/main.h
index f379929..2890e84 100644
--- a/src/main.h
+++ b/src/main.h
@@ -23,6 +23,7 @@
#include
#include
#include "Network.h"
+#include "Enrollment.h"
#include "MotionSensors.h"
#include "I2C.h"
diff --git a/src/mqtt.cpp b/src/mqtt.cpp
index 0581d94..229a92a 100644
--- a/src/mqtt.cpp
+++ b/src/mqtt.cpp
@@ -1,4 +1,5 @@
#include "globals.h"
+#include "defaults.h"
#include "string_utils.h"
#include
@@ -189,3 +190,13 @@ bool sendDeleteDiscovery(const String &domain, const String &name)
String discoveryTopic = Sprintf("homeassistant/%s/espresense_%06lx/%s/config", domain, CHIPID, slug.c_str());
return pub(discoveryTopic.c_str(), 0, false, "");
}
+
+bool alias(const String &alias, const String &id)
+{
+ Serial.printf("Setting %s->%s\n", alias.c_str(), id.c_str());
+ doc.clear();
+ doc["id"] = id;
+ serializeJson(doc, buffer);
+ String settingsTopic = CHANNEL + "/settings/" + alias + "/config";
+ return pub(settingsTopic.c_str(), 0, true, buffer);
+}
diff --git a/src/mqtt.h b/src/mqtt.h
index 6e5ca3a..26b1507 100644
--- a/src/mqtt.h
+++ b/src/mqtt.h
@@ -23,3 +23,5 @@ bool sendSwitchDiscovery(const String &name, const String &entityCategory);
bool sendNumberDiscovery(const String &name, const String &entityCategory);
bool sendDeleteDiscovery(const String &domain, const String &name);
+
+bool alias(const String &alias, const String &id);