feat: add MQTT repeater support, web config panel, and EastMesh-specific firmware workflows
Этот коммит содержится в:
@@ -882,6 +882,8 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
_callbacks->formatRadioStatsReply(reply);
|
||||
} else if (sender_timestamp == 0 && memcmp(command, "stats-core", 10) == 0 && (command[10] == 0 || command[10] == ' ')) {
|
||||
_callbacks->formatStatsReply(reply);
|
||||
} else if (sender_timestamp == 0 && memcmp(command, "memory", 6) == 0 && (command[6] == 0 || command[6] == ' ')) {
|
||||
_callbacks->formatMemoryReply(reply, 160);
|
||||
} else {
|
||||
strcpy(reply, "Unknown command");
|
||||
}
|
||||
|
||||
@@ -83,6 +83,7 @@ public:
|
||||
virtual void formatStatsReply(char *reply) = 0;
|
||||
virtual void formatRadioStatsReply(char *reply) = 0;
|
||||
virtual void formatPacketStatsReply(char *reply) = 0;
|
||||
virtual void formatMemoryReply(char *reply, size_t reply_size) = 0;
|
||||
virtual mesh::LocalIdentity& getSelfId() = 0;
|
||||
virtual void saveIdentity(const mesh::LocalIdentity& new_id) = 0;
|
||||
virtual void clearStats() = 0;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "Mesh.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
class StatsFormatHelper {
|
||||
public:
|
||||
@@ -52,4 +53,23 @@ public:
|
||||
driver.getPacketsRecvErrors()
|
||||
);
|
||||
}
|
||||
|
||||
static void formatMemoryStats(char* reply, size_t reply_size) {
|
||||
if (reply == nullptr || reply_size == 0) {
|
||||
return;
|
||||
}
|
||||
#if defined(ESP32)
|
||||
snprintf(reply, reply_size,
|
||||
"{\"heap_free\":%u,\"heap_min\":%u,\"heap_max\":%u,\"psram_free\":%u,\"psram_min\":%u,\"psram_max\":%u}",
|
||||
ESP.getFreeHeap(),
|
||||
ESP.getMinFreeHeap(),
|
||||
ESP.getMaxAllocHeap(),
|
||||
ESP.getFreePsram(),
|
||||
ESP.getMinFreePsram(),
|
||||
ESP.getMaxAllocPsram()
|
||||
);
|
||||
#else
|
||||
snprintf(reply, reply_size, "{\"supported\":false}");
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
#include "JWTHelper.h"
|
||||
|
||||
#ifdef WITH_MQTT_UPLINK
|
||||
|
||||
#include <ctype.h>
|
||||
#include <ed_25519.h>
|
||||
#include <mbedtls/base64.h>
|
||||
#include <string.h>
|
||||
|
||||
size_t JWTHelper::base64UrlEncode(const uint8_t* input, size_t input_len, char* output, size_t output_size) {
|
||||
if (input == nullptr || output == nullptr || output_size < 2) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t encoded_len = 0;
|
||||
if (mbedtls_base64_encode(reinterpret_cast<unsigned char*>(output), output_size - 1, &encoded_len, input,
|
||||
input_len) != 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < encoded_len; ++i) {
|
||||
if (output[i] == '+') {
|
||||
output[i] = '-';
|
||||
} else if (output[i] == '/') {
|
||||
output[i] = '_';
|
||||
}
|
||||
}
|
||||
|
||||
while (encoded_len > 0 && output[encoded_len - 1] == '=') {
|
||||
encoded_len--;
|
||||
}
|
||||
output[encoded_len] = 0;
|
||||
return encoded_len;
|
||||
}
|
||||
|
||||
bool JWTHelper::createAuthToken(const mesh::LocalIdentity& identity, const char* audience, time_t issued_at,
|
||||
time_t expires_at, char* token, size_t token_size, const char* owner,
|
||||
const char* email) {
|
||||
if (audience == nullptr || token == nullptr || token_size < 32 || issued_at <= 0 || expires_at <= issued_at) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static const char header_json[] = "{\"alg\":\"Ed25519\",\"typ\":\"JWT\"}";
|
||||
char header[96];
|
||||
size_t header_len = base64UrlEncode(reinterpret_cast<const uint8_t*>(header_json), strlen(header_json), header,
|
||||
sizeof(header));
|
||||
if (header_len == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char public_key_hex[65];
|
||||
mesh::Utils::toHex(public_key_hex, identity.pub_key, PUB_KEY_SIZE);
|
||||
for (size_t i = 0; public_key_hex[i] != 0; ++i) {
|
||||
public_key_hex[i] = toupper(static_cast<unsigned char>(public_key_hex[i]));
|
||||
}
|
||||
|
||||
char payload_json[384];
|
||||
int payload_json_len = snprintf(payload_json, sizeof(payload_json),
|
||||
"{\"publicKey\":\"%s\",\"aud\":\"%s\",\"iat\":%lu,\"exp\":%lu",
|
||||
public_key_hex, audience, static_cast<unsigned long>(issued_at),
|
||||
static_cast<unsigned long>(expires_at));
|
||||
if (payload_json_len <= 0 || static_cast<size_t>(payload_json_len) >= sizeof(payload_json)) {
|
||||
return false;
|
||||
}
|
||||
if (owner != nullptr && owner[0] != 0) {
|
||||
payload_json_len += snprintf(payload_json + payload_json_len, sizeof(payload_json) - payload_json_len,
|
||||
",\"owner\":\"%s\"", owner);
|
||||
}
|
||||
if (email != nullptr && email[0] != 0) {
|
||||
payload_json_len += snprintf(payload_json + payload_json_len, sizeof(payload_json) - payload_json_len,
|
||||
",\"email\":\"%s\"", email);
|
||||
}
|
||||
if (payload_json_len <= 0 || static_cast<size_t>(payload_json_len) + 2 >= sizeof(payload_json)) {
|
||||
return false;
|
||||
}
|
||||
payload_json[payload_json_len++] = '}';
|
||||
payload_json[payload_json_len] = 0;
|
||||
|
||||
char payload[384];
|
||||
size_t payload_len = base64UrlEncode(reinterpret_cast<const uint8_t*>(payload_json), payload_json_len, payload,
|
||||
sizeof(payload));
|
||||
if (payload_len == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char signing_input[512];
|
||||
int signing_input_len = snprintf(signing_input, sizeof(signing_input), "%s.%s", header, payload);
|
||||
if (signing_input_len <= 0 || static_cast<size_t>(signing_input_len) >= sizeof(signing_input)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t signature[64];
|
||||
identity.sign(signature, reinterpret_cast<const uint8_t*>(signing_input), signing_input_len);
|
||||
|
||||
char signature_hex[129];
|
||||
for (size_t i = 0; i < sizeof(signature); ++i) {
|
||||
snprintf(&signature_hex[i * 2], 3, "%02X", signature[i]);
|
||||
}
|
||||
signature_hex[128] = 0;
|
||||
|
||||
int total_len = snprintf(token, token_size, "%s.%s", signing_input, signature_hex);
|
||||
return total_len > 0 && static_cast<size_t>(total_len) < token_size;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef WITH_MQTT_UPLINK
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <Identity.h>
|
||||
|
||||
class JWTHelper {
|
||||
public:
|
||||
static bool createAuthToken(const mesh::LocalIdentity& identity, const char* audience, time_t issued_at,
|
||||
time_t expires_at, char* token, size_t token_size, const char* owner = nullptr,
|
||||
const char* email = nullptr);
|
||||
|
||||
private:
|
||||
static size_t base64UrlEncode(const uint8_t* input, size_t input_len, char* output, size_t output_size);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,112 @@
|
||||
#pragma once
|
||||
|
||||
namespace mqtt_ca_certs {
|
||||
|
||||
// EastMesh currently presents a Let's Encrypt E7-issued certificate.
|
||||
static const char kEastmeshIsrgRootX1Pem[] =
|
||||
R"PEM(-----BEGIN CERTIFICATE-----
|
||||
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
|
||||
-----END CERTIFICATE-----
|
||||
)PEM";
|
||||
|
||||
// LetsMesh currently presents a certificate issued by Google Trust Services WE1.
|
||||
static const char kLetsmeshWe1Pem[] =
|
||||
R"PEM(-----BEGIN CERTIFICATE-----
|
||||
MIICnzCCAiWgAwIBAgIQf/MZd5csIkp2FV0TttaF4zAKBggqhkjOPQQDAzBHMQsw
|
||||
CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU
|
||||
MBIGA1UEAxMLR1RTIFJvb3QgUjQwHhcNMjMxMjEzMDkwMDAwWhcNMjkwMjIwMTQw
|
||||
MDAwWjA7MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVR29vZ2xlIFRydXN0IFNlcnZp
|
||||
Y2VzMQwwCgYDVQQDEwNXRTEwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARvzTr+
|
||||
Z1dHTCEDhUDCR127WEcPQMFcF4XGGTfn1XzthkubgdnXGhOlCgP4mMTG6J7/EFmP
|
||||
LCaY9eYmJbsPAvpWo4H+MIH7MA4GA1UdDwEB/wQEAwIBhjAdBgNVHSUEFjAUBggr
|
||||
BgEFBQcDAQYIKwYBBQUHAwIwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU
|
||||
kHeSNWfE/6jMqeZ72YB5e8yT+TgwHwYDVR0jBBgwFoAUgEzW63T/STaj1dj8tT7F
|
||||
avCUHYwwNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzAChhhodHRwOi8vaS5wa2ku
|
||||
Z29vZy9yNC5jcnQwKwYDVR0fBCQwIjAgoB6gHIYaaHR0cDovL2MucGtpLmdvb2cv
|
||||
ci9yNC5jcmwwEwYDVR0gBAwwCjAIBgZngQwBAgEwCgYIKoZIzj0EAwMDaAAwZQIx
|
||||
AOcCq1HW90OVznX+0RGU1cxAQXomvtgM8zItPZCuFQ8jSBJSjz5keROv9aYsAm5V
|
||||
sQIwJonMaAFi54mrfhfoFNZEfuNMSQ6/bIBiNLiyoX46FohQvKeIoJ99cx7sUkFN
|
||||
7uJW
|
||||
-----END CERTIFICATE-----
|
||||
)PEM";
|
||||
|
||||
static const char kCombinedPem[] =
|
||||
R"PEM(-----BEGIN CERTIFICATE-----
|
||||
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
|
||||
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
|
||||
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
|
||||
WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
|
||||
ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
|
||||
MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
|
||||
h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
|
||||
0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
|
||||
A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
|
||||
T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
|
||||
B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
|
||||
B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
|
||||
KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
|
||||
OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
|
||||
jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
|
||||
qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
|
||||
rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
|
||||
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
|
||||
hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
|
||||
ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
|
||||
3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
|
||||
NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
|
||||
ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
|
||||
TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
|
||||
jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
|
||||
oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
|
||||
4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
|
||||
mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
|
||||
emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICnzCCAiWgAwIBAgIQf/MZd5csIkp2FV0TttaF4zAKBggqhkjOPQQDAzBHMQsw
|
||||
CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU
|
||||
MBIGA1UEAxMLR1RTIFJvb3QgUjQwHhcNMjMxMjEzMDkwMDAwWhcNMjkwMjIwMTQw
|
||||
MDAwWjA7MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVR29vZ2xlIFRydXN0IFNlcnZp
|
||||
Y2VzMQwwCgYDVQQDEwNXRTEwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARvzTr+
|
||||
Z1dHTCEDhUDCR127WEcPQMFcF4XGGTfn1XzthkubgdnXGhOlCgP4mMTG6J7/EFmP
|
||||
LCaY9eYmJbsPAvpWo4H+MIH7MA4GA1UdDwEB/wQEAwIBhjAdBgNVHSUEFjAUBggr
|
||||
BgEFBQcDAQYIKwYBBQUHAwIwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU
|
||||
kHeSNWfE/6jMqeZ72YB5e8yT+TgwHwYDVR0jBBgwFoAUgEzW63T/STaj1dj8tT7F
|
||||
avCUHYwwNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzAChhhodHRwOi8vaS5wa2ku
|
||||
Z29vZy9yNC5jcnQwKwYDVR0fBCQwIjAgoB6gHIYaaHR0cDovL2MucGtpLmdvb2cv
|
||||
ci9yNC5jcmwwEwYDVR0gBAwwCjAIBgZngQwBAgEwCgYIKoZIzj0EAwMDaAAwZQIx
|
||||
AOcCq1HW90OVznX+0RGU1cxAQXomvtgM8zItPZCuFQ8jSBJSjz5keROv9aYsAm5V
|
||||
sQIwJonMaAFi54mrfhfoFNZEfuNMSQ6/bIBiNLiyoX46FohQvKeIoJ99cx7sUkFN
|
||||
7uJW
|
||||
-----END CERTIFICATE-----
|
||||
)PEM";
|
||||
|
||||
} // namespace mqtt_ca_certs
|
||||
@@ -0,0 +1,82 @@
|
||||
#include "MQTTPrefs.h"
|
||||
|
||||
#ifdef WITH_MQTT_UPLINK
|
||||
|
||||
#include <helpers/TxtDataHelpers.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace {
|
||||
constexpr uint32_t kFixedStatusIntervalMs = 300000;
|
||||
}
|
||||
|
||||
void MQTTPrefsStore::setDefaults(MQTTPrefs& prefs) {
|
||||
memset(&prefs, 0, sizeof(prefs));
|
||||
prefs.magic = kMagic;
|
||||
prefs.enabled_mask = 0x01;
|
||||
prefs.packets_enabled = 1;
|
||||
prefs.raw_enabled = 0;
|
||||
prefs.status_enabled = 1;
|
||||
prefs.tx_enabled = 0;
|
||||
prefs.web_enabled = 1;
|
||||
prefs.wifi_powersave = 0;
|
||||
prefs.status_interval_ms = kFixedStatusIntervalMs;
|
||||
StrHelper::strncpy(prefs.iata, MQTT_DEFAULT_IATA, sizeof(prefs.iata));
|
||||
#ifdef WIFI_SSID
|
||||
StrHelper::strncpy(prefs.wifi_ssid, WIFI_SSID, sizeof(prefs.wifi_ssid));
|
||||
#endif
|
||||
#ifdef WIFI_PWD
|
||||
StrHelper::strncpy(prefs.wifi_pwd, WIFI_PWD, sizeof(prefs.wifi_pwd));
|
||||
#endif
|
||||
}
|
||||
|
||||
bool MQTTPrefsStore::load(FILESYSTEM* fs, MQTTPrefs& prefs) {
|
||||
setDefaults(prefs);
|
||||
if (fs == nullptr || !fs->exists(kFilename)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(RP2040_PLATFORM)
|
||||
File file = fs->open(kFilename, "r");
|
||||
#else
|
||||
File file = fs->open(kFilename);
|
||||
#endif
|
||||
if (!file) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t bytes_to_read = min(static_cast<size_t>(file.size()), sizeof(prefs));
|
||||
bool ok = bytes_to_read >= sizeof(prefs.magic) &&
|
||||
file.read(reinterpret_cast<uint8_t*>(&prefs), bytes_to_read) == bytes_to_read;
|
||||
file.close();
|
||||
|
||||
if (!ok || prefs.magic != kMagic) {
|
||||
return false;
|
||||
}
|
||||
if (prefs.wifi_powersave > 2) {
|
||||
prefs.wifi_powersave = 0;
|
||||
}
|
||||
prefs.web_enabled = prefs.web_enabled ? 1 : 0;
|
||||
prefs.status_interval_ms = kFixedStatusIntervalMs;
|
||||
prefs.enabled_mask &= 0x07;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MQTTPrefsStore::save(FILESYSTEM* fs, const MQTTPrefs& prefs) {
|
||||
if (fs == nullptr) {
|
||||
return false;
|
||||
}
|
||||
fs->remove(kFilename);
|
||||
#if defined(RP2040_PLATFORM)
|
||||
File file = fs->open(kFilename, "w");
|
||||
#else
|
||||
File file = fs->open(kFilename, "w", true);
|
||||
#endif
|
||||
if (!file) {
|
||||
return false;
|
||||
}
|
||||
bool ok = file.write(reinterpret_cast<const uint8_t*>(&prefs), sizeof(prefs)) == sizeof(prefs);
|
||||
file.close();
|
||||
return ok;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef WITH_MQTT_UPLINK
|
||||
|
||||
#include <helpers/IdentityStore.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifndef MQTT_DEFAULT_IATA
|
||||
#define MQTT_DEFAULT_IATA "MEL"
|
||||
#endif
|
||||
|
||||
struct MQTTPrefs {
|
||||
uint32_t magic;
|
||||
uint8_t enabled_mask;
|
||||
uint8_t packets_enabled;
|
||||
uint8_t raw_enabled;
|
||||
uint8_t status_enabled;
|
||||
uint8_t tx_enabled;
|
||||
uint8_t web_enabled;
|
||||
uint8_t wifi_powersave;
|
||||
uint32_t status_interval_ms;
|
||||
char iata[8];
|
||||
char wifi_ssid[33];
|
||||
char wifi_pwd[65];
|
||||
char owner_public_key[65];
|
||||
char owner_email[96];
|
||||
};
|
||||
|
||||
class MQTTPrefsStore {
|
||||
public:
|
||||
static void setDefaults(MQTTPrefs& prefs);
|
||||
static bool load(FILESYSTEM* fs, MQTTPrefs& prefs);
|
||||
static bool save(FILESYSTEM* fs, const MQTTPrefs& prefs);
|
||||
|
||||
private:
|
||||
static constexpr uint32_t kMagic = 0x4D515454;
|
||||
static constexpr const char* kFilename = "/mqtt_prefs";
|
||||
};
|
||||
|
||||
#endif
|
||||
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
@@ -0,0 +1,176 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef WITH_MQTT_UPLINK
|
||||
|
||||
#include <Mesh.h>
|
||||
#include <helpers/CommonCLI.h>
|
||||
#include <target.h>
|
||||
|
||||
#include "JWTHelper.h"
|
||||
#include "MQTTPrefs.h"
|
||||
|
||||
#if defined(ESP_PLATFORM)
|
||||
#include <WiFi.h>
|
||||
#include <esp_https_server.h>
|
||||
#include <mqtt_client.h>
|
||||
#endif
|
||||
|
||||
class MQTTWebCommandRunner {
|
||||
public:
|
||||
virtual ~MQTTWebCommandRunner() = default;
|
||||
virtual void runWebCommand(const char* command, char* reply, size_t reply_size) = 0;
|
||||
virtual const char* getWebAdminPassword() const = 0;
|
||||
};
|
||||
|
||||
struct MQTTStatusSnapshot {
|
||||
int battery_mv;
|
||||
uint32_t uptime_secs;
|
||||
uint16_t error_flags;
|
||||
uint16_t queue_len;
|
||||
int noise_floor;
|
||||
uint32_t tx_air_secs;
|
||||
uint32_t rx_air_secs;
|
||||
uint32_t recv_errors;
|
||||
float radio_freq;
|
||||
float radio_bw;
|
||||
uint8_t radio_sf;
|
||||
uint8_t radio_cr;
|
||||
};
|
||||
|
||||
class MQTTUplink {
|
||||
public:
|
||||
explicit MQTTUplink(mesh::RTCClock& rtc, mesh::LocalIdentity& identity);
|
||||
|
||||
void begin(FILESYSTEM* fs);
|
||||
void end();
|
||||
void loop(const MQTTStatusSnapshot& snapshot);
|
||||
void publishPacket(const mesh::Packet& packet, bool is_tx, int rssi, float snr);
|
||||
|
||||
bool isRunning() const { return _running; }
|
||||
bool isActive() const;
|
||||
|
||||
void formatStatusReply(char* reply, size_t reply_size) const;
|
||||
void formatWebStatusReply(char* reply, size_t reply_size) const;
|
||||
|
||||
bool setEndpointEnabled(uint8_t bit, bool enabled);
|
||||
bool isEndpointEnabled(uint8_t bit) const;
|
||||
bool setPacketsEnabled(bool enabled);
|
||||
bool isPacketsEnabled() const { return _prefs.packets_enabled != 0; }
|
||||
bool setRawEnabled(bool enabled);
|
||||
bool isRawEnabled() const { return _prefs.raw_enabled != 0; }
|
||||
bool setStatusEnabled(bool enabled);
|
||||
bool isStatusEnabled() const { return _prefs.status_enabled != 0; }
|
||||
bool setTxEnabled(bool enabled);
|
||||
bool isTxEnabled() const { return _prefs.tx_enabled != 0; }
|
||||
bool setWebEnabled(bool enabled);
|
||||
bool isWebEnabled() const { return _prefs.web_enabled != 0; }
|
||||
bool setIata(const char* iata);
|
||||
const char* getIata() const { return _prefs.iata; }
|
||||
void setNodeNameSource(const char* node_name) { _node_name = node_name; }
|
||||
void setWebCommandRunner(MQTTWebCommandRunner* runner) { _web_runner = runner; }
|
||||
bool setWifiSSID(const char* ssid);
|
||||
bool setWifiPassword(const char* pwd);
|
||||
const char* getWifiSSID() const { return _prefs.wifi_ssid; }
|
||||
bool setWifiPowerSave(const char* mode);
|
||||
const char* getWifiPowerSave() const;
|
||||
bool setOwnerPublicKey(const char* owner_public_key);
|
||||
const char* getOwnerPublicKey() const { return _prefs.owner_public_key; }
|
||||
bool setOwnerEmail(const char* owner_email);
|
||||
const char* getOwnerEmail() const { return _prefs.owner_email; }
|
||||
void formatWifiStatusReply(char* reply, size_t reply_size) const;
|
||||
void reconnectWifi();
|
||||
|
||||
private:
|
||||
#if defined(ESP_PLATFORM)
|
||||
struct BrokerSpec {
|
||||
const char* key;
|
||||
const char* label;
|
||||
const char* host;
|
||||
const char* uri;
|
||||
uint8_t bit;
|
||||
};
|
||||
|
||||
struct BrokerState {
|
||||
const BrokerSpec* spec;
|
||||
esp_mqtt_client_handle_t client;
|
||||
bool connected;
|
||||
unsigned long last_connect_attempt;
|
||||
time_t token_expires_at;
|
||||
char username[70];
|
||||
char token[512];
|
||||
char client_id[48];
|
||||
char status_topic[128];
|
||||
char packets_topic[128];
|
||||
char raw_topic[128];
|
||||
char offline_payload[256];
|
||||
};
|
||||
|
||||
struct WebRouteContext {
|
||||
MQTTUplink* self;
|
||||
};
|
||||
#endif
|
||||
|
||||
FILESYSTEM* _fs;
|
||||
mesh::RTCClock* _rtc;
|
||||
mesh::LocalIdentity* _identity;
|
||||
MQTTPrefs _prefs;
|
||||
bool _running;
|
||||
bool _wifi_started;
|
||||
bool _sntp_started;
|
||||
bool _have_time_sync;
|
||||
unsigned long _wifi_sta_started_at;
|
||||
unsigned long _last_wifi_attempt;
|
||||
unsigned long _last_status_publish;
|
||||
MQTTStatusSnapshot _last_status;
|
||||
char _device_id[65];
|
||||
const char* _node_name;
|
||||
MQTTWebCommandRunner* _web_runner;
|
||||
|
||||
#if defined(ESP_PLATFORM)
|
||||
static constexpr uint8_t kEastmeshBit = 0x01;
|
||||
static constexpr uint8_t kLetsmeshEuBit = 0x02;
|
||||
static constexpr uint8_t kLetsmeshUsBit = 0x04;
|
||||
static const BrokerSpec kBrokerSpecs[3];
|
||||
|
||||
BrokerState _brokers[3];
|
||||
httpd_handle_t _web_server;
|
||||
char _web_token[33];
|
||||
WebRouteContext _web_route_context;
|
||||
|
||||
static void handleMqttEvent(void* handler_args, esp_event_base_t base, int32_t event_id, void* event_data);
|
||||
static esp_err_t handleWebIndex(httpd_req_t* req);
|
||||
static esp_err_t handleWebLogin(httpd_req_t* req);
|
||||
static esp_err_t handleWebCommand(httpd_req_t* req);
|
||||
void ensureWebServer();
|
||||
void stopWebServer();
|
||||
bool startWebServer();
|
||||
bool isWebAuthorized(httpd_req_t* req) const;
|
||||
bool readRequestBody(httpd_req_t* req, char* buffer, size_t buffer_size) const;
|
||||
void refreshWebToken();
|
||||
void ensureWifi();
|
||||
void updateTimeSync();
|
||||
bool hasEnabledBroker() const;
|
||||
void refreshIdentityStrings();
|
||||
void refreshBrokerState(BrokerState& broker);
|
||||
void ensureBroker(BrokerState& broker);
|
||||
void destroyBroker(BrokerState& broker);
|
||||
bool refreshToken(BrokerState& broker);
|
||||
void publishStatus(bool online);
|
||||
void publishOnlineStatus(BrokerState& broker);
|
||||
void queuePublish(BrokerState& broker, const char* topic, const char* payload, bool retain);
|
||||
int buildStatusJson(char* buffer, size_t buffer_size, bool online) const;
|
||||
int buildPacketJson(char* buffer, size_t buffer_size, const mesh::Packet& packet, bool is_tx, int rssi,
|
||||
float snr) const;
|
||||
int buildRawJson(char* buffer, size_t buffer_size, const mesh::Packet& packet, bool is_tx, int rssi, float snr) const;
|
||||
static wifi_ps_type_t toEspPowerSave(uint8_t mode);
|
||||
static const char* getPowerSaveLabel(uint8_t mode);
|
||||
static void escapeJsonString(const char* input, char* output, size_t output_size);
|
||||
static void makeSafeToken(const char* input, char* output, size_t output_size);
|
||||
static void bytesToHexUpper(const uint8_t* src, size_t len, char* dst, size_t dst_size);
|
||||
static void formatIsoTimestamp(time_t ts, char* dst, size_t dst_size);
|
||||
#endif
|
||||
|
||||
bool savePrefs();
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
namespace mqtt_web_panel_cert {
|
||||
|
||||
static const char kServerCertPem[] =
|
||||
"-----BEGIN CERTIFICATE-----\n"
|
||||
"MIIBiDCCAS2gAwIBAgIUYPagAbDjmNMHx+sqAC5qvOnlywIwCgYIKoZIzj0EAwIw\n"
|
||||
"GTEXMBUGA1UEAwwObWVzaGNvcmUtbG9jYWwwHhcNMjYwNDA5MDE0MjAzWhcNMzYw\n"
|
||||
"NDA2MDE0MjAzWjAZMRcwFQYDVQQDDA5tZXNoY29yZS1sb2NhbDBZMBMGByqGSM49\n"
|
||||
"AgEGCCqGSM49AwEHA0IABJWLgQ/4sDJKykqR9a5brDLR9RikwoZ/6n1Od38rbfzV\n"
|
||||
"ykP2LJIdrxU6zS1gzLk33klycB7AWFL1W7klStdiSpmjUzBRMB0GA1UdDgQWBBSk\n"
|
||||
"dpP2r+UPriHT+hXLiyFdjkFgmTAfBgNVHSMEGDAWgBSkdpP2r+UPriHT+hXLiyFd\n"
|
||||
"jkFgmTAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0kAMEYCIQCWS5ZRbON4\n"
|
||||
"71RrwcogEzGuQTNdkbtkRc+517nZ63ZOawIhAPwSy/0yMyhqAoOMpVnhIb+w995f\n"
|
||||
"zH4nApL2Q2U0KI2l\n"
|
||||
"-----END CERTIFICATE-----\n";
|
||||
|
||||
static const char kServerKeyPem[] =
|
||||
"-----BEGIN EC PRIVATE KEY-----\n"
|
||||
"MHcCAQEEIGX71nLSiA8qUJXuiZRNzaGizNbMqgOxALSPd/HzfzWAoAoGCCqGSM49\n"
|
||||
"AwEHoUQDQgAElYuBD/iwMkrKSpH1rlusMtH1GKTChn/qfU53fytt/NXKQ/Yskh2v\n"
|
||||
"FTrNLWDMuTfeSXJwHsBYUvVbuSVK12JKmQ==\n"
|
||||
"-----END EC PRIVATE KEY-----\n";
|
||||
|
||||
}
|
||||
Ссылка в новой задаче
Block a user