patch file CLRF fixup and repeater-observer hybrid first implementation

This commit is contained in:
liquidraver
2026-04-09 11:02:58 +02:00
parent 530565d02f
commit 0898699c33
11 changed files with 482 additions and 64 deletions
+2
View File
@@ -0,0 +1,2 @@
patches/zephyr/*.patch text eol=lf
patches/modules/loramac-node/*.patch text eol=crlf
+58 -4
View File
@@ -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
+9
View File
@@ -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
+27
View File
@@ -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 165535 *(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 |
+1 -1
View File
@@ -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);
+230
View File
@@ -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();
+28
View File
@@ -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,
-59
View File
@@ -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)
+70
View File
@@ -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;
}
+5
View File
@@ -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