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
@@ -4,6 +4,7 @@
|
||||
#include "UsbLoggingWatchdog.h"
|
||||
#include <helpers/ui/DisplayPowerSettings.h>
|
||||
#include "CLICommandUtils.h"
|
||||
#include "GpsPowerPolicy.h"
|
||||
#include "FloodAdvertCLI.h"
|
||||
#include "StorageLayout.h"
|
||||
#include "radiolib/RadioPowerLimits.h"
|
||||
@@ -771,6 +772,7 @@ void CommonCLI::loadPrefs(FILESYSTEM* fs) {
|
||||
_prefs->bridge_uart = 0;
|
||||
#endif
|
||||
_prefs->bridge_format = mesh::bridge::ESPNOW_FORMAT_WRAPPED;
|
||||
_prefs->gps_sync_interval_hours = 0;
|
||||
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
bool node_prefs_needs_migration = false;
|
||||
@@ -940,6 +942,7 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
|
||||
// from a previous load of a newer image.
|
||||
_prefs->usb_debug_enabled = 0;
|
||||
_prefs->trace_when_repeat_off = 0;
|
||||
_prefs->gps_sync_interval_hours = 0;
|
||||
#if defined(RP2040_PLATFORM)
|
||||
File file = fs->open(filename, "r");
|
||||
#else
|
||||
@@ -1329,12 +1332,18 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
|
||||
if (file.available() >= (int)sizeof(_prefs->espnow_bridge_enabled)) {
|
||||
file.read((uint8_t *)&_prefs->espnow_bridge_enabled,
|
||||
sizeof(_prefs->espnow_bridge_enabled));
|
||||
if (file.available() >= (int)sizeof(_prefs->usb_debug_enabled)) {
|
||||
file.read((uint8_t *)&_prefs->usb_debug_enabled,
|
||||
sizeof(_prefs->usb_debug_enabled));
|
||||
if (file.available() >= (int)sizeof(_prefs->trace_when_repeat_off)) {
|
||||
file.read((uint8_t *)&_prefs->trace_when_repeat_off,
|
||||
sizeof(_prefs->trace_when_repeat_off));
|
||||
if (file.available() >= (int)sizeof(_prefs->gps_sync_interval_hours)) {
|
||||
uint16_t hours = 0;
|
||||
if (file.read((uint8_t *)&hours, sizeof(hours)) == sizeof(hours)) {
|
||||
_prefs->gps_sync_interval_hours = hours;
|
||||
if (file.available() >= (int)sizeof(_prefs->usb_debug_enabled)) {
|
||||
file.read((uint8_t *)&_prefs->usb_debug_enabled,
|
||||
sizeof(_prefs->usb_debug_enabled));
|
||||
if (file.available() >= (int)sizeof(_prefs->trace_when_repeat_off)) {
|
||||
file.read((uint8_t *)&_prefs->trace_when_repeat_off,
|
||||
sizeof(_prefs->trace_when_repeat_off));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1443,6 +1452,8 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
|
||||
_prefs->reboot_interval = constrain(_prefs->reboot_interval, 0, 255);
|
||||
|
||||
_prefs->gps_enabled = constrain(_prefs->gps_enabled, 0, 1);
|
||||
_prefs->gps_sync_interval_hours = constrain(_prefs->gps_sync_interval_hours,
|
||||
0, mesh::gps::MAX_SYNC_INTERVAL_HOURS);
|
||||
_prefs->advert_loc_policy = constrain(_prefs->advert_loc_policy, 0, 2);
|
||||
|
||||
_prefs->rx_boosted_gain = constrain(_prefs->rx_boosted_gain, 0, 1); // boolean
|
||||
@@ -1665,6 +1676,7 @@ static bool writeCommonPrefsImage(Writer& writer, NodePrefs* prefs) {
|
||||
WRITE_COMMON_PREFS(&prefs->bridge_format); // 863
|
||||
WRITE_COMMON_PREFS(&prefs->primary_radio_preamble); // appended primary tuple field
|
||||
WRITE_COMMON_PREFS(&prefs->espnow_bridge_enabled); // appended Full ESP-NOW intent
|
||||
WRITE_COMMON_PREFS(&prefs->gps_sync_interval_hours); // published GPS sync cadence
|
||||
WRITE_COMMON_PREFS(&prefs->usb_debug_enabled); // appended USB debug intent
|
||||
WRITE_COMMON_PREFS(&prefs->trace_when_repeat_off); // appended repeater trace exception
|
||||
|
||||
@@ -1826,6 +1838,7 @@ void CommonCLI::savePrefs(FILESYSTEM* fs, PrefsSaveRouting::Scope scope) {
|
||||
file.write((uint8_t *)&_prefs->bridge_format, sizeof(_prefs->bridge_format)); // 863
|
||||
file.write((uint8_t *)&_prefs->primary_radio_preamble, sizeof(_prefs->primary_radio_preamble)); // appended
|
||||
file.write((uint8_t *)&_prefs->espnow_bridge_enabled, sizeof(_prefs->espnow_bridge_enabled)); // appended
|
||||
file.write((uint8_t *)&_prefs->gps_sync_interval_hours, sizeof(_prefs->gps_sync_interval_hours)); // published GPS sync cadence
|
||||
file.write((uint8_t *)&_prefs->usb_debug_enabled, sizeof(_prefs->usb_debug_enabled)); // appended
|
||||
file.write((uint8_t *)&_prefs->trace_when_repeat_off, sizeof(_prefs->trace_when_repeat_off)); // appended
|
||||
|
||||
@@ -3400,6 +3413,28 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
|
||||
return;
|
||||
}
|
||||
#if ENV_INCLUDE_GPS == 1
|
||||
if (strncmp(config, "gps.sync.interval", 17) == 0
|
||||
&& (config[17] == 0 || config[17] == ' ' || config[17] == '\t')) {
|
||||
const char* value = config + 17;
|
||||
while (*value == ' ' || *value == '\t') ++value;
|
||||
uint16_t hours = 0;
|
||||
if (!mesh::gps::parseSyncIntervalHours(value, hours)) {
|
||||
strcpy(reply, "Error: GPS sync interval must be 1..336 hours");
|
||||
} else if (_sensors->getLocationProvider() == nullptr) {
|
||||
strcpy(reply, "Error: GPS unavailable");
|
||||
} else {
|
||||
const uint16_t previous = _prefs->gps_sync_interval_hours;
|
||||
_prefs->gps_sync_interval_hours = hours;
|
||||
if (!trySavePrefs()) {
|
||||
_prefs->gps_sync_interval_hours = previous;
|
||||
strcpy(reply, "Error: GPS sync interval could not be saved");
|
||||
} else {
|
||||
_sensors->applyGpsTimeSyncInterval(hours);
|
||||
snprintf(reply, 160, "OK - GPS sync interval %u hours (saved)", (unsigned)hours);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (strncmp(config, "gps ", 4) == 0) {
|
||||
if (strcmp(config + 4, "on") != 0 && strcmp(config + 4, "off") != 0) {
|
||||
strcpy(reply, "Error: use set gps on|off");
|
||||
@@ -4766,6 +4801,17 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep
|
||||
return;
|
||||
}
|
||||
#if ENV_INCLUDE_GPS == 1
|
||||
if (strcmp(config, "gps.sync.interval") == 0) {
|
||||
auto* provider = _sensors->getLocationProvider();
|
||||
if (provider == nullptr) {
|
||||
strcpy(reply, "Error: GPS unavailable");
|
||||
} else {
|
||||
const uint16_t hours = provider->getTimeSyncIntervalHours();
|
||||
if (hours == 0) strcpy(reply, "> default (board GPS sync policy)");
|
||||
else snprintf(reply, 160, "> %u hours", (unsigned)hours);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (strcmp(config, "gps") == 0) {
|
||||
char gps_command[] = "gps";
|
||||
handleCommand(sender_timestamp, gps_command, reply);
|
||||
|
||||
@@ -183,7 +183,7 @@ public:
|
||||
// logging artifact's enabled-at-first-boot behavior.
|
||||
uint8_t usb_logging_enabled = 1;
|
||||
// USB diagnostics are opt-in independently of the packet output gate.
|
||||
// Appended after ESP-NOW intent in /com_prefs, not beside usb_log.
|
||||
// Appended after GPS sync cadence in /com_prefs, not beside usb_log.
|
||||
uint8_t usb_debug_enabled = 0;
|
||||
// Repeater-only opt-in for routed trace diagnostics while repeat is off.
|
||||
// Appended after usb_debug in /com_prefs; existing images stay disabled.
|
||||
@@ -202,6 +202,8 @@ public:
|
||||
// Old preference images did not have this byte; they default to enabled to
|
||||
// retain the previous combined-Full behavior after an upgrade.
|
||||
uint8_t espnow_bridge_enabled = 1;
|
||||
// Appended after the ESP-NOW intent. Zero keeps the board's legacy cadence.
|
||||
uint16_t gps_sync_interval_hours = 0;
|
||||
uint8_t retry_preset = 0;
|
||||
uint8_t direct_retry_attempts = 0;
|
||||
uint16_t direct_retry_base_ms = 0;
|
||||
@@ -352,6 +354,7 @@ private:
|
||||
void structure() override {
|
||||
def("en", _parent->gps_enabled);
|
||||
def("int", _parent->gps_interval);
|
||||
def("sync_hours", _parent->gps_sync_interval_hours);
|
||||
def("adv_loc", _parent->advert_loc_policy);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,22 @@ namespace gps {
|
||||
|
||||
static const uint32_t DEFAULT_UPDATE_INTERVAL_SEC = 1;
|
||||
static const uint32_t MAX_UPDATE_INTERVAL_SEC = 24UL * 60UL * 60UL;
|
||||
static const uint16_t MAX_SYNC_INTERVAL_HOURS = 14U * 24U;
|
||||
|
||||
// Saturate while parsing, not after an overflowing atoi/strtoul. Still inspect
|
||||
// every character so an oversized value with trailing junk is never accepted.
|
||||
inline bool parseSyncIntervalHours(const char* value, uint16_t& configured_hours) {
|
||||
if (value == nullptr || *value == 0) return false;
|
||||
uint16_t parsed = 0;
|
||||
for (const char* p = value; *p; ++p) {
|
||||
if (*p < '0' || *p > '9') return false;
|
||||
const uint16_t next = parsed * 10U + static_cast<uint16_t>(*p - '0');
|
||||
parsed = next > MAX_SYNC_INTERVAL_HOURS ? MAX_SYNC_INTERVAL_HOURS : next;
|
||||
}
|
||||
if (parsed == 0) return false;
|
||||
configured_hours = parsed;
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool parseUpdateInterval(const char* value, uint32_t& configured_sec) {
|
||||
if (value == nullptr || *value == 0) return false;
|
||||
|
||||
@@ -73,6 +73,14 @@ protected:
|
||||
#endif
|
||||
|
||||
public:
|
||||
void applyGpsTimeSyncInterval(uint16_t hours) {
|
||||
#if ENV_INCLUDE_GPS
|
||||
LocationProvider* location = getLocationProvider();
|
||||
if (location != nullptr) location->setTimeSyncIntervalHours(hours);
|
||||
#else
|
||||
(void)hours;
|
||||
#endif
|
||||
}
|
||||
struct VoltageSensorReading {
|
||||
uint8_t channel;
|
||||
float voltage;
|
||||
@@ -108,6 +116,9 @@ public:
|
||||
// for the manager's established transport ownership.
|
||||
if (!gps_transport_available || !telemetryGpsDetected()) return false;
|
||||
LocationProvider* location = getLocationProvider();
|
||||
if (location != nullptr && location->getTimeSyncIntervalHours() != 0) {
|
||||
min_interval_secs = static_cast<uint64_t>(location->getTimeSyncIntervalHours()) * 3600UL;
|
||||
}
|
||||
return location != nullptr && location->getGPSPowerSaving()
|
||||
&& location->requestTimeSync(min_interval_secs);
|
||||
#else
|
||||
|
||||
+154
-42
@@ -17,6 +17,8 @@
|
||||
#endif
|
||||
#if MESH_ESP32_TINYUSB_NONBLOCKING
|
||||
#include "esp32-hal-tinyusb.h"
|
||||
// Build-local Arduino hook queries its actual CDC descriptor's IN endpoint.
|
||||
extern "C" bool meshEsp32TinyUsbTxPending();
|
||||
#endif
|
||||
#if defined(NRF52_PLATFORM) && defined(USE_TINYUSB)
|
||||
#include <Adafruit_TinyUSB.h>
|
||||
@@ -29,6 +31,7 @@
|
||||
(defined(USE_TINYUSB) || defined(ENABLE_USB_INTERFACE) || defined(OTA_FOLDER_SERIAL))
|
||||
#define MESH_NRF52_PRIMARY_USB_NONBLOCKING 1
|
||||
#include <Adafruit_TinyUSB.h>
|
||||
#include <nrf.h>
|
||||
// Build-local CDC query installed by scripts/nrf52_usb_power_fix.py. It does
|
||||
// not clear/cancel an endpoint or change its USB bulk data-toggle state.
|
||||
extern "C" bool mesh_tud_cdc_n_tx_pending(uint8_t instance);
|
||||
@@ -111,29 +114,50 @@ static std::atomic<bool> esp32_tinyusb_terminal_discard_pending{false};
|
||||
static std::atomic<uint32_t> esp32_tinyusb_terminal_dropped_bytes{0};
|
||||
static uint32_t esp32_tinyusb_terminal_reported_dropped_bytes = 0;
|
||||
static uint32_t esp32_tinyusb_taken_reset_generation = 0;
|
||||
static bool esp32_tinyusb_event_handler_registered = false;
|
||||
static bool esp32_tinyusb_was_connected = false;
|
||||
static std::atomic<bool> esp32_tinyusb_was_connected{false};
|
||||
// 0: ordinary; 1: detached; 2: awaiting fresh mount; 3: preparing quarantine.
|
||||
static std::atomic<uint8_t> esp32_tinyusb_reenumeration_state{0};
|
||||
static std::atomic<bool> esp32_tinyusb_owner_dtr{false};
|
||||
static std::atomic<uint32_t> esp32_tinyusb_reattach_after{0};
|
||||
static constexpr uint32_t esp32_tinyusb_detach_millis = 20;
|
||||
|
||||
static bool canAccessEsp32TinyUsb(void*) {
|
||||
return !xPortInIsrContext() && tud_cdc_n_connected(0)
|
||||
return !xPortInIsrContext()
|
||||
&& esp32_tinyusb_reenumeration_state.load(std::memory_order_acquire) == 0
|
||||
&& tud_cdc_n_connected(0)
|
||||
&& esp32_tinyusb_clean_generation.load(std::memory_order_acquire)
|
||||
== esp32_tinyusb_reset_generation.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
static void handleEsp32TinyUsbEvent(void*, esp_event_base_t, int32_t event_id,
|
||||
void* event_data) {
|
||||
// This is the Arduino event task, not TinyUSB's owner. Only publish an
|
||||
// epoch: queue cleanup and endpoint access remain in application service.
|
||||
if (event_id == ARDUINO_USB_CDC_TX_EVENT) noteUsbLoggingTxComplete();
|
||||
bool closed = event_id == ARDUINO_USB_CDC_DISCONNECTED_EVENT;
|
||||
if (event_id == ARDUINO_USB_CDC_LINE_STATE_EVENT && event_data != nullptr) {
|
||||
const auto* event = static_cast<const arduino_usb_cdc_event_data_t*>(event_data);
|
||||
closed = !event->line_state.dtr;
|
||||
}
|
||||
if (closed) {
|
||||
clearUsbLoggingClientActivity();
|
||||
esp32_tinyusb_reset_generation.fetch_add(1, std::memory_order_acq_rel);
|
||||
static void requestEsp32TinyUsbReenumeration(bool force_reconnect = false) {
|
||||
uint8_t expected = 0;
|
||||
if (!esp32_tinyusb_reenumeration_state.compare_exchange_strong(
|
||||
expected, 3, std::memory_order_acq_rel)) {
|
||||
// A later watchdog stage may restart an already reattached device whose
|
||||
// host never delivered a fresh mount. Owner callbacks never force this.
|
||||
if (!force_reconnect || expected != 2
|
||||
|| !esp32_tinyusb_reenumeration_state.compare_exchange_strong(
|
||||
expected, 3, std::memory_order_acq_rel)) return;
|
||||
}
|
||||
// The pinned ESP32 S2/S3 DCD disconnect is one bounded soft-disconnect
|
||||
// register write, not a send into TinyUSB's owner queue. Isolate an armed
|
||||
// old packet immediately; only the application loop may reattach later.
|
||||
(void)tud_disconnect();
|
||||
esp32_tinyusb_reattach_after.store(millis() + esp32_tinyusb_detach_millis,
|
||||
std::memory_order_release);
|
||||
// Publish only a fully prepared detach. The other core must never connect
|
||||
// before this callback has actually disconnected and started its deadline.
|
||||
esp32_tinyusb_reenumeration_state.store(1, std::memory_order_release);
|
||||
}
|
||||
|
||||
static void purgeEsp32TinyUsbRx() {
|
||||
// Owner-task session hooks run before new-host RX callbacks. A fixed snapshot
|
||||
// bounds this purge even if an application reader consumes bytes concurrently.
|
||||
int pending = Serial.available();
|
||||
while (pending-- > 0) (void)Serial.read();
|
||||
// At unmount the class endpoints have already been reset. read_flush also
|
||||
// rearms OUT, so it must not address an unopened/reset endpoint there.
|
||||
if (tud_mounted()) tud_cdc_n_read_flush(0);
|
||||
}
|
||||
|
||||
class Esp32TinyUsbFifoStream : public Stream {
|
||||
@@ -246,18 +270,59 @@ static Esp32TinyUsbBufferedStream<false> buffered_esp32_tinyusb_terminal_port;
|
||||
|
||||
static void clearEsp32TinyUsbTx(void*) {
|
||||
(void)tud_cdc_n_write_clear(0);
|
||||
if (tud_mounted() && meshEsp32TinyUsbTxPending()) {
|
||||
requestEsp32TinyUsbReenumeration();
|
||||
}
|
||||
}
|
||||
|
||||
static void closeEsp32TinyUsbSession(bool device_boundary) {
|
||||
clearUsbLoggingClientActivity();
|
||||
esp32_tinyusb_reset_generation.fetch_add(1, std::memory_order_acq_rel);
|
||||
// Do this synchronously in TinyUSB's owner, never in its delayed Arduino
|
||||
// event handler or the main-loop cleanup (which could purge fresh RX).
|
||||
purgeEsp32TinyUsbRx();
|
||||
if (!device_boundary
|
||||
&& !nonblocking_esp32_tinyusb_port.tryRunExclusive(clearEsp32TinyUsbTx)) {
|
||||
// A writer already in flight can still arm old bytes after the close.
|
||||
// Physically quarantine it instead of racing an endpoint abort/FIFO purge.
|
||||
requestEsp32TinyUsbReenumeration();
|
||||
}
|
||||
}
|
||||
|
||||
static void purgeEsp32TinyUsbDetachedQueues(void*) {
|
||||
purgeEsp32TinyUsbRx();
|
||||
(void)tud_cdc_n_write_clear(0);
|
||||
}
|
||||
|
||||
static void serviceEsp32TinyUsbPorts() {
|
||||
if (xPortInIsrContext()) return;
|
||||
if (esp32_tinyusb_queue_busy.test_and_set(std::memory_order_acquire)) return;
|
||||
const uint8_t reenumeration =
|
||||
esp32_tinyusb_reenumeration_state.load(std::memory_order_acquire);
|
||||
if (reenumeration != 0) {
|
||||
if (reenumeration == 1
|
||||
&& (int32_t)(millis() - esp32_tinyusb_reattach_after.load(
|
||||
std::memory_order_acquire)) >= 0
|
||||
&& (!usb_logging_watchdog_detached
|
||||
|| (int32_t)(millis() - usb_logging_watchdog_attach_at) >= 0)
|
||||
&& nonblocking_esp32_tinyusb_port.tryRunExclusive(
|
||||
purgeEsp32TinyUsbDetachedQueues)) {
|
||||
// Publish before connect: a fast owner mount callback can run immediately.
|
||||
esp32_tinyusb_reenumeration_state.store(2, std::memory_order_release);
|
||||
(void)tud_connect();
|
||||
usb_logging_watchdog_detached = false;
|
||||
}
|
||||
esp32_tinyusb_queue_busy.clear(std::memory_order_release);
|
||||
return; // Only the fresh owner-task mount hook can reopen this gate.
|
||||
}
|
||||
// Polling also catches a physical disconnect without a CDC line-state
|
||||
// event. Events capture quick close/reopen pairs between service calls.
|
||||
const bool connected = tud_cdc_n_connected(0);
|
||||
if (esp32_tinyusb_was_connected && !connected) {
|
||||
if (esp32_tinyusb_was_connected.exchange(connected,
|
||||
std::memory_order_acq_rel) && !connected) {
|
||||
clearUsbLoggingClientActivity();
|
||||
esp32_tinyusb_reset_generation.fetch_add(1, std::memory_order_acq_rel);
|
||||
}
|
||||
esp32_tinyusb_was_connected = connected;
|
||||
const uint32_t generation =
|
||||
esp32_tinyusb_reset_generation.load(std::memory_order_acquire);
|
||||
if (generation != esp32_tinyusb_clean_generation.load(std::memory_order_acquire)) {
|
||||
@@ -268,8 +333,8 @@ static void serviceEsp32TinyUsbPorts() {
|
||||
esp32_tinyusb_text_queue.discardPending();
|
||||
esp32_tinyusb_terminal_reported_dropped_bytes =
|
||||
esp32_tinyusb_terminal_dropped_bytes.load(std::memory_order_relaxed);
|
||||
// Do not purge Serial's RX queue here: the new host may already have sent
|
||||
// its first query. Protocol owners reset their partial parser separately.
|
||||
// The synchronous owner hook already purged old RX. Keep the new host's
|
||||
// first query, while protocol owners reset their partial parser separately.
|
||||
esp32_tinyusb_clean_generation.store(generation, std::memory_order_release);
|
||||
}
|
||||
if (esp32_tinyusb_terminal_discard_pending.exchange(false,
|
||||
@@ -477,6 +542,9 @@ static constexpr uint32_t primary_usb_session_settle_millis = 8;
|
||||
static std::atomic<uint32_t> primary_usb_reset_settle_until{0};
|
||||
static std::atomic<bool> nrf52_usb_reenumeration_pending{false};
|
||||
static std::atomic<uint32_t> nrf52_usb_reattach_after{0};
|
||||
// Only the application service loop owns this phase. USB callbacks merely
|
||||
// quarantine the pull-up and publish the atomic request below.
|
||||
static bool nrf52_usb_stack_detach_issued = false;
|
||||
static constexpr uint32_t nrf52_usb_detach_millis = 20;
|
||||
|
||||
static void requestNrf52UsbSessionReenumeration() {
|
||||
@@ -490,9 +558,14 @@ static void requestNrf52UsbSessionReenumeration() {
|
||||
std::memory_order_acq_rel)) return;
|
||||
primary_usb_line_state_dtr.store(false, std::memory_order_release);
|
||||
primary_usb_reset_generation.fetch_add(1, std::memory_order_acq_rel);
|
||||
nrf52_usb_reattach_after.store(millis() + nrf52_usb_detach_millis,
|
||||
std::memory_order_release);
|
||||
TinyUSBDevice.detach();
|
||||
// Match the Nordic driver's bounded physical-disconnect operation now so
|
||||
// an armed packet cannot reach a rapidly reopened host. Do NOT call
|
||||
// TinyUSBDevice.detach() from this CDC/SOF callback: dcd_disconnect also
|
||||
// queues UNPLUGGED with an infinite FreeRTOS send timeout. A full event
|
||||
// queue would then make its sole consumer wait forever on its own queue.
|
||||
NRF_USBD->USBPULLUP = 0;
|
||||
__ISB();
|
||||
__DSB();
|
||||
}
|
||||
|
||||
static void clearNrf52UsbTxForSession(uint8_t instance) {
|
||||
@@ -503,8 +576,20 @@ static void clearNrf52UsbTxForSession(uint8_t instance) {
|
||||
}
|
||||
|
||||
static void serviceNrf52UsbSessionReenumeration() {
|
||||
if (!nrf52_usb_reenumeration_pending.load(std::memory_order_acquire)
|
||||
|| (int32_t)(millis() - nrf52_usb_reattach_after.load(
|
||||
if (!nrf52_usb_reenumeration_pending.load(std::memory_order_acquire)) return;
|
||||
if (!nrf52_usb_stack_detach_issued) {
|
||||
// Called only from the application loop, never a TinyUSB owner callback.
|
||||
// The owner can therefore drain a full event queue while this supported
|
||||
// detach operation enqueues the reset, instead of blocking on itself.
|
||||
TinyUSBDevice.detach();
|
||||
nrf52_usb_stack_detach_issued = true;
|
||||
// Start the quiet interval at the actual stack detach, not at a possibly
|
||||
// much earlier callback request while the application loop was occupied.
|
||||
nrf52_usb_reattach_after.store(millis() + nrf52_usb_detach_millis,
|
||||
std::memory_order_release);
|
||||
return;
|
||||
}
|
||||
if ((int32_t)(millis() - nrf52_usb_reattach_after.load(
|
||||
std::memory_order_acquire)) < 0
|
||||
|| tud_mounted()) return;
|
||||
#if defined(USE_TINYUSB)
|
||||
@@ -519,6 +604,7 @@ static void serviceNrf52UsbSessionReenumeration() {
|
||||
#if defined(USE_TINYUSB)
|
||||
usb_logging_watchdog_detached = false;
|
||||
#endif
|
||||
nrf52_usb_stack_detach_issued = false;
|
||||
nrf52_usb_reenumeration_pending.store(false, std::memory_order_release);
|
||||
}
|
||||
|
||||
@@ -940,9 +1026,6 @@ void setUsbDebugEnabled(bool enabled) {
|
||||
}
|
||||
|
||||
void setUsbLoggingEnabled(bool enabled) {
|
||||
#if MESH_ESP32_TINYUSB_NONBLOCKING
|
||||
const bool was_enabled = isUsbLoggingEnabled();
|
||||
#endif
|
||||
usb_logging_enabled.store(enabled, std::memory_order_relaxed);
|
||||
if (!enabled) {
|
||||
clearUsbLoggingClientActivity();
|
||||
@@ -950,12 +1033,9 @@ void setUsbLoggingEnabled(bool enabled) {
|
||||
}
|
||||
usb_logging_preference_known.store(true, std::memory_order_relaxed);
|
||||
setPlatformDebugOutputEnabled(isUsbDebugLoggingEnabled());
|
||||
#if MESH_ESP32_TINYUSB_NONBLOCKING
|
||||
// Text shares one chronological queue. When leaving logging mode, discard
|
||||
// its residual application bytes before later Binary/mOTA traffic can start.
|
||||
// An in-flight producer is gated immediately and cleaned by the next service.
|
||||
if (was_enabled && !enabled) discardUsbTerminalOutput();
|
||||
#endif
|
||||
// Turning diagnostics off is not a protocol switch. Retain functional
|
||||
// replies in the shared text queue; actual Binary/mOTA ownership changes
|
||||
// explicitly call discardUsbTerminalOutput() after their bounded barrier.
|
||||
}
|
||||
|
||||
bool saveUsbLoggingBootPreference(bool enabled) {
|
||||
@@ -994,12 +1074,6 @@ void beginUsbLoggingPort() {
|
||||
// ESP32 Companion stream; setUsbLoggingEnabled() restores them only when the
|
||||
// saved settings explicitly enable both USB output and debug verbosity.
|
||||
setPlatformDebugOutputEnabled(isUsbDebugLoggingEnabled());
|
||||
#if MESH_ESP32_TINYUSB_NONBLOCKING
|
||||
if (!esp32_tinyusb_event_handler_registered) {
|
||||
Serial.onEvent(ARDUINO_USB_CDC_ANY_EVENT, handleEsp32TinyUsbEvent);
|
||||
esp32_tinyusb_event_handler_registered = true;
|
||||
}
|
||||
#endif
|
||||
#if MESH_ESP32_HWCDC_SESSION_GUARD
|
||||
// If every pre-begin resize failed, begin() may have recovered by allocating
|
||||
// HWCDC's built-in ring. Discover it without replacing a live ISR-owned ring.
|
||||
@@ -1039,6 +1113,9 @@ void serviceUsbLoggingPort() {
|
||||
// in delay(), Serial.end(), or a host-progress wait.
|
||||
if (usb_logging_watchdog_detached
|
||||
&& int32_t(millis() - usb_logging_watchdog_attach_at) >= 0
|
||||
#if MESH_ESP32_TINYUSB_NONBLOCKING
|
||||
&& esp32_tinyusb_reenumeration_state.load(std::memory_order_acquire) == 0
|
||||
#endif
|
||||
#if defined(NRF52_PLATFORM)
|
||||
&& !nrf52_usb_reenumeration_pending.load(std::memory_order_acquire)
|
||||
&& !tud_mounted()
|
||||
@@ -1561,7 +1638,7 @@ UsbLoggingRecoveryResult recoverUsbLoggingTransport(uint8_t stage) {
|
||||
probeUsbLoggingTransport();
|
||||
} else {
|
||||
if (!usb_logging_watchdog_detached) {
|
||||
if (!tud_disconnect()) return UsbLoggingRecoveryResult::Deferred;
|
||||
requestEsp32TinyUsbReenumeration(true);
|
||||
usb_logging_watchdog_attach_at = millis() + 50;
|
||||
usb_logging_watchdog_detached = true;
|
||||
}
|
||||
@@ -1609,7 +1686,8 @@ bool isUsbLoggingTransportRecoveryPending() {
|
||||
#elif defined(NRF52_PLATFORM) && defined(ENABLE_USB_INTERFACE)
|
||||
return nrf52_usb_reenumeration_pending.load(std::memory_order_acquire);
|
||||
#elif MESH_ESP32_TINYUSB_NONBLOCKING
|
||||
return usb_logging_watchdog_detached;
|
||||
const auto state = esp32_tinyusb_reenumeration_state.load(std::memory_order_acquire);
|
||||
return usb_logging_watchdog_detached || state == 1 || state == 3;
|
||||
#elif MESH_ESP32_HWCDC_SESSION_GUARD
|
||||
return esp32_hwcdc_cleanup_pending;
|
||||
#else
|
||||
@@ -1629,6 +1707,40 @@ extern "C" void meshTinyUsbLoggingTxComplete(uint8_t instance) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#if MESH_ESP32_TINYUSB_NONBLOCKING
|
||||
// These strong bridges are called synchronously by the build-local framework
|
||||
// copy, not by Arduino's delayed event queue. Do not include weak declarations
|
||||
// in this translation unit or these definitions would inherit that attribute.
|
||||
extern "C" void meshEsp32TinyUsbTxComplete() {
|
||||
mesh::noteUsbLoggingTxComplete();
|
||||
}
|
||||
|
||||
extern "C" void meshEsp32TinyUsbCdcLineState(bool dtr) {
|
||||
// Never use USBCDC::dtr here: a delayed framework unplug event can overwrite
|
||||
// that member after a new host opens. These samples come from the owner.
|
||||
const bool previous = mesh::esp32_tinyusb_owner_dtr.exchange(
|
||||
dtr, std::memory_order_acq_rel);
|
||||
if (previous != dtr) mesh::closeEsp32TinyUsbSession(dtr);
|
||||
}
|
||||
|
||||
extern "C" bool meshEsp32TinyUsbAcceptRx() {
|
||||
// A late old OUT completion while DTR is low is not fresh-host input.
|
||||
return mesh::esp32_tinyusb_reenumeration_state.load(std::memory_order_acquire) == 0
|
||||
&& tud_cdc_n_connected(0);
|
||||
}
|
||||
|
||||
extern "C" void meshEsp32TinyUsbDeviceSessionBoundary(bool mounted) {
|
||||
mesh::closeEsp32TinyUsbSession(true);
|
||||
mesh::esp32_tinyusb_owner_dtr.store(false, std::memory_order_release);
|
||||
mesh::esp32_tinyusb_was_connected.store(false, std::memory_order_release);
|
||||
if (mounted) {
|
||||
uint8_t expected = 2;
|
||||
(void)mesh::esp32_tinyusb_reenumeration_state.compare_exchange_strong(
|
||||
expected, 0, std::memory_order_acq_rel);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(NRF52_PLATFORM) && (defined(ENABLE_USB_INTERFACE) || defined(USE_TINYUSB))
|
||||
// A protocol-only, weak-declaration-free translation unit supplies strong
|
||||
// versions of TinyUSB's callbacks and forwards here. Preserve Adafruit's CDC0
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <ESP32_CH390.h>
|
||||
#include <helpers/UsbLogging.h>
|
||||
|
||||
/**
|
||||
* Bring up the repository's CH390 lwIP interface from the board build flags.
|
||||
@@ -16,7 +17,10 @@ static inline bool beginConfiguredCH390(const char* hostname = nullptr) {
|
||||
config.int_gpio = ETH_INT_PIN;
|
||||
if (hostname && hostname[0] != '\0') {
|
||||
if (!CH390.setHostname(hostname)) {
|
||||
Serial.printf("Network: invalid Ethernet hostname %s\n", hostname);
|
||||
if (mesh::isUsbDebugLoggingEnabled()
|
||||
&& mesh::usbLoggingPort().availableForWrite() > 0) {
|
||||
mesh::usbLoggingPort().printf("Network: invalid Ethernet hostname %s\n", hostname);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!CH390.begin(config)) return false;
|
||||
|
||||
@@ -419,7 +419,6 @@ class RAK12500LocationProvider : public LocationProvider {
|
||||
unsigned long _next_time_check = 0;
|
||||
unsigned long _last_time_sync = 0;
|
||||
uint8_t _valid_time_samples = 0;
|
||||
static const unsigned long TIME_SYNC_INTERVAL = 1800000;
|
||||
int _pin_en = -1;
|
||||
public:
|
||||
void setRTCClock(mesh::RTCClock* clock) { _clock = clock; }
|
||||
@@ -480,7 +479,8 @@ public:
|
||||
}
|
||||
|
||||
if (!_time_sync_needed && _clock != NULL
|
||||
&& (unsigned long)(now - _last_time_sync) > TIME_SYNC_INTERVAL) {
|
||||
&& static_cast<uint32_t>(now - _last_time_sync)
|
||||
>= periodicTimeSyncIntervalMillis()) {
|
||||
_time_sync_needed = true;
|
||||
}
|
||||
if (_time_sync_needed && _clock != NULL && _valid_time_samples > 2) {
|
||||
@@ -1401,7 +1401,7 @@ bool EnvironmentSensorManager::gpsIsAwake(uint8_t ioPin,
|
||||
|
||||
void EnvironmentSensorManager::armGpsPowerSavingCycle() {
|
||||
if (!powersaving_enabled || !_location->getGPSPowerSaving()) return;
|
||||
_location->syncTime();
|
||||
_location->syncTimeForPowerSavingCycle();
|
||||
_location->setNextGPSOn(0);
|
||||
_location->setNextSleep();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "Mesh.h"
|
||||
#include <helpers/GpsPowerPolicy.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
@@ -22,6 +23,7 @@ protected:
|
||||
unsigned long _next_gps_on = 0;
|
||||
unsigned long _gps_on_duration_secs = GPS_POWERSAVING_ON_DURATION_SECS;
|
||||
unsigned long _gps_off_duration_secs = GPS_POWERSAVING_OFF_DURATION_SECS;
|
||||
uint16_t _gps_sync_interval_hours = 0; // 0 retains the board's legacy policy
|
||||
unsigned long _last_valid_time_sync = 0;
|
||||
uint32_t _last_time_sync_request_ms = 0;
|
||||
uint32_t _last_time_sync_applied_ms = 0;
|
||||
@@ -41,6 +43,25 @@ protected:
|
||||
}
|
||||
|
||||
public:
|
||||
void setTimeSyncIntervalHours(uint16_t hours) {
|
||||
_gps_sync_interval_hours = hours > mesh::gps::MAX_SYNC_INTERVAL_HOURS
|
||||
? mesh::gps::MAX_SYNC_INTERVAL_HOURS : hours;
|
||||
// A sleeping receiver must adopt a shorter/longer setting now, not
|
||||
// wait for its old (possibly week-long) deadline. No forced GPS wake.
|
||||
if (_next_gps_on != 0) setNextWake();
|
||||
}
|
||||
uint16_t getTimeSyncIntervalHours() const { return _gps_sync_interval_hours; }
|
||||
uint32_t periodicTimeSyncIntervalMillis() const {
|
||||
return _gps_sync_interval_hours != 0
|
||||
? static_cast<uint32_t>(_gps_sync_interval_hours) * 3600000UL
|
||||
: 1800000UL; // existing always-on receiver policy: 30 minutes
|
||||
}
|
||||
// Position telemetry may power the receiver between scheduled clock
|
||||
// acquisitions. Do not turn that into an early clock-sync request.
|
||||
void syncTimeForPowerSavingCycle() {
|
||||
if (_gps_sync_interval_hours == 0) syncTime();
|
||||
else (void)requestTimeSync(static_cast<uint64_t>(_gps_sync_interval_hours) * 3600UL);
|
||||
}
|
||||
virtual void syncTime() { _time_sync_needed = true; }
|
||||
virtual bool waitingTimeSync() { return _time_sync_needed; }
|
||||
// Telemetry requests share an in-progress acquisition and rate-limit new
|
||||
@@ -95,7 +116,24 @@ public:
|
||||
virtual void enablePowerSaving(bool enabled) { setGPSPowerSaving(enabled); }
|
||||
virtual bool isPowerSavingEnabled() { return getGPSPowerSaving(); }
|
||||
virtual void setNextWake() {
|
||||
setNextGPSOn(millis() + _gps_off_duration_secs * 1000UL);
|
||||
const uint32_t now = static_cast<uint32_t>(millis());
|
||||
uint32_t wait_ms = _gps_off_duration_secs * 1000UL;
|
||||
if (_gps_sync_interval_hours != 0) {
|
||||
wait_ms = periodicTimeSyncIntervalMillis();
|
||||
// Anchor to clock acquisition, not an unrelated location query's
|
||||
// power-off. Repeated position queries must not postpone sync.
|
||||
uint32_t age = UINT32_MAX;
|
||||
if (_time_sync_applied_seen) age = now - _last_time_sync_applied_ms;
|
||||
if (_time_sync_request_seen) {
|
||||
const uint32_t request_age = now - _last_time_sync_request_ms;
|
||||
if (request_age < age) age = request_age;
|
||||
}
|
||||
if (age < wait_ms) wait_ms -= age;
|
||||
// A failed/timed-out acquisition gets a full interval backoff,
|
||||
// rather than immediately powering back on in a retry loop.
|
||||
}
|
||||
const uint32_t deadline = now + wait_ms;
|
||||
setNextGPSOn(deadline == 0 ? 1 : deadline); // 0 means no deadline
|
||||
}
|
||||
virtual unsigned long getNextWake() { return getNextGPSOn(); }
|
||||
virtual void setNextSleep() {
|
||||
|
||||
@@ -55,7 +55,6 @@ class MicroNMEALocationProvider : public LocationProvider {
|
||||
unsigned long next_check = 0;
|
||||
long time_valid = 0;
|
||||
unsigned long _last_time_sync = 0;
|
||||
static const unsigned long TIME_SYNC_INTERVAL = 1800000; // Re-sync every 30 minutes
|
||||
|
||||
#ifdef GPS_L76K
|
||||
void configureL76K() {
|
||||
@@ -269,7 +268,9 @@ public :
|
||||
if ((long)(millis() - next_check) > 0) {
|
||||
next_check = millis() + 1000;
|
||||
// Re-enable time sync periodically when GPS has valid fix
|
||||
if (!_time_sync_needed && _clock != NULL && (millis() - _last_time_sync) > TIME_SYNC_INTERVAL) {
|
||||
if (!_time_sync_needed && _clock != NULL
|
||||
&& static_cast<uint32_t>(millis() - _last_time_sync)
|
||||
>= periodicTimeSyncIntervalMillis()) {
|
||||
_time_sync_needed = true;
|
||||
}
|
||||
const long timestamp = getTimestamp();
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <Wire.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <helpers/UsbLogging.h>
|
||||
|
||||
#include "TouchTapDetector.h"
|
||||
@@ -37,18 +39,23 @@ public:
|
||||
#endif
|
||||
|
||||
if (_present) {
|
||||
if (mesh::isUsbDebugLoggingEnabled())
|
||||
mesh::usbDebugPort().printf("Touch: CHSC6X found at 0x%02X\n", CHSC6X_I2C_ADDR);
|
||||
logDiagnostic("Touch: CHSC6X found at 0x%02X\n", CHSC6X_I2C_ADDR);
|
||||
} else if (mesh::isUsbDebugLoggingEnabled()) {
|
||||
// Report what is actually on the bus, so an unexpected controller or
|
||||
// address can be identified from a normal boot log.
|
||||
mesh::usbDebugPort().printf("Touch: idle or absent at 0x%02X; I2C bus currently answers:",
|
||||
CHSC6X_I2C_ADDR);
|
||||
char record[640];
|
||||
size_t length = snprintf(record, sizeof(record),
|
||||
"Touch: idle or absent at 0x%02X; I2C bus currently answers:",
|
||||
CHSC6X_I2C_ADDR);
|
||||
for (uint8_t addr = 8; addr < 0x78; addr++) {
|
||||
_wire->beginTransmission(addr);
|
||||
if (_wire->endTransmission() == 0) mesh::usbDebugPort().printf(" 0x%02X", addr);
|
||||
if (_wire->endTransmission() == 0 && length + 6 < sizeof(record)) {
|
||||
length += snprintf(record + length, sizeof(record) - length,
|
||||
" 0x%02X", addr);
|
||||
}
|
||||
}
|
||||
mesh::usbDebugPort().println();
|
||||
record[length++] = '\n';
|
||||
writeDiagnostic(record, length);
|
||||
}
|
||||
return _present;
|
||||
}
|
||||
@@ -60,12 +67,31 @@ public:
|
||||
bool pressed = readPressed();
|
||||
if (pressed && !_present) {
|
||||
_present = true; // answered late; the boot probe caught it mid-idle
|
||||
mesh::usbDebugPort().println("Touch: CHSC6X responding");
|
||||
logDiagnostic("Touch: CHSC6X responding\n");
|
||||
}
|
||||
return _detector.update(now_ms, pressed);
|
||||
}
|
||||
|
||||
private:
|
||||
static void writeDiagnostic(const char* data, size_t length) {
|
||||
if (!mesh::isUsbDebugLoggingEnabled()) return;
|
||||
Stream& output = mesh::usbLoggingPort();
|
||||
if (output.availableForWrite() < static_cast<int>(length)) return;
|
||||
output.write(reinterpret_cast<const uint8_t*>(data), length);
|
||||
}
|
||||
|
||||
static void logDiagnostic(const char* format, ...) {
|
||||
if (!mesh::isUsbDebugLoggingEnabled()) return;
|
||||
char record[128];
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
const int length = vsnprintf(record, sizeof(record), format, args);
|
||||
va_end(args);
|
||||
if (length > 0 && static_cast<size_t>(length) < sizeof(record)) {
|
||||
writeDiagnostic(record, static_cast<size_t>(length));
|
||||
}
|
||||
}
|
||||
|
||||
TwoWire* _wire = NULL;
|
||||
bool _present = false;
|
||||
TouchTapDetector _detector;
|
||||
@@ -113,15 +139,17 @@ private:
|
||||
_logged = key;
|
||||
|
||||
if (!buf) {
|
||||
mesh::usbDebugPort().printf("Touch: short read (%u of %u bytes)\n", got, CHSC6X_READ_LEN);
|
||||
logDiagnostic("Touch: short read (%u of %u bytes)\n", got, CHSC6X_READ_LEN);
|
||||
return;
|
||||
}
|
||||
mesh::usbDebugPort().printf("Touch: raw %02X %02X %02X %02X %02X", buf[0], buf[1], buf[2], buf[3], buf[4]);
|
||||
#ifdef PIN_TOUCH_INT
|
||||
// Pulled up, so an unfitted R13 sits steady HIGH and a wired INT pulses LOW.
|
||||
mesh::usbDebugPort().printf(" INT=%d", digitalRead(PIN_TOUCH_INT));
|
||||
logDiagnostic("Touch: raw %02X %02X %02X %02X %02X INT=%d\n",
|
||||
buf[0], buf[1], buf[2], buf[3], buf[4], digitalRead(PIN_TOUCH_INT));
|
||||
#else
|
||||
logDiagnostic("Touch: raw %02X %02X %02X %02X %02X\n",
|
||||
buf[0], buf[1], buf[2], buf[3], buf[4]);
|
||||
#endif
|
||||
mesh::usbDebugPort().println();
|
||||
}
|
||||
#else
|
||||
void logRaw(uint8_t, const uint8_t*) {}
|
||||
|
||||
Reference in new issue
Block a user