Enroll using Identity Resolving Keys (#553)
* Inital IRK * Add Enroll button on device
This commit is contained in:
parent
406ef9d8b1
commit
b298730daa
|
@ -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);
|
||||
if ((idType != newIdType) || !id.equals(newId)) {
|
||||
id = newId;
|
||||
idType = newIdType;
|
||||
if (!newName.isEmpty()) name = newName;
|
||||
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++)
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
||||
|
|
130
src/main.cpp
130
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 <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;
|
||||
}
|
||||
else if (Motion::Command(command, pay)){}
|
||||
else changed = 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);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include <freertos/timers.h>
|
||||
#include <rom/rtc.h>
|
||||
#include "Network.h"
|
||||
#include "Enrollment.h"
|
||||
|
||||
#include "MotionSensors.h"
|
||||
#include "I2C.h"
|
||||
|
|
11
src/mqtt.cpp
11
src/mqtt.cpp
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue