Files
firmware/src/SerialConsole.cpp
Thomas Göttgens 023351a979 Remove dead code (#11082)
Removed:
- MessageStore::addFromPacket and addFromString, superseded by
  tryAddFromPacket
- GeoCoord rangeRadiansToMeters, distanceTo, bearingTo
- Router::rawSend, declared virtual with no override and no caller
- ContentHandler handleHotspot, handleFs, handleAdminSettings,
  handleAdminSettingsApply, handleDeleteFsContent and their commented
  route registrations, plus the now unreachable htmlDeleteDir and the
  handleUpdateFs declaration that had no definition
- ContentHelper replaceAll
- OnScreenKeyboardModule popup chain: showPopup, clearPopup, drawPopup,
  drawPopupOverlay and their state, unreachable since the frame based UI
  was replaced by baseUI
- DebugRenderer drawDebugInfoTrampoline, drawDebugInfoSettingsTrampoline
  and the orphaned drawFrameSettings
- NodeListRenderer calculateMaxScroll, drawColumns and a stale extern
  haveGlyphs declaration with no definition
- UIRenderer::haveGlyphs, Screen::blink,
  NotificationRenderer::showKeyboardMessagePopupWithTitle,
  VirtualKeyboard::getInputText
- InkHUD touchNavLeft, touchNavRight, Applet::getActiveNodeCount,
  ThreadedMessageApplet::saveMessagesToFlash
- TwoButton::setHandlerUp, TwoButtonExtended setHandlerUp,
  setJoystickDownHandlers, setJoystickUpHandlers
- CannedMessageModule LaunchRepeatDestination, isCharInputAllowed,
  hasMessages
- TrafficManagementModule resetStats, recordRouterHopPreserved,
  saturatingIncrement
- UnitConversions::MetersPerSecondToMilesPerHour
- EncryptedStorage getSessionRemainingSeconds
- BMI270Sensor::writeRegisters, GPS::hasFlow, FSCommon copyFile,
  SerialConsole consolePrintf, buzz playLongPressLeadUp,
  memGet displayPercentHeapFree
2026-07-20 11:55:17 +02:00

275 lines
9.0 KiB
C++

#include "SerialConsole.h"
#include "Default.h"
#include "NodeDB.h"
#include "PowerFSM.h"
#include "Throttle.h"
#include "concurrency/LockGuard.h"
#include "configuration.h"
#include "main.h"
#include "time.h"
#if defined(ARDUINO_USB_CDC_ON_BOOT) && ARDUINO_USB_CDC_ON_BOOT
#define IS_USB_SERIAL
#ifdef SERIAL_HAS_ON_RECEIVE
#undef SERIAL_HAS_ON_RECEIVE
#endif
#include "HWCDC.h"
#endif
#ifdef RP2040_SLOW_CLOCK
#define Port Serial2
#else
#ifdef USER_DEBUG_PORT // change by WayenWeng
#define Port USER_DEBUG_PORT
#else
#define Port Serial
#endif
#endif
// Defaulting to the formerly removed phone_timeout_secs value of 15 minutes
#define SERIAL_CONNECTION_TIMEOUT (15 * 60) * 1000UL
SerialConsole *console;
#ifdef MESHTASTIC_PHONEAPI_ACCESS_CONTROL
// Last-seen USB-CDC host link (DTR/mount) state, sampled each runOnce() so a
// physical unplug/replug re-locks the per-connection admin auth (see runOnce()).
// Kept at file scope rather than as a member both because there is exactly one
// console singleton and because adding per-instance members to the PhoneAPI
// hierarchy has historically perturbed nRF52 USB-CDC enumeration (see PhoneAPI.h).
// Only compiled on lockdown (nRF52) builds.
static bool s_serialLinkUp = false;
#endif
/// Create the shared serial console once and register receive wakeups.
void consoleInit()
{
if (console) {
return;
}
auto sc = new SerialConsole(); // Must be dynamically allocated because we are now inheriting from thread
#if defined(SERIAL_HAS_ON_RECEIVE)
// onReceive does only exist for HardwareSerial not for USB CDC serial
Port.onReceive([sc]() { sc->rxInt(); });
#else
(void)sc;
#endif
DEBUG_PORT.rpInit(); // Simply sets up semaphore
}
/// Initialize console, protobuf transport, serial port, and worker thread state.
SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port), concurrency::OSThread("SerialConsole")
{
api_type = TYPE_SERIAL;
assert(!console);
console = this;
canWrite = false; // We don't send packets to our port until it has talked to us first
#ifdef RP2040_SLOW_CLOCK
Port.setTX(SERIAL2_TX);
Port.setRX(SERIAL2_RX);
#endif
Port.begin(SERIAL_BAUD);
// Boot with console TX in non-blocking mode: no host is provably listening yet.
setHostDraining(false);
time_t timeout = millis();
while (!Port) {
if (Throttle::isWithinTimespanMs(timeout, FIVE_SECONDS_MS)) {
delay(100);
} else {
break;
}
}
#if !ARCH_PORTDUINO
emitRebooted();
#endif
}
/// Service one serial API iteration and select the next polling interval.
int32_t SerialConsole::runOnce()
{
#ifdef MESHTASTIC_PHONEAPI_ACCESS_CONTROL
// Lockdown (nRF52) builds only. The SerialConsole is a process-lifetime
// singleton, so its inherited PhoneAPI object - and therefore its entry in
// the per-connection admin-auth slot table (keyed by PhoneAPI*) - is reused
// for every USB/serial client for the whole boot. Nothing re-locks that slot
// when the operator unplugs and a different client plugs in before the
// 15-minute inactivity timeout fires, so a fresh client would inherit the
// prior operator's admin authorization. Re-lock when the physical USB-CDC link
// drops - the serial analog of the BLE onDisconnect() -> close() session reset.
//
// On the nRF52 TinyUSB (Adafruit) core, (bool)Port == tud_cdc_n_connected():
// it goes false on cable unplug or host port-close (DTR de-assert). close()
// frees the auth slot and resets PhoneAPI state, so whoever connects next
// re-locks via handleStartConfig()'s !isConnected() branch on their first
// want_config - the same physical-link boundary BLE enforces in onConnect().
// Console transports without a real DTR line (e.g. a UART USER_DEBUG_PORT) hold
// this constant, so no edge fires and we fall back to the existing inactivity
// timeout - no worse than the pre-fix behavior.
const bool linkUp = static_cast<bool>(Port);
if (s_serialLinkUp && !linkUp)
close();
s_serialLinkUp = linkUp;
#endif
#ifdef HELTEC_MESH_SOLAR
// After enabling the mesh solar serial port module configuration, command processing is handled by the serial port module.
if (moduleConfig.serial.enabled && moduleConfig.serial.override_console_serial_port &&
moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MS_CONFIG) {
return 250;
}
#endif
int32_t delay = runOncePart();
#if defined(SERIAL_HAS_ON_RECEIVE) || defined(CONFIG_IDF_TARGET_ESP32S2)
return Port.available() ? delay : INT32_MAX;
#elif defined(IS_USB_SERIAL)
return HWCDC::isPlugged() ? delay : (1000 * 20);
#else
return delay;
#endif
}
/// Flush raw output while preserving queued protobuf frames.
void SerialConsole::flush()
{
// HWCDC::flush()'s no-progress path discards queued TX bytes, which would tear a
// framed protobuf stream; framed output is drained by the TX interrupt instead.
if (usingProtobufs)
return;
Port.flush();
}
/// Write raw console data only before protobuf framing becomes active.
size_t SerialConsole::write(uint8_t c)
{
// Once a protobuf client is active, unframed bytes would corrupt its stream.
if (usingProtobufs)
return 1;
if (c == '\n')
RedirectablePrint::write('\r');
return RedirectablePrint::write(c);
}
/// Wake the serial worker when PhoneAPI queues output.
void SerialConsole::onNowHasData(uint32_t fromRadioNum)
{
setIntervalFromNow(0);
}
/// Wake the serial worker when receive activity is signaled.
void SerialConsole::rxInt()
{
setIntervalFromNow(0);
}
/// Infer serial client connectivity from recent API contact.
bool SerialConsole::checkIsConnected()
{
return Throttle::isWithinTimespanMs(lastContactMsec, SERIAL_CONNECTION_TIMEOUT);
}
/// Select bounded or non-blocking HWCDC writes based on host liveness.
void SerialConsole::setHostDraining(bool draining)
{
#ifdef IS_USB_SERIAL
// Timeout 0 makes HWCDC writes drop instead of block when the host stops draining;
// bounded blocking is restored while an API client is connected so frames aren't truncated.
Port.setTxTimeoutMs(draining ? 100 : 0);
#else
(void)draining;
#endif
}
/// Update HWCDC timeout mode around generic connection handling.
void SerialConsole::onConnectionChanged(bool connected)
{
// Order matters on disconnect: make console TX non-blocking *before* the
// PowerFSM/close handling below emits more log lines to a dead port.
if (!connected) {
setHostDraining(false);
// Keep any retained tail: HWCDC may still hold its prefix, and dropping metadata
// would let the next frame header land inside that frame's declared payload.
}
StreamAPI::onConnectionChanged(connected);
if (connected)
setHostDraining(true);
}
/// Continue retained USB CDC output under the shared stream lock.
bool SerialConsole::finishPendingFrame()
{
#ifdef IS_USB_SERIAL
concurrency::LockGuard guard(&streamLock);
return frameWriter.finishPendingFrame(Port);
#else
return true;
#endif
}
/// Protect the retained log buffer from being overwritten.
bool SerialConsole::canEncodeLogRecord()
{
#ifdef IS_USB_SERIAL
concurrency::LockGuard guard(&streamLock);
return frameWriter.isIdle();
#else
return true;
#endif
}
/// Frame USB CDC output and retain any unwritten tail.
bool SerialConsole::writeFrame(uint8_t *buf, size_t len, bool bestEffort)
{
#ifdef IS_USB_SERIAL
if (len == 0 || !canWrite)
return false;
const size_t totalLen = buildFrameHeader(buf, len);
concurrency::LockGuard guard(&streamLock);
return frameWriter.writeFrame(Port, buf, totalLen, bestEffort);
#else
return StreamAPI::writeFrame(buf, len, bestEffort);
#endif
}
/**
* we override this to notice when we've received a protobuf over the serial
* stream. Then we shut off debug serial output.
*/
bool SerialConsole::handleToRadio(const uint8_t *buf, size_t len)
{
// only talk to the API once the configuration has been loaded and we're sure the serial port is not disabled.
if (config.has_lora && config.security.serial_enabled) {
// The host just sent us bytes, so it is alive and draining the port:
// restore normal bounded-blocking TX before any API response is written.
setHostDraining(true);
// Switch to protobufs for log messages
usingProtobufs = true;
canWrite = true;
return StreamAPI::handleToRadio(buf, len);
} else {
return false;
}
}
/// Route logs without allowing raw bytes into an active protobuf stream.
void SerialConsole::log_to_serial(const char *logLevel, const char *format, va_list arg)
{
if (usingProtobufs) {
if (config.security.debug_log_api_enabled && !pauseBluetoothLogging) {
meshtastic_LogRecord_Level ll = RedirectablePrint::getLogLevel(logLevel);
auto thread = concurrency::OSThread::currentThread;
emitLogRecord(ll, thread ? thread->ThreadName.c_str() : "", format, arg);
}
return;
}
RedirectablePrint::log_to_serial(logLevel, format, arg);
}