mirror of
https://github.com/ratspeak/ratdeck.git
synced 2026-05-15 20:05:10 +00:00
TCP scaling, GUI pooling, dev mode, protocol improvements
- TCP: wider drain budgets, TCP_NODELAY, no flush, PSRAM buffers, queue announces until hub ID - GUI: object pool Messages/Contacts screens, partial MessageView status, targeted tab refresh - LVGL throttled to 5fps when dimmed, non-blocking RSSI monitor, bounded I2S writes - LXMF batch drain, BLE frame mutex, LoRa interrupt-driven RX, name cache cap 300 - Developer mode in settings: custom radio params (freq/txp/SF/BW/CR/preamble) behind warning
This commit is contained in:
@@ -8,10 +8,16 @@ TCPClientInterface::TCPClientInterface(const char* host, uint16_t port, const ch
|
||||
_OUT = true;
|
||||
_bitrate = 1000000;
|
||||
_HW_MTU = 500;
|
||||
_rxBuffer = (uint8_t*)ps_malloc(RX_BUFFER_SIZE);
|
||||
if (!_rxBuffer) _rxBuffer = (uint8_t*)malloc(RX_BUFFER_SIZE);
|
||||
_txBuffer = (uint8_t*)ps_malloc(TX_BUFFER_SIZE);
|
||||
if (!_txBuffer) _txBuffer = (uint8_t*)malloc(TX_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
TCPClientInterface::~TCPClientInterface() {
|
||||
stop();
|
||||
if (_rxBuffer) { free(_rxBuffer); _rxBuffer = nullptr; }
|
||||
if (_txBuffer) { free(_txBuffer); _txBuffer = nullptr; }
|
||||
}
|
||||
|
||||
bool TCPClientInterface::start() {
|
||||
@@ -38,10 +44,12 @@ void TCPClientInterface::tryConnect() {
|
||||
_escaped = false;
|
||||
_rxPos = 0;
|
||||
_hubTransportIdKnown = false;
|
||||
_pendingAnnounces.clear();
|
||||
_lastRxTime = millis();
|
||||
|
||||
// Set TCP write timeout to prevent blocking on half-open sockets
|
||||
_client.setTimeout(5); // 5 second write timeout
|
||||
_client.setNoDelay(true); // Disable Nagle — send immediately
|
||||
|
||||
Serial.printf("[TCP] Connected to %s:%d\n", _host.c_str(), _port);
|
||||
} else {
|
||||
@@ -72,26 +80,34 @@ void TCPClientInterface::loop() {
|
||||
return; // Will reconnect on next loop iteration
|
||||
}
|
||||
|
||||
// Drain multiple incoming frames per loop (up to 5, time-boxed)
|
||||
// Drain multiple incoming frames per loop (up to 15, time-boxed)
|
||||
unsigned long tcpStart = millis();
|
||||
for (int i = 0; i < 5 && _client.available() && (millis() - tcpStart < TCP_LOOP_BUDGET_MS); i++) {
|
||||
for (int i = 0; i < 15 && _client.available() && (millis() - tcpStart < TCP_LOOP_BUDGET_MS); i++) {
|
||||
unsigned long rxStart = millis();
|
||||
int len = readFrame();
|
||||
if (len > 0) {
|
||||
_lastRxTime = millis();
|
||||
_hubRxCount++;
|
||||
|
||||
// Learn hub transport_id from incoming Header2 packets
|
||||
// Learn hub transport_id from incoming Header2 packets (once per connection)
|
||||
if (len >= 35) {
|
||||
uint8_t flags = _rxBuffer[0];
|
||||
uint8_t header_type = (flags >> 6) & 0x01;
|
||||
if (header_type == 1) { // Header2
|
||||
if (!_hubTransportIdKnown) {
|
||||
char hex[33];
|
||||
for (int j = 0; j < 16; j++) sprintf(hex + j*2, "%02x", _rxBuffer[j+2]);
|
||||
Serial.printf("[TCP] Learned hub transport_id: %.8s\n", hex);
|
||||
}
|
||||
if (header_type == 1 && !_hubTransportIdKnown) {
|
||||
memcpy(_hubTransportId, _rxBuffer + 2, 16);
|
||||
_hubTransportIdKnown = true;
|
||||
char hex[33];
|
||||
for (int j = 0; j < 16; j++) sprintf(hex + j*2, "%02x", _hubTransportId[j]);
|
||||
Serial.printf("[TCP] Learned hub transport_id: %.8s\n", hex);
|
||||
|
||||
// Flush any announces that were queued before hub ID was known
|
||||
for (auto& pending : _pendingAnnounces) {
|
||||
sendFrame(pending.data(), pending.size());
|
||||
InterfaceImpl::handle_outgoing(pending);
|
||||
Serial.printf("[TCP] TX %d bytes (flushed pending announce) to %s:%d\n",
|
||||
(int)pending.size(), _host.c_str(), _port);
|
||||
}
|
||||
_pendingAnnounces.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,7 +145,12 @@ void TCPClientInterface::send_outgoing(const RNS::Bytes& data) {
|
||||
|
||||
// Build Header2 packet: flags(1) + hops(1) + transport_id(16) + original[2:]
|
||||
size_t new_len = data.size() + 16;
|
||||
uint8_t wrapped[1024];
|
||||
if (new_len > RX_BUFFER_SIZE) {
|
||||
Serial.printf("[TCP] H1->H2 wrap too large (%d bytes), dropping\n", (int)new_len);
|
||||
_txDropCount++;
|
||||
return;
|
||||
}
|
||||
uint8_t wrapped[RX_BUFFER_SIZE];
|
||||
wrapped[0] = new_flags;
|
||||
wrapped[1] = data.data()[1]; // hops
|
||||
memcpy(wrapped + 2, _hubTransportId, 16); // transport_id
|
||||
@@ -144,7 +165,7 @@ void TCPClientInterface::send_outgoing(const RNS::Bytes& data) {
|
||||
else if (data.size() >= 35 && memcmp(data.data() + 2, _hubTransportId, 16) != 0) {
|
||||
// Header2 with wrong transport_id → fix it
|
||||
// Transport::outbound() may have used _received_from=destination_hash
|
||||
uint8_t fixed[1024];
|
||||
uint8_t fixed[RX_BUFFER_SIZE];
|
||||
memcpy(fixed, data.data(), data.size());
|
||||
memcpy(fixed + 2, _hubTransportId, 16);
|
||||
|
||||
@@ -157,11 +178,21 @@ void TCPClientInterface::send_outgoing(const RNS::Bytes& data) {
|
||||
}
|
||||
}
|
||||
|
||||
// Passthrough: announces, correct Header2, pre-hub-discovery
|
||||
sendFrame(data.data(), data.size());
|
||||
// Queue announces until hub transport_id is learned
|
||||
if (!_hubTransportIdKnown) {
|
||||
uint8_t flags = data.size() >= 1 ? data.data()[0] : 0;
|
||||
uint8_t packet_type = flags & 0x03;
|
||||
if (packet_type == 0x01 && _pendingAnnounces.size() < 3) {
|
||||
_pendingAnnounces.push_back(data);
|
||||
Serial.printf("[TCP] TX %d bytes (queued announce, hub ID pending) to %s:%d\n",
|
||||
(int)data.size(), _host.c_str(), _port);
|
||||
return;
|
||||
}
|
||||
sendFrame(data.data(), data.size());
|
||||
Serial.printf("[TCP] TX %d bytes (no hub ID yet) to %s:%d\n", (int)data.size(), _host.c_str(), _port);
|
||||
} else {
|
||||
// Passthrough: announces, correct Header2
|
||||
sendFrame(data.data(), data.size());
|
||||
Serial.printf("[TCP] TX %d bytes (passthrough) to %s:%d\n", (int)data.size(), _host.c_str(), _port);
|
||||
}
|
||||
InterfaceImpl::handle_outgoing(data);
|
||||
@@ -170,20 +201,27 @@ void TCPClientInterface::send_outgoing(const RNS::Bytes& data) {
|
||||
// HDLC-like framing: [0x7E] [escaped data] [0x7E]
|
||||
// Buffered write — single syscall instead of per-byte writes
|
||||
void TCPClientInterface::sendFrame(const uint8_t* data, size_t len) {
|
||||
uint8_t buf[1024]; // Max frame size (matches _rxBuffer)
|
||||
if (!_txBuffer) return;
|
||||
// Worst case: every byte escapes (2x) + 2 delimiters
|
||||
size_t maxFrameLen = len * 2 + 2;
|
||||
if (maxFrameLen > TX_BUFFER_SIZE) {
|
||||
Serial.printf("[TCP] TX frame too large (%d bytes), dropping\n", (int)len);
|
||||
_txDropCount++;
|
||||
return;
|
||||
}
|
||||
size_t pos = 0;
|
||||
buf[pos++] = FRAME_START;
|
||||
for (size_t i = 0; i < len && pos < sizeof(buf) - 2; i++) {
|
||||
_txBuffer[pos++] = FRAME_START;
|
||||
for (size_t i = 0; i < len && pos < TX_BUFFER_SIZE - 2; i++) {
|
||||
if (data[i] == FRAME_START || data[i] == FRAME_ESC) {
|
||||
buf[pos++] = FRAME_ESC;
|
||||
buf[pos++] = data[i] ^ FRAME_XOR;
|
||||
_txBuffer[pos++] = FRAME_ESC;
|
||||
_txBuffer[pos++] = data[i] ^ FRAME_XOR;
|
||||
} else {
|
||||
buf[pos++] = data[i];
|
||||
_txBuffer[pos++] = data[i];
|
||||
}
|
||||
}
|
||||
buf[pos++] = FRAME_START;
|
||||
_client.write(buf, pos);
|
||||
_client.flush();
|
||||
_txBuffer[pos++] = FRAME_START;
|
||||
_client.write(_txBuffer, pos);
|
||||
// No flush() — TCP_NODELAY sends immediately without Nagle delay
|
||||
}
|
||||
|
||||
int TCPClientInterface::readFrame() {
|
||||
@@ -194,7 +232,7 @@ int TCPClientInterface::readFrame() {
|
||||
int bytesRead = 0;
|
||||
constexpr int MAX_BYTES_PER_CALL = 1024;
|
||||
|
||||
while (_client.available() && _rxPos < sizeof(_rxBuffer) && bytesRead < MAX_BYTES_PER_CALL) {
|
||||
while (_client.available() && _rxPos < RX_BUFFER_SIZE && bytesRead < MAX_BYTES_PER_CALL) {
|
||||
uint8_t b = _client.read();
|
||||
bytesRead++;
|
||||
|
||||
@@ -229,7 +267,7 @@ int TCPClientInterface::readFrame() {
|
||||
}
|
||||
|
||||
// Buffer overflow protection
|
||||
if (_rxPos >= sizeof(_rxBuffer)) {
|
||||
if (_rxPos >= RX_BUFFER_SIZE) {
|
||||
Serial.printf("[TCP] Frame too large (%d bytes), dropping\n", (int)_rxPos);
|
||||
_inFrame = false;
|
||||
_escaped = false;
|
||||
|
||||
Reference in New Issue
Block a user