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);