Enroll using Identity Resolving Keys (#553)

* Inital IRK
* Add Enroll button on device
This commit is contained in:
Darrell 2022-07-18 02:30:20 -04:00 committed by GitHub
parent 406ef9d8b1
commit b298730daa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 585 additions and 105 deletions

View File

@ -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<uint8_t *, String> &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++)

View File

@ -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<Differential<float>> output;

View File

@ -1,4 +1,85 @@
#include "BleFingerprintCollection.h"
#include <sstream>
/* 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<std::pair<uint8_t*,String>> 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<BleFingerprint *> 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<BleFingerprint *>* const BleFingerprintCollection::getNative()
{

View File

@ -21,7 +21,11 @@ public:
const std::list<BleFingerprint *>* const getNative();
const std::list<BleFingerprint *> 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<std::pair<uint8_t*,String>> irks;
static String knownMacs, knownIrks, include, exclude, query;
static float skipDistance, maxDistance, absorption;
static int refRssi, forgetMs, skipMs;
static String countIds;

View File

@ -2,6 +2,8 @@
#include <string_utils.h>
#include <SPIFFS.h>
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;

View File

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

View File

@ -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

264
src/Enrollment.cpp Normal file
View File

@ -0,0 +1,264 @@
#include "Enrollment.h"
#include "mqtt.h"
#include <NimBLEDevice.h>
#include <NimBLEAdvertisedDevice.h>
#include <NimBLEService.h>
#include <NimBLECharacteristic.h>
#include <NimBLEDescriptor.h>
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);
}
}

12
src/Enrollment.h Normal file
View File

@ -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();
}

View File

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

View File

@ -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 <a href='https://espresense.com/configuration/settings#scanning' target='_blank'></a>", 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 <a href='https://espresense.com/configuration/settings#counting' target='_blank'></a>", false);
@ -194,6 +209,7 @@ void setupNetwork()
WiFiSettings.heading("Misc <a href='https://espresense.com/configuration/settings#misc' target='_blank'></a>", 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);

View File

@ -23,6 +23,7 @@
#include <freertos/timers.h>
#include <rom/rtc.h>
#include "Network.h"
#include "Enrollment.h"
#include "MotionSensors.h"
#include "I2C.h"

View File

@ -1,4 +1,5 @@
#include "globals.h"
#include "defaults.h"
#include "string_utils.h"
#include <WiFi.h>
@ -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);
}

View File

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