Merge USB session safety and preserve GPS preference compatibility
This commit is contained in:
commit
24fcbf3247
62 files changed
+2479
-240
No files matched your search
@@ -1002,6 +1002,7 @@ bool DataStore::loadPrefsInt(const char *filename,
|
||||
// Files written before this tail existed always started Bluetooth at boot.
|
||||
// Do not inherit a runtime off value when loading one of those images.
|
||||
loaded_prefs.bluetooth_enabled = 1;
|
||||
loaded_prefs.gps_sync_interval_hours = 0;
|
||||
// Older images have no independent debug preference. Never inherit a
|
||||
// runtime-on value when loading one of those images.
|
||||
loaded_prefs.usb_debug_enabled = 0;
|
||||
@@ -1012,7 +1013,7 @@ bool DataStore::loadPrefsInt(const char *filename,
|
||||
// actually emitted by those releases, but reject a truncated field/group
|
||||
// or an unknown tail before any value reaches the live preferences.
|
||||
static const uint32_t MIN_PREFS_SIZE = 84;
|
||||
static const uint32_t KNOWN_PREFS_SIZES[] = {
|
||||
static const uint16_t KNOWN_PREFS_SIZES[] = {
|
||||
84, 85, 90, 91, 92, 93, 140, 141, 142, 143, 144, 155,
|
||||
156, 157, 158, 159,
|
||||
159 + sizeof(loaded_prefs.bluetooth_name),
|
||||
@@ -1061,11 +1062,18 @@ bool DataStore::loadPrefsInt(const char *filename,
|
||||
232,
|
||||
#endif
|
||||
#ifdef TBEAM_1W
|
||||
240, // Independent USB debug preference, after the existing tail
|
||||
241, // GPS time-sync cadence in hours, appended after Bluetooth enable
|
||||
#elif defined(RP2040_PLATFORM) && defined(ENABLE_WIFI_INTERFACE)
|
||||
330,
|
||||
331,
|
||||
#else
|
||||
233,
|
||||
234,
|
||||
#endif
|
||||
#ifdef TBEAM_1W
|
||||
242, // Independent USB debug preference, after the published GPS tail
|
||||
#elif defined(RP2040_PLATFORM) && defined(ENABLE_WIFI_INTERFACE)
|
||||
332,
|
||||
#else
|
||||
235,
|
||||
#endif
|
||||
};
|
||||
const uint32_t prefs_size = file.size();
|
||||
@@ -1210,6 +1218,8 @@ bool DataStore::loadPrefsInt(const char *filename,
|
||||
sizeof(loaded_prefs.one_key_dm_enabled));
|
||||
readOptionalField(&loaded_prefs.bluetooth_enabled,
|
||||
sizeof(loaded_prefs.bluetooth_enabled));
|
||||
readOptionalField(&loaded_prefs.gps_sync_interval_hours,
|
||||
sizeof(loaded_prefs.gps_sync_interval_hours));
|
||||
readOptionalField(&loaded_prefs.usb_debug_enabled,
|
||||
sizeof(loaded_prefs.usb_debug_enabled));
|
||||
|
||||
@@ -1364,20 +1374,16 @@ bool DataStore::savePrefs(const CompanionNodePrefs& _prefs, double node_lat, dou
|
||||
success = success && file.write((uint8_t *)_prefs.wifi_pwd,
|
||||
sizeof(_prefs.wifi_pwd)) == sizeof(_prefs.wifi_pwd);
|
||||
#endif
|
||||
success = success && file.write((uint8_t *)&_prefs.flood_retry_attempts,
|
||||
sizeof(_prefs.flood_retry_attempts)) == sizeof(_prefs.flood_retry_attempts);
|
||||
success = success && file.write((uint8_t *)&_prefs.flood_retry_max_path,
|
||||
sizeof(_prefs.flood_retry_max_path)) == sizeof(_prefs.flood_retry_max_path);
|
||||
success = success && file.write((uint8_t *)&_prefs.flood_retry_group_max_path,
|
||||
sizeof(_prefs.flood_retry_group_max_path)) == sizeof(_prefs.flood_retry_group_max_path);
|
||||
success = success && file.write((uint8_t *)&_prefs.flood_retry_advert_enabled,
|
||||
sizeof(_prefs.flood_retry_advert_enabled)) == sizeof(_prefs.flood_retry_advert_enabled);
|
||||
success = success && file.write((uint8_t *)&_prefs.one_key_dm_enabled,
|
||||
sizeof(_prefs.one_key_dm_enabled)) == sizeof(_prefs.one_key_dm_enabled);
|
||||
success = success && file.write((uint8_t *)&_prefs.bluetooth_enabled,
|
||||
sizeof(_prefs.bluetooth_enabled)) == sizeof(_prefs.bluetooth_enabled);
|
||||
success = success && file.write((uint8_t *)&_prefs.usb_debug_enabled,
|
||||
sizeof(_prefs.usb_debug_enabled)) == sizeof(_prefs.usb_debug_enabled);
|
||||
// Pack only the explicit persisted tail, never the class's padded layout.
|
||||
// memcpy retains the GPS field's established native byte representation.
|
||||
uint8_t tail[9] = {
|
||||
_prefs.flood_retry_attempts, _prefs.flood_retry_max_path,
|
||||
_prefs.flood_retry_group_max_path, _prefs.flood_retry_advert_enabled,
|
||||
_prefs.one_key_dm_enabled, _prefs.bluetooth_enabled, 0, 0,
|
||||
_prefs.usb_debug_enabled};
|
||||
memcpy(tail + 6, &_prefs.gps_sync_interval_hours,
|
||||
sizeof(_prefs.gps_sync_interval_hours));
|
||||
success = success && file.write(tail, sizeof(tail)) == sizeof(tail);
|
||||
|
||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) || defined(ESP32_PLATFORM) || defined(RP2040_PLATFORM)
|
||||
success = file.commit(success);
|
||||
|
||||
@@ -74,6 +74,7 @@
|
||||
#endif
|
||||
|
||||
#include <helpers/CLICommandUtils.h>
|
||||
#include <helpers/GpsPowerPolicy.h>
|
||||
#if COMPANION_FEATURE_TEXT_TERMINAL
|
||||
#include <helpers/TracePathHelpers.h>
|
||||
#endif
|
||||
@@ -2308,6 +2309,7 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe
|
||||
_prefs.buzzer_quiet = DEFAULT_BUZZER_QUIET ? 1 : 0;
|
||||
_prefs.gps_enabled = 0; // GPS disabled by default
|
||||
_prefs.gps_interval = 0; // Use the default 1-second fix-processing interval
|
||||
_prefs.gps_sync_interval_hours = 0; // Retain the board's default time-sync cadence
|
||||
_prefs.autoadd_config = DEFAULT_AUTOADD_CONFIG;
|
||||
_prefs.path_hash_mode = DEFAULT_PATH_HASH_MODE;
|
||||
#ifdef RADIO_FEM_TXGAIN
|
||||
@@ -2606,6 +2608,8 @@ void MyMesh::begin(bool has_display, bool radio_available,
|
||||
_prefs.vibe_quiet = constrain(_prefs.vibe_quiet, 0, 1);
|
||||
_prefs.gps_enabled = constrain(_prefs.gps_enabled, 0, 1); // Ensure boolean 0 or 1
|
||||
_prefs.gps_interval = constrain(_prefs.gps_interval, 0, 86400); // Max 24 hours
|
||||
_prefs.gps_sync_interval_hours = constrain(_prefs.gps_sync_interval_hours,
|
||||
0, mesh::gps::MAX_SYNC_INTERVAL_HOURS);
|
||||
_prefs.autoadd_config &= AUTO_ADD_OVERWRITE_OLDEST | AUTO_ADD_CHAT | AUTO_ADD_REPEATER | AUTO_ADD_ROOM_SERVER | AUTO_ADD_SENSOR;
|
||||
_prefs.path_hash_mode = constrain(_prefs.path_hash_mode, 0, 2);
|
||||
_prefs.radio_fem_rxgain_override = constrain(_prefs.radio_fem_rxgain_override, 0, 1);
|
||||
@@ -3223,6 +3227,37 @@ bool MyMesh::handleLocalControlCommand(const char* command, char* reply,
|
||||
}
|
||||
|
||||
#if ENV_INCLUDE_GPS == 1
|
||||
if (strcmp(command, "get gps.sync.interval") == 0) {
|
||||
auto* provider = sensors.getLocationProvider();
|
||||
if (provider == nullptr) {
|
||||
snprintf(reply, reply_size, "Error: GPS unavailable");
|
||||
} else {
|
||||
const uint16_t hours = provider->getTimeSyncIntervalHours();
|
||||
if (hours == 0) snprintf(reply, reply_size, "> default (board GPS sync policy)");
|
||||
else snprintf(reply, reply_size, "> %u hours", (unsigned)hours);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
const char* gps_sync_value = nullptr;
|
||||
if (strncmp(command, "set gps.sync.interval", 21) == 0
|
||||
&& (command[21] == 0 || command[21] == ' ' || command[21] == '\t')) {
|
||||
gps_sync_value = command + 21;
|
||||
while (*gps_sync_value == ' ' || *gps_sync_value == '\t') ++gps_sync_value;
|
||||
}
|
||||
if (gps_sync_value) {
|
||||
uint16_t hours = 0;
|
||||
if (!mesh::gps::parseSyncIntervalHours(gps_sync_value, hours)) {
|
||||
snprintf(reply, reply_size, "Error: GPS sync interval must be 1..336 hours");
|
||||
} else if (sensors.getLocationProvider() == nullptr) {
|
||||
snprintf(reply, reply_size, "Error: GPS unavailable");
|
||||
} else if (!savePreference(_prefs.gps_sync_interval_hours, hours)) {
|
||||
snprintf(reply, reply_size, "Error: GPS sync interval could not be saved");
|
||||
} else {
|
||||
sensors.applyGpsTimeSyncInterval(hours);
|
||||
snprintf(reply, reply_size, "OK - GPS sync interval %u hours (saved)", (unsigned)hours);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (strcmp(command, "get gps") == 0) {
|
||||
const char* gps = sensors.getSettingByKey("gps");
|
||||
snprintf(reply, reply_size, gps ? "> %s" : "Error: GPS unavailable",
|
||||
@@ -5210,6 +5245,8 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
} else {
|
||||
text[tlen] = 0; // ensure null
|
||||
|
||||
BaseSerialInterface* reply_route = _serial->captureReplyRoute();
|
||||
beginUsbLoggingReplyBarrier(reply_route);
|
||||
reply_buf[0] = 0;
|
||||
bool handled = false;
|
||||
#if defined(MESHCORE_EXTRAFS_HIL)
|
||||
@@ -5225,7 +5262,9 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
out_frame[0] = RESP_CODE_CLI_REPLY;
|
||||
int rlen = strlen(reply_buf);
|
||||
memcpy(&out_frame[1], reply_buf, rlen);
|
||||
_serial->writeFrame(out_frame, 1 + rlen);
|
||||
const bool reply_queued = _serial->writeFrameToRoute(
|
||||
reply_route, out_frame, 1 + rlen) == static_cast<size_t>(1 + rlen);
|
||||
endUsbLoggingReplyBarrier(reply_queued);
|
||||
}
|
||||
} else if (cmd_frame[0] == CMD_SEND_TXT_MSG && len >= 14) {
|
||||
int i = 1;
|
||||
|
||||
@@ -303,6 +303,10 @@ public:
|
||||
bool handleLocalControlCommand(const char* command, char* reply,
|
||||
size_t reply_size);
|
||||
void applyUsbLoggingState(bool enabled);
|
||||
// A framed USB logging change cannot abandon its requester before the
|
||||
// acknowledgement has entered the transport. Other transports stay live.
|
||||
void beginUsbLoggingReplyBarrier(BaseSerialInterface* route);
|
||||
void endUsbLoggingReplyBarrier(bool reply_queued);
|
||||
bool handleTxRoutingCommand(const char* command, char* reply, size_t reply_size);
|
||||
|
||||
int getRecentlyHeard(AdvertPath dest[], int max_num);
|
||||
@@ -449,6 +453,7 @@ public:
|
||||
char interval_str[12]; // Max: 24 hours = 86400 seconds (5 digits + null)
|
||||
sprintf(interval_str, "%u", _prefs.gps_interval);
|
||||
sensors.setSettingValue("gps_interval", interval_str);
|
||||
sensors.applyGpsTimeSyncInterval(_prefs.gps_sync_interval_hours);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -110,8 +110,10 @@ public:
|
||||
uint8_t one_key_dm_enabled = 0;
|
||||
// Appended after all existing fields so older images keep Bluetooth enabled.
|
||||
uint8_t bluetooth_enabled = 1;
|
||||
// Published tail: zero retains the board's legacy GPS sync cadence.
|
||||
uint16_t gps_sync_interval_hours = 0;
|
||||
// Debug verbosity is independent of the packet/output master switch. Keep
|
||||
// this after every prior persisted field so legacy images default to quiet.
|
||||
// this after the published GPS cadence so legacy images default to quiet.
|
||||
uint8_t usb_debug_enabled = 0;
|
||||
|
||||
private:
|
||||
|
||||
@@ -628,6 +628,12 @@ static bool usb_host_session_connected = false;
|
||||
static bool usb_logging_terminal_mode = false;
|
||||
static bool usb_logging_network_parked = false;
|
||||
static bool usb_protocol_initialized = false;
|
||||
#if MESH_USB_LOGGING_AVAILABLE
|
||||
static bool usb_logging_reply_hold = false;
|
||||
static bool usb_logging_reply_staged = false;
|
||||
static bool usb_logging_reply_pending = false;
|
||||
static bool usb_logging_reply_state = false;
|
||||
#endif
|
||||
static mesh::UsbBinaryStartupProbe usb_binary_startup_probe;
|
||||
static mesh::UsbAsciiSessionDefault usb_ascii_session_default;
|
||||
#if COMPANION_FEATURE_USB_MOTA_SOURCE
|
||||
@@ -741,6 +747,10 @@ static bool hasObservableActiveUsbTerminalClient() {
|
||||
}
|
||||
|
||||
static void cancelUsbSerialOperations() {
|
||||
#if MESH_USB_LOGGING_AVAILABLE
|
||||
// A cancelled reply must not enable diagnostics for the next USB host.
|
||||
usb_logging_reply_pending = false;
|
||||
#endif
|
||||
// Contact enumeration uses the manager's pinned streaming route; delayed
|
||||
// single replies capture their own route inside MyMesh. Cancel only USB's
|
||||
// ownership so a simultaneous BLE/WiFi operation keeps running.
|
||||
@@ -1236,6 +1246,16 @@ static void serviceUsbLoggingOwnership(bool logging_enabled) {
|
||||
}
|
||||
|
||||
void MyMesh::applyUsbLoggingState(bool enabled) {
|
||||
#if MESH_USB_LOGGING_AVAILABLE
|
||||
if (usb_logging_reply_hold) {
|
||||
usb_logging_reply_staged = true;
|
||||
usb_logging_reply_state = enabled;
|
||||
return;
|
||||
}
|
||||
// A newer explicit setting (including one from BLE/WiFi) wins over an
|
||||
// earlier USB command whose acknowledgement is still backpressured.
|
||||
usb_logging_reply_pending = false;
|
||||
#endif
|
||||
// Park the framed transport before opening the diagnostic gate, including
|
||||
// TCP/browser commands which execute outside the mesh dispatcher.
|
||||
if (enabled && usb_protocol_initialized) serviceUsbLoggingOwnership(true);
|
||||
@@ -1243,6 +1263,52 @@ void MyMesh::applyUsbLoggingState(bool enabled) {
|
||||
if (usb_protocol_initialized) serviceUsbLoggingOwnership(enabled);
|
||||
}
|
||||
|
||||
void MyMesh::beginUsbLoggingReplyBarrier(BaseSerialInterface* route) {
|
||||
#if MESH_USB_LOGGING_AVAILABLE
|
||||
usb_logging_reply_hold = usb_protocol_initialized
|
||||
&& route == &usb_serial_interface
|
||||
&& !mesh::hasDedicatedUsbLoggingPort()
|
||||
&& !usb_serial_interface.isPassthroughMode();
|
||||
usb_logging_reply_staged = false;
|
||||
#else
|
||||
(void)route;
|
||||
#endif
|
||||
}
|
||||
|
||||
void MyMesh::endUsbLoggingReplyBarrier(bool reply_queued) {
|
||||
#if MESH_USB_LOGGING_AVAILABLE
|
||||
usb_logging_reply_hold = false;
|
||||
if (usb_logging_reply_staged) {
|
||||
// No response fallback: a full queue leaves logging unchanged for this
|
||||
// boot. Its saved preference can still take effect at the next boot.
|
||||
usb_logging_reply_pending = reply_queued;
|
||||
usb_logging_reply_staged = false;
|
||||
}
|
||||
#else
|
||||
(void)reply_queued;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void serviceUsbLoggingReplyBarrier() {
|
||||
#if MESH_USB_LOGGING_AVAILABLE
|
||||
if (!usb_logging_reply_pending) return;
|
||||
if (!usb_serial_interface.isConnected()
|
||||
|| usb_serial_interface.isPassthroughMode()) {
|
||||
usb_logging_reply_pending = false;
|
||||
return;
|
||||
}
|
||||
// One nonblocking transport pass. Never wait on an unread USB host or
|
||||
// include RX parser work in this check: a partial next command cannot hold
|
||||
// the already queued acknowledgement. Once the complete frame is admitted,
|
||||
// the ordered native FIFO sends it before any new terminal/log bytes.
|
||||
usb_serial_interface.loop();
|
||||
if (usb_serial_interface.isWriteBusy()) return;
|
||||
const bool enabled = usb_logging_reply_state;
|
||||
usb_logging_reply_pending = false;
|
||||
the_mesh.applyUsbLoggingState(enabled);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void expireUsbBinaryStartupProbeBeforeDispatch() {
|
||||
const uint32_t now = millis();
|
||||
if (!usb_binary_startup_probe.hasTimedOut(now)) return;
|
||||
@@ -1263,6 +1329,9 @@ static void expireUsbBinaryStartupProbeBeforeDispatch() {
|
||||
void MyMesh::applyUsbLoggingState(bool enabled) {
|
||||
mesh::setUsbLoggingEnabled(enabled);
|
||||
}
|
||||
|
||||
void MyMesh::beginUsbLoggingReplyBarrier(BaseSerialInterface*) {}
|
||||
void MyMesh::endUsbLoggingReplyBarrier(bool) {}
|
||||
#endif
|
||||
|
||||
#if defined(ENABLE_USB_INTERFACE) && (COMPANION_FEATURE_NETWORK_TERMINAL || defined(WITH_WEBCONFIG))
|
||||
@@ -3403,6 +3472,7 @@ void loop() {
|
||||
#if defined(ENABLE_USB_INTERFACE)
|
||||
serviceUsbTerminalHostSessionReset();
|
||||
serviceUsbAsciiSessionDefault();
|
||||
serviceUsbLoggingReplyBarrier();
|
||||
serviceUsbLoggingOwnership(mesh::isUsbLoggingEnabled()); // Before framed dispatch.
|
||||
#endif
|
||||
// Identify nRF52 CDC 1 when a terminal opens it. Doing this on the connection
|
||||
|
||||
@@ -5154,6 +5154,21 @@ void MyMesh::cancelPendingSerialOutput() {
|
||||
|
||||
void MyMesh::servicePendingSerialOutput() {
|
||||
Stream& console = mesh::usbConsolePort();
|
||||
const auto write_pending = [&]() {
|
||||
const int available = console.availableForWrite();
|
||||
if (available <= 0 || serial_log_pending_size == 0) return;
|
||||
// A low-memory HWCDC setup can have a 256/512-byte ring. Never require a
|
||||
// whole 640-byte stored line to fit that ring: retain and retry its suffix.
|
||||
const size_t attempt = serial_log_pending_size < static_cast<size_t>(available)
|
||||
? serial_log_pending_size : static_cast<size_t>(available);
|
||||
size_t written = console.write(
|
||||
reinterpret_cast<const uint8_t*>(serial_log_pending), attempt);
|
||||
if (written > attempt) written = attempt;
|
||||
serial_log_pending_size -= written;
|
||||
if (written > 0 && serial_log_pending_size > 0) {
|
||||
memmove(serial_log_pending, serial_log_pending + written, serial_log_pending_size);
|
||||
}
|
||||
};
|
||||
|
||||
if (serial_acl_next >= 0) {
|
||||
if (serial_log_pending_size == 0) {
|
||||
@@ -5181,63 +5196,58 @@ void MyMesh::servicePendingSerialOutput() {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (console.availableForWrite() < static_cast<int>(serial_log_pending_size)) return;
|
||||
size_t written = console.write(
|
||||
reinterpret_cast<const uint8_t*>(serial_log_pending), serial_log_pending_size);
|
||||
if (written > serial_log_pending_size) written = serial_log_pending_size;
|
||||
serial_log_pending_size -= written;
|
||||
if (written > 0 && serial_log_pending_size > 0) {
|
||||
memmove(serial_log_pending, serial_log_pending + written, serial_log_pending_size);
|
||||
}
|
||||
write_pending();
|
||||
return;
|
||||
}
|
||||
|
||||
if (serial_recent_next >= 0) {
|
||||
char record[64];
|
||||
int length = 0;
|
||||
const SimpleMeshTables::RecentRepeaterInfo* next_info = nullptr;
|
||||
int next_index = -1;
|
||||
if (serial_recent_header) {
|
||||
length = snprintf(record, sizeof(record), "Recent repeaters (%d):\n",
|
||||
serial_recent_count);
|
||||
} else if (serial_recent_count == 0) {
|
||||
length = snprintf(record, sizeof(record), "-none-\r\n");
|
||||
} else if (serial_recent_next < serial_recent_count) {
|
||||
const auto* tables = static_cast<const SimpleMeshTables*>(getTables());
|
||||
const auto* info = tables ? tables->getNextRecentRepeaterBySortKey(
|
||||
serial_recent_has_cursor ? &serial_recent_cursor : nullptr,
|
||||
serial_recent_cursor_index, next_index) : nullptr;
|
||||
if (info == nullptr) {
|
||||
if (serial_log_pending_size == 0) {
|
||||
char record[64];
|
||||
int length = 0;
|
||||
int next_index = -1;
|
||||
if (serial_recent_header) {
|
||||
length = snprintf(record, sizeof(record), "Recent repeaters (%d):\n",
|
||||
serial_recent_count);
|
||||
} else if (serial_recent_count == 0) {
|
||||
length = snprintf(record, sizeof(record), "-none-\r\n");
|
||||
} else if (serial_recent_next < serial_recent_count) {
|
||||
const auto* tables = static_cast<const SimpleMeshTables*>(getTables());
|
||||
const auto* info = tables ? tables->getNextRecentRepeaterBySortKey(
|
||||
serial_recent_has_cursor ? &serial_recent_cursor : nullptr,
|
||||
serial_recent_cursor_index, next_index) : nullptr;
|
||||
if (info == nullptr) {
|
||||
serial_recent_next = -1;
|
||||
return;
|
||||
}
|
||||
char prefix[MAX_ROUTE_HASH_BYTES * 2 + 1];
|
||||
char snr[12];
|
||||
mesh::Utils::toHex(prefix, info->prefix, info->prefix_len);
|
||||
formatLocalSnrX4(snr, sizeof(snr), info->snr_x4);
|
||||
length = snprintf(record, sizeof(record), "%s,%s%s\n", prefix,
|
||||
snr[0] == '-' ? "" : " ", snr);
|
||||
// Snapshot the row before a short write. Its next pass must retry only
|
||||
// the retained suffix, even if the live recent-repeater table changes.
|
||||
serial_recent_cursor = *info;
|
||||
serial_recent_cursor_index = next_index;
|
||||
serial_recent_has_cursor = true;
|
||||
} else {
|
||||
serial_recent_next = -1;
|
||||
return;
|
||||
}
|
||||
next_info = info;
|
||||
char prefix[MAX_ROUTE_HASH_BYTES * 2 + 1];
|
||||
char snr[12];
|
||||
mesh::Utils::toHex(prefix, info->prefix, info->prefix_len);
|
||||
formatLocalSnrX4(snr, sizeof(snr), info->snr_x4);
|
||||
length = snprintf(record, sizeof(record), "%s,%s%s\n", prefix,
|
||||
snr[0] == '-' ? "" : " ", snr);
|
||||
} else {
|
||||
serial_recent_next = -1;
|
||||
return;
|
||||
// One bounded row per pass. HWCDC can return a short write even after a
|
||||
// capacity preflight because another diagnostic task shares its TX ring.
|
||||
if (length <= 0 || static_cast<size_t>(length) >= sizeof(record)) {
|
||||
serial_recent_next = -1;
|
||||
return;
|
||||
}
|
||||
memcpy(serial_log_pending, record, length);
|
||||
serial_log_pending_size = static_cast<size_t>(length);
|
||||
}
|
||||
// One complete row per pass, admitted atomically only when it fits.
|
||||
if (length <= 0 || static_cast<size_t>(length) >= sizeof(record)) {
|
||||
serial_recent_next = -1;
|
||||
return;
|
||||
}
|
||||
if (console.availableForWrite() < length
|
||||
|| console.write(reinterpret_cast<const uint8_t*>(record), length)
|
||||
!= static_cast<size_t>(length)) return;
|
||||
write_pending();
|
||||
if (serial_log_pending_size != 0) return;
|
||||
if (serial_recent_header) {
|
||||
serial_recent_header = false;
|
||||
} else {
|
||||
if (next_info != nullptr) {
|
||||
serial_recent_cursor = *next_info;
|
||||
serial_recent_cursor_index = next_index;
|
||||
serial_recent_has_cursor = true;
|
||||
}
|
||||
if (serial_recent_count == 0 || ++serial_recent_next >= serial_recent_count) {
|
||||
serial_recent_next = -1;
|
||||
}
|
||||
@@ -5247,11 +5257,13 @@ void MyMesh::servicePendingSerialOutput() {
|
||||
if (!serial_log_active) {
|
||||
// CommonCLI's synchronous EOF is suppressed until the queued dump ends.
|
||||
static const char eof[] = " -> EOF\r\n";
|
||||
if (serial_log_eof_pending
|
||||
&& console.availableForWrite() >= static_cast<int>(sizeof(eof) - 1)
|
||||
&& console.write(reinterpret_cast<const uint8_t*>(eof), sizeof(eof) - 1)
|
||||
== sizeof(eof) - 1) {
|
||||
serial_log_eof_pending = false;
|
||||
if (serial_log_eof_pending) {
|
||||
if (serial_log_pending_size == 0) {
|
||||
memcpy(serial_log_pending, eof, sizeof(eof) - 1);
|
||||
serial_log_pending_size = sizeof(eof) - 1;
|
||||
}
|
||||
write_pending();
|
||||
if (serial_log_pending_size == 0) serial_log_eof_pending = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -5303,16 +5315,7 @@ void MyMesh::servicePendingSerialOutput() {
|
||||
serial_log_pending[serial_log_pending_size++] = '\n';
|
||||
}
|
||||
}
|
||||
if (serial_log_pending_size > 0
|
||||
&& console.availableForWrite() >= static_cast<int>(serial_log_pending_size)) {
|
||||
size_t written = console.write(
|
||||
reinterpret_cast<const uint8_t*>(serial_log_pending), serial_log_pending_size);
|
||||
if (written > serial_log_pending_size) written = serial_log_pending_size;
|
||||
serial_log_pending_size -= written;
|
||||
if (written > 0 && serial_log_pending_size > 0) {
|
||||
memmove(serial_log_pending, serial_log_pending + written, serial_log_pending_size);
|
||||
}
|
||||
}
|
||||
write_pending();
|
||||
if (serial_log_remaining == 0 && serial_log_pending_size == 0) {
|
||||
serial_log_dump.close();
|
||||
serial_log_active = false;
|
||||
|
||||
@@ -980,6 +980,7 @@ protected:
|
||||
char interval_str[12];
|
||||
sprintf(interval_str, "%u", _prefs.gps_interval);
|
||||
sensors.setSettingValue("gps_interval", interval_str);
|
||||
sensors.applyGpsTimeSyncInterval(_prefs.gps_sync_interval_hours);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -1712,6 +1712,20 @@ void MyMesh::cancelPendingSerialOutput() {
|
||||
|
||||
void MyMesh::servicePendingSerialOutput() {
|
||||
Stream& console = mesh::usbConsolePort();
|
||||
const auto write_pending = [&]() {
|
||||
const int available = console.availableForWrite();
|
||||
if (available <= 0 || serial_log_pending_size == 0) return;
|
||||
// Retain a larger stored line across low-memory 256/512-byte HWCDC rings.
|
||||
const size_t attempt = serial_log_pending_size < static_cast<size_t>(available)
|
||||
? serial_log_pending_size : static_cast<size_t>(available);
|
||||
size_t written = console.write(
|
||||
reinterpret_cast<const uint8_t*>(serial_log_pending), attempt);
|
||||
if (written > attempt) written = attempt;
|
||||
serial_log_pending_size -= written;
|
||||
if (written > 0 && serial_log_pending_size > 0) {
|
||||
memmove(serial_log_pending, serial_log_pending + written, serial_log_pending_size);
|
||||
}
|
||||
};
|
||||
if (serial_acl_next >= 0) {
|
||||
if (serial_log_pending_size == 0) {
|
||||
if (serial_acl_header) {
|
||||
@@ -1738,24 +1752,19 @@ void MyMesh::servicePendingSerialOutput() {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (console.availableForWrite() < static_cast<int>(serial_log_pending_size)) return;
|
||||
size_t written = console.write(
|
||||
reinterpret_cast<const uint8_t*>(serial_log_pending), serial_log_pending_size);
|
||||
if (written > serial_log_pending_size) written = serial_log_pending_size;
|
||||
serial_log_pending_size -= written;
|
||||
if (written > 0 && serial_log_pending_size > 0) {
|
||||
memmove(serial_log_pending, serial_log_pending + written, serial_log_pending_size);
|
||||
}
|
||||
write_pending();
|
||||
return;
|
||||
}
|
||||
if (!serial_log_active) {
|
||||
// CommonCLI's synchronous EOF is suppressed until the queued dump ends.
|
||||
static const char eof[] = " -> EOF\r\n";
|
||||
if (serial_log_eof_pending
|
||||
&& console.availableForWrite() >= static_cast<int>(sizeof(eof) - 1)
|
||||
&& console.write(reinterpret_cast<const uint8_t*>(eof), sizeof(eof) - 1)
|
||||
== sizeof(eof) - 1) {
|
||||
serial_log_eof_pending = false;
|
||||
if (serial_log_eof_pending) {
|
||||
if (serial_log_pending_size == 0) {
|
||||
memcpy(serial_log_pending, eof, sizeof(eof) - 1);
|
||||
serial_log_pending_size = sizeof(eof) - 1;
|
||||
}
|
||||
write_pending();
|
||||
if (serial_log_pending_size == 0) serial_log_eof_pending = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -1807,16 +1816,7 @@ void MyMesh::servicePendingSerialOutput() {
|
||||
serial_log_pending[serial_log_pending_size++] = '\n';
|
||||
}
|
||||
}
|
||||
if (serial_log_pending_size > 0
|
||||
&& console.availableForWrite() >= static_cast<int>(serial_log_pending_size)) {
|
||||
size_t written = console.write(
|
||||
reinterpret_cast<const uint8_t*>(serial_log_pending), serial_log_pending_size);
|
||||
if (written > serial_log_pending_size) written = serial_log_pending_size;
|
||||
serial_log_pending_size -= written;
|
||||
if (written > 0 && serial_log_pending_size > 0) {
|
||||
memmove(serial_log_pending, serial_log_pending + written, serial_log_pending_size);
|
||||
}
|
||||
}
|
||||
write_pending();
|
||||
if (serial_log_remaining == 0 && serial_log_pending_size == 0) {
|
||||
serial_log_dump.close();
|
||||
serial_log_active = false;
|
||||
|
||||
@@ -412,6 +412,7 @@ protected:
|
||||
char interval_str[12];
|
||||
sprintf(interval_str, "%u", _prefs.gps_interval);
|
||||
sensors.setSettingValue("gps_interval", interval_str);
|
||||
sensors.applyGpsTimeSyncInterval(_prefs.gps_sync_interval_hours);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -257,6 +257,7 @@ private:
|
||||
char interval_str[12];
|
||||
sprintf(interval_str, "%u", _prefs.gps_interval);
|
||||
sensors.setSettingValue("gps_interval", interval_str);
|
||||
sensors.applyGpsTimeSyncInterval(_prefs.gps_sync_interval_hours);
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user