mirror of
https://github.com/liquidraver/ZephCore.git
synced 2026-09-02 01:38:19 +00:00
patch file CLRF fixup and repeater-observer hybrid first implementation
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
patches/zephyr/*.patch text eol=lf
|
||||
patches/modules/loramac-node/*.patch text eol=crlf
|
||||
+58
-4
@@ -42,18 +42,57 @@ function(zephcore_apply_patches PATCH_DIR TARGET_DIR LABEL)
|
||||
endif()
|
||||
foreach(PATCH_FILE ${PATCH_FILES})
|
||||
get_filename_component(PATCH_NAME ${PATCH_FILE} NAME)
|
||||
set(PATCH_FILE_TO_APPLY "${PATCH_FILE}")
|
||||
# Prepare LF/CRLF patch variants for robust matching across clones.
|
||||
file(READ "${PATCH_FILE}" _patch_content)
|
||||
string(REPLACE "\r\n" "\n" _patch_lf "${_patch_content}")
|
||||
set(_patch_crlf "${_patch_lf}")
|
||||
string(REPLACE "\n" "\r\n" _patch_crlf "${_patch_crlf}")
|
||||
set(_tmp_patch_dir "${CMAKE_BINARY_DIR}/zephcore_patch_tmp/${LABEL}")
|
||||
file(MAKE_DIRECTORY "${_tmp_patch_dir}")
|
||||
set(_tmp_patch_lf "${_tmp_patch_dir}/${PATCH_NAME}.lf")
|
||||
set(_tmp_patch_crlf "${_tmp_patch_dir}/${PATCH_NAME}.crlf")
|
||||
file(WRITE "${_tmp_patch_lf}" "${_patch_lf}")
|
||||
file(WRITE "${_tmp_patch_crlf}" "${_patch_crlf}")
|
||||
# Dry-run check
|
||||
execute_process(
|
||||
COMMAND git apply --check "${PATCH_FILE}"
|
||||
COMMAND git apply --check "${PATCH_FILE_TO_APPLY}"
|
||||
WORKING_DIRECTORY "${TARGET_DIR}"
|
||||
RESULT_VARIABLE PATCH_CHECK
|
||||
ERROR_VARIABLE PATCH_ERR
|
||||
)
|
||||
# Retry check with explicit LF/CRLF variants to avoid EOL drift issues.
|
||||
if(NOT PATCH_CHECK EQUAL 0)
|
||||
execute_process(
|
||||
COMMAND git apply --check "${_tmp_patch_lf}"
|
||||
WORKING_DIRECTORY "${TARGET_DIR}"
|
||||
RESULT_VARIABLE PATCH_CHECK_LF
|
||||
ERROR_VARIABLE PATCH_ERR_LF
|
||||
)
|
||||
if(PATCH_CHECK_LF EQUAL 0)
|
||||
set(PATCH_FILE_TO_APPLY "${_tmp_patch_lf}")
|
||||
set(PATCH_CHECK 0)
|
||||
else()
|
||||
execute_process(
|
||||
COMMAND git apply --check "${_tmp_patch_crlf}"
|
||||
WORKING_DIRECTORY "${TARGET_DIR}"
|
||||
RESULT_VARIABLE PATCH_CHECK_CRLF
|
||||
ERROR_VARIABLE PATCH_ERR_CRLF
|
||||
)
|
||||
if(PATCH_CHECK_CRLF EQUAL 0)
|
||||
set(PATCH_FILE_TO_APPLY "${_tmp_patch_crlf}")
|
||||
set(PATCH_CHECK 0)
|
||||
else()
|
||||
# Keep the most relevant error from the last attempted variant.
|
||||
set(PATCH_ERR "${PATCH_ERR_CRLF}")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
# Stale patches from previous build: reset affected files, retry
|
||||
if(NOT PATCH_CHECK EQUAL 0)
|
||||
# Extract affected file paths from numstat
|
||||
execute_process(
|
||||
COMMAND git apply --numstat "${PATCH_FILE}"
|
||||
COMMAND git apply --numstat "${PATCH_FILE_TO_APPLY}"
|
||||
WORKING_DIRECTORY "${TARGET_DIR}"
|
||||
OUTPUT_VARIABLE PATCH_NUMSTAT
|
||||
ERROR_QUIET
|
||||
@@ -74,7 +113,7 @@ function(zephcore_apply_patches PATCH_DIR TARGET_DIR LABEL)
|
||||
)
|
||||
# Retry dry-run
|
||||
execute_process(
|
||||
COMMAND git apply --check "${PATCH_FILE}"
|
||||
COMMAND git apply --check "${PATCH_FILE_TO_APPLY}"
|
||||
WORKING_DIRECTORY "${TARGET_DIR}"
|
||||
RESULT_VARIABLE PATCH_CHECK
|
||||
ERROR_VARIABLE PATCH_ERR
|
||||
@@ -94,7 +133,7 @@ function(zephcore_apply_patches PATCH_DIR TARGET_DIR LABEL)
|
||||
endif()
|
||||
# Apply
|
||||
execute_process(
|
||||
COMMAND git apply "${PATCH_FILE}"
|
||||
COMMAND git apply "${PATCH_FILE_TO_APPLY}"
|
||||
WORKING_DIRECTORY "${TARGET_DIR}"
|
||||
RESULT_VARIABLE APPLY_RESULT
|
||||
ERROR_VARIABLE APPLY_ERR
|
||||
@@ -425,12 +464,27 @@ if(CONFIG_ZEPHCORE_ROLE_REPEATER)
|
||||
target_include_directories(app PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/adapters/usb
|
||||
)
|
||||
if(CONFIG_ZEPHCORE_REPEATER_UPLINK AND CONFIG_MQTT_LIB)
|
||||
target_sources(app PRIVATE
|
||||
app/observer_creds.cpp
|
||||
adapters/wifi/ZephyrWiFiStation.c
|
||||
adapters/mqtt/ZephyrMQTTPublisher.c
|
||||
)
|
||||
target_include_directories(app PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/adapters/wifi
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/adapters/mqtt
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/app
|
||||
)
|
||||
elseif(CONFIG_ZEPHCORE_REPEATER_UPLINK)
|
||||
message(WARNING "ZephCore repeater uplink requested, but MQTT is disabled (likely prod.conf). Build will exclude uplink runtime.")
|
||||
endif()
|
||||
target_compile_definitions(app PRIVATE ZEPHCORE_REPEATER=1)
|
||||
elseif(CONFIG_ZEPHCORE_ROLE_OBSERVER)
|
||||
message(STATUS "ZephCore Role: OBSERVER")
|
||||
target_sources(app PRIVATE
|
||||
app/main_observer.cpp
|
||||
app/ObserverMesh.cpp
|
||||
app/observer_creds.cpp
|
||||
app/RepeaterDataStore.cpp
|
||||
adapters/wifi/ZephyrWiFiStation.c
|
||||
adapters/mqtt/ZephyrMQTTPublisher.c
|
||||
|
||||
@@ -84,6 +84,15 @@ endchoice
|
||||
|
||||
if ZEPHCORE_ROLE_REPEATER
|
||||
|
||||
config ZEPHCORE_REPEATER_UPLINK
|
||||
bool "Enable repeater WiFi+MQTT uplink"
|
||||
depends on SOC_FAMILY_ESPRESSIF_ESP32
|
||||
default n
|
||||
help
|
||||
Adds observer-style WiFi station and MQTT packet publishing to
|
||||
repeater builds. Configuration is stored in LittleFS and applied
|
||||
on reboot.
|
||||
|
||||
config ZEPHCORE_MAX_CLIENTS
|
||||
int "Maximum ACL clients"
|
||||
default 32
|
||||
|
||||
@@ -134,6 +134,33 @@ Regions control which flood packets the repeater forwards. The region tree is hi
|
||||
|
||||
---
|
||||
|
||||
## Repeater Uplink (ESP32 + `CONFIG_ZEPHCORE_REPEATER_UPLINK`)
|
||||
|
||||
These commands configure observer-style WiFi+MQTT packet reporting from repeater role.
|
||||
All `set uplink.*` changes are saved immediately and only applied after reboot.
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `get uplink.status` | Uplink runtime state: enabled flag, WiFi state, MQTT state, reboot-required flag |
|
||||
| `get uplink.enable` | Uplink enable flag (`on`/`off`) |
|
||||
| `get uplink.wifi.ssid` | Configured WiFi SSID |
|
||||
| `get uplink.mqtt.host` | Configured MQTT broker host |
|
||||
| `get uplink.mqtt.port` | Configured MQTT broker port |
|
||||
| `get uplink.mqtt.tls` | MQTT TLS mode (`0`/`1`) |
|
||||
| `get uplink.mqtt.user` | Configured MQTT username |
|
||||
| `get uplink.mqtt.iata` | Configured IATA/site code used in MQTT topic |
|
||||
| `set uplink.enable <on\|off>` | Enable or disable repeater uplink *(reboot required)* |
|
||||
| `set uplink.wifi.ssid <ssid>` | Set WiFi SSID *(reboot required)* |
|
||||
| `set uplink.wifi.psk <psk>` | Set WiFi password *(reboot required)* |
|
||||
| `set uplink.mqtt.host <host>` | Set MQTT host *(reboot required)* |
|
||||
| `set uplink.mqtt.port <port>` | Set MQTT port 1–65535 *(reboot required)* |
|
||||
| `set uplink.mqtt.tls <0\|1>` | Set MQTT TLS mode *(reboot required)* |
|
||||
| `set uplink.mqtt.user <user>` | Set MQTT username *(reboot required)* |
|
||||
| `set uplink.mqtt.password <pass>` | Set MQTT password *(reboot required)* |
|
||||
| `set uplink.mqtt.iata <code>` | Set MQTT site code *(reboot required)* |
|
||||
|
||||
---
|
||||
|
||||
## `get` — Read Configuration
|
||||
|
||||
| Command | Returns |
|
||||
|
||||
@@ -325,7 +325,7 @@ static void run_poll_loop(void)
|
||||
|
||||
/* ========== Publisher thread ========== */
|
||||
|
||||
#define MQTT_THREAD_STACK_SIZE 8192
|
||||
#define MQTT_THREAD_STACK_SIZE 12288
|
||||
#define MQTT_THREAD_PRIORITY 5
|
||||
|
||||
static void mqtt_thread_fn(void *p1, void *p2, void *p3);
|
||||
|
||||
@@ -20,6 +20,11 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#if IS_ENABLED(CONFIG_ZEPHCORE_REPEATER_UPLINK) && IS_ENABLED(CONFIG_MQTT_LIB)
|
||||
#include "observer_creds.h"
|
||||
#include <ZephyrWiFiStation.h>
|
||||
#include <ZephyrMQTTPublisher.h>
|
||||
#endif
|
||||
|
||||
/* Helper to get radio driver for stats — uses LoRaRadioBase (works for SX126x and LR1110) */
|
||||
static inline mesh::LoRaRadioBase& getRadioDriver(mesh::Radio* radio) {
|
||||
@@ -42,6 +47,16 @@ static void simple_sort(T* arr, int count, Comparator cmp) {
|
||||
|
||||
LOG_MODULE_REGISTER(zephcore_repeater, CONFIG_ZEPHCORE_MAIN_LOG_LEVEL);
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZEPHCORE_REPEATER_UPLINK) && IS_ENABLED(CONFIG_MQTT_LIB)
|
||||
static RepeaterMesh *s_uplink_mesh;
|
||||
static void uplink_time_sync_cb(uint32_t unix_ts)
|
||||
{
|
||||
if (s_uplink_mesh) {
|
||||
s_uplink_mesh->getRTCClock()->setCurrentTime(unix_ts);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Protocol constants */
|
||||
#define FIRMWARE_VER_LEVEL 2
|
||||
|
||||
@@ -468,6 +483,13 @@ void RepeaterMesh::logRxRaw(float snr, float rssi, const uint8_t raw[], int len)
|
||||
#endif
|
||||
(void)snr;
|
||||
(void)rssi;
|
||||
#if IS_ENABLED(CONFIG_ZEPHCORE_REPEATER_UPLINK) && IS_ENABLED(CONFIG_MQTT_LIB)
|
||||
_uplink_last_rssi = rssi;
|
||||
_uplink_last_raw_len = len <= (int)sizeof(_uplink_last_raw) ? len : (int)sizeof(_uplink_last_raw);
|
||||
if (_uplink_last_raw_len > 0) {
|
||||
memcpy(_uplink_last_raw, raw, _uplink_last_raw_len);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void RepeaterMesh::logRx(mesh::Packet* pkt, int len, float score) {
|
||||
@@ -476,6 +498,10 @@ void RepeaterMesh::logRx(mesh::Packet* pkt, int len, float score) {
|
||||
len, pkt->getPayloadType(), pkt->isRouteDirect() ? "D" : "F",
|
||||
pkt->payload_len, (int)_radio->getLastSNR(), (int)_radio->getLastRSSI());
|
||||
}
|
||||
#if IS_ENABLED(CONFIG_ZEPHCORE_REPEATER_UPLINK) && IS_ENABLED(CONFIG_MQTT_LIB)
|
||||
_uplink_last_score = score;
|
||||
publishUplinkPacket(pkt);
|
||||
#endif
|
||||
}
|
||||
|
||||
void RepeaterMesh::logTx(mesh::Packet* pkt, int len) {
|
||||
@@ -805,6 +831,17 @@ RepeaterMesh::RepeaterMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::Mil
|
||||
initNodePrefs(&_prefs);
|
||||
strcpy(_prefs.node_name, "Repeater");
|
||||
_prefs.advert_loc_policy = ADVERT_LOC_PREFS; // Repeaters always advertise prefs coordinates
|
||||
#if IS_ENABLED(CONFIG_ZEPHCORE_REPEATER_UPLINK) && IS_ENABLED(CONFIG_MQTT_LIB)
|
||||
memset(&_uplink_creds, 0, sizeof(_uplink_creds));
|
||||
observer_creds_init(&_uplink_creds);
|
||||
_uplink_reboot_required = false;
|
||||
memset(_uplink_pubkey_hex, 0, sizeof(_uplink_pubkey_hex));
|
||||
memset(_uplink_packets_topic, 0, sizeof(_uplink_packets_topic));
|
||||
memset(_uplink_status_topic, 0, sizeof(_uplink_status_topic));
|
||||
_uplink_last_score = 0.0f;
|
||||
_uplink_last_rssi = 0.0f;
|
||||
_uplink_last_raw_len = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
void RepeaterMesh::begin(RepeaterDataStore* store) {
|
||||
@@ -837,6 +874,27 @@ void RepeaterMesh::begin(RepeaterDataStore* store) {
|
||||
|
||||
LOG_INF("RepeaterMesh started: %s (freq=%.2f bw=%.0f sf=%d cr=%d)",
|
||||
_prefs.node_name, (double)_prefs.freq, (double)_prefs.bw, _prefs.sf, _prefs.cr);
|
||||
#if IS_ENABLED(CONFIG_ZEPHCORE_REPEATER_UPLINK) && IS_ENABLED(CONFIG_MQTT_LIB)
|
||||
observer_creds_load(&_uplink_creds, _store->getBasePath());
|
||||
mesh::Utils::toHex(_uplink_pubkey_hex, self_id.pub_key, PUB_KEY_SIZE);
|
||||
_uplink_pubkey_hex[PUB_KEY_SIZE * 2] = '\0';
|
||||
|
||||
const char *iata = _uplink_creds.mqtt_iata[0] ? _uplink_creds.mqtt_iata : "XXX";
|
||||
snprintf(_uplink_packets_topic, sizeof(_uplink_packets_topic),
|
||||
"meshcore/%s/%s/packets", iata, _uplink_pubkey_hex);
|
||||
snprintf(_uplink_status_topic, sizeof(_uplink_status_topic),
|
||||
"meshcore/%s/%s/status", iata, _uplink_pubkey_hex);
|
||||
|
||||
if (isUplinkEnabled() && _uplink_creds.wifi_ssid[0] && _uplink_creds.mqtt_host[0]) {
|
||||
s_uplink_mesh = this;
|
||||
zc_wifi_station_start(&_uplink_creds, uplink_time_sync_cb);
|
||||
mqtt_publisher_start(&_uplink_creds, _prefs.node_name,
|
||||
_uplink_status_topic, _uplink_packets_topic);
|
||||
LOG_INF("Repeater uplink active: %s", _uplink_packets_topic);
|
||||
} else {
|
||||
LOG_INF("Repeater uplink inactive");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
double RepeaterMesh::getNodeLat() const {
|
||||
@@ -1037,6 +1095,12 @@ void RepeaterMesh::handleCommand(uint32_t sender_timestamp, char* command, char*
|
||||
command += 3;
|
||||
}
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZEPHCORE_REPEATER_UPLINK) && IS_ENABLED(CONFIG_MQTT_LIB)
|
||||
if (handleUplinkCommand(command, reply)) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
// ACL commands - supports BOTH formats for app compatibility:
|
||||
// Old Arduino: setperm {pubkey-hex} {permissions} (pubkey is long, perms is short)
|
||||
// MeshCore App: setperm {permissions} {pubkey-hex} (perms is short 2-char hex, pubkey is long)
|
||||
@@ -1202,6 +1266,172 @@ void RepeaterMesh::handleCommand(uint32_t sender_timestamp, char* command, char*
|
||||
}
|
||||
}
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZEPHCORE_REPEATER_UPLINK) && IS_ENABLED(CONFIG_MQTT_LIB)
|
||||
bool RepeaterMesh::saveUplinkCreds()
|
||||
{
|
||||
if (!_store) return false;
|
||||
return observer_creds_save(&_uplink_creds, _store->getBasePath());
|
||||
}
|
||||
|
||||
bool RepeaterMesh::handleUplinkCommand(const char *command, char *reply)
|
||||
{
|
||||
if (memcmp(command, "get uplink.", 11) == 0) {
|
||||
const char *key = command + 11;
|
||||
if (strcmp(key, "enable") == 0) {
|
||||
snprintf(reply, CLI_REPLY_SIZE, "> %s", isUplinkEnabled() ? "on" : "off");
|
||||
} else if (strcmp(key, "wifi.ssid") == 0) {
|
||||
snprintf(reply, CLI_REPLY_SIZE, "> %s", _uplink_creds.wifi_ssid[0] ? _uplink_creds.wifi_ssid : "(not set)");
|
||||
} else if (strcmp(key, "mqtt.host") == 0) {
|
||||
snprintf(reply, CLI_REPLY_SIZE, "> %s", _uplink_creds.mqtt_host[0] ? _uplink_creds.mqtt_host : "(not set)");
|
||||
} else if (strcmp(key, "mqtt.port") == 0) {
|
||||
snprintf(reply, CLI_REPLY_SIZE, "> %u", (unsigned)_uplink_creds.mqtt_port);
|
||||
} else if (strcmp(key, "mqtt.tls") == 0) {
|
||||
snprintf(reply, CLI_REPLY_SIZE, "> %u", (unsigned)_uplink_creds.mqtt_tls);
|
||||
} else if (strcmp(key, "mqtt.user") == 0) {
|
||||
snprintf(reply, CLI_REPLY_SIZE, "> %s", _uplink_creds.mqtt_user[0] ? _uplink_creds.mqtt_user : "(not set)");
|
||||
} else if (strcmp(key, "mqtt.iata") == 0) {
|
||||
snprintf(reply, CLI_REPLY_SIZE, "> %s", _uplink_creds.mqtt_iata[0] ? _uplink_creds.mqtt_iata : "(not set)");
|
||||
} else if (strcmp(key, "status") == 0) {
|
||||
snprintf(reply, CLI_REPLY_SIZE, "> enabled=%s wifi=%s mqtt=%s reboot_required=%s",
|
||||
isUplinkEnabled() ? "yes" : "no",
|
||||
zc_wifi_station_is_connected() ? "up" : "down",
|
||||
mqtt_publisher_is_connected() ? "up" : "down",
|
||||
_uplink_reboot_required ? "yes" : "no");
|
||||
} else {
|
||||
snprintf(reply, CLI_REPLY_SIZE, "unknown config: uplink.%s", key);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (memcmp(command, "set uplink.", 11) == 0) {
|
||||
const char *cfg = command + 11;
|
||||
const char *val = strchr(cfg, ' ');
|
||||
if (!val) {
|
||||
strcpy(reply, "Error: value required");
|
||||
return true;
|
||||
}
|
||||
int key_len = (int)(val - cfg);
|
||||
val++;
|
||||
|
||||
if (key_len == 6 && memcmp(cfg, "enable", 6) == 0) {
|
||||
if (strcmp(val, "on") == 0) {
|
||||
setUplinkEnabled(true);
|
||||
} else if (strcmp(val, "off") == 0) {
|
||||
setUplinkEnabled(false);
|
||||
} else {
|
||||
strcpy(reply, "Error: must be on or off");
|
||||
return true;
|
||||
}
|
||||
} else if (key_len == 9 && memcmp(cfg, "wifi.ssid", 9) == 0) {
|
||||
StrHelper::strncpy(_uplink_creds.wifi_ssid, val, sizeof(_uplink_creds.wifi_ssid));
|
||||
} else if (key_len == 8 && memcmp(cfg, "wifi.psk", 8) == 0) {
|
||||
StrHelper::strncpy(_uplink_creds.wifi_psk, val, sizeof(_uplink_creds.wifi_psk));
|
||||
} else if (key_len == 9 && memcmp(cfg, "mqtt.host", 9) == 0) {
|
||||
StrHelper::strncpy(_uplink_creds.mqtt_host, val, sizeof(_uplink_creds.mqtt_host));
|
||||
} else if (key_len == 9 && memcmp(cfg, "mqtt.port", 9) == 0) {
|
||||
int port = atoi(val);
|
||||
if (port < 1 || port > 65535) {
|
||||
strcpy(reply, "Error: port range 1-65535");
|
||||
return true;
|
||||
}
|
||||
_uplink_creds.mqtt_port = (uint16_t)port;
|
||||
} else if (key_len == 8 && memcmp(cfg, "mqtt.tls", 8) == 0) {
|
||||
_uplink_creds.mqtt_tls = (atoi(val) != 0) ? 1 : 0;
|
||||
} else if (key_len == 9 && memcmp(cfg, "mqtt.user", 9) == 0) {
|
||||
StrHelper::strncpy(_uplink_creds.mqtt_user, val, sizeof(_uplink_creds.mqtt_user));
|
||||
} else if (key_len == 13 && memcmp(cfg, "mqtt.password", 13) == 0) {
|
||||
StrHelper::strncpy(_uplink_creds.mqtt_password, val, sizeof(_uplink_creds.mqtt_password));
|
||||
} else if (key_len == 9 && memcmp(cfg, "mqtt.iata", 9) == 0) {
|
||||
StrHelper::strncpy(_uplink_creds.mqtt_iata, val, sizeof(_uplink_creds.mqtt_iata));
|
||||
} else {
|
||||
snprintf(reply, CLI_REPLY_SIZE, "unknown config: uplink.%.*s", key_len, cfg);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!saveUplinkCreds()) {
|
||||
strcpy(reply, "Error: save failed");
|
||||
return true;
|
||||
}
|
||||
markUplinkRebootRequired();
|
||||
strcpy(reply, "OK - reboot to apply");
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void RepeaterMesh::publishUplinkPacket(mesh::Packet *pkt)
|
||||
{
|
||||
if (!isUplinkEnabled() || !mqtt_publisher_is_connected()) return;
|
||||
if (_uplink_packets_topic[0] == '\0') return;
|
||||
|
||||
char raw_hex[MAX_TRANS_UNIT * 2 + 1];
|
||||
mesh::Utils::toHex(raw_hex, _uplink_last_raw, _uplink_last_raw_len);
|
||||
raw_hex[_uplink_last_raw_len * 2] = '\0';
|
||||
|
||||
uint8_t hash_bytes[MAX_HASH_SIZE];
|
||||
char hash_hex[MAX_HASH_SIZE * 2 + 1];
|
||||
pkt->calculatePacketHash(hash_bytes);
|
||||
mesh::Utils::toHex(hash_hex, hash_bytes, MAX_HASH_SIZE);
|
||||
hash_hex[MAX_HASH_SIZE * 2] = '\0';
|
||||
|
||||
uint32_t now_epoch = getRTCClock()->getCurrentTime();
|
||||
struct tm tm_now;
|
||||
time_t t = (time_t)now_epoch;
|
||||
gmtime_r(&t, &tm_now);
|
||||
|
||||
char ts_buf[48];
|
||||
char time_buf[12], date_buf[32];
|
||||
snprintf(ts_buf, sizeof(ts_buf), "%04d-%02d-%02dT%02d:%02d:%02d.000000",
|
||||
tm_now.tm_year + 1900, tm_now.tm_mon + 1, tm_now.tm_mday,
|
||||
tm_now.tm_hour, tm_now.tm_min, tm_now.tm_sec);
|
||||
snprintf(time_buf, sizeof(time_buf), "%02d:%02d:%02d",
|
||||
tm_now.tm_hour, tm_now.tm_min, tm_now.tm_sec);
|
||||
snprintf(date_buf, sizeof(date_buf), "%d/%d/%04d",
|
||||
tm_now.tm_mday, tm_now.tm_mon + 1, tm_now.tm_year + 1900);
|
||||
|
||||
static char json_buf[1024];
|
||||
int json_len = snprintf(json_buf, sizeof(json_buf),
|
||||
"{"
|
||||
"\"type\":\"PACKET\","
|
||||
"\"origin\":\"%s\","
|
||||
"\"origin_id\":\"%s\","
|
||||
"\"timestamp\":\"%s\","
|
||||
"\"direction\":\"rx\","
|
||||
"\"time\":\"%s\","
|
||||
"\"date\":\"%s\","
|
||||
"\"len\":\"%d\","
|
||||
"\"packet_type\":\"%u\","
|
||||
"\"route\":\"%s\","
|
||||
"\"payload_len\":\"%u\","
|
||||
"\"raw\":\"%s\","
|
||||
"\"SNR\":\"%d\","
|
||||
"\"RSSI\":\"%d\","
|
||||
"\"score\":\"%d\","
|
||||
"\"hash\":\"%s\""
|
||||
"}",
|
||||
_prefs.node_name,
|
||||
_uplink_pubkey_hex,
|
||||
ts_buf,
|
||||
time_buf,
|
||||
date_buf,
|
||||
_uplink_last_raw_len,
|
||||
(unsigned)pkt->getPayloadType(),
|
||||
pkt->isRouteDirect() ? "D" : "F",
|
||||
(unsigned)pkt->payload_len,
|
||||
raw_hex,
|
||||
(int)pkt->getSNR(),
|
||||
(int)_uplink_last_rssi,
|
||||
(int)(_uplink_last_score * 1000.0f),
|
||||
hash_hex);
|
||||
|
||||
if (json_len <= 0 || json_len >= (int)sizeof(json_buf)) {
|
||||
return;
|
||||
}
|
||||
mqtt_publisher_enqueue(_uplink_packets_topic, json_buf, json_len);
|
||||
}
|
||||
#endif
|
||||
|
||||
void RepeaterMesh::loop() {
|
||||
mesh::Mesh::loop();
|
||||
|
||||
|
||||
@@ -23,6 +23,9 @@
|
||||
#include <helpers/StatsFormatHelper.h>
|
||||
#include <helpers/NodePrefs.h>
|
||||
#include "RepeaterDataStore.h"
|
||||
#if IS_ENABLED(CONFIG_ZEPHCORE_REPEATER_UPLINK)
|
||||
#include "observer_creds.h"
|
||||
#endif
|
||||
|
||||
#ifndef FIRMWARE_VERSION
|
||||
#define FIRMWARE_VERSION "v1.14.1-zephyr"
|
||||
@@ -105,6 +108,17 @@ class RepeaterMesh : public mesh::Mesh, public CommonCLICallbacks {
|
||||
uint8_t pending_sf;
|
||||
uint8_t pending_cr;
|
||||
int matching_peer_indexes[MAX_CLIENTS];
|
||||
#if IS_ENABLED(CONFIG_ZEPHCORE_REPEATER_UPLINK)
|
||||
ObserverCreds _uplink_creds;
|
||||
bool _uplink_reboot_required;
|
||||
char _uplink_pubkey_hex[PUB_KEY_SIZE * 2 + 1];
|
||||
char _uplink_packets_topic[160];
|
||||
char _uplink_status_topic[160];
|
||||
float _uplink_last_score;
|
||||
float _uplink_last_rssi;
|
||||
uint8_t _uplink_last_raw[MAX_TRANS_UNIT];
|
||||
int _uplink_last_raw_len;
|
||||
#endif
|
||||
|
||||
void putNeighbour(const mesh::Identity& id, uint32_t timestamp, float snr);
|
||||
void sendNodeDiscoverReq();
|
||||
@@ -150,6 +164,20 @@ protected:
|
||||
void onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) override;
|
||||
bool onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override;
|
||||
void onControlDataRecv(mesh::Packet* packet) override;
|
||||
#if IS_ENABLED(CONFIG_ZEPHCORE_REPEATER_UPLINK)
|
||||
bool handleUplinkCommand(const char *command, char *reply);
|
||||
void markUplinkRebootRequired() { _uplink_reboot_required = true; }
|
||||
bool isUplinkEnabled() const { return (_uplink_creds._reserved[0] & 0x01) != 0; }
|
||||
void setUplinkEnabled(bool en) {
|
||||
if (en) {
|
||||
_uplink_creds._reserved[0] |= 0x01;
|
||||
} else {
|
||||
_uplink_creds._reserved[0] &= (uint8_t)~0x01;
|
||||
}
|
||||
}
|
||||
bool saveUplinkCreds();
|
||||
void publishUplinkPacket(mesh::Packet *pkt);
|
||||
#endif
|
||||
|
||||
public:
|
||||
RepeaterMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms,
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/drivers/uart.h>
|
||||
#include <zephyr/sys/ring_buffer.h>
|
||||
#include <zephyr/fs/fs.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
|
||||
#define CLI_REPLY_SIZE 256
|
||||
@@ -32,64 +31,6 @@ LOG_MODULE_REGISTER(zephcore_observer_main, CONFIG_ZEPHCORE_MAIN_LOG_LEVEL);
|
||||
#include <ZephyrMQTTPublisher.h>
|
||||
#include "observer_creds.h"
|
||||
|
||||
/* ========== observer_creds_load / observer_creds_save ========== */
|
||||
|
||||
extern "C" bool observer_creds_load(struct ObserverCreds *creds,
|
||||
const char *base_path)
|
||||
{
|
||||
char path[96];
|
||||
snprintf(path, sizeof(path), "%s/obs_creds", base_path);
|
||||
|
||||
struct fs_file_t f;
|
||||
fs_file_t_init(&f);
|
||||
|
||||
int rc = fs_open(&f, path, FS_O_READ);
|
||||
if (rc < 0) {
|
||||
LOG_DBG("obs_creds not found (%d) — using defaults", rc);
|
||||
return false;
|
||||
}
|
||||
|
||||
ssize_t n = fs_read(&f, creds, sizeof(*creds));
|
||||
fs_close(&f);
|
||||
|
||||
if (n != (ssize_t)sizeof(*creds)) {
|
||||
LOG_WRN("obs_creds truncated (%d/%d) — resetting", (int)n,
|
||||
(int)sizeof(*creds));
|
||||
memset(creds, 0, sizeof(*creds));
|
||||
observer_creds_init(creds);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
extern "C" bool observer_creds_save(const struct ObserverCreds *creds,
|
||||
const char *base_path)
|
||||
{
|
||||
char path[96];
|
||||
snprintf(path, sizeof(path), "%s/obs_creds", base_path);
|
||||
|
||||
struct fs_file_t f;
|
||||
fs_file_t_init(&f);
|
||||
|
||||
int rc = fs_open(&f, path, FS_O_WRITE | FS_O_CREATE | FS_O_TRUNC);
|
||||
if (rc < 0) {
|
||||
LOG_ERR("Cannot open %s for write: %d", path, rc);
|
||||
return false;
|
||||
}
|
||||
|
||||
ssize_t n = fs_write(&f, creds, sizeof(*creds));
|
||||
fs_close(&f);
|
||||
|
||||
if (n != (ssize_t)sizeof(*creds)) {
|
||||
LOG_ERR("obs_creds write failed (%d/%d)", (int)n,
|
||||
(int)sizeof(*creds));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* ========== LED (optional) ========== */
|
||||
|
||||
#if DT_NODE_HAS_PROP(DT_ALIAS(led0), gpios)
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "observer_creds.h"
|
||||
|
||||
#include <zephyr/fs/fs.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
LOG_MODULE_REGISTER(zephcore_observer_creds, CONFIG_ZEPHCORE_DATASTORE_LOG_LEVEL);
|
||||
|
||||
extern "C" bool observer_creds_load(struct ObserverCreds *creds,
|
||||
const char *base_path)
|
||||
{
|
||||
char path[96];
|
||||
snprintf(path, sizeof(path), "%s/obs_creds", base_path);
|
||||
|
||||
struct fs_file_t f;
|
||||
fs_file_t_init(&f);
|
||||
|
||||
int rc = fs_open(&f, path, FS_O_READ);
|
||||
if (rc < 0) {
|
||||
LOG_DBG("obs_creds not found (%d), using defaults", rc);
|
||||
memset(creds, 0, sizeof(*creds));
|
||||
observer_creds_init(creds);
|
||||
return false;
|
||||
}
|
||||
|
||||
ssize_t n = fs_read(&f, creds, sizeof(*creds));
|
||||
fs_close(&f);
|
||||
|
||||
if (n != (ssize_t)sizeof(*creds)) {
|
||||
LOG_WRN("obs_creds truncated (%d/%d), resetting", (int)n,
|
||||
(int)sizeof(*creds));
|
||||
memset(creds, 0, sizeof(*creds));
|
||||
observer_creds_init(creds);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
extern "C" bool observer_creds_save(const struct ObserverCreds *creds,
|
||||
const char *base_path)
|
||||
{
|
||||
char path[96];
|
||||
snprintf(path, sizeof(path), "%s/obs_creds", base_path);
|
||||
|
||||
struct fs_file_t f;
|
||||
fs_file_t_init(&f);
|
||||
|
||||
int rc = fs_open(&f, path, FS_O_WRITE | FS_O_CREATE | FS_O_TRUNC);
|
||||
if (rc < 0) {
|
||||
LOG_ERR("Cannot open %s for write: %d", path, rc);
|
||||
return false;
|
||||
}
|
||||
|
||||
ssize_t n = fs_write(&f, creds, sizeof(*creds));
|
||||
fs_close(&f);
|
||||
|
||||
if (n != (ssize_t)sizeof(*creds)) {
|
||||
LOG_ERR("obs_creds write failed (%d/%d)", (int)n,
|
||||
(int)sizeof(*creds));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -22,3 +22,8 @@ CONFIG_ENTROPY_GENERATOR=y
|
||||
# Custom USB init with 1200 baud touch detection for UF2 bootloader (nRF52 only).
|
||||
# ESP32 boards use usb_serial — this setting is ignored there.
|
||||
CONFIG_CDC_ACM_SERIAL_INITIALIZE_AT_BOOT=n
|
||||
|
||||
# ========== Optional ESP32 repeater uplink ==========
|
||||
# Observer-style WiFi+MQTT reporting from repeater role.
|
||||
# Runtime-configured and reboot-applied.
|
||||
CONFIG_ZEPHCORE_REPEATER_UPLINK=n
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
# Repeater uplink config (ESP32)
|
||||
#
|
||||
# Use with repeater + prod when WiFi+MQTT uplink is needed:
|
||||
# -DEXTRA_CONF_FILE="boards/common/repeater.conf;boards/common/prod.conf;boards/common/repeater_uplink.conf"
|
||||
#
|
||||
# This restores the minimum network/MQTT/TLS settings required by
|
||||
# CONFIG_ZEPHCORE_REPEATER_UPLINK in production builds.
|
||||
|
||||
# IPv4 + sockets
|
||||
CONFIG_NETWORKING=y
|
||||
CONFIG_NET_IPV4=y
|
||||
CONFIG_NET_IPV6=n
|
||||
CONFIG_NET_TCP=y
|
||||
CONFIG_NET_UDP=y
|
||||
CONFIG_NET_SOCKETS=y
|
||||
CONFIG_NET_SOCKETS_SOCKOPT_TLS=y
|
||||
CONFIG_NET_MAX_CONTEXTS=8
|
||||
CONFIG_NET_MGMT=y
|
||||
CONFIG_NET_MGMT_EVENT=y
|
||||
CONFIG_NET_MGMT_EVENT_STACK_SIZE=4096
|
||||
CONFIG_DNS_RESOLVER=y
|
||||
CONFIG_DNS_SERVER_IP_ADDRESSES=y
|
||||
|
||||
# WiFi STA
|
||||
CONFIG_WIFI=y
|
||||
CONFIG_NET_L2_WIFI_MGMT=y
|
||||
CONFIG_NET_L2_ETHERNET=y
|
||||
CONFIG_NET_DHCPV4=y
|
||||
|
||||
# MQTT + TLS
|
||||
CONFIG_MQTT_LIB=y
|
||||
CONFIG_MQTT_LIB_TLS=y
|
||||
CONFIG_MQTT_KEEPALIVE=60
|
||||
|
||||
# mbedTLS
|
||||
CONFIG_MBEDTLS=y
|
||||
CONFIG_MBEDTLS_BUILTIN=y
|
||||
CONFIG_MBEDTLS_ENABLE_HEAP=y
|
||||
CONFIG_MBEDTLS_HEAP_SIZE=50000
|
||||
CONFIG_MBEDTLS_SSL_MAX_CONTENT_LEN=4096
|
||||
CONFIG_MBEDTLS_SSL_SERVER_NAME_INDICATION=y
|
||||
CONFIG_MBEDTLS_CIPHERSUITE_TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256=y
|
||||
CONFIG_MBEDTLS_CIPHERSUITE_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256=y
|
||||
|
||||
# SNTP (used by ZephyrWiFiStation)
|
||||
CONFIG_SNTP=y
|
||||
|
||||
# Stack sizes for network threads
|
||||
# NOTE: Do NOT set CONFIG_MAIN_STACK_SIZE or CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE here.
|
||||
# esp32_common.conf sets both to 8192 which is required for the repeater's crypto +
|
||||
# routing operations. Overriding to a lower value here caused stack overflows (freeze).
|
||||
CONFIG_NET_RX_STACK_SIZE=2048
|
||||
Reference in New Issue
Block a user