[BLE] Add support for decrypting PVVX, BTHome v2 and Victron BLE frames (#2219)

* Changing WebUI to include display device name, and change it to select drop down rather than checkbox

* Fix mqttDiscovery to require WebUI and ESP32 for displayDeviceName

* Fix mqttDiscovery to require WebUI and ESP32 and ESP8266 for displayDeviceName and ForceDeviceName

* Changing WebUI to include display device name, and change it to select drop down rather than checkbox

* Fixes for WebUI and BT for supporting custom setting Display name

* Fixes for WebUI and BT for supporting custom setting Display name

* Move DISPLAY_DEVICE_NAME to User_config.h

* Update docs to include change for Display temperature

* Update docs to include change for Display temperature

* Fix minor cosmetic bug where devices were not linking in HA to the gateway using via_device as it should be the gateway mac address not name

* Add support for decrypting BTHome v2 frames

* Add support for decrypting BTHome v2 frames

* Add support for decrypting BTHome v2 frames

* BTHome fix issue with theengs-plug

* BTHome fix issue with theengs-plug

* Adding support for all BLE encrypted methods, support in UI and gatewayBT for specific MACAddress AES Keys

* Fix lint

* Fix build issue with theengs-bridge-v11 and esp32dev-all-test and revert the documentation to Units of measurement displayed

* Revert docs

* Revert displayDeviceName and Units of measurement

* Revert displayDeviceName and Units of measurement

* Revert displayDeviceName and Units of measurement

* Revert minor typo

* Revert minor typo

* Revert minor typo

* Bug in Victron as nonce should be 16 bytes

* Shortened the client side javascript for BLE key validation that is commented out due to image constrains on theengs-bridge-v11
This commit is contained in:
Peter Lambrechtsen authored and GitHub committed 2025-08-06 19:59:33 -05:00
1 parent a0d5b7d181
commit 2d29ee4524
7 files changed
+399 -1

No files matched your search

+4
View File
@@ -30,6 +30,10 @@ Ability to change the mqtt settings, if the change is unsuccessful it will rever
Ability to change the display of sensor to Metric or Imperial, and disable the WebUI Authentication Ability to change the display of sensor to Metric or Imperial, and disable the WebUI Authentication
## Bluetooth Low Energy - BLE
Ability to add the default AES BLE decryption key, and multiple per-device `macaddress:aeskey` allowing for devices that cannot have their AES key changed.
## Logging ## Logging
Ability to temporarily change the logging level. Ability to temporarily change the logging level.
+5
View File
@@ -63,6 +63,11 @@ extern char mqtt_topic[];
extern char gateway_name[]; extern char gateway_name[];
extern unsigned long lastDiscovery; // Time of the last discovery to trigger automaticaly to off after DiscoveryAutoOffTimer extern unsigned long lastDiscovery; // Time of the last discovery to trigger automaticaly to off after DiscoveryAutoOffTimer
#if BLEDecryptor
extern char ble_aes[];
extern StaticJsonDocument<JSON_BLE_AES_CUSTOM_KEYS> ble_aes_keys;
#endif
extern bool enqueueJsonObject(const StaticJsonDocument<JSON_MSG_BUFFER>& jsonDoc, int timeout); extern bool enqueueJsonObject(const StaticJsonDocument<JSON_MSG_BUFFER>& jsonDoc, int timeout);
extern bool enqueueJsonObject(const StaticJsonDocument<JSON_MSG_BUFFER>& jsonDoc); extern bool enqueueJsonObject(const StaticJsonDocument<JSON_MSG_BUFFER>& jsonDoc);
extern void buildTopicFromId(JsonObject& Jsondata, const char* origin); extern void buildTopicFromId(JsonObject& Jsondata, const char* origin);
+12
View File
@@ -652,6 +652,18 @@ bool isAduplicateSignal(uint64_t);
void storeSignalValue(uint64_t); void storeSignalValue(uint64_t);
#endif #endif
#ifdef ZgatewayBT
# ifndef BLEDecryptor
# define BLEDecryptor true //true if decrypt encrypted PVVX or BTHome v2 service data
# endif
# ifndef JSON_BLE_AES_CUSTOM_KEYS
# define JSON_BLE_AES_CUSTOM_KEYS 256 // 42 byte BLE Custom Key * 6 rounded up to 256.
# endif
# ifndef BLE_AES
# define BLE_AES "00112233445566778899001122334455"
# endif
#endif
#define convertTemp_CtoF(c) ((c * 1.8) + 32) #define convertTemp_CtoF(c) ((c * 1.8) + 32)
#define convertTemp_FtoC(f) ((f - 32) * 5 / 9) #define convertTemp_FtoC(f) ((f - 32) * 5 / 9)
+14 -1
View File
@@ -62,7 +62,11 @@
#else #else
# define configure_6 # define configure_6
#endif #endif
#define configure_7 #ifdef BLEDecryptor
# define configure_7 "<p><form action='bl' method='post'><button>Configure BLE</button></form></p>"
#else
# define configure_7
#endif
#define configure_8 #define configure_8
/*------------------- ----------------------*/ /*------------------- ----------------------*/
@@ -219,6 +223,15 @@ const char config_lora_body[] = body_header
"</form>" "</form>"
"</fieldset>" body_footer_config_menu; "</fieldset>" body_footer_config_menu;
#ifdef BLEDecryptor
const char config_ble_body[] = body_header "<fieldset class=\"set1\"><legend><span><b>Configure BLE</b></span></legend><form method='post' action='bl'><p><b>BLE AES Default Key (32 char hex)</b></p><input id='bk' name='bk' minlength='32' maxlength='32' placeholder=" BLE_AES " value='%s' oninput='bkv()'><p id='bke' style='color:red;'></p><hr><p><b>BLE Key Pairs</b></p><p>MacAddress:AESKey with space separator</p><p><textarea id='kp' name='kp' placeholder='A4C138012345:00112233445566778899001122334455' rows='3' cols='46' oninput='kpv()'>%s</textarea></p><p id='kpe' style='color:red;'></p><br><button id='s' name='save' type='submit' class='button bgrn'>Save</button></form></fieldset>" body_footer_config_menu;
// Client side javascript validation of the Default AES Key is hex, and the custom keys are in the correct format. This pushes the theengs-bridge-v11 way of the file size, so reudcing it now and leaving it commented out
//const char ble_script[] = "function bkv(){let e=document.getElementById('bk'),t=document.getElementById('bke'),l=e.value.trim();0===l.length||/^[A-Fa-f0-9]{32}$/.test(l)?(t.textContent='',e.style.color=''):(t.textContent='Invalid key',e.style.color='red')}function kpv(){let e=document.getElementById('kp'),t=document.getElementById('kpe'),l=e.value.split(/ /),n=/^[A-Fa-f0-9]{12}:[A-Fa-f0-9]{32}$/,o=l.map((e,t)=>({line:e,index:t})).filter(e=>!n.test(e.line));if(0===e.value.trim().length||0===o.length)t.textContent='',e.style.color='';else{let i=o.map(e=>e.index+1).join(', ');t.textContent=`Invalid format on line(s): ${i}`,e.style.color='red'}}";
const char ble_script[] = "";
#endif
const char footer[] = "<div style='text-align:right;font-size:11px;'><hr/><a href='https://community.openmqttgateway.com' target='_blank' style='color:#aaa;'>%s</a></div></div></body></html>"; const char footer[] = "<div style='text-align:right;font-size:11px;'><hr/><a href='https://community.openmqttgateway.com' target='_blank' style='color:#aaa;'>%s</a></div></div></body></html>";
// Source file - https://github.com/1technophile/OpenMQTTGateway/blob/54decb4b65c7894b926ac3a89de0c6b2a3021506/docs/.vuepress/public/favicon-16x16.png // Source file - https://github.com/1technophile/OpenMQTTGateway/blob/54decb4b65c7894b926ac3a89de0c6b2a3021506/docs/.vuepress/public/favicon-16x16.png
+232
View File
@@ -57,6 +57,10 @@ BTConfig_s BTConfig;
# if BLEDecoder # if BLEDecoder
# include <decoder.h> # include <decoder.h>
# if BLEDecryptor
# include "mbedtls/ccm.h"
# include "mbedtls/aes.h"
# endif
TheengsDecoder decoder; TheengsDecoder decoder;
# endif # endif
@@ -1154,6 +1158,27 @@ void launchBTDiscovery(bool overrideDiscovery) {
void launchBTDiscovery(bool overrideDiscovery) {} void launchBTDiscovery(bool overrideDiscovery) {}
# endif # endif
# if BLEDecryptor
// ** TODO - Hex string to bytes, there is probably a function for this already just need to find it
int hexToBytes(String hex, uint8_t *out, size_t maxLen) {
int len = hex.length();
if (len % 2 || len / 2 > maxLen) return -1;
for (int i = 0, j = 0; i < len; i += 2, j++) {
out[j] = (uint8_t) strtol(hex.substring(i, i + 2).c_str(), nullptr, 16);
}
return len / 2;
}
// Reverse bytes
void reverseBytes(uint8_t *data, size_t length) {
size_t i;
for (i = 0; i < length / 2; i++) {
uint8_t temp = data[i];
data[i] = data[length - 1 - i];
data[length - 1 - i] = temp;
}
}
# endif
# if BLEDecoder # if BLEDecoder
void process_bledata(JsonObject& BLEdata) { void process_bledata(JsonObject& BLEdata) {
yield(); // Necessary to let the loop run in case of connectivity issues yield(); // Necessary to let the loop run in case of connectivity issues
@@ -1166,6 +1191,213 @@ void process_bledata(JsonObject& BLEdata) {
int model_id = BTConfig.extDecoderEnable ? -1 : decoder.decodeBLEJson(BLEdata); int model_id = BTConfig.extDecoderEnable ? -1 : decoder.decodeBLEJson(BLEdata);
int mac_type = BLEdata["mac_type"].as<int>(); int mac_type = BLEdata["mac_type"].as<int>();
# if BLEDecryptor
if (BLEdata["encr"] && (BLEdata["encr"].as<int>() >0 && BLEdata["encr"].as<int>() <=2)) {
// Decrypting Encrypted BLE Data PVVX, BTHome or Victron
Log.trace(F("[BLEDecryptor] Decrypt ENCR:%d ModelID:%s Payload:%s" CR), BLEdata["encr"].as<int>(), BLEdata["model_id"].as<const char*>(), BLEdata["cipher"].as<const char*>());
// MAC address
String macWOdots = BLEdata["id"].as<String>(); // Mac Address without dots
macWOdots.replace(":", "");
unsigned char macAddress[6];
int maclen = hexToBytes(macWOdots, macAddress, 6);
if (maclen != 6) {
Log.error(F("[BLEDecryptor] Invalid MAC Address length %d" CR), maclen);
return;
}
// AES decryption key
unsigned char bleaeskey[16];
int bleaeskeylength = 0;
if (ble_aes_keys.containsKey(macWOdots)){
Log.trace(F("[BLEDecryptor] Custom AES key %s" CR), ble_aes_keys[macWOdots].as<const char*>());
bleaeskeylength = hexToBytes(ble_aes_keys[macWOdots], bleaeskey, 16);
} else {
Log.trace(F("[BLEDecryptor] Default AES key" CR));
bleaeskeylength = hexToBytes(ble_aes, bleaeskey, 16);
}
// Check AES Key
if (bleaeskeylength != 16) {
Log.error(F("[BLEDecryptor] Invalid key length %d" CR), bleaeskeylength);
return;
}
// Build nonce and aad
uint8_t nonce[16];
int noncelength = 0;
unsigned char aad[1];
int aadLength;
if (BLEdata["encr"].as<int>() == 1){ // PVVX Encrypted
noncelength = 11; // 11 bytes
reverseBytes(macAddress, 6); // 6 bytes: device address in reverse
memcpy(nonce, macAddress, 6);
int maclen = hexToBytes(macWOdots, macAddress, 6);
unsigned char servicedata[16];
int servicedatalen = hexToBytes(BLEdata["servicedata"].as<String>(), servicedata, 16);
nonce[6] = servicedatalen + 3; // 1 byte : length of (service data + type and UUID)
nonce[7] = 0x16; // 1 byte : "16" -> AD type for "Service Data - 16-bit UUID"
nonce[8] = 0x1A; // 2 bytes: "1a18" -> UUID 181a in little-endian
nonce[9] = 0x18; //
unsigned char ctr[1]; // 1 byte : counter
int ctrlen = hexToBytes(BLEdata["ctr"].as<String>(), ctr, 1);
if (ctrlen != 1) {
Log.error(F("[BLEDecryptor] Invalid counter length %d" CR), ctrlen);
return;
}
nonce[10] = ctr[0];
aad[0] = 0x11;
aadLength = 1;
Log.trace(F("[BLEDecryptor] PVVX nonce %s" CR), NimBLEUtils::dataToHexString(nonce, noncelength).c_str());
} else if (BLEdata["encr"].as<int>() == 2){ // BTHome V2 Encrypted
noncelength = 13; // 13 bytes
memcpy(nonce, macAddress, 6);
nonce[6] = 0xD2; // UUID
nonce[7] = 0xFC;
nonce[8] = 0x41; // BTHome Device Data encrypted payload byte
unsigned char ctr[4]; // Counter
int ctrlen = hexToBytes(BLEdata["ctr"].as<String>(), ctr, 4);
if (ctrlen != 4) {
Log.error(F("[BLEDecryptor] Invalid counter length %d" CR), ctrlen);
return;
}
memcpy(&nonce[9], ctr, 4);
aad[0] = 0x00;
aadLength = 0;
Log.trace(F("[BLEDecryptor] BTHomeV2 nonce %s" CR), NimBLEUtils::dataToHexString(nonce, noncelength).c_str());
} else if (BLEdata["encr"].as<int>() == 3){
nonce[16] = {0}; // Victron has a 16 byte zero padded nonce with IV bytes 6,7
unsigned char iv[2];
int ivlen = hexToBytes(BLEdata["ctr"].as<String>(), iv, 2);
if (ivlen != 2) {
Log.error(F("[BLEDecryptor] Invalid iv length %d" CR), ivlen);
return;
}
memcpy(nonce, iv, 2);
Log.trace(F("[BLEDecryptor] Victron nonce %s" CR), NimBLEUtils::dataToHexString(nonce, 16).c_str());
} else {
return; // No match
}
// Ciphertext to bytes
int cipherlen = sizeof(BLEdata["cipher"].as<String>());
unsigned char ciphertext[cipherlen];
int ciphertextlen = hexToBytes(BLEdata["cipher"].as<String>(), ciphertext, cipherlen);
unsigned char decrypted[ciphertextlen]; // Decrypted payload
// Decrypt ciphertext
if (BLEdata["encr"].as<int>() == 1 || BLEdata["encr"].as<int>() == 2) {
// Decrypt PVVX and BTHome V2 ciphertext using AES CCM
mbedtls_ccm_context ctx;
mbedtls_ccm_init(&ctx);
if (mbedtls_ccm_setkey(&ctx, MBEDTLS_CIPHER_ID_AES, bleaeskey, 128) != 0) {
Log.error(F("[BLEDecryptor] Failed to set AES key to mbedtls" CR));
return;
}
// Message Integrity Check (MIC)
unsigned char mic[4];
int miclen = hexToBytes(BLEdata["mic"].as<String>(), mic, 4);
if (miclen != 4) {
Log.error(F("[BLEDecryptor] Invalid MIC length %d" CR), miclen);
return;
}
int ret = mbedtls_ccm_auth_decrypt(
&ctx, // AES Key
ciphertextlen, // length of ciphertext
nonce, noncelength, // Nonce
aad, aadLength, // AAD
ciphertext, // input ciphertext
decrypted, // output plaintext
mic, sizeof(mic) // Message Integrity Check
);
mbedtls_ccm_free(&ctx);
if (ret == 0) {
Log.notice(F("[BLEDecryptor] Decryption successful" CR));
} else if (ret == MBEDTLS_ERR_CCM_AUTH_FAILED) {
Log.error(F("[BLEDecryptor] Authentication failed." CR));
return;
} else {
Log.error(F("[BLEDecryptor] Decryption failed with error: %X" CR), ret);
return;
}
// Build new servicedata
if (BLEdata["encr"].as<int>() == 1){ // PVVX
BLEdata["servicedata"] = NimBLEUtils::dataToHexString(decrypted, ciphertextlen);
} else if (BLEdata["encr"].as<int>() == 2) { // BTHomeV2
// Build new servicedata
uint8_t newservicedata[3 + ciphertextlen];
newservicedata[0] = 0x40; // Decrypted BTHomeV2 Packet Type
newservicedata[1] = 0x00; // Packet counter which the PVVX BTHome non-encrypted has but the encrypted does not
newservicedata[2] = 0x00; // **TODO Convert the ctr to the packet counter or just stick with 0?
memcpy(&newservicedata[3], decrypted, ciphertextlen);
BLEdata["servicedata"] = NimBLEUtils::dataToHexString(newservicedata, ciphertextlen + 3);
} else {
return;
}
Log.trace(F("[BLEDecryptor] Decrypted servicedata %s" CR), BLEdata["servicedata"].as<const char*>());
} else if (BLEdata["encr"].as<int>() == 3) {
// Decrypt Victron Energy encrypted advertisements.
size_t nc_off = 0;
uint8_t stream_block[16] = {0};
mbedtls_aes_context ctx;
mbedtls_aes_init(&ctx);
mbedtls_aes_setkey_enc(&ctx, bleaeskey, 128);
int ret = mbedtls_aes_crypt_ctr(
&ctx, // AES Key
ciphertextlen, // length of ciphertext
&nc_off,
nonce, // 16 byte nonce with 2 bytes iv
stream_block,
ciphertext, // input ciphertext
decrypted // output plaintext
);
mbedtls_aes_free(&ctx);
if (ret == 0) {
Log.notice(F("[BLEDecryptor] Victron Decryption successful" CR));
} else if (ret == MBEDTLS_ERR_CCM_AUTH_FAILED) {
Log.error(F("[BLEDecryptor] Victron Authentication failed." CR));
return;
} else {
Log.error(F("[BLEDecryptor] Victron decryption failed with error: %X" CR), ret);
return;
}
// Build new manufacturerdata
unsigned char manufacturerdata[10 + ciphertextlen];
int manufacturerdatalen = hexToBytes(BLEdata["manufacturerdata"].as<String>(), manufacturerdata, 10);
manufacturerdata[2] = 0x11; // Replace byte 2 with "11" indicate decrypted data
manufacturerdata[7] = 0xff; // Replace byte 7 with "ff" to indicate decrypted data
manufacturerdata[8] = 0xff; // Replace byte 8 with "ff" to indicate decrypted data
memcpy(&manufacturerdata[8], decrypted, ciphertextlen); // Append the decrypted payload to the manufacturer data
BLEdata["manufacturerdata"] = NimBLEUtils::dataToHexString(manufacturerdata, 10 + ciphertextlen); // Rebuild manufacturerdata
Log.trace(F("[BLEDecryptor] Victron decrypted manufacturerdata %s" CR), BLEdata["manufacturerdata"].as<const char*>());
}
// Print before and after decoder post decryption
// serializeJsonPretty(BLEdata, Serial);
model_id = BTConfig.extDecoderEnable ? -1 : decoder.decodeBLEJson(BLEdata);
// serializeJsonPretty(BLEdata, Serial);
Log.trace(F("[BLEDecryptor] Decrypted model_id %d" CR), model_id);
// Remove the cipher fields from BLEdata
BLEdata.remove("encr");
BLEdata.remove("cipher");
BLEdata.remove("ctr");
BLEdata.remove("mic");
}
# endif
// Convert prmacs to RMACS until or if OMG gets Identity MAC/IRK decoding // Convert prmacs to RMACS until or if OMG gets Identity MAC/IRK decoding
if (BLEdata["prmac"]) { if (BLEdata["prmac"]) {
BLEdata.remove("prmac"); BLEdata.remove("prmac");
+39
View File
@@ -94,6 +94,12 @@ bool ethConnected = false;
char mqtt_topic[parameters_size + 1] = Base_Topic; char mqtt_topic[parameters_size + 1] = Base_Topic;
char gateway_name[parameters_size + 1] = Gateway_Name; char gateway_name[parameters_size + 1] = Gateway_Name;
unsigned long lastDiscovery = 0; unsigned long lastDiscovery = 0;
#ifdef BLEDecryptor
char ble_aes[parameters_size] = BLE_AES;
StaticJsonDocument<JSON_BLE_AES_CUSTOM_KEYS> ble_aes_keys;
#endif
#if !MQTT_BROKER_MODE #if !MQTT_BROKER_MODE
ss_cnt_parameters cnt_parameters_array[cnt_parameters_array_size] = CNT_PARAMS_ARR; ss_cnt_parameters cnt_parameters_array[cnt_parameters_array_size] = CNT_PARAMS_ARR;
#endif #endif
@@ -2022,6 +2028,10 @@ void saveConfig() {
# endif # endif
json["gateway_name"] = gateway_name; json["gateway_name"] = gateway_name;
json["ota_pass"] = ota_pass; json["ota_pass"] = ota_pass;
# ifdef BLEDecryptor
json["ble_aes"] = ble_aes;
json["ble_aes_keys"] = ble_aes_keys;
# endif
File configFile = SPIFFS.open("/config.json", "w"); File configFile = SPIFFS.open("/config.json", "w");
if (!configFile) { if (!configFile) {
@@ -2169,6 +2179,16 @@ bool loadConfigFromFlash() {
} }
# endif # endif
} }
# ifdef BLEDecryptor
if (json.containsKey("ble_aes")) {
strcpy(ble_aes, json["ble_aes"]);
Log.trace(F("loaded default BLE AES key %s" CR), ble_aes);
}
if (json.containsKey("ble_aes_keys")) {
ble_aes_keys = json["ble_aes_keys"];
Log.trace(F("loaded %d custom BLE AES keys" CR), ble_aes_keys.size());
}
# endif
result = true; result = true;
} else { } else {
Log.warning(F("failed to load json config" CR)); Log.warning(F("failed to load json config" CR));
@@ -3441,6 +3461,25 @@ void XtoSYS(const char* topicOri, JsonObject& SYSdata) { // json object decoding
mqttSetupPending = true; // trigger reconnect in loop using the new topic/name mqttSetupPending = true; // trigger reconnect in loop using the new topic/name
} }
#ifdef BLEDecryptor
if (SYSdata.containsKey("ble_aes") || SYSdata.containsKey("ble_aes_keys")) {
if (SYSdata.containsKey("ble_aes")) {
strncpy(ble_aes, SYSdata["ble_aes"], parameters_size);
}
if (SYSdata.containsKey("ble_aes_keys")) {
Log.warning(F("Contains ble_aes_keys" CR));
ble_aes_keys = SYSdata["ble_aes_keys"];
} else {
// If no keys are passed clear the object.
StaticJsonDocument<JSON_BLE_AES_CUSTOM_KEYS> jsonBLEBuffer;
ble_aes_keys = jsonBLEBuffer.to<JsonObject>();
}
# ifndef ESPWifiManualSetup
saveConfig();
# endif
}
#endif
#if !MQTT_BROKER_MODE #if !MQTT_BROKER_MODE
# ifdef MQTTsetMQTT # ifdef MQTTsetMQTT
+93
View File
@@ -27,6 +27,7 @@
# include <WebServer.h> // Docs for this are here - https://github.com/espressif/arduino-esp32/tree/master/libraries/WebServer # include <WebServer.h> // Docs for this are here - https://github.com/espressif/arduino-esp32/tree/master/libraries/WebServer
# include <WiFi.h> # include <WiFi.h>
# include "TheengsCommon.h"
# include "config_WebContent.h" # include "config_WebContent.h"
# include "config_WebUI.h" # include "config_WebUI.h"
@@ -954,6 +955,94 @@ void handleLO() {
server.send(200, "text/html", response); server.send(200, "text/html", response);
} }
# ifdef BLEDecryptor
/**
* @brief /BL - Config BLE
* T: handleBL: uri: /bl, args: 3, method: 1
* T: handleBL Arg: 0, dn=on - Force Device Name
* T: handleBL Arg: 1, bk=on - BLE AES Key
* T: handleBL Arg: 2, save=
*/
void handleBL() {
WEBUI_TRACE_LOG(F("handleBL: uri: %s, args: %d, method: %d" CR), server.uri(), server.args(), server.method());
WEBUI_SECURE
StaticJsonDocument<JSON_MSG_BUFFER> jsonBuffer;
JsonObject WEBtoSYS = jsonBuffer.to<JsonObject>();
if (server.args()) {
for (uint8_t i = 0; i < server.args(); i++) {
WEBUI_TRACE_LOG(F("handleBL Arg: %d, %s=%s" CR), i, server.argName(i).c_str(), server.arg(i).c_str());
}
bool update = false;
if (server.hasArg("save")) {
// Default BLE AES Key
if (server.hasArg("bk")) {
WEBtoSYS["ble_aes"] = server.arg("bk");
update = true;
}
// Split Custom BLE AES key pair string add to config
if (server.hasArg("kp")) {
String kp = server.arg("kp");
while (kp.length() > 0) {
int kpindex = kp.indexOf(' ');
if (kpindex == -1) {
if (kp.indexOf(':') == 12) {
WEBtoSYS["ble_aes_keys"][kp.substring(0, 12)] = kp.substring(13, 45);
}
break;
} else {
if (kp.indexOf(':') == 12) {
WEBtoSYS["ble_aes_keys"][kp.substring(0, 12)] = kp.substring(13, 45);
}
kp = kp.substring(kpindex+1);
}
}
update = true;
}
if (update) {
Log.warning(F("[BLE] Save Config" CR));
String topic = String(mqtt_topic) + String(gateway_name) + String(subjectMQTTtoSYSset);
XtoSYS((char*)topic.c_str(), WEBtoSYS);
} else {
Log.warning(F("[BLE] No changes" CR));
}
}
}
// Build BLE Key Pair string
std::string aeskeysstring;
JsonObject root = ble_aes_keys.as<JsonObject>();
for (JsonPair kv : root) {
aeskeysstring += kv.key().c_str();
aeskeysstring += ":";
aeskeysstring += kv.value().as<const char*>();
aeskeysstring += " ";
}
aeskeysstring.pop_back();
char jsonChar[100];
serializeJson(modules, jsonChar, measureJson(modules) + 1);
char buffer[WEB_TEMPLATE_BUFFER_MAX_SIZE];
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, header_html, (String(gateway_name) + " - Configure BLE").c_str());
String response = String(buffer);
response += String(ble_script);
response += String(script);
response += String(style);
int logLevel = Log.getLevel();
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, config_ble_body, jsonChar, gateway_name, ble_aes, aeskeysstring.c_str());
response += String(buffer);
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, footer, OMG_VERSION);
response += String(buffer);
server.send(200, "text/html", response);
}
# endif
# ifdef ZgatewayLORA # ifdef ZgatewayLORA
# include "config_LORA.h" # include "config_LORA.h"
extern void LORAConfig_fromJson(JsonObject& LORAdata); extern void LORAConfig_fromJson(JsonObject& LORAdata);
@@ -1688,6 +1777,10 @@ void WebUISetup() {
# endif # endif
server.on("/lo", handleLO); // Configure Logging server.on("/lo", handleLO); // Configure Logging
# ifdef BLEDecryptor
server.on("/bl", HTTP_POST, handleBL); // Configure BLE
# endif
server.on("/rt", handleRT); // Reset configuration ( Erase and Restart ) server.on("/rt", handleRT); // Reset configuration ( Erase and Restart )
server.on("/favicon.ico", handleFavicon); // Information server.on("/favicon.ico", handleFavicon); // Information
server.begin(); server.begin();