mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-10-24 23:12:21 +00:00
Enable the shared terminal on WiFi-only Companions
This commit is contained in:
1 parent
c946adadfe
commit
373c2daebe
7 files changed
+65
-29
No files matched your search
@@ -2,6 +2,15 @@
|
||||
#include <helpers/CompanionJohnConfig.h>
|
||||
#include <helpers/ota/OtaMemoryPolicy.h>
|
||||
|
||||
// The role terminal is also used by WebConfig on WiFi-only Companions. Its
|
||||
// parser and reply tracking do not require a physical USB transport.
|
||||
#if defined(ENABLE_USB_INTERFACE) \
|
||||
|| (defined(ESP32_PLATFORM) && defined(WIFI_SSID) && !defined(WEBCONFIG_DISABLED))
|
||||
#define COMPANION_FEATURE_TEXT_TERMINAL 1
|
||||
#else
|
||||
#define COMPANION_FEATURE_TEXT_TERMINAL 0
|
||||
#endif
|
||||
|
||||
// Features are selected independently so adding a transport or changing an
|
||||
// image-size profile cannot accidentally remove unrelated Companion behavior.
|
||||
// Keep the legacy umbrella as a compatibility input for existing target files;
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
#endif
|
||||
|
||||
#include <helpers/CLICommandUtils.h>
|
||||
#ifdef ENABLE_USB_INTERFACE
|
||||
#if COMPANION_FEATURE_TEXT_TERMINAL
|
||||
#include <helpers/TracePathHelpers.h>
|
||||
#endif
|
||||
|
||||
@@ -224,7 +224,7 @@ static bool save_filter(const ContactInfo& c);
|
||||
// First 16 bytes of SHA-256("#testing"), encoded as base64.
|
||||
#define TESTING_GROUP_PSK "zeXoLPUVZH3LVHp5pPBl0Q=="
|
||||
|
||||
#ifdef ENABLE_USB_INTERFACE
|
||||
#if COMPANION_FEATURE_TEXT_TERMINAL
|
||||
static const char* terminalContactTypeName(uint8_t type) {
|
||||
if (type == ADV_TYPE_CHAT) return "Chat";
|
||||
if (type == ADV_TYPE_REPEATER) return "Repeater";
|
||||
@@ -708,7 +708,7 @@ void MyMesh::onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef ENABLE_USB_INTERFACE
|
||||
#if COMPANION_FEATURE_TEXT_TERMINAL
|
||||
if (hasTerminalOutput() && _terminal_display.shouldShowAdvert()) {
|
||||
Stream& output = terminalOutput();
|
||||
output.printf("\r\nADVERT from -> %s\r\n", contact.name);
|
||||
@@ -759,7 +759,7 @@ int MyMesh::getRecentlyHeard(AdvertPath dest[], int max_num) {
|
||||
return max_num;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_USB_INTERFACE
|
||||
#if COMPANION_FEATURE_TEXT_TERMINAL
|
||||
void MyMesh::onContactVisit(const ContactInfo& contact) {
|
||||
if (contact.type == ADV_TYPE_NONE) return;
|
||||
|
||||
@@ -779,7 +779,7 @@ void MyMesh::onContactPathUpdated(const ContactInfo &contact) {
|
||||
|
||||
scheduleContactWrite(contact);
|
||||
|
||||
#ifdef ENABLE_USB_INTERFACE
|
||||
#if COMPANION_FEATURE_TEXT_TERMINAL
|
||||
if (hasTerminalOutput()) {
|
||||
terminalOutput().printf("\r\nPATH updated -> %s\r\n> ", contact.name);
|
||||
}
|
||||
@@ -815,7 +815,7 @@ void MyMesh::expireExpectedAcks() {
|
||||
|
||||
if (entry.expires_at == now || millisHasNowPassed(entry.expires_at)) {
|
||||
if (!hasActiveRetries(entry.retry_key)) {
|
||||
#ifdef ENABLE_USB_INTERFACE
|
||||
#if COMPANION_FEATURE_TEXT_TERMINAL
|
||||
if (entry.terminal_origin && hasTerminalOutput()) {
|
||||
terminalOutput().print("\r\n ERROR: timed out, no ACK.\r\n> ");
|
||||
}
|
||||
@@ -872,7 +872,7 @@ ContactInfo* MyMesh::processAck(const uint8_t *data) {
|
||||
out_frame, 9);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_USB_INTERFACE
|
||||
#if COMPANION_FEATURE_TEXT_TERMINAL
|
||||
if (expected_ack_table[i].terminal_origin && hasTerminalOutput()) {
|
||||
terminalOutput().printf("\r\n Got ACK! (round trip: %lu ms)\r\n> ",
|
||||
(unsigned long)trip_time);
|
||||
@@ -927,7 +927,7 @@ void MyMesh::queueMessage(const ContactInfo &from, uint8_t txt_type, mesh::Packe
|
||||
_serial->writeFrame(frame, 1);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_USB_INTERFACE
|
||||
#if COMPANION_FEATURE_TEXT_TERMINAL
|
||||
if (hasTerminalOutput()) {
|
||||
Stream& output = terminalOutput();
|
||||
const char* kind = txt_type == TXT_TYPE_CLI_DATA ? "CLI" : "MSG";
|
||||
@@ -1058,7 +1058,7 @@ void MyMesh::onCommandDataRecv(const ContactInfo &from, mesh::Packet *pkt, uint3
|
||||
markConnectionActive(from); // in case this is from a server, and we have a connection
|
||||
bool terminal_command_reply = false;
|
||||
uint32_t terminal_command_elapsed_millis = 0;
|
||||
#ifdef ENABLE_USB_INTERFACE
|
||||
#if COMPANION_FEATURE_TEXT_TERMINAL
|
||||
terminal_command_reply = _terminal_command.takeReply(
|
||||
from.id.pub_key, _ms->getMillis(), terminal_command_elapsed_millis);
|
||||
if (terminal_command_reply) _terminal_command_target[0] = 0;
|
||||
@@ -1127,7 +1127,7 @@ void MyMesh::onChannelMessageRecv(const mesh::GroupChannel &channel, mesh::Packe
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef ENABLE_USB_INTERFACE
|
||||
#if COMPANION_FEATURE_TEXT_TERMINAL
|
||||
if (hasTerminalOutput()
|
||||
&& _terminal_display.shouldShowChannel(is_emergency_channel)) {
|
||||
ChannelDetails details;
|
||||
@@ -1269,18 +1269,18 @@ void MyMesh::onContactResponse(const ContactInfo &contact, const uint8_t *data,
|
||||
|
||||
if (pending_login && memcmp(&pending_login, contact.id.pub_key, 4) == 0) { // check for login response
|
||||
// yes, is response to pending sendLogin()
|
||||
#ifdef ENABLE_USB_INTERFACE
|
||||
#if COMPANION_FEATURE_TEXT_TERMINAL
|
||||
const bool terminal_login_response = _terminal_login_pending
|
||||
&& memcmp(_terminal_login_key, contact.id.pub_key,
|
||||
sizeof(_terminal_login_key)) == 0;
|
||||
#endif
|
||||
int i = 0;
|
||||
#ifdef ENABLE_USB_INTERFACE
|
||||
#if COMPANION_FEATURE_TEXT_TERMINAL
|
||||
bool login_success = false;
|
||||
bool modern_login = false;
|
||||
#endif
|
||||
if (len >= 6 && memcmp(&data[4], "OK", 2) == 0) { // legacy Repeater login OK response
|
||||
#ifdef ENABLE_USB_INTERFACE
|
||||
#if COMPANION_FEATURE_TEXT_TERMINAL
|
||||
login_success = true;
|
||||
#endif
|
||||
out_frame[i++] = PUSH_CODE_LOGIN_SUCCESS;
|
||||
@@ -1288,7 +1288,7 @@ void MyMesh::onContactResponse(const ContactInfo &contact, const uint8_t *data,
|
||||
memcpy(&out_frame[i], contact.id.pub_key, 6);
|
||||
i += 6; // pub_key_prefix
|
||||
} else if (len >= 13 && data[4] == RESP_SERVER_LOGIN_OK) { // new login response
|
||||
#ifdef ENABLE_USB_INTERFACE
|
||||
#if COMPANION_FEATURE_TEXT_TERMINAL
|
||||
login_success = true;
|
||||
modern_login = true;
|
||||
#endif
|
||||
@@ -1314,7 +1314,7 @@ void MyMesh::onContactResponse(const ContactInfo &contact, const uint8_t *data,
|
||||
// no Binary route. Binary logins retain their exact requester even if a
|
||||
// different transport has issued a command in the meantime.
|
||||
writePendingSerialFrame(out_frame, i);
|
||||
#ifdef ENABLE_USB_INTERFACE
|
||||
#if COMPANION_FEATURE_TEXT_TERMINAL
|
||||
if (terminal_login_response) {
|
||||
if (hasTerminalOutput()) {
|
||||
Stream& output = terminalOutput();
|
||||
@@ -1474,7 +1474,7 @@ void MyMesh::onTraceRecv(mesh::Packet *packet, uint32_t tag, uint32_t auth_code,
|
||||
i += path_len >> path_sz;
|
||||
out_frame[i++] = (int8_t)(packet->getSNR() * 4); // extra/final SNR (to this node)
|
||||
|
||||
#ifdef ENABLE_USB_INTERFACE
|
||||
#if COMPANION_FEATURE_TEXT_TERMINAL
|
||||
if (hasTerminalOutput() && _terminal_trace_pending
|
||||
&& tag == _terminal_trace_tag && auth_code == _terminal_trace_auth) {
|
||||
Stream& output = terminalOutput();
|
||||
@@ -1539,7 +1539,7 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe
|
||||
_iter_total_count = 0;
|
||||
_iter_table_revision = 0;
|
||||
_cli_rescue = false;
|
||||
#ifdef ENABLE_USB_INTERFACE
|
||||
#if COMPANION_FEATURE_TEXT_TERMINAL
|
||||
_terminal_mode = false;
|
||||
_terminal_output = NULL;
|
||||
_terminal_recipient_set = false;
|
||||
@@ -6776,7 +6776,7 @@ void MyMesh::appendRxPowerSavingAdjustmentNote(char* reply, size_t reply_size,
|
||||
(unsigned)_prefs.rx_ps_level);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_USB_INTERFACE
|
||||
#if COMPANION_FEATURE_TEXT_TERMINAL
|
||||
Stream& MyMesh::terminalOutput() {
|
||||
return _terminal_output != NULL ? *_terminal_output : Serial;
|
||||
}
|
||||
@@ -8468,7 +8468,7 @@ void MyMesh::loop() {
|
||||
#ifdef COMPANION_MESH_CLOCK_SYNC
|
||||
_clock_sync.loop();
|
||||
#endif
|
||||
#ifdef ENABLE_USB_INTERFACE
|
||||
#if COMPANION_FEATURE_TEXT_TERMINAL
|
||||
serviceTerminalLogin();
|
||||
serviceTerminalCommand();
|
||||
serviceTerminalTrace();
|
||||
|
||||
@@ -122,7 +122,7 @@ struct AdvertPath {
|
||||
};
|
||||
|
||||
class MyMesh : public BaseChatMesh, public DataStoreHost, public UIShutdownGuard
|
||||
#ifdef ENABLE_USB_INTERFACE
|
||||
#if COMPANION_FEATURE_TEXT_TERMINAL
|
||||
, public ContactVisitor
|
||||
#endif
|
||||
#ifdef WITH_WEBCONFIG
|
||||
@@ -187,7 +187,7 @@ public:
|
||||
void execCommand(char* cmd, char* reply) override;
|
||||
bool supportsCliTerminal() const override { return true; }
|
||||
void execAdminCommand(char* cmd, char* reply) override;
|
||||
#ifdef ENABLE_USB_INTERFACE
|
||||
#if COMPANION_FEATURE_TEXT_TERMINAL
|
||||
bool supportsStreamTerminal() const override { return true; }
|
||||
bool beginStreamTerminal(Stream& output) override;
|
||||
bool ownsStreamTerminal(const Stream& output) const override {
|
||||
@@ -207,7 +207,7 @@ public:
|
||||
bool advert();
|
||||
void enterCLIRescue();
|
||||
|
||||
#ifdef ENABLE_USB_INTERFACE
|
||||
#if COMPANION_FEATURE_TEXT_TERMINAL
|
||||
void enterTerminalMode();
|
||||
void exitTerminalMode();
|
||||
// Clear state owned by the current text-terminal host without changing the
|
||||
@@ -281,7 +281,7 @@ protected:
|
||||
bool onContactPathRecv(ContactInfo& from, uint8_t* in_path, uint8_t in_path_len, uint8_t* out_path, uint8_t out_path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override;
|
||||
void onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path_len, const uint8_t* path) override;
|
||||
void onContactPathUpdated(const ContactInfo &contact) override;
|
||||
#ifdef ENABLE_USB_INTERFACE
|
||||
#if COMPANION_FEATURE_TEXT_TERMINAL
|
||||
void onContactVisit(const ContactInfo& contact) override;
|
||||
#endif
|
||||
ContactInfo* processAck(const uint8_t *data) override;
|
||||
@@ -403,7 +403,7 @@ private:
|
||||
void formatWiFiPowerSaving(char* reply, size_t reply_size) const;
|
||||
void syncWiFiPowerSaving();
|
||||
#endif
|
||||
#ifdef ENABLE_USB_INTERFACE
|
||||
#if COMPANION_FEATURE_TEXT_TERMINAL
|
||||
Stream& terminalOutput();
|
||||
bool hasTerminalOutput() const { return _terminal_output != NULL; }
|
||||
void printTerminalBanner(bool show_binary_stop);
|
||||
@@ -515,7 +515,7 @@ private:
|
||||
bool _iter_start_pending;
|
||||
bool _iter_contact_pending;
|
||||
bool _cli_rescue;
|
||||
#ifdef ENABLE_USB_INTERFACE
|
||||
#if COMPANION_FEATURE_TEXT_TERMINAL
|
||||
bool _terminal_mode;
|
||||
Stream* _terminal_output;
|
||||
mesh::TerminalDisplayFilter _terminal_display;
|
||||
@@ -620,7 +620,7 @@ private:
|
||||
uint8_t text_fingerprint[MAX_HASH_SIZE];
|
||||
uint8_t retry_key[MAX_HASH_SIZE];
|
||||
BaseSerialInterface* reply_route;
|
||||
#ifdef ENABLE_USB_INTERFACE
|
||||
#if COMPANION_FEATURE_TEXT_TERMINAL
|
||||
bool terminal_origin;
|
||||
#endif
|
||||
};
|
||||
@@ -635,7 +635,7 @@ private:
|
||||
void expireExpectedAcks();
|
||||
AckTableEntry* findPendingTextMessage(
|
||||
const uint8_t text_fingerprint[MAX_HASH_SIZE], uint32_t message_timestamp);
|
||||
#ifdef ENABLE_USB_INTERFACE
|
||||
#if COMPANION_FEATURE_TEXT_TERMINAL
|
||||
void rememberTerminalAck(ContactInfo& recipient, const char* text,
|
||||
uint32_t message_timestamp, uint32_t expected_ack,
|
||||
uint32_t est_timeout,
|
||||
|
||||
@@ -1086,6 +1086,14 @@ void MyMesh::endStreamTerminal(Stream& output) {
|
||||
enterUsbTerminalMode();
|
||||
}
|
||||
}
|
||||
#elif defined(WITH_WEBCONFIG)
|
||||
bool MyMesh::beginStreamTerminal(Stream& output) {
|
||||
return enterNetworkTerminalMode(output);
|
||||
}
|
||||
|
||||
void MyMesh::endStreamTerminal(Stream& output) {
|
||||
exitNetworkTerminalMode(output);
|
||||
}
|
||||
#endif
|
||||
|
||||
void halt() {
|
||||
|
||||
@@ -75,7 +75,7 @@ def requirements(platform, defines, target):
|
||||
parts["mqtt_connections_buffers"] = 24576
|
||||
if "ENABLE_OTA" in defines:
|
||||
parts["ota_source_scratch"] = 8192
|
||||
if (companion and "ENABLE_USB_INTERFACE" in defines and "WIFI_SSID" in defines
|
||||
if (companion and "WIFI_SSID" in defines
|
||||
and "WEBCONFIG_DISABLED" not in defines):
|
||||
parts["browser_terminal_session"] = 2048
|
||||
if "BOARD_HAS_PSRAM" not in defines:
|
||||
|
||||
@@ -73,6 +73,9 @@ class FirmwareRamTest(unittest.TestCase):
|
||||
psram = ram.requirements("ESP32_PLATFORM", {**defines, "BOARD_HAS_PSRAM": 1}, "v4_companion")
|
||||
self.assertEqual(plain["required_heap_bytes"] - base["required_heap_bytes"], 6144)
|
||||
self.assertEqual(psram["required_heap_bytes"] - base["required_heap_bytes"], 2048)
|
||||
wifi_only = dict(defines)
|
||||
del wifi_only["ENABLE_USB_INTERFACE"]
|
||||
self.assertEqual(ram.requirements("ESP32_PLATFORM", wifi_only, "v4_companion"), plain)
|
||||
repeater = ram.requirements("ESP32_PLATFORM", defines, "v4_repeater")
|
||||
self.assertNotIn("browser_terminal_session", repeater["components"])
|
||||
|
||||
@@ -237,7 +240,7 @@ int main() {
|
||||
base = ram.requirements("ESP32_PLATFORM", {}, "companion")["required_heap_bytes"]
|
||||
flags = {"BLE_PIN_CODE": 1, "WIFI_SSID": "test", "WITH_MQTT_BRIDGE": 1}
|
||||
both = ram.requirements("ESP32_PLATFORM", flags, "companion")["required_heap_bytes"]
|
||||
self.assertEqual(both - base, 32768 + 49152 + 24576)
|
||||
self.assertEqual(both - base, 32768 + 49152 + 24576 + 6144)
|
||||
flags["COMPANION_EXCLUSIVE_WIFI_BLE"] = 1
|
||||
self.assertEqual(ram.requirements("ESP32_PLATFORM", flags, "companion")["required_heap_bytes"], both - 32768)
|
||||
self.assertGreater(ram.requirements("NRF52_PLATFORM", {"DISPLAY_CLASS": "ST7735Display"}, "companion")["required_heap_bytes"],
|
||||
|
||||
@@ -9,6 +9,22 @@ ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
class WebTerminalStreamTest(unittest.TestCase):
|
||||
def test_terminal_feature_compiles_with_wifi_without_usb(self):
|
||||
for defines, enabled in [
|
||||
(["ESP32_PLATFORM", 'WIFI_SSID="ssid"'], True),
|
||||
(["ESP32_PLATFORM", 'WIFI_SSID="ssid"', "WEBCONFIG_DISABLED"], False),
|
||||
(["NRF52_PLATFORM", "ENABLE_USB_INTERFACE"], True),
|
||||
(["ESP32_PLATFORM"], False),
|
||||
]:
|
||||
with self.subTest(defines=defines):
|
||||
result = subprocess.run([
|
||||
"c++", "-E", "-dM", "-x", "c++", "-", "-I" + str(ROOT / "src"),
|
||||
"-include", str(ROOT / "examples/companion_radio/CompanionFeatures.h"),
|
||||
*["-D" + value for value in defines],
|
||||
], input="", text=True, capture_output=True, check=True)
|
||||
self.assertIn("#define COMPANION_FEATURE_TEXT_TERMINAL " + str(int(enabled)),
|
||||
result.stdout)
|
||||
|
||||
def test_growth_drain_failure_and_release(self):
|
||||
headers = {
|
||||
"Arduino.h": """#pragma once
|
||||
|
||||
Reference in new issue
Block a user