#pragma once #if defined(ESP32) && defined(TCP_CONSOLE_PORT) && defined(ADMIN_PASSWORD) #include #include #include #include #ifndef TCP_CONSOLE_MAX_CLIENTS #define TCP_CONSOLE_MAX_CLIENTS 2 #endif #ifndef TCP_CONSOLE_TIMEOUT_MS #define TCP_CONSOLE_TIMEOUT_MS 300000 // 5 minutes inactivity timeout #endif class TCPConsole { WiFiServer _server; WiFiClient _clients[TCP_CONSOLE_MAX_CLIENTS]; bool _authenticated[TCP_CONSOLE_MAX_CLIENTS]; unsigned long _last_active[TCP_CONSOLE_MAX_CLIENTS]; char _cmd_buf[TCP_CONSOLE_MAX_CLIENTS][160]; int _cmd_len[TCP_CONSOLE_MAX_CLIENTS]; const char* _password; const char* _node_name; void sendToClient(int i, const char* msg) { if (_clients[i] && _clients[i].connected()) { _clients[i].print(msg); } } void disconnectClient(int i) { _clients[i].stop(); _authenticated[i] = false; _cmd_buf[i][0] = 0; _cmd_len[i] = 0; } public: TCPConsole(const char* password, const char* node_name) : _server(TCP_CONSOLE_PORT), _password(password), _node_name(node_name) { for (int i = 0; i < TCP_CONSOLE_MAX_CLIENTS; i++) { _authenticated[i] = false; _cmd_buf[i][0] = 0; _cmd_len[i] = 0; _last_active[i] = 0; } } void begin() { _server.begin(); Serial.printf("TCP Console listening on port %d\n", TCP_CONSOLE_PORT); } // Call this from loop(), passing the mesh's handleCommand function template void loop(T& mesh) { // Accept new clients WiFiClient newClient = _server.available(); if (newClient) { for (int i = 0; i < TCP_CONSOLE_MAX_CLIENTS; i++) { if (!_clients[i] || !_clients[i].connected()) { _clients[i] = newClient; _authenticated[i] = false; _cmd_buf[i][0] = 0; _cmd_len[i] = 0; _last_active[i] = millis(); sendToClient(i, "MeshCore Console\r\nPassword: "); break; } } } // Handle connected clients for (int i = 0; i < TCP_CONSOLE_MAX_CLIENTS; i++) { if (!_clients[i] || !_clients[i].connected()) continue; // Inactivity timeout if (millis() - _last_active[i] > TCP_CONSOLE_TIMEOUT_MS) { sendToClient(i, "\r\nTimeout. Disconnecting.\r\n"); disconnectClient(i); continue; } // Read available data while (_clients[i].available()) { _last_active[i] = millis(); char c = _clients[i].read(); if (c == '\n') continue; // ignore LF, handle CR only if (c != '\r' && _cmd_len[i] < 158) { _cmd_buf[i][_cmd_len[i]++] = c; _cmd_buf[i][_cmd_len[i]] = 0; if (!_authenticated[i]) { sendToClient(i, "*"); // mask password input } else { _clients[i].print(c); // echo command } continue; } // Got CR — process command sendToClient(i, "\r\n"); _cmd_buf[i][_cmd_len[i]] = 0; if (!_authenticated[i]) { // Authentication if (strcmp(_cmd_buf[i], _password) == 0) { _authenticated[i] = true; char welcome[80]; snprintf(welcome, sizeof(welcome), "Welcome to %s console.\r\n> ", _node_name); sendToClient(i, welcome); } else { sendToClient(i, "Wrong password. Disconnecting.\r\n"); disconnectClient(i); } } else { // Execute command if (strlen(_cmd_buf[i]) > 0) { char reply[160]; reply[0] = 0; mesh.handleCommand(0, _cmd_buf[i], reply); if (reply[0]) { sendToClient(i, " -> "); sendToClient(i, reply); sendToClient(i, "\r\n"); } } sendToClient(i, "> "); } _cmd_buf[i][0] = 0; _cmd_len[i] = 0; } } } }; #endif // ESP32 && TCP_CONSOLE_PORT