Add saved raw radio2 KISS host sessions to Full firmware
This commit is contained in:
1 parent
8a8c80ee6a
commit
cb3c92a8da
50 files changed
+2750
-74
No files matched your search
@@ -1,4 +1,5 @@
|
||||
#include "Dispatcher.h"
|
||||
#include "helpers/HostKissHooks.h"
|
||||
#include "helpers/ota/OtaTiming.h"
|
||||
#if MESH_ENABLE_OTA_KISS_DIRECT
|
||||
#include "helpers/ota/OtaDirectWire.h"
|
||||
@@ -289,6 +290,9 @@ bool Dispatcher::supportsForeignRadioProtocol(RadioProfileProtocol protocol) con
|
||||
#endif
|
||||
#if MESH_ENABLE_ETHERNET_TUNNEL
|
||||
case RadioProfileProtocol::Ethernet: return _radio->supportsEthernetTunnel();
|
||||
#endif
|
||||
#if MESH_ENABLE_HOST_KISS
|
||||
case RadioProfileProtocol::Kiss: return _radio->supportsHostKiss();
|
||||
#endif
|
||||
default: return false;
|
||||
}
|
||||
@@ -370,6 +374,9 @@ bool Dispatcher::queueForeignRadioFrame(const uint8_t* bytes, size_t length,
|
||||
void Dispatcher::finishForeignRadioFrame(bool success, bool account_airtime) {
|
||||
if (!hasForeignRadioFrame()) return;
|
||||
const bool direct_ota = foreign_radio_protocol == RadioProfileProtocol::MeshCore;
|
||||
#if MESH_ENABLE_HOST_KISS
|
||||
const bool host_kiss = foreign_radio_protocol == RadioProfileProtocol::Kiss;
|
||||
#endif
|
||||
if (account_airtime) {
|
||||
const uint32_t elapsed = (uint32_t)_ms->getMillis() - foreign_radio_start;
|
||||
total_air_time += elapsed;
|
||||
@@ -400,6 +407,10 @@ void Dispatcher::finishForeignRadioFrame(bool success, bool account_airtime) {
|
||||
// either transport's application counters or trigger its callbacks.
|
||||
if (direct_ota) {
|
||||
if (success) ++n_sent_direct;
|
||||
#if MESH_ENABLE_HOST_KISS
|
||||
} else if (host_kiss) {
|
||||
hostkiss::sendComplete(success);
|
||||
#endif
|
||||
} else onForeignRadioSendComplete(success);
|
||||
}
|
||||
|
||||
@@ -1086,6 +1097,11 @@ void Dispatcher::checkRecv() {
|
||||
&& _radio->receiveProfileGeneration() == profiles->generation[profile]) {
|
||||
rx_air_time += _radio->getProfileAirtime(profile, len);
|
||||
++_radio->profiles()->rx_packets[profile];
|
||||
#if MESH_ENABLE_HOST_KISS
|
||||
if (profiles->params(profile).protocol == RadioProfileProtocol::Kiss) {
|
||||
hostkiss::receive(raw, (size_t)len, profile, profiles->generation[profile], snr, rssi);
|
||||
} else
|
||||
#endif
|
||||
onForeignRadioFrame(raw, (size_t)len, profile, profiles->generation[profile], snr, rssi);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -114,6 +114,9 @@ public:
|
||||
virtual bool validateProfile(const RadioProfileParams&) const { return false; }
|
||||
virtual bool supportsMeshtext() const { return false; }
|
||||
virtual bool supportsEthernetTunnel() const { return false; }
|
||||
#if MESH_ENABLE_HOST_KISS
|
||||
virtual bool supportsHostKiss() const { return false; }
|
||||
#endif
|
||||
#if MESH_ENABLE_TX_BEACONS
|
||||
// Check the requested antenna power at the prospective beacon frequency.
|
||||
// Validation must not retune the radio or alter its current power setting.
|
||||
@@ -678,6 +681,17 @@ public:
|
||||
beacon_wake_at = now + delay;
|
||||
beacon_wake_enabled = enabled;
|
||||
}
|
||||
#endif
|
||||
#if MESH_ENABLE_HOST_KISS
|
||||
// The raw host modem can use radio2 only. It shares the bounded scheduler
|
||||
// slot and normal TX admission; it never falls back onto radio1.
|
||||
bool submitHostKissFrame(const uint8_t* bytes, size_t length, uint32_t generation) {
|
||||
return queueForeignRadioFrame(bytes, length, 1, generation, RadioProfileProtocol::Kiss);
|
||||
}
|
||||
bool cancelHostKissFrame() { return discardQueuedForeignRadioFrame(RadioProfileProtocol::Kiss); }
|
||||
bool hostKissFramePending() const {
|
||||
return hasForeignRadioFrame() && foreign_radio_protocol == RadioProfileProtocol::Kiss;
|
||||
}
|
||||
#endif
|
||||
Radio* getProfileRadio() { return _radio; }
|
||||
bool isDualRadioActive() const { return _radio->profiles() && _radio->profiles()->enabled(); }
|
||||
|
||||
+2
-2
@@ -9,7 +9,7 @@ namespace mesh {
|
||||
|
||||
enum class RadioProfileMode : uint8_t { Off = 0, Rx = 1, RxTx = 2 };
|
||||
enum class RadioCrossMode : uint8_t { Auto = 0, On = 1, Off = 2 };
|
||||
enum class RadioProfileProtocol : uint8_t { MeshCore = 0, Meshtext = 1, Ethernet = 2 };
|
||||
enum class RadioProfileProtocol : uint8_t { MeshCore = 0, Meshtext = 1, Ethernet = 2, Kiss = 3 };
|
||||
|
||||
// These are modulation profiles of ONE transceiver, never additional radios.
|
||||
struct RadioProfileParams {
|
||||
@@ -150,7 +150,7 @@ class RadioProfiles {
|
||||
if (!(p.freq >= 137 && p.freq <= 2500)
|
||||
|| p.sf < 5 || p.sf > 12 || p.cr < 5 || p.cr > 8
|
||||
|| (p.protocol != RadioProfileProtocol::MeshCore && p.protocol != RadioProfileProtocol::Meshtext
|
||||
&& p.protocol != RadioProfileProtocol::Ethernet)
|
||||
&& p.protocol != RadioProfileProtocol::Ethernet && p.protocol != RadioProfileProtocol::Kiss)
|
||||
|| (p.preamble != 0 && (p.preamble < 8 || p.preamble > MaxPreamble))) return false;
|
||||
// The first ten entries are the shared SX126x/SX127x bandwidth subset.
|
||||
static constexpr float bandwidths[] = {7.8f, 10.4f, 15.6f, 20.8f, 31.25f, 41.7f,
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#endif
|
||||
#include "PrefsSaveReplyGuard.h"
|
||||
#include "UsbLoggingWatchdog.h"
|
||||
#include "HostKiss.h"
|
||||
#include "PowerSavingDiagnostics.h"
|
||||
#include "HilStartupTrace.h"
|
||||
#if defined(ESP32_PLATFORM)
|
||||
@@ -182,6 +183,9 @@ void CommonCLI::loop() {
|
||||
servicePendingTxPower();
|
||||
servicePendingRxSettings();
|
||||
_radio_profiles.loop();
|
||||
#if MESH_ENABLE_HOST_KISS
|
||||
mesh::hostkiss::loop();
|
||||
#endif
|
||||
loopManagement();
|
||||
#if MESH_ENABLE_TX_BEACONS
|
||||
loopBeacons();
|
||||
@@ -3486,6 +3490,9 @@ bool CommonCLI::handleUDPBridgeCommand(uint32_t sender_timestamp,
|
||||
void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* reply) {
|
||||
PrefsSaveReplyGuard save_reply(_prefs_save_failures, reply);
|
||||
mesh::cli::normalizeCommandVerb(command);
|
||||
#if MESH_ENABLE_HOST_KISS
|
||||
if (mesh::hostkiss::handleCommand(command, reply, 157, sender_timestamp == 0)) return;
|
||||
#endif
|
||||
if (mesh::power::handleBatteryCalibrationCommand(*_board, command, reply, 160,
|
||||
[this](float multiplier, char* result, size_t capacity) {
|
||||
return applyAndSaveAdcMultiplier(multiplier, result, capacity);
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
#include "ManagementFeatureConfig.h"
|
||||
#include "CommonCLI.h"
|
||||
#include "HostKissConfig.h"
|
||||
#if MESH_ENABLE_HOST_KISS
|
||||
#include "HostKiss.h"
|
||||
#endif
|
||||
#include <new>
|
||||
#if MESH_ENABLE_MANAGEMENT_REPORTING
|
||||
#include "ManagementReporter.h"
|
||||
@@ -264,6 +268,10 @@ static bool resolveDataScope(DataRouteState& route, TransportKey& scope,
|
||||
|
||||
void CommonCLI::beginManagement(mesh::Mesh& mesh, FILESYSTEM* fs) {
|
||||
_management_mesh = &mesh; _management_fs = fs;
|
||||
#if MESH_ENABLE_HOST_KISS
|
||||
if (auto* radio = _callbacks->getProfileRadio())
|
||||
mesh::hostkiss::begin(fs, *radio, _radio_profiles, mesh);
|
||||
#endif
|
||||
#if MESH_ENABLE_TX_BEACONS
|
||||
beginBeacons();
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,456 @@
|
||||
#include "HostKiss.h"
|
||||
#if MESH_ENABLE_HOST_KISS
|
||||
|
||||
#include "HostKissCodec.h"
|
||||
#include "HostKissSettings.h"
|
||||
#include "RadioProfileCLI.h"
|
||||
#include "UsbLogging.h"
|
||||
#include <Dispatcher.h>
|
||||
#include <new>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#if defined(ESP32)
|
||||
#include <WiFi.h>
|
||||
#include <errno.h>
|
||||
#include <lwip/sockets.h>
|
||||
#endif
|
||||
|
||||
namespace mesh { namespace hostkiss {
|
||||
namespace {
|
||||
constexpr size_t ByteBudget = 128;
|
||||
constexpr uint16_t TcpPort = 5004;
|
||||
Settings settings;
|
||||
Radio* radio = nullptr;
|
||||
RadioProfileCLI* profiles = nullptr;
|
||||
Dispatcher* dispatcher = nullptr;
|
||||
Codec* codec = nullptr; // All frame buffers are absent while the setting is off.
|
||||
bool usb_session = false, suspended = false, leased = false, transmitting = false;
|
||||
bool ready_pending = false, completion_pending = false, completion_success = false;
|
||||
uint8_t quarantine_return = 0;
|
||||
uint32_t profile_generation = 0, intent_generation = 0;
|
||||
uint32_t received = 0, transmitted = 0, failed = 0, discarded = 0;
|
||||
#if defined(ESP32)
|
||||
struct Tcp {
|
||||
WiFiServer server;
|
||||
WiFiClient client;
|
||||
uint32_t address = 0, last_attempt = 0;
|
||||
bool listening = false, attempted = false, connected = false;
|
||||
Tcp() : server(TcpPort, 2) {}
|
||||
};
|
||||
Tcp* tcp = nullptr;
|
||||
#endif
|
||||
|
||||
bool tcpConnected() {
|
||||
#if defined(ESP32)
|
||||
return tcp && tcp->connected;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
bool active() { return usb_session || tcpConnected(); }
|
||||
|
||||
void releaseRadio() {
|
||||
if (dispatcher) dispatcher->cancelHostKissFrame();
|
||||
transmitting = false;
|
||||
const auto* p = radio ? radio->profiles() : nullptr;
|
||||
// This service is the sole owner of the Kiss overlay. Its generation may
|
||||
// already have changed; let RadioProfileCLI reconcile that change itself.
|
||||
// An unrelated foreign protocol must never be cleared on our way out.
|
||||
if (leased && profiles && p && p->secondary.params.protocol == RadioProfileProtocol::Kiss)
|
||||
profiles->clearSecondaryProtocol();
|
||||
leased = false;
|
||||
}
|
||||
|
||||
void resetFrames() {
|
||||
if (codec) codec->reset();
|
||||
ready_pending = completion_pending = false;
|
||||
quarantine_return = 0;
|
||||
}
|
||||
|
||||
void closeSession() {
|
||||
releaseRadio();
|
||||
resetFrames();
|
||||
usb_session = suspended = false;
|
||||
setUsbBinaryStreamOwned(false);
|
||||
#if defined(ESP32)
|
||||
if (tcp) { tcp->client.stop(); tcp->connected = false; }
|
||||
#endif
|
||||
}
|
||||
|
||||
const char* admission() {
|
||||
if (!radio || !profiles || !dispatcher || !radio->supportsHostKiss()) return "unsupported radio";
|
||||
if (!profiles->manualSecondaryConfigured()
|
||||
|| profiles->savedSecondaryConfig().mode != RadioProfileMode::RxTx) return "configure saved radio2 rxtx";
|
||||
if (profiles->savedSecondaryConfig().params.protocol != RadioProfileProtocol::MeshCore)
|
||||
return "radio2 protocol reserved";
|
||||
const auto* p = radio->profiles();
|
||||
if (!p || !profiles->secondaryRuntimeAvailable()) return "temporary or pending radio settings";
|
||||
if (p->secondary.mode != RadioProfileMode::RxTx || p->primary_temporary || p->secondary_temporary)
|
||||
return "radio2 unavailable";
|
||||
if (p->secondary.params.protocol != (leased ? RadioProfileProtocol::Kiss : RadioProfileProtocol::MeshCore))
|
||||
return "radio2 owned by another service";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool leaseRadio() {
|
||||
if (admission() || !profiles->useSecondaryProtocol(RadioProfileProtocol::Kiss)) return false;
|
||||
const auto* p = radio->profiles();
|
||||
if (!p || p->secondary.params.protocol != RadioProfileProtocol::Kiss) return false;
|
||||
leased = true;
|
||||
profile_generation = p->generation[1];
|
||||
intent_generation = profiles->secondaryIntentGeneration();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool current() {
|
||||
if (!leased || !active() || suspended || admission()) return false;
|
||||
const auto* p = radio->profiles();
|
||||
return p && p->generation[1] == profile_generation
|
||||
&& profiles->secondaryIntentGeneration() == intent_generation;
|
||||
}
|
||||
|
||||
const char* state(const Selection& selected, const char* reason) {
|
||||
if (!selected.enabled) return "off";
|
||||
if (suspended) return "suspended";
|
||||
if (!codec) return "unavailable";
|
||||
if (active()) return "connected";
|
||||
if (reason) return "blocked";
|
||||
#if defined(ESP32)
|
||||
if (selected.transport == Transport::Tcp) {
|
||||
if (!(WiFi.getMode() & WIFI_STA) || WiFi.status() != WL_CONNECTED
|
||||
|| !uint32_t(WiFi.localIP())) return "waiting-wifi";
|
||||
return tcp && tcp->listening ? "listening" : "starting";
|
||||
}
|
||||
#endif
|
||||
return "ready";
|
||||
}
|
||||
|
||||
void invalidateSession() {
|
||||
++discarded;
|
||||
releaseRadio();
|
||||
resetFrames();
|
||||
if (usb_session) {
|
||||
// Keep binary input quarantined until RETURN or a USB session boundary.
|
||||
// Releasing it to the ASCII parser could interpret a stale binary tail.
|
||||
suspended = true;
|
||||
} else {
|
||||
closeSession();
|
||||
#if defined(ESP32)
|
||||
// Drop accepts queued in the old profile session, not only its owner.
|
||||
if (tcp) { tcp->server.end(); tcp->listening = tcp->attempted = false; }
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void queueControls() {
|
||||
if (!codec) return;
|
||||
// Completion must survive a full RX queue, rather than being silently lost.
|
||||
if (completion_pending && codec->queuedOutput() < Codec::QueueDepth) {
|
||||
codec->queueTxDone(completion_success);
|
||||
completion_pending = false;
|
||||
ready_pending = true;
|
||||
}
|
||||
if (ready_pending && codec->queuedOutput() < Codec::QueueDepth) {
|
||||
codec->queueReady();
|
||||
ready_pending = false;
|
||||
}
|
||||
}
|
||||
|
||||
void pumpTransmit() {
|
||||
if (!current() || transmitting || completion_pending || dispatcher->hostKissFramePending()) return;
|
||||
const uint8_t* bytes;
|
||||
size_t length;
|
||||
if (codec->peekTransmit(bytes, length)
|
||||
&& dispatcher->submitHostKissFrame(bytes, length, profile_generation)) {
|
||||
codec->consumeTransmit();
|
||||
transmitting = true;
|
||||
}
|
||||
}
|
||||
|
||||
void stopTransport() {
|
||||
closeSession();
|
||||
#if defined(ESP32)
|
||||
if (tcp) tcp->server.end();
|
||||
delete tcp;
|
||||
tcp = nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool allocate(const Selection& selection, Codec*& next_codec
|
||||
#if defined(ESP32)
|
||||
, Tcp*& next_tcp
|
||||
#endif
|
||||
) {
|
||||
next_codec = codec;
|
||||
#if defined(ESP32)
|
||||
next_tcp = tcp;
|
||||
#endif
|
||||
if (!selection.enabled) return true;
|
||||
if (!next_codec) next_codec = new (std::nothrow) Codec;
|
||||
if (!next_codec) return false;
|
||||
#if defined(ESP32)
|
||||
if (selection.transport == Transport::Tcp && !next_tcp) {
|
||||
next_tcp = new (std::nothrow) Tcp;
|
||||
if (!next_tcp) {
|
||||
if (next_codec != codec) delete next_codec;
|
||||
next_codec = codec;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
#if defined(ESP32)
|
||||
void loopTcp() {
|
||||
if (!tcp) return;
|
||||
const bool station = (WiFi.getMode() & WIFI_STA) && WiFi.status() == WL_CONNECTED;
|
||||
const uint32_t address = station ? uint32_t(WiFi.localIP()) : 0;
|
||||
if (!address && !tcp->address && !tcp->listening && !tcp->connected) return;
|
||||
if (!address || address != tcp->address) {
|
||||
closeSession();
|
||||
tcp->server.end();
|
||||
tcp->listening = tcp->attempted = false;
|
||||
tcp->address = address;
|
||||
if (!address) return;
|
||||
// Never bind this unauthenticated modem to the setup AP.
|
||||
tcp->server = WiFiServer(IPAddress(address), TcpPort, 2);
|
||||
}
|
||||
const uint32_t now = millis();
|
||||
if (tcp->listening && !bool(tcp->server)) {
|
||||
tcp->server.end();
|
||||
tcp->listening = tcp->attempted = false;
|
||||
}
|
||||
if (!tcp->listening && (!tcp->attempted || uint32_t(now - tcp->last_attempt) >= 1000)) {
|
||||
tcp->last_attempt = now;
|
||||
tcp->attempted = true;
|
||||
tcp->server.begin();
|
||||
tcp->listening = bool(tcp->server);
|
||||
}
|
||||
if (tcp->connected && !tcp->client.connected()) closeSession();
|
||||
if (tcp->listening) {
|
||||
WiFiClient incoming = tcp->server.available();
|
||||
if (incoming) {
|
||||
if (tcp->connected || admission()) incoming.stop();
|
||||
else {
|
||||
resetFrames();
|
||||
if (!leaseRadio()) incoming.stop();
|
||||
else {
|
||||
tcp->client = incoming;
|
||||
tcp->client.setNoDelay(true);
|
||||
tcp->connected = true;
|
||||
suspended = false;
|
||||
ready_pending = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!tcp->connected) return;
|
||||
queueControls();
|
||||
const uint8_t* bytes;
|
||||
size_t length;
|
||||
if (codec->peekOutput(bytes, length)) {
|
||||
if (length > ByteBudget) length = ByteBudget;
|
||||
const int written = ::send(tcp->client.fd(), bytes, length, MSG_DONTWAIT);
|
||||
if (written > 0) codec->consumeOutput(size_t(written));
|
||||
else if (written < 0 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) {
|
||||
closeSession(); return;
|
||||
}
|
||||
}
|
||||
for (size_t i = 0; i < ByteBudget && codec->canAcceptByte(); ++i) {
|
||||
uint8_t byte;
|
||||
const int got = ::recv(tcp->client.fd(), &byte, 1, MSG_DONTWAIT);
|
||||
if (got == 1) {
|
||||
codec->feed(byte);
|
||||
if (codec->returned()) { closeSession(); break; }
|
||||
} else {
|
||||
if (!got || (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR)) closeSession();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
} // namespace
|
||||
|
||||
void begin(FILESYSTEM* fs, Radio& r, RadioProfileCLI& p, Dispatcher& d) {
|
||||
stopTransport();
|
||||
delete codec;
|
||||
codec = nullptr;
|
||||
radio = &r;
|
||||
profiles = &p;
|
||||
dispatcher = &d;
|
||||
received = transmitted = failed = discarded = 0;
|
||||
settings.begin(fs);
|
||||
const auto& selected = settings.selection();
|
||||
if (!selected.enabled || !r.supportsHostKiss()) return;
|
||||
#if !defined(ESP32)
|
||||
if (selected.transport == Transport::Tcp) return;
|
||||
#endif
|
||||
Codec* next_codec = nullptr;
|
||||
#if defined(ESP32)
|
||||
Tcp* next_tcp = nullptr;
|
||||
if (allocate(selected, next_codec, next_tcp)) { codec = next_codec; tcp = next_tcp; }
|
||||
#else
|
||||
if (allocate(selected, next_codec)) codec = next_codec;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool handleCommand(const char* command, char* reply, size_t capacity, bool local) {
|
||||
const bool get = !strncmp(command, "get ", 4);
|
||||
const bool set = !strncmp(command, "set ", 4);
|
||||
if (!get && !set) return false;
|
||||
const char* key = command + 4;
|
||||
bool transport = !strncmp(key, "kiss.transport", 14)
|
||||
&& (!key[14] || key[14] == ' ');
|
||||
const bool status = !strncmp(key, "kiss", 4) && (!key[4] || key[4] == ' ');
|
||||
if (!transport && !status) return false;
|
||||
const char* value = key + (transport ? 14 : 4);
|
||||
if (get) {
|
||||
if (*value) { snprintf(reply, capacity, "Error: use get kiss%s", transport ? ".transport" : ""); return true; }
|
||||
const auto& selected = settings.selection();
|
||||
if (transport) snprintf(reply, capacity, "> %s", selected.transport == Transport::Usb ? "usb" : "tcp");
|
||||
else {
|
||||
const char* reason = admission();
|
||||
snprintf(reply, capacity,
|
||||
"> %s transport=%s state=%s rx=%lu tx=%lu fail=%lu drop=%lu bad=%lu%s%s",
|
||||
selected.enabled ? "on" : "off", selected.transport == Transport::Usb ? "usb" : "tcp:5004",
|
||||
state(selected, reason),
|
||||
(unsigned long)received, (unsigned long)transmitted, (unsigned long)failed,
|
||||
(unsigned long)(discarded + (codec ? codec->droppedFrames() : 0)),
|
||||
(unsigned long)(codec ? codec->malformedFrames() : 0),
|
||||
selected.enabled && !active() && reason ? " reason=" : "",
|
||||
selected.enabled && !active() && reason ? reason : "");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (!local) { snprintf(reply, capacity, "Error: KISS settings require a local connection"); return true; }
|
||||
while (*value == ' ') ++value;
|
||||
Selection candidate = settings.selection();
|
||||
if (transport) {
|
||||
if (!strcmp(value, "usb")) candidate.transport = Transport::Usb;
|
||||
else if (!strcmp(value, "tcp")) candidate.transport = Transport::Tcp;
|
||||
else { snprintf(reply, capacity, "Error: use set kiss.transport usb|tcp"); return true; }
|
||||
#if !defined(ESP32)
|
||||
if (candidate.transport == Transport::Tcp) { snprintf(reply, capacity, "Error: TCP KISS requires ESP32 station WiFi"); return true; }
|
||||
#endif
|
||||
} else if (!strcmp(value, "on")) candidate.enabled = true;
|
||||
else if (!strcmp(value, "off")) candidate.enabled = false;
|
||||
else { snprintf(reply, capacity, "Error: use set kiss on|off"); return true; }
|
||||
if (candidate.enabled && (!radio || !radio->supportsHostKiss())) {
|
||||
snprintf(reply, capacity, "Error: raw radio2 KISS is unsupported by this radio"); return true;
|
||||
}
|
||||
if (candidate == settings.selection() && settings.healthy()) { snprintf(reply, capacity, "OK - KISS unchanged"); return true; }
|
||||
Codec* next_codec;
|
||||
#if defined(ESP32)
|
||||
Tcp* next_tcp;
|
||||
if (!allocate(candidate, next_codec, next_tcp)) {
|
||||
#else
|
||||
if (!allocate(candidate, next_codec)) {
|
||||
#endif
|
||||
snprintf(reply, capacity, "Error: insufficient memory; KISS unchanged"); return true;
|
||||
}
|
||||
// Persist before disrupting the old stream. A failed save leaves its exact
|
||||
// radio lease and pending frames intact.
|
||||
if (!settings.save(candidate)) {
|
||||
if (next_codec != codec) delete next_codec;
|
||||
#if defined(ESP32)
|
||||
if (next_tcp != tcp) delete next_tcp;
|
||||
#endif
|
||||
snprintf(reply, capacity, "Error: KISS save failed; service unchanged"); return true;
|
||||
}
|
||||
closeSession();
|
||||
codec = next_codec;
|
||||
#if defined(ESP32)
|
||||
tcp = next_tcp;
|
||||
if (tcp && (!candidate.enabled || candidate.transport != Transport::Tcp)) {
|
||||
tcp->server.end(); delete tcp; tcp = nullptr;
|
||||
}
|
||||
#endif
|
||||
if (!candidate.enabled) { delete codec; codec = nullptr; }
|
||||
snprintf(reply, capacity, candidate.enabled
|
||||
? "OK - KISS on (%s); get kiss for status" : "OK - KISS off",
|
||||
candidate.transport == Transport::Usb ? "USB: send KISS FEND to start" : "station TCP 5004");
|
||||
return true;
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (!settings.selection().enabled || !codec) return;
|
||||
if (active() && !suspended && !current()) invalidateSession();
|
||||
#if defined(ESP32)
|
||||
if (settings.selection().transport == Transport::Tcp) loopTcp();
|
||||
#endif
|
||||
pumpTransmit();
|
||||
}
|
||||
|
||||
bool serviceUsb() {
|
||||
if (!settings.selection().enabled || settings.selection().transport != Transport::Usb) return false;
|
||||
Stream& port = usbCompanionPort();
|
||||
if (!usb_session) {
|
||||
if (!port.available() || port.peek() != Codec::Fend) return false;
|
||||
resetFrames();
|
||||
usb_session = true;
|
||||
suspended = !codec || !leaseRadio();
|
||||
setUsbBinaryStreamOwned(true);
|
||||
ready_pending = !suspended;
|
||||
}
|
||||
// USB text writers may have been admitted just before the binary claim.
|
||||
// Their nonblocking retirement must finish before READY or host bytes move.
|
||||
if (!isUsbBinaryStreamReady()) return true;
|
||||
if (!codec) {
|
||||
// Allocation/capability failures must also keep binary bytes out of CLI.
|
||||
// This three-state RETURN recognizer needs no packet buffer.
|
||||
for (size_t i = 0; i < ByteBudget && port.available(); ++i) {
|
||||
const int value = port.read();
|
||||
if (value < 0) break;
|
||||
if (value == Codec::Fend) {
|
||||
if (quarantine_return == 2) { closeSession(); return true; }
|
||||
quarantine_return = 1;
|
||||
} else if (value == 0xff && quarantine_return == 1) quarantine_return = 2;
|
||||
else quarantine_return = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (!suspended && !current()) invalidateSession();
|
||||
if (!suspended) {
|
||||
queueControls();
|
||||
const uint8_t* bytes;
|
||||
size_t length;
|
||||
if (codec->peekOutput(bytes, length)) {
|
||||
if (length > ByteBudget) length = ByteBudget;
|
||||
codec->consumeOutput(port.write(bytes, length));
|
||||
}
|
||||
}
|
||||
for (size_t i = 0; i < ByteBudget && port.available() && codec->canAcceptByte(); ++i) {
|
||||
const int value = port.read();
|
||||
if (value < 0) break;
|
||||
codec->feed(uint8_t(value));
|
||||
if (codec->returned()) { closeSession(); return true; }
|
||||
if (suspended) codec->consumeTransmit(); // Never queue TX while suspended.
|
||||
}
|
||||
pumpTransmit();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool usbActive() { return usb_session; }
|
||||
bool radioActive() { return current(); }
|
||||
void resetUsbSession() { if (usb_session) closeSession(); }
|
||||
|
||||
void receive(const uint8_t* bytes, size_t length, uint8_t profile,
|
||||
uint32_t generation, float, float) {
|
||||
if (!codec || profile != 1 || generation != profile_generation || !current()) return;
|
||||
if (codec->queueReceive(bytes, length)) ++received;
|
||||
}
|
||||
|
||||
void sendComplete(bool success) {
|
||||
// A completion from an invalidated session must not reach its replacement.
|
||||
if (!transmitting) return;
|
||||
transmitting = false;
|
||||
if (!current()) return;
|
||||
if (success) ++transmitted;
|
||||
else ++failed;
|
||||
completion_success = success;
|
||||
completion_pending = true;
|
||||
}
|
||||
|
||||
}} // namespace mesh::hostkiss
|
||||
#endif
|
||||
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <helpers/HostKissConfig.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#if MESH_ENABLE_HOST_KISS
|
||||
#include <helpers/IdentityStore.h>
|
||||
namespace mesh {
|
||||
class Radio;
|
||||
class RadioProfileCLI;
|
||||
class Dispatcher;
|
||||
namespace hostkiss {
|
||||
void begin(FILESYSTEM* fs, Radio& radio, RadioProfileCLI& profiles, Dispatcher& dispatcher);
|
||||
bool handleCommand(const char* command, char* reply, size_t capacity, bool local);
|
||||
void loop();
|
||||
bool serviceUsb();
|
||||
bool usbActive();
|
||||
bool radioActive();
|
||||
void resetUsbSession();
|
||||
void receive(const uint8_t* bytes, size_t length, uint8_t profile,
|
||||
uint32_t generation, float snr, float rssi);
|
||||
void sendComplete(bool success);
|
||||
}}
|
||||
#else
|
||||
namespace mesh { namespace hostkiss {
|
||||
template<class F, class R, class P, class D> inline void begin(F*, R&, P&, D&) {}
|
||||
inline bool handleCommand(const char*, char*, size_t, bool) { return false; }
|
||||
inline void loop() {}
|
||||
inline bool serviceUsb() { return false; }
|
||||
inline bool usbActive() { return false; }
|
||||
inline bool radioActive() { return false; }
|
||||
inline void resetUsbSession() {}
|
||||
inline void receive(const uint8_t*, size_t, uint8_t, uint32_t, float, float) {}
|
||||
inline void sendComplete(bool) {}
|
||||
}}
|
||||
#endif
|
||||
@@ -0,0 +1,144 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace mesh { namespace hostkiss {
|
||||
|
||||
// Generic KISS framing, independently implemented. No RNode commands or
|
||||
// over-air fragmentation are implied by the host stream.
|
||||
class Codec {
|
||||
public:
|
||||
static constexpr uint8_t Fend = 0xc0, Fesc = 0xdb, Tfend = 0xdc, Tfesc = 0xdd;
|
||||
static constexpr size_t MaximumFrame = 255, QueueDepth = 2;
|
||||
static constexpr size_t MaximumEncoded = MaximumFrame * 2 + 3;
|
||||
static constexpr uint8_t Ready = 0x0f, TxDone = 0xf8;
|
||||
private:
|
||||
struct Frame { uint8_t bytes[MaximumFrame]; uint16_t length = 0; } tx_[QueueDepth];
|
||||
struct Output { uint8_t bytes[MaximumEncoded]; uint16_t length = 0; } out_[QueueDepth];
|
||||
uint8_t tx_head_ = 0, tx_count_ = 0, out_head_ = 0, out_count_ = 0;
|
||||
uint8_t input_[MaximumFrame];
|
||||
uint16_t input_size_ = 0, output_offset_ = 0;
|
||||
uint8_t command_ = 0;
|
||||
bool framed_ = false, has_command_ = false, escaped_ = false, discard_ = false;
|
||||
bool return_ = false;
|
||||
uint32_t malformed_ = 0, dropped_ = 0;
|
||||
|
||||
void malformed() { if (!discard_) ++malformed_; discard_ = true; }
|
||||
bool queueOutput(uint8_t command, const uint8_t* bytes, size_t length) {
|
||||
if (length > MaximumFrame || out_count_ >= QueueDepth || (!bytes && length)) {
|
||||
++dropped_; return false;
|
||||
}
|
||||
auto& frame = out_[(out_head_ + out_count_) % QueueDepth];
|
||||
size_t at = 0;
|
||||
frame.bytes[at++] = Fend;
|
||||
// Commands used by this service never need escaping.
|
||||
frame.bytes[at++] = command;
|
||||
for (size_t i = 0; i < length; ++i) {
|
||||
if (bytes[i] == Fend || bytes[i] == Fesc) {
|
||||
frame.bytes[at++] = Fesc;
|
||||
frame.bytes[at++] = bytes[i] == Fend ? Tfend : Tfesc;
|
||||
} else frame.bytes[at++] = bytes[i];
|
||||
}
|
||||
frame.bytes[at++] = Fend;
|
||||
frame.length = uint16_t(at);
|
||||
++out_count_;
|
||||
return true;
|
||||
}
|
||||
public:
|
||||
void reset() {
|
||||
tx_head_ = tx_count_ = out_head_ = out_count_ = 0;
|
||||
input_size_ = output_offset_ = 0;
|
||||
framed_ = has_command_ = escaped_ = discard_ = return_ = false;
|
||||
}
|
||||
bool canAcceptByte() const { return tx_count_ < QueueDepth && !return_; }
|
||||
// False leaves ownership of this byte with the transport (backpressure).
|
||||
bool feed(uint8_t byte) {
|
||||
if (!canAcceptByte()) return false;
|
||||
if (byte == Fend) {
|
||||
if (framed_ && has_command_ && !discard_) {
|
||||
if (escaped_) malformed();
|
||||
else if (command_ == 0xff) {
|
||||
if (!input_size_) return_ = true;
|
||||
else malformed();
|
||||
} else if (command_ == 0) {
|
||||
if (!input_size_) malformed();
|
||||
else {
|
||||
auto& frame = tx_[(tx_head_ + tx_count_) % QueueDepth];
|
||||
memcpy(frame.bytes, input_, input_size_);
|
||||
frame.length = input_size_;
|
||||
++tx_count_;
|
||||
}
|
||||
}
|
||||
}
|
||||
framed_ = true;
|
||||
has_command_ = escaped_ = discard_ = false;
|
||||
input_size_ = 0;
|
||||
return true;
|
||||
}
|
||||
if (!framed_ || discard_) return true;
|
||||
if (!has_command_) {
|
||||
has_command_ = true;
|
||||
command_ = byte;
|
||||
// Port 0 DATA always means radio2. Other ports and unsupported KISS
|
||||
// parameter commands cannot alter the radio or reach MeshCore radio1.
|
||||
if (command_ != 0 && command_ != 0xff) discard_ = true;
|
||||
return true;
|
||||
}
|
||||
if (escaped_) {
|
||||
escaped_ = false;
|
||||
if (byte == Tfend) byte = Fend;
|
||||
else if (byte == Tfesc) byte = Fesc;
|
||||
else { malformed(); return true; }
|
||||
} else if (byte == Fesc) {
|
||||
escaped_ = true;
|
||||
return true;
|
||||
}
|
||||
if (input_size_ >= MaximumFrame) { malformed(); return true; }
|
||||
input_[input_size_++] = byte;
|
||||
return true;
|
||||
}
|
||||
bool returned() const { return return_; }
|
||||
bool peekTransmit(const uint8_t*& bytes, size_t& length) const {
|
||||
if (!tx_count_) { bytes = nullptr; length = 0; return false; }
|
||||
bytes = tx_[tx_head_].bytes;
|
||||
length = tx_[tx_head_].length;
|
||||
return true;
|
||||
}
|
||||
void consumeTransmit() {
|
||||
if (tx_count_) { tx_head_ = (tx_head_ + 1) % QueueDepth; --tx_count_; }
|
||||
}
|
||||
bool queueReceive(const uint8_t* bytes, size_t length) {
|
||||
if (!length) { ++dropped_; return false; }
|
||||
return queueOutput(0, bytes, length);
|
||||
}
|
||||
bool queueReady() { const uint8_t value = 1; return queueOutput(Ready, &value, 1); }
|
||||
bool queueTxDone(bool success) {
|
||||
// Match the existing standalone modem's SetHardware extension. A command
|
||||
// byte of 0xf8 would instead mean port 15, command 8 to a generic parser.
|
||||
const uint8_t value[] = {TxDone, uint8_t(success ? 1 : 0)};
|
||||
return queueOutput(0x06, value, sizeof(value));
|
||||
}
|
||||
bool peekOutput(const uint8_t*& bytes, size_t& length) const {
|
||||
if (!out_count_) { bytes = nullptr; length = 0; return false; }
|
||||
bytes = out_[out_head_].bytes + output_offset_;
|
||||
length = out_[out_head_].length - output_offset_;
|
||||
return true;
|
||||
}
|
||||
void consumeOutput(size_t length) {
|
||||
if (!out_count_) return;
|
||||
const size_t remaining = out_[out_head_].length - output_offset_;
|
||||
if (length >= remaining) {
|
||||
output_offset_ = 0;
|
||||
out_head_ = (out_head_ + 1) % QueueDepth;
|
||||
--out_count_;
|
||||
} else output_offset_ += uint16_t(length);
|
||||
}
|
||||
uint8_t queuedTransmit() const { return tx_count_; }
|
||||
uint8_t queuedOutput() const { return out_count_; }
|
||||
uint32_t malformedFrames() const { return malformed_; }
|
||||
uint32_t droppedFrames() const { return dropped_; }
|
||||
};
|
||||
|
||||
}} // namespace mesh::hostkiss
|
||||
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
// A host-assisted raw radio2 modem in Full firmware. Keep this independent of
|
||||
// chip/Arduino headers: all translation units must choose identical layouts.
|
||||
// Full infrastructure builds opt in explicitly; reduced builds stay unchanged.
|
||||
#ifndef MESH_ENABLE_HOST_KISS
|
||||
#if (defined(ESP32_PLATFORM) || defined(NRF52_PLATFORM)) \
|
||||
&& defined(COMPANION_RADIO_FULL) && COMPANION_RADIO_FULL \
|
||||
&& !(defined(MESH_PRIMARY_ESPNOW) && MESH_PRIMARY_ESPNOW)
|
||||
#define MESH_ENABLE_HOST_KISS 1
|
||||
#else
|
||||
#define MESH_ENABLE_HOST_KISS 0
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// ESP-NOW-only images have no independent LoRa modem for this transport.
|
||||
#if defined(MESH_PRIMARY_ESPNOW) && MESH_PRIMARY_ESPNOW
|
||||
#undef MESH_ENABLE_HOST_KISS
|
||||
#define MESH_ENABLE_HOST_KISS 0
|
||||
#endif
|
||||
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include "HostKissConfig.h"
|
||||
|
||||
#if MESH_ENABLE_HOST_KISS
|
||||
namespace mesh {
|
||||
namespace hostkiss {
|
||||
// Called by the Dispatcher only after radio2 protocol/generation validation.
|
||||
// This is the sole RX consumer, before MeshCore logs, parsing and bridges.
|
||||
void receive(const uint8_t* bytes, size_t length, uint8_t profile,
|
||||
uint32_t generation, float snr, float rssi);
|
||||
void sendComplete(bool success);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,76 @@
|
||||
#include "HostKissSettings.h"
|
||||
#if MESH_ENABLE_HOST_KISS
|
||||
#include "CheckedSettingsFile.h"
|
||||
#include "FilePresence.h"
|
||||
#include "PersistentStoreFormat.h"
|
||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||
#include "AtomicFileWriter.h"
|
||||
#else
|
||||
#include "ContactFileTransaction.h"
|
||||
#endif
|
||||
|
||||
namespace mesh { namespace hostkiss {
|
||||
namespace {
|
||||
constexpr char Path[] = "/host_kiss";
|
||||
constexpr size_t ImageSize = 12;
|
||||
void encode(uint8_t (&image)[ImageSize], const Selection& value) {
|
||||
memset(image, 0, sizeof(image));
|
||||
memcpy(image, "HKS1", 4);
|
||||
image[4] = value.enabled ? 1 : 0;
|
||||
image[5] = uint8_t(value.transport);
|
||||
storage::writeLE32(image + 8, storage::updateCRC32(0xffffffff, image, 8));
|
||||
}
|
||||
bool valid(const uint8_t (&image)[ImageSize]) {
|
||||
return memcmp(image, "HKS1", 4) == 0 && image[4] <= 1 && image[5] <= 1
|
||||
&& image[6] == 0 && image[7] == 0
|
||||
&& storage::readLE32(image + 8) == storage::updateCRC32(0xffffffff, image, 8);
|
||||
}
|
||||
}
|
||||
bool Settings::begin(FILESYSTEM* fs) {
|
||||
fs_ = fs;
|
||||
healthy_ = false;
|
||||
selection_ = {};
|
||||
if (!fs_) return false;
|
||||
#if defined(ESP32_PLATFORM) || defined(RP2040_PLATFORM) || defined(ESP32)
|
||||
if (!ContactFileTransaction::recover(fs_, Path, filePresence<FILESYSTEM>)) return false;
|
||||
#endif
|
||||
bool present = false;
|
||||
if (!filePresence(fs_, Path, present)) return false;
|
||||
if (!present) { healthy_ = true; return true; }
|
||||
uint8_t image[ImageSize];
|
||||
const auto result = storage::readFixedSettingsFileResult(fs_, Path, image, sizeof(image));
|
||||
if (result == storage::FixedSettingsRead::Unreadable) return false;
|
||||
healthy_ = true; // A complete malformed record may be explicitly replaced.
|
||||
if (result != storage::FixedSettingsRead::Complete || !valid(image)) return false;
|
||||
selection_.enabled = image[4] != 0;
|
||||
selection_.transport = Transport(image[5]);
|
||||
return true;
|
||||
}
|
||||
bool Settings::save(const Selection& selection) {
|
||||
if (!healthy_ || !fs_ || uint8_t(selection.transport) > 1) return false;
|
||||
uint8_t image[ImageSize];
|
||||
encode(image, selection);
|
||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||
AtomicFileWriter writer(fs_, Path);
|
||||
const bool wrote = writer && writer.write(image, sizeof(image)) == sizeof(image);
|
||||
const bool saved = writer.commit(wrote);
|
||||
if (writer.outcome() == AtomicFileWriter::Outcome::Indeterminate) healthy_ = false;
|
||||
if (!saved) return false;
|
||||
#else
|
||||
ContactFileTransaction writer(fs_, Path, filePresence<FILESYSTEM>);
|
||||
if (!writer || writer.write(image, sizeof(image)) != sizeof(image)) {
|
||||
if (writer.outcome() == ContactFileTransaction::Outcome::Indeterminate) healthy_ = false;
|
||||
return false;
|
||||
}
|
||||
writer.commit();
|
||||
if (writer.outcome() == ContactFileTransaction::Outcome::NotCommitted) return false;
|
||||
#endif
|
||||
uint8_t verified[ImageSize];
|
||||
healthy_ = storage::readFixedSettingsFile(fs_, Path, verified, sizeof(verified))
|
||||
&& memcmp(image, verified, sizeof(image)) == 0;
|
||||
if (!healthy_) return false;
|
||||
selection_ = selection;
|
||||
return true;
|
||||
}
|
||||
}} // namespace mesh::hostkiss
|
||||
#endif
|
||||
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include <helpers/HostKissConfig.h>
|
||||
#if MESH_ENABLE_HOST_KISS
|
||||
#include <helpers/IdentityStore.h>
|
||||
|
||||
namespace mesh { namespace hostkiss {
|
||||
enum class Transport : uint8_t { Usb = 0, Tcp = 1 };
|
||||
struct Selection {
|
||||
bool enabled = false;
|
||||
Transport transport = Transport::Usb;
|
||||
bool operator==(const Selection& value) const {
|
||||
return enabled == value.enabled && transport == value.transport;
|
||||
}
|
||||
};
|
||||
class Settings {
|
||||
FILESYSTEM* fs_ = nullptr;
|
||||
bool healthy_ = false;
|
||||
Selection selection_;
|
||||
public:
|
||||
bool begin(FILESYSTEM* fs);
|
||||
bool save(const Selection& selection);
|
||||
const Selection& selection() const { return selection_; }
|
||||
bool healthy() const { return healthy_; }
|
||||
};
|
||||
}}
|
||||
#endif
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "BeaconFeatureConfig.h"
|
||||
#include "EthernetTunnelConfig.h"
|
||||
#include "HostKissConfig.h"
|
||||
#include "ota/OtaDirectConfig.h"
|
||||
|
||||
// Full Companion includes channel chat on both supported platforms. Keep this
|
||||
@@ -37,5 +38,5 @@
|
||||
// dispatcher slot. Direct OTA retains its temporary MeshCore profile; foreign
|
||||
// transports keep their existing radio2-only ownership.
|
||||
#ifndef MESH_ENABLE_FOREIGN_RADIO
|
||||
#define MESH_ENABLE_FOREIGN_RADIO (MESH_ENABLE_MESHTEXT_CHAT || MESH_ENABLE_ETHERNET_TUNNEL || MESH_ENABLE_OTA_KISS_DIRECT)
|
||||
#define MESH_ENABLE_FOREIGN_RADIO (MESH_ENABLE_MESHTEXT_CHAT || MESH_ENABLE_ETHERNET_TUNNEL || MESH_ENABLE_OTA_KISS_DIRECT || MESH_ENABLE_HOST_KISS)
|
||||
#endif
|
||||
@@ -184,7 +184,7 @@ bool RadioProfileCLI::prepareSavedImage(const char* path, const RadioProfileConf
|
||||
uint16_t preamble, RadioCrossMode cross, int8_t manual_secondary) {
|
||||
if (!fs_ || hold_) return false;
|
||||
uint8_t image[ImageSize] = {'R', '2', 1, (uint8_t)config.mode};
|
||||
#if MESH_ENABLE_MESHTEXT_CHAT || MESH_ENABLE_ETHERNET_TUNNEL
|
||||
#if MESH_ENABLE_MESHTEXT_CHAT || MESH_ENABLE_ETHERNET_TUNNEL || MESH_ENABLE_HOST_KISS
|
||||
// Version 2 keeps the 24-byte layout and CRC; bit 7 records explicit radio2
|
||||
// intent. Other settings can save an unused Off slot without claiming it.
|
||||
image[2] = 2;
|
||||
@@ -408,7 +408,7 @@ void RadioProfileCLI::begin(FILESYSTEM* fs, Radio* radio, RTCClock* rtc, bool in
|
||||
recoverable_corrupt_store_ = false;
|
||||
manual_secondary_ = false;
|
||||
secondary_intent_generation_ = 0;
|
||||
#if MESH_ENABLE_MESHTEXT_CHAT || MESH_ENABLE_ETHERNET_TUNNEL
|
||||
#if MESH_ENABLE_MESHTEXT_CHAT || MESH_ENABLE_ETHERNET_TUNNEL || MESH_ENABLE_HOST_KISS
|
||||
auto_secondary_requested_ = secondary_protocol_overlay_ = false;
|
||||
owns_secondary_runtime_ = external_secondary_override_ = false;
|
||||
#endif
|
||||
@@ -512,7 +512,7 @@ void RadioProfileCLI::publish() {
|
||||
? (validReplySetting(reply_setting_) ? reply_setting_ & 7 : RADIO_TX_BOTH) : RADIO_TX_AUTO;
|
||||
profiles->reply_force = infrastructure_replies_
|
||||
&& validReplySetting(reply_setting_) && (reply_setting_ & 8);
|
||||
#if MESH_ENABLE_MESHTEXT_CHAT || MESH_ENABLE_ETHERNET_TUNNEL
|
||||
#if MESH_ENABLE_MESHTEXT_CHAT || MESH_ENABLE_ETHERNET_TUNNEL || MESH_ENABLE_HOST_KISS
|
||||
protectExternalSecondary();
|
||||
if (external_secondary_override_) return;
|
||||
#endif
|
||||
@@ -522,7 +522,7 @@ void RadioProfileCLI::publish() {
|
||||
if (hasReplyMutation() && !remote_delivered_) return;
|
||||
publish_pending_ = false;
|
||||
RadioProfileConfig config = temp_active_ ? temporary_ : saved_;
|
||||
#if MESH_ENABLE_MESHTEXT_CHAT || MESH_ENABLE_ETHERNET_TUNNEL
|
||||
#if MESH_ENABLE_MESHTEXT_CHAT || MESH_ENABLE_ETHERNET_TUNNEL || MESH_ENABLE_HOST_KISS
|
||||
if (auto_secondary_requested_ && canAutoSecondary()) config = auto_secondary_;
|
||||
else if (secondary_protocol_overlay_ && manualSecondaryConfigured() && !secondaryRuntimeBusy()) {
|
||||
config.params.protocol = secondary_protocol_;
|
||||
@@ -530,7 +530,7 @@ void RadioProfileCLI::publish() {
|
||||
}
|
||||
#endif
|
||||
profiles->setSecondary(config, temp_active_);
|
||||
#if MESH_ENABLE_MESHTEXT_CHAT || MESH_ENABLE_ETHERNET_TUNNEL
|
||||
#if MESH_ENABLE_MESHTEXT_CHAT || MESH_ENABLE_ETHERNET_TUNNEL || MESH_ENABLE_HOST_KISS
|
||||
owns_secondary_runtime_ = auto_secondary_requested_ || secondary_protocol_overlay_;
|
||||
if (owns_secondary_runtime_) {
|
||||
owned_secondary_snapshot_ = config;
|
||||
@@ -539,7 +539,7 @@ void RadioProfileCLI::publish() {
|
||||
#endif
|
||||
}
|
||||
|
||||
#if MESH_ENABLE_MESHTEXT_CHAT || MESH_ENABLE_ETHERNET_TUNNEL
|
||||
#if MESH_ENABLE_MESHTEXT_CHAT || MESH_ENABLE_ETHERNET_TUNNEL || MESH_ENABLE_HOST_KISS
|
||||
bool RadioProfileCLI::runtimeSecondaryUnchanged() const {
|
||||
if (!owns_secondary_runtime_ || !radio_ || !radio_->profiles()) return true;
|
||||
const auto* profiles = radio_->profiles();
|
||||
@@ -564,7 +564,7 @@ bool RadioProfileCLI::hasSecondarySchedule() const {
|
||||
}
|
||||
|
||||
bool RadioProfileCLI::secondaryRuntimeBusy() const {
|
||||
#if MESH_ENABLE_MESHTEXT_CHAT || MESH_ENABLE_ETHERNET_TUNNEL
|
||||
#if MESH_ENABLE_MESHTEXT_CHAT || MESH_ENABLE_ETHERNET_TUNNEL || MESH_ENABLE_HOST_KISS
|
||||
if (external_secondary_override_ || !runtimeSecondaryUnchanged()) return true;
|
||||
#endif
|
||||
if (!fs_ || !radio_) return true;
|
||||
@@ -576,14 +576,14 @@ bool RadioProfileCLI::secondaryRuntimeBusy() const {
|
||||
|
||||
void RadioProfileCLI::noteSecondaryManualChange() {
|
||||
++secondary_intent_generation_;
|
||||
#if MESH_ENABLE_MESHTEXT_CHAT || MESH_ENABLE_ETHERNET_TUNNEL
|
||||
#if MESH_ENABLE_MESHTEXT_CHAT || MESH_ENABLE_ETHERNET_TUNNEL || MESH_ENABLE_HOST_KISS
|
||||
auto_secondary_requested_ = secondary_protocol_overlay_ = false;
|
||||
owns_secondary_runtime_ = external_secondary_override_ = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool RadioProfileCLI::canAutoSecondary() const {
|
||||
#if MESH_ENABLE_MESHTEXT_CHAT || MESH_ENABLE_ETHERNET_TUNNEL
|
||||
#if MESH_ENABLE_MESHTEXT_CHAT || MESH_ENABLE_ETHERNET_TUNNEL || MESH_ENABLE_HOST_KISS
|
||||
if (manual_secondary_ || saved_.mode != RadioProfileMode::Off || secondaryRuntimeBusy()) return false;
|
||||
const auto& current = radio_->profiles()->secondary;
|
||||
return current.mode == RadioProfileMode::Off || (auto_secondary_requested_
|
||||
@@ -594,7 +594,7 @@ bool RadioProfileCLI::canAutoSecondary() const {
|
||||
}
|
||||
|
||||
bool RadioProfileCLI::requestAutoSecondary(const RadioProfileConfig& config) {
|
||||
#if MESH_ENABLE_MESHTEXT_CHAT || MESH_ENABLE_ETHERNET_TUNNEL
|
||||
#if MESH_ENABLE_MESHTEXT_CHAT || MESH_ENABLE_ETHERNET_TUNNEL || MESH_ENABLE_HOST_KISS
|
||||
if (!canAutoSecondary() || config.mode == RadioProfileMode::Off
|
||||
|| (uint8_t)config.mode > (uint8_t)RadioProfileMode::RxTx
|
||||
|| !radio_->validateProfile(config.params)) return false;
|
||||
@@ -614,7 +614,7 @@ bool RadioProfileCLI::requestAutoSecondary(const RadioProfileConfig& config) {
|
||||
}
|
||||
|
||||
void RadioProfileCLI::clearAutoSecondary() {
|
||||
#if MESH_ENABLE_MESHTEXT_CHAT || MESH_ENABLE_ETHERNET_TUNNEL
|
||||
#if MESH_ENABLE_MESHTEXT_CHAT || MESH_ENABLE_ETHERNET_TUNNEL || MESH_ENABLE_HOST_KISS
|
||||
if (!auto_secondary_requested_) return;
|
||||
protectExternalSecondary();
|
||||
auto_secondary_requested_ = false;
|
||||
@@ -624,7 +624,7 @@ void RadioProfileCLI::clearAutoSecondary() {
|
||||
}
|
||||
|
||||
bool RadioProfileCLI::autoSecondaryActive() const {
|
||||
#if MESH_ENABLE_MESHTEXT_CHAT || MESH_ENABLE_ETHERNET_TUNNEL
|
||||
#if MESH_ENABLE_MESHTEXT_CHAT || MESH_ENABLE_ETHERNET_TUNNEL || MESH_ENABLE_HOST_KISS
|
||||
if (!auto_secondary_requested_ || !canAutoSecondary()) return false;
|
||||
const auto& current = radio_->profiles()->secondary;
|
||||
return !radio_->profiles()->secondary_temporary && current.mode == auto_secondary_.mode
|
||||
@@ -635,7 +635,7 @@ bool RadioProfileCLI::autoSecondaryActive() const {
|
||||
}
|
||||
|
||||
bool RadioProfileCLI::useSecondaryProtocol(RadioProfileProtocol protocol) {
|
||||
#if MESH_ENABLE_MESHTEXT_CHAT || MESH_ENABLE_ETHERNET_TUNNEL
|
||||
#if MESH_ENABLE_MESHTEXT_CHAT || MESH_ENABLE_ETHERNET_TUNNEL || MESH_ENABLE_HOST_KISS
|
||||
if (protocol == RadioProfileProtocol::MeshCore) {
|
||||
clearSecondaryProtocol();
|
||||
return true;
|
||||
@@ -646,6 +646,9 @@ bool RadioProfileCLI::useSecondaryProtocol(RadioProfileProtocol protocol) {
|
||||
#endif
|
||||
#if MESH_ENABLE_ETHERNET_TUNNEL
|
||||
protocol == RadioProfileProtocol::Ethernet ||
|
||||
#endif
|
||||
#if MESH_ENABLE_HOST_KISS
|
||||
protocol == RadioProfileProtocol::Kiss ||
|
||||
#endif
|
||||
false;
|
||||
if (!supported_protocol || !manualSecondaryConfigured()
|
||||
@@ -669,7 +672,7 @@ bool RadioProfileCLI::useSecondaryProtocol(RadioProfileProtocol protocol) {
|
||||
}
|
||||
|
||||
void RadioProfileCLI::clearSecondaryProtocol() {
|
||||
#if MESH_ENABLE_MESHTEXT_CHAT || MESH_ENABLE_ETHERNET_TUNNEL
|
||||
#if MESH_ENABLE_MESHTEXT_CHAT || MESH_ENABLE_ETHERNET_TUNNEL || MESH_ENABLE_HOST_KISS
|
||||
if (!secondary_protocol_overlay_) return;
|
||||
protectExternalSecondary();
|
||||
secondary_protocol_overlay_ = false;
|
||||
@@ -1146,7 +1149,7 @@ bool RadioProfileCLI::handle(const char* command, char* reply, size_t capacity,
|
||||
return true;
|
||||
}
|
||||
if ((base || temporary) && verb == Get && !*args) {
|
||||
#if MESH_ENABLE_MESHTEXT_CHAT || MESH_ENABLE_ETHERNET_TUNNEL
|
||||
#if MESH_ENABLE_MESHTEXT_CHAT || MESH_ENABLE_ETHERNET_TUNNEL || MESH_ENABLE_HOST_KISS
|
||||
if (base && autoSecondaryActive()) {
|
||||
formatConfig(reply, capacity, auto_secondary_, false, 0, true);
|
||||
return true;
|
||||
@@ -1161,7 +1164,7 @@ bool RadioProfileCLI::handle(const char* command, char* reply, size_t capacity,
|
||||
temporary, temp_pending_ ? pending_duration_ms_ : temp_remaining_ms_);
|
||||
return true;
|
||||
}
|
||||
#if MESH_ENABLE_MESHTEXT_CHAT || MESH_ENABLE_ETHERNET_TUNNEL
|
||||
#if MESH_ENABLE_MESHTEXT_CHAT || MESH_ENABLE_ETHERNET_TUNNEL || MESH_ENABLE_HOST_KISS
|
||||
if (base && verb == Set && !strcmp(args, "auto")) {
|
||||
// This clears only saved permission, never an active/pending lease or a
|
||||
// schedule. Remote acknowledgement staging must not weaken that contract.
|
||||
@@ -1322,7 +1325,7 @@ bool RadioProfileCLI::handle(const char* command, char* reply, size_t capacity,
|
||||
noteSecondaryManualChange();
|
||||
pending_temporary_ = config; pending_duration_ms_ = minutes * 60000UL;
|
||||
temp_start_ms_ = millis() + 2000; temp_pending_ = true;
|
||||
#if MESH_ENABLE_MESHTEXT_CHAT || MESH_ENABLE_ETHERNET_TUNNEL
|
||||
#if MESH_ENABLE_MESHTEXT_CHAT || MESH_ENABLE_ETHERNET_TUNNEL || MESH_ENABLE_HOST_KISS
|
||||
publish_pending_ = true; publish_after_ms_ = temp_start_ms_;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ class RadioProfileCLI {
|
||||
// Saved Off can be an explicit veto, rather than an unused secondary slot.
|
||||
bool manual_secondary_ = false;
|
||||
uint32_t secondary_intent_generation_ = 0;
|
||||
#if MESH_ENABLE_MESHTEXT_CHAT || MESH_ENABLE_ETHERNET_TUNNEL
|
||||
#if MESH_ENABLE_MESHTEXT_CHAT || MESH_ENABLE_ETHERNET_TUNNEL || MESH_ENABLE_HOST_KISS
|
||||
RadioProfileConfig auto_secondary_;
|
||||
bool auto_secondary_requested_ = false;
|
||||
bool secondary_protocol_overlay_ = false;
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <atomic>
|
||||
|
||||
namespace mesh {
|
||||
|
||||
// Guard cached text Stream references as well as new port lookups. Binary
|
||||
// owners write through their separate raw facade and are never wrapped here.
|
||||
class UsbBinaryTextGuard : public Stream {
|
||||
std::atomic<Stream*> _port{nullptr};
|
||||
bool (*_owned)();
|
||||
bool (*_enter)();
|
||||
void (*_leave)();
|
||||
bool enter() { return _enter ? _enter() : !_owned(); }
|
||||
void leave() { if (_leave) _leave(); }
|
||||
public:
|
||||
explicit UsbBinaryTextGuard(bool (*owned)(), bool (*enter)() = nullptr,
|
||||
void (*leave)() = nullptr)
|
||||
: _owned(owned), _enter(enter), _leave(leave) { }
|
||||
void use(Stream& port) { _port.store(&port, std::memory_order_release); }
|
||||
int available() override {
|
||||
auto* port = _port.load(std::memory_order_acquire);
|
||||
return !_owned() && port ? port->available() : 0;
|
||||
}
|
||||
int read() override {
|
||||
auto* port = _port.load(std::memory_order_acquire);
|
||||
return !_owned() && port ? port->read() : -1;
|
||||
}
|
||||
int peek() override {
|
||||
auto* port = _port.load(std::memory_order_acquire);
|
||||
return !_owned() && port ? port->peek() : -1;
|
||||
}
|
||||
void flush() override {
|
||||
if (!enter()) return;
|
||||
auto* port = _port.load(std::memory_order_acquire);
|
||||
if (port) port->flush();
|
||||
leave();
|
||||
}
|
||||
int availableForWrite() override {
|
||||
auto* port = _port.load(std::memory_order_acquire);
|
||||
return !_owned() && port ? port->availableForWrite() : 0;
|
||||
}
|
||||
size_t write(uint8_t byte) override { return write(&byte, 1); }
|
||||
size_t write(const uint8_t* data, size_t length) override {
|
||||
if (!data) return 0;
|
||||
// Suppressed text is deliberately consumed, never deferred into the next
|
||||
// CLI session. The caller must not retry it after the raw owner releases.
|
||||
if (!enter()) return length;
|
||||
auto* port = _port.load(std::memory_order_acquire);
|
||||
const size_t written = port ? port->write(data, length) : 0;
|
||||
leave();
|
||||
return written;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace mesh
|
||||
@@ -1,5 +1,8 @@
|
||||
#include "UsbLogging.h"
|
||||
#include "UsbLoggingClientActivity.h"
|
||||
#if MESH_ENABLE_HOST_KISS
|
||||
#include "UsbBinaryTextGuard.h"
|
||||
#endif
|
||||
|
||||
#if defined(ARDUINO)
|
||||
#include <Arduino.h>
|
||||
@@ -91,6 +94,19 @@ static std::atomic<bool> usb_logging_enabled{true};
|
||||
#endif
|
||||
static std::atomic<bool> usb_logging_preference_known{false};
|
||||
static std::atomic<bool> usb_debug_enabled{false};
|
||||
#if MESH_ENABLE_HOST_KISS
|
||||
static std::atomic<bool> usb_binary_stream_owned{false};
|
||||
static std::atomic<uint32_t> usb_text_writers{0};
|
||||
|
||||
static bool beginUsbTextWrite() {
|
||||
if (usb_binary_stream_owned.load()) return false;
|
||||
usb_text_writers.fetch_add(1);
|
||||
if (!usb_binary_stream_owned.load()) return true;
|
||||
usb_text_writers.fetch_sub(1);
|
||||
return false;
|
||||
}
|
||||
static void endUsbTextWrite() { usb_text_writers.fetch_sub(1); }
|
||||
#endif
|
||||
static std::atomic<uint32_t> usb_logging_tx_progress{0};
|
||||
static std::atomic<bool> usb_logging_tx_waiting{false};
|
||||
#if MESH_ESP32_TINYUSB_NONBLOCKING \
|
||||
@@ -364,7 +380,11 @@ static void serviceEsp32TinyUsbPorts() {
|
||||
esp32_tinyusb_terminal_reported_dropped_bytes =
|
||||
esp32_tinyusb_terminal_dropped_bytes.load(std::memory_order_relaxed);
|
||||
}
|
||||
if (canAccessEsp32TinyUsb(nullptr)) {
|
||||
if (isUsbBinaryStreamOwned()) {
|
||||
esp32_tinyusb_text_queue.discardPending();
|
||||
esp32_tinyusb_terminal_reported_dropped_bytes =
|
||||
esp32_tinyusb_terminal_dropped_bytes.load(std::memory_order_relaxed);
|
||||
} else if (canAccessEsp32TinyUsb(nullptr)) {
|
||||
esp32_tinyusb_text_queue.service();
|
||||
const uint32_t dropped =
|
||||
esp32_tinyusb_terminal_dropped_bytes.load(std::memory_order_relaxed);
|
||||
@@ -1245,6 +1265,31 @@ static WholeRecordNonBlockingStream<>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if MESH_ENABLE_HOST_KISS
|
||||
bool isUsbBinaryStreamOwned() {
|
||||
return usb_binary_stream_owned.load();
|
||||
}
|
||||
|
||||
void setUsbBinaryStreamOwned(bool owned) {
|
||||
usb_binary_stream_owned.store(owned);
|
||||
if (owned) {
|
||||
discardUsbTerminalOutput();
|
||||
#if MESH_ESP32_HWCDC_SESSION_GUARD
|
||||
invalidateEsp32SdkLogging();
|
||||
#endif
|
||||
}
|
||||
setPlatformDebugOutputEnabled(isUsbDebugLoggingEnabled());
|
||||
}
|
||||
|
||||
bool isUsbBinaryStreamReady() {
|
||||
// The producer may have obtained its cached Stream before ownership changed.
|
||||
// Never wait for it here: the raw session retries this gate next loop.
|
||||
if (!isUsbBinaryStreamOwned() || usb_text_writers.load() != 0) return false;
|
||||
discardUsbTerminalOutput();
|
||||
return !hasPendingUsbTerminalOutput();
|
||||
}
|
||||
#endif
|
||||
|
||||
bool isUsbLoggingEnabled() {
|
||||
return usb_logging_enabled.load(std::memory_order_relaxed);
|
||||
}
|
||||
@@ -1598,7 +1643,7 @@ bool resetUsbCompanionTransport() {
|
||||
return true;
|
||||
}
|
||||
|
||||
Stream& usbLoggingPort() {
|
||||
static Stream& rawUsbLoggingPort() {
|
||||
#if defined(MESH_DUAL_CDC_LOGGING)
|
||||
if (!isUsbLoggingPacketStream()) {
|
||||
if (isUsbLoggingEnabled() && dedicated_usb_logging_port_started
|
||||
@@ -1620,6 +1665,16 @@ Stream& usbLoggingPort() {
|
||||
#endif
|
||||
}
|
||||
|
||||
Stream& usbLoggingPort() {
|
||||
#if MESH_ENABLE_HOST_KISS
|
||||
static UsbBinaryTextGuard guarded(isUsbBinaryStreamOwned, beginUsbTextWrite, endUsbTextWrite);
|
||||
guarded.use(rawUsbLoggingPort());
|
||||
return guarded;
|
||||
#else
|
||||
return rawUsbLoggingPort();
|
||||
#endif
|
||||
}
|
||||
|
||||
Stream& usbDebugPort() {
|
||||
if (!isUsbDebugLoggingEnabled()) return null_usb_logging_stream;
|
||||
return usbLoggingPort();
|
||||
@@ -1655,7 +1710,7 @@ Stream& usbMotaPort() {
|
||||
#endif
|
||||
}
|
||||
|
||||
Stream& usbTerminalPort(bool enabled) {
|
||||
static Stream& rawUsbTerminalPort(bool enabled) {
|
||||
if (!enabled) return null_usb_logging_stream;
|
||||
#if MESH_ESP32_TINYUSB_NONBLOCKING
|
||||
return buffered_esp32_tinyusb_terminal_port;
|
||||
@@ -1666,7 +1721,7 @@ Stream& usbTerminalPort(bool enabled) {
|
||||
#endif
|
||||
}
|
||||
|
||||
Stream& usbConsolePort() {
|
||||
static Stream& rawUsbConsolePort() {
|
||||
#if MESH_ESP32_TINYUSB_NONBLOCKING
|
||||
return buffered_esp32_tinyusb_terminal_port;
|
||||
#elif MESH_ESP32_HWCDC_SESSION_GUARD
|
||||
@@ -1678,7 +1733,29 @@ Stream& usbConsolePort() {
|
||||
#endif
|
||||
}
|
||||
|
||||
Stream& usbTerminalPort(bool enabled) {
|
||||
#if MESH_ENABLE_HOST_KISS
|
||||
static UsbBinaryTextGuard guarded(isUsbBinaryStreamOwned, beginUsbTextWrite, endUsbTextWrite);
|
||||
if (!enabled) return null_usb_logging_stream;
|
||||
guarded.use(rawUsbTerminalPort(true));
|
||||
return guarded;
|
||||
#else
|
||||
return rawUsbTerminalPort(enabled);
|
||||
#endif
|
||||
}
|
||||
|
||||
Stream& usbConsolePort() {
|
||||
#if MESH_ENABLE_HOST_KISS
|
||||
static UsbBinaryTextGuard guarded(isUsbBinaryStreamOwned, beginUsbTextWrite, endUsbTextWrite);
|
||||
guarded.use(rawUsbConsolePort());
|
||||
return guarded;
|
||||
#else
|
||||
return rawUsbConsolePort();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool canAcceptUsbConsoleCommand() {
|
||||
if (isUsbBinaryStreamOwned()) return false;
|
||||
#if MESH_ESP32_TINYUSB_NONBLOCKING
|
||||
return buffered_esp32_tinyusb_terminal_port.availableForWrite()
|
||||
>= static_cast<int>(esp32_tinyusb_functional_reserve);
|
||||
@@ -1709,6 +1786,11 @@ void serviceUsbTerminalPort() {
|
||||
primary_usb_terminal_discard_pending.store(true, std::memory_order_release);
|
||||
return;
|
||||
}
|
||||
if (isUsbBinaryStreamOwned()) {
|
||||
if (!buffered_primary_usb_terminal_port.tryDiscardPending())
|
||||
primary_usb_terminal_discard_pending.store(true, std::memory_order_release);
|
||||
return;
|
||||
}
|
||||
buffered_primary_usb_terminal_port.service();
|
||||
#elif MESH_ESP32_HWCDC_SESSION_GUARD
|
||||
serviceEsp32HwcdcTxKick();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "UsbLoggingStatus.h"
|
||||
#include "HostKissConfig.h"
|
||||
|
||||
// Canonical USB-capable images compile at least one of these two diagnostics.
|
||||
// A master USB output gate and a separate debug-verbosity gate keep packet
|
||||
@@ -53,6 +54,19 @@
|
||||
|
||||
namespace mesh {
|
||||
|
||||
// A raw host session owns the primary USB byte stream without changing the
|
||||
// saved packet/debug logging preference. Functional text and diagnostics must
|
||||
// stay out of that binary stream, including writers retaining a Stream reference.
|
||||
#if MESH_ENABLE_HOST_KISS
|
||||
bool isUsbBinaryStreamOwned();
|
||||
void setUsbBinaryStreamOwned(bool owned);
|
||||
bool isUsbBinaryStreamReady();
|
||||
#else
|
||||
inline bool isUsbBinaryStreamOwned() { return false; }
|
||||
inline void setUsbBinaryStreamOwned(bool) { }
|
||||
inline bool isUsbBinaryStreamReady() { return true; }
|
||||
#endif
|
||||
|
||||
#ifndef MESH_ESP32_USB_TX_BUFFER_SIZE
|
||||
#define MESH_ESP32_USB_TX_BUFFER_SIZE 4096
|
||||
#endif
|
||||
@@ -83,7 +97,7 @@ void setUsbLoggingEnabled(bool enabled);
|
||||
bool isUsbDebugEnabled();
|
||||
void setUsbDebugEnabled(bool enabled);
|
||||
inline bool isUsbDebugLoggingEnabled() {
|
||||
return isUsbLoggingEnabled() && isUsbDebugEnabled();
|
||||
return !isUsbBinaryStreamOwned() && isUsbLoggingEnabled() && isUsbDebugEnabled();
|
||||
}
|
||||
|
||||
// Kept as a platform-neutral persistence hook. Single-TTY builds need no
|
||||
|
||||
@@ -19,6 +19,9 @@ public:
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE
|
||||
bool supportsMeshtext() const override { return true; }
|
||||
#endif
|
||||
#if MESH_ENABLE_HOST_KISS
|
||||
bool supportsHostKiss() const override { return true; }
|
||||
#endif
|
||||
|
||||
void powerOff() {
|
||||
if (isCarrierWaveActive()) setCarrierWave(0, 0);
|
||||
@@ -26,10 +29,14 @@ public:
|
||||
}
|
||||
|
||||
protected:
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE || MESH_ENABLE_HOST_KISS
|
||||
bool applyProtocol(mesh::RadioProfileProtocol protocol) override {
|
||||
if (protocol != mesh::RadioProfileProtocol::MeshCore
|
||||
&& protocol != mesh::RadioProfileProtocol::Meshtext) return false;
|
||||
&& protocol != mesh::RadioProfileProtocol::Meshtext
|
||||
#if MESH_ENABLE_HOST_KISS
|
||||
&& protocol != mesh::RadioProfileProtocol::Kiss
|
||||
#endif
|
||||
) return false;
|
||||
const uint8_t sync = protocol == mesh::RadioProfileProtocol::Meshtext ? 0x2b : 0x12;
|
||||
return static_cast<CustomLLCC68*>(_radio)->setProfileProtocol(sync) == RADIOLIB_ERR_NONE;
|
||||
}
|
||||
|
||||
@@ -35,6 +35,9 @@ public:
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE
|
||||
bool supportsMeshtext() const override { return true; }
|
||||
#endif
|
||||
#if MESH_ENABLE_HOST_KISS
|
||||
bool supportsHostKiss() const override { return true; }
|
||||
#endif
|
||||
|
||||
void setDeepInitCallback(DeepInitCallback callback) { _deep_init = callback; }
|
||||
|
||||
@@ -48,10 +51,14 @@ protected:
|
||||
void setDriverSwitchFeatures(uint8_t features) override {
|
||||
static_cast<CustomLR1110*>(_radio)->setProfileSwitchFeatures(features);
|
||||
}
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE || MESH_ENABLE_HOST_KISS
|
||||
bool applyProtocol(mesh::RadioProfileProtocol protocol) override {
|
||||
if (protocol != mesh::RadioProfileProtocol::MeshCore
|
||||
&& protocol != mesh::RadioProfileProtocol::Meshtext) return false;
|
||||
&& protocol != mesh::RadioProfileProtocol::Meshtext
|
||||
#if MESH_ENABLE_HOST_KISS
|
||||
&& protocol != mesh::RadioProfileProtocol::Kiss
|
||||
#endif
|
||||
) return false;
|
||||
const uint8_t sync = protocol == mesh::RadioProfileProtocol::Meshtext ? 0x2b : 0x12;
|
||||
return static_cast<CustomLR1110*>(_radio)->setProfileProtocol(sync) == RADIOLIB_ERR_NONE;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,9 @@ public:
|
||||
bool supportsMorseRx() const override { return MESH_ENABLE_MORSE_RX && supportsRadioDeepInit(); }
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE
|
||||
bool supportsMeshtext() const override { return true; }
|
||||
#endif
|
||||
#if MESH_ENABLE_HOST_KISS
|
||||
bool supportsHostKiss() const override { return true; }
|
||||
#endif
|
||||
void setDeepInitCallback(DeepInitCallback callback) { _deep_init = callback; }
|
||||
|
||||
@@ -34,10 +37,14 @@ public:
|
||||
}
|
||||
|
||||
protected:
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE || MESH_ENABLE_HOST_KISS
|
||||
bool applyProtocol(mesh::RadioProfileProtocol protocol) override {
|
||||
if (protocol != mesh::RadioProfileProtocol::MeshCore
|
||||
&& protocol != mesh::RadioProfileProtocol::Meshtext) return false;
|
||||
&& protocol != mesh::RadioProfileProtocol::Meshtext
|
||||
#if MESH_ENABLE_HOST_KISS
|
||||
&& protocol != mesh::RadioProfileProtocol::Kiss
|
||||
#endif
|
||||
) return false;
|
||||
const uint8_t sync = protocol == mesh::RadioProfileProtocol::Meshtext ? 0x2b : 0x12;
|
||||
return static_cast<CustomLR1121*>(_radio)->setProfileProtocol(sync) == RADIOLIB_ERR_NONE;
|
||||
}
|
||||
|
||||
@@ -173,6 +173,9 @@ public:
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE
|
||||
bool supportsMeshtext() const override { return true; }
|
||||
#endif
|
||||
#if MESH_ENABLE_HOST_KISS
|
||||
bool supportsHostKiss() const override { return true; }
|
||||
#endif
|
||||
|
||||
void powerOff() {
|
||||
if (isCarrierWaveActive()) setCarrierWave(0, 0);
|
||||
@@ -198,10 +201,14 @@ protected:
|
||||
recordApply(stage, status);
|
||||
}
|
||||
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE || MESH_ENABLE_HOST_KISS
|
||||
bool applyProtocol(mesh::RadioProfileProtocol protocol) override {
|
||||
if (protocol != mesh::RadioProfileProtocol::MeshCore
|
||||
&& protocol != mesh::RadioProfileProtocol::Meshtext) return false;
|
||||
&& protocol != mesh::RadioProfileProtocol::Meshtext
|
||||
#if MESH_ENABLE_HOST_KISS
|
||||
&& protocol != mesh::RadioProfileProtocol::Kiss
|
||||
#endif
|
||||
) return false;
|
||||
const uint8_t sync = protocol == mesh::RadioProfileProtocol::Meshtext ? 0x2b : 0x12;
|
||||
return recordApply(mesh::lr2021::ApplyStatus::Stage::Protocol,
|
||||
static_cast<CustomLR2021*>(_radio)->setProfileProtocol(sync)) == RADIOLIB_ERR_NONE;
|
||||
|
||||
@@ -31,17 +31,23 @@ public:
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE
|
||||
bool supportsMeshtext() const override { return true; }
|
||||
#endif
|
||||
#if MESH_ENABLE_HOST_KISS
|
||||
bool supportsHostKiss() const override { return true; }
|
||||
#endif
|
||||
#if MESH_ENABLE_MORSE_RX
|
||||
bool supportsMorseRx() const override { return supportsRadioDeepInit(); }
|
||||
#endif
|
||||
void setDeepInitCallback(DeepInitCallback callback) { _deep_init = callback; }
|
||||
|
||||
protected:
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE || MESH_ENABLE_HOST_KISS
|
||||
bool applyProtocol(mesh::RadioProfileProtocol protocol) override {
|
||||
uint8_t sync;
|
||||
if (protocol == mesh::RadioProfileProtocol::MeshCore) sync = 0x12;
|
||||
else if (protocol == mesh::RadioProfileProtocol::Meshtext) sync = 0x2b;
|
||||
#if MESH_ENABLE_HOST_KISS
|
||||
else if (protocol == mesh::RadioProfileProtocol::Kiss) sync = 0x12;
|
||||
#endif
|
||||
else return false;
|
||||
return static_cast<CustomSTM32WLx*>(_radio)->setProfileProtocol(sync) == RADIOLIB_ERR_NONE;
|
||||
}
|
||||
|
||||
@@ -69,6 +69,9 @@ public:
|
||||
}
|
||||
~CustomSX1262Wrapper() { unregisterInterruptAction(); }
|
||||
CustomSX1262Wrapper(CustomSX1262& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { }
|
||||
#if MESH_ENABLE_HOST_KISS
|
||||
bool supportsHostKiss() const override { return true; }
|
||||
#endif
|
||||
bool supportsCarrierWave() const override { return true; }
|
||||
bool supportsMorseRx() const override { return MESH_ENABLE_MORSE_RX && supportsRadioDeepInit(); }
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE
|
||||
@@ -103,7 +106,7 @@ protected:
|
||||
return restoreSX126xPacketRamp(static_cast<CustomSX1262*>(_radio)) == RADIOLIB_ERR_NONE
|
||||
&& RadioLibWrapper::restoreCarrierWaveModem();
|
||||
}
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE || MESH_ENABLE_ETHERNET_WIRE
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE || MESH_ENABLE_ETHERNET_WIRE || MESH_ENABLE_HOST_KISS
|
||||
bool applyProtocol(mesh::RadioProfileProtocol protocol) override {
|
||||
uint8_t sync = 0x12;
|
||||
if (protocol == mesh::RadioProfileProtocol::Meshtext) {
|
||||
@@ -113,11 +116,15 @@ protected:
|
||||
} else if (protocol == mesh::RadioProfileProtocol::Ethernet) {
|
||||
if (!supportsEthernetTunnel()) return false;
|
||||
sync = 0x16;
|
||||
#endif
|
||||
#if MESH_ENABLE_HOST_KISS
|
||||
} else if (protocol == mesh::RadioProfileProtocol::Kiss) {
|
||||
if (!supportsHostKiss()) return false;
|
||||
#endif
|
||||
} else if (protocol != mesh::RadioProfileProtocol::MeshCore) return false;
|
||||
// No alternate wire implementation means no modem write is needed.
|
||||
// Ethernet-only builds must still restore the MeshCore syncword.
|
||||
#if !MESH_ENABLE_ETHERNET_WIRE
|
||||
#if !MESH_ENABLE_ETHERNET_WIRE && !MESH_ENABLE_HOST_KISS
|
||||
if (!supportsMeshtext()) return protocol == mesh::RadioProfileProtocol::MeshCore;
|
||||
#endif
|
||||
auto* radio = static_cast<CustomSX1262*>(_radio);
|
||||
|
||||
@@ -23,6 +23,9 @@ public:
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE
|
||||
bool supportsMeshtext() const override { return true; }
|
||||
#endif
|
||||
#if MESH_ENABLE_HOST_KISS
|
||||
bool supportsHostKiss() const override { return true; }
|
||||
#endif
|
||||
|
||||
void powerOff() {
|
||||
if (isCarrierWaveActive()) setCarrierWave(0, 0);
|
||||
@@ -30,10 +33,14 @@ public:
|
||||
}
|
||||
|
||||
protected:
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE || MESH_ENABLE_HOST_KISS
|
||||
bool applyProtocol(mesh::RadioProfileProtocol protocol) override {
|
||||
if (protocol != mesh::RadioProfileProtocol::MeshCore
|
||||
&& protocol != mesh::RadioProfileProtocol::Meshtext) return false;
|
||||
&& protocol != mesh::RadioProfileProtocol::Meshtext
|
||||
#if MESH_ENABLE_HOST_KISS
|
||||
&& protocol != mesh::RadioProfileProtocol::Kiss
|
||||
#endif
|
||||
) return false;
|
||||
const uint8_t sync = protocol == mesh::RadioProfileProtocol::Meshtext ? 0x2b : 0x12;
|
||||
return static_cast<CustomSX1268*>(_radio)->setProfileProtocol(sync) == RADIOLIB_ERR_NONE;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@ class CustomSX1276Wrapper : public RadioLibWrapper {
|
||||
public:
|
||||
~CustomSX1276Wrapper() { unregisterInterruptAction(); }
|
||||
CustomSX1276Wrapper(CustomSX1276& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { }
|
||||
#if MESH_ENABLE_HOST_KISS
|
||||
bool supportsHostKiss() const override { return true; }
|
||||
#endif
|
||||
bool supportsCarrierWave() const override { return supportsRadioDeepInit(); }
|
||||
bool supportsMorseRx() const override { return MESH_ENABLE_MORSE_RX && supportsRadioDeepInit(); }
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE
|
||||
@@ -26,7 +29,7 @@ public:
|
||||
}
|
||||
|
||||
protected:
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE || MESH_ENABLE_ETHERNET_WIRE
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE || MESH_ENABLE_ETHERNET_WIRE || MESH_ENABLE_HOST_KISS
|
||||
bool applyProtocol(mesh::RadioProfileProtocol protocol) override {
|
||||
uint8_t sync = 0x12;
|
||||
if (protocol == mesh::RadioProfileProtocol::Meshtext) {
|
||||
@@ -36,6 +39,10 @@ protected:
|
||||
} else if (protocol == mesh::RadioProfileProtocol::Ethernet) {
|
||||
if (!supportsEthernetTunnel()) return false;
|
||||
sync = 0x16;
|
||||
#endif
|
||||
#if MESH_ENABLE_HOST_KISS
|
||||
} else if (protocol == mesh::RadioProfileProtocol::Kiss) {
|
||||
if (!supportsHostKiss()) return false;
|
||||
#endif
|
||||
} else if (protocol != mesh::RadioProfileProtocol::MeshCore) return false;
|
||||
return static_cast<CustomSX1276*>(_radio)->setProfileProtocol(sync) == RADIOLIB_ERR_NONE;
|
||||
|
||||
@@ -615,7 +615,7 @@ bool RadioLibWrapper::restoreAfterDeepInit() {
|
||||
restored = _radio->setPreambleLength(currentPreambleLength())
|
||||
== RADIOLIB_ERR_NONE;
|
||||
}
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE || MESH_ENABLE_ETHERNET_WIRE
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE || MESH_ENABLE_ETHERNET_WIRE || MESH_ENABLE_HOST_KISS
|
||||
restored = restored && applyProtocol(_physical_protocol);
|
||||
#endif
|
||||
if (_dbm_valid) {
|
||||
@@ -647,7 +647,7 @@ mesh::RadioParamApplyResult RadioLibWrapper::trySetParams(float freq, float bw,
|
||||
const uint8_t previous_sf = _cur_sf;
|
||||
const uint8_t previous_cr = _cur_cr;
|
||||
const uint16_t previous_preamble = _physical_preamble;
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE || MESH_ENABLE_ETHERNET_WIRE
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE || MESH_ENABLE_ETHERNET_WIRE || MESH_ENABLE_HOST_KISS
|
||||
const auto previous_protocol = _physical_protocol;
|
||||
#endif
|
||||
const uint8_t previous_profile = _active_profile;
|
||||
@@ -664,7 +664,7 @@ mesh::RadioParamApplyResult RadioLibWrapper::trySetParams(float freq, float bw,
|
||||
_physical_preamble = profilePreamble(0);
|
||||
|
||||
bool success = applyParams(freq, bw, sf, cr);
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE || MESH_ENABLE_ETHERNET_WIRE
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE || MESH_ENABLE_ETHERNET_WIRE || MESH_ENABLE_HOST_KISS
|
||||
success = success && applyProtocol(requested.protocol);
|
||||
if (success) _physical_protocol = requested.protocol;
|
||||
#endif
|
||||
@@ -701,7 +701,7 @@ mesh::RadioParamApplyResult RadioLibWrapper::trySetParams(float freq, float bw,
|
||||
_profile_generation = previous_generation;
|
||||
_profile_refresh_required = previous_refresh;
|
||||
_physical_preamble = previous_preamble;
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE || MESH_ENABLE_ETHERNET_WIRE
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE || MESH_ENABLE_ETHERNET_WIRE || MESH_ENABLE_HOST_KISS
|
||||
_physical_protocol = previous_protocol;
|
||||
#endif
|
||||
_rx_ps_enabled = previous_rxps;
|
||||
@@ -712,7 +712,7 @@ mesh::RadioParamApplyResult RadioLibWrapper::trySetParams(float freq, float bw,
|
||||
bool restored = had_previous_params
|
||||
&& _radio->standby() == RADIOLIB_ERR_NONE
|
||||
&& applyParams(previous_freq, previous_bw, previous_sf, previous_cr);
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE || MESH_ENABLE_ETHERNET_WIRE
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE || MESH_ENABLE_ETHERNET_WIRE || MESH_ENABLE_HOST_KISS
|
||||
restored = restored && applyProtocol(previous_protocol);
|
||||
#endif
|
||||
|
||||
@@ -745,6 +745,11 @@ bool RadioLibWrapper::validateProfile(const mesh::RadioProfileParams& p) const {
|
||||
if (!mesh::RadioProfiles::valid(p)) return false;
|
||||
#endif
|
||||
if (p.protocol == mesh::RadioProfileProtocol::Meshtext && !supportsMeshtext()) return false;
|
||||
#if MESH_ENABLE_HOST_KISS
|
||||
if (p.protocol == mesh::RadioProfileProtocol::Kiss && !supportsHostKiss()) return false;
|
||||
#else
|
||||
if (p.protocol == mesh::RadioProfileProtocol::Kiss) return false;
|
||||
#endif
|
||||
#if MESH_ENABLE_ETHERNET_WIRE
|
||||
if (p.protocol == mesh::RadioProfileProtocol::Ethernet && !supportsEthernetTunnel()) return false;
|
||||
#else
|
||||
@@ -846,12 +851,12 @@ mesh::RadioParamApplyResult RadioLibWrapper::tuneProfile(uint8_t profile) {
|
||||
const bool old_refresh = _profile_refresh_required;
|
||||
const bool old_params_valid = _params_valid;
|
||||
const uint16_t old_preamble = _physical_preamble;
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE || MESH_ENABLE_ETHERNET_WIRE
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE || MESH_ENABLE_ETHERNET_WIRE || MESH_ENABLE_HOST_KISS
|
||||
const auto old_protocol = _physical_protocol;
|
||||
#endif
|
||||
_physical_preamble = preamble;
|
||||
bool applied = applyParams(p.freq, p.bw, p.sf, p.cr);
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE || MESH_ENABLE_ETHERNET_WIRE
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE || MESH_ENABLE_ETHERNET_WIRE || MESH_ENABLE_HOST_KISS
|
||||
// Changing only the protocol still changes the sync word. Conversely a
|
||||
// failed write must not publish the new generation or cached wire format.
|
||||
if (applied && (_physical_protocol != p.protocol || _profile_refresh_required)) {
|
||||
@@ -903,12 +908,12 @@ mesh::RadioParamApplyResult RadioLibWrapper::tuneProfile(uint8_t profile) {
|
||||
_profile_generation = old_generation;
|
||||
_profile_refresh_required = old_refresh;
|
||||
_physical_preamble = old_preamble;
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE || MESH_ENABLE_ETHERNET_WIRE
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE || MESH_ENABLE_ETHERNET_WIRE || MESH_ENABLE_HOST_KISS
|
||||
_physical_protocol = old_protocol;
|
||||
#endif
|
||||
bool restored = old_params_valid && _radio->standby() == RADIOLIB_ERR_NONE
|
||||
&& applyParams(old_freq, old_bw, old_sf, old_cr);
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE || MESH_ENABLE_ETHERNET_WIRE
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE || MESH_ENABLE_ETHERNET_WIRE || MESH_ENABLE_HOST_KISS
|
||||
restored = restored && applyProtocol(old_protocol);
|
||||
#endif
|
||||
if (!restored) restored = restoreAfterDeepInit();
|
||||
@@ -1016,7 +1021,7 @@ void RadioLibWrapper::cacheBeaconRestoreParams() {
|
||||
_active_profile = profile;
|
||||
_profile_generation = _profiles.generation[profile];
|
||||
_physical_preamble = profilePreamble(profile);
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE || MESH_ENABLE_ETHERNET_WIRE
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE || MESH_ENABLE_ETHERNET_WIRE || MESH_ENABLE_HOST_KISS
|
||||
_physical_protocol = p.protocol;
|
||||
#endif
|
||||
_profile_refresh_required = true;
|
||||
@@ -1051,7 +1056,7 @@ mesh::RadioParamApplyResult RadioLibWrapper::prepareBeaconTransmit(
|
||||
? 16 : (p.preamble ? p.preamble : 32);
|
||||
bool applied = _radio->standby() == RADIOLIB_ERR_NONE
|
||||
&& applyParams(p.freq, p.bw, p.sf, p.cr);
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE || MESH_ENABLE_ETHERNET_WIRE
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE || MESH_ENABLE_ETHERNET_WIRE || MESH_ENABLE_HOST_KISS
|
||||
applied = applied && applyProtocol(p.protocol);
|
||||
if (applied) _physical_protocol = p.protocol;
|
||||
#endif
|
||||
@@ -1094,7 +1099,7 @@ mesh::RadioParamApplyResult RadioLibWrapper::restoreBeaconReceive() {
|
||||
&& _radio->standby() == RADIOLIB_ERR_NONE;
|
||||
beginProfileRetune(false);
|
||||
restored = restored && applyParams(p.freq, p.bw, p.sf, p.cr);
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE || MESH_ENABLE_ETHERNET_WIRE
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE || MESH_ENABLE_ETHERNET_WIRE || MESH_ENABLE_HOST_KISS
|
||||
restored = restored && applyProtocol(p.protocol);
|
||||
#endif
|
||||
restored = _dbm_applied = restored && applyCachedTxPower(_cur_dbm) == RADIOLIB_ERR_NONE;
|
||||
@@ -2954,7 +2959,7 @@ bool RadioLibWrapper::hilPacketTransmitAllowed() const {
|
||||
return mesh::hilTransmitFrequencyAllowed(_cur_freq)
|
||||
&& _cur_freq == p.freq && _cur_bw == p.bw && _cur_sf == p.sf && _cur_cr == p.cr
|
||||
&& _physical_preamble == profilePreamble(_active_profile)
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE || MESH_ENABLE_ETHERNET_WIRE
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE || MESH_ENABLE_ETHERNET_WIRE || MESH_ENABLE_HOST_KISS
|
||||
&& _physical_protocol == p.protocol
|
||||
#endif
|
||||
;
|
||||
|
||||
@@ -116,7 +116,7 @@ protected:
|
||||
mesh::RadioProfiles _profiles;
|
||||
uint8_t _active_profile = 0;
|
||||
uint16_t _physical_preamble = 0;
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE || MESH_ENABLE_ETHERNET_WIRE
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE || MESH_ENABLE_ETHERNET_WIRE || MESH_ENABLE_HOST_KISS
|
||||
mesh::RadioProfileProtocol _physical_protocol = mesh::RadioProfileProtocol::MeshCore;
|
||||
// Wire settings are a transaction with the modulation tuple. Unsupported
|
||||
// wrappers decline rather than claiming a profile was applied.
|
||||
|
||||
Reference in new issue
Block a user