M1 step 4b: IRC Service on the device; 8-bit frame buffer

- IrcService: networking on its own task, TLS with the built-in CA
  bundle (or trust-on-first-use pinning when self-signed is allowed),
  plain TCP when TLS is off; connects only while Wi-Fi is Connected,
  marks pauses for Monitoring, reconnects with backoff, pings a quiet
  server, writes Logs, raises Notifications for Mentions
- Session: forget /quit once disconnected (it was handled every loop);
  the server Buffer never counts as unread (MOTD showed as [4])
- Status Bar: unread count
- Frame buffer 16 -> 8-bit colour (M1 Q46): min free heap with IRC on
  TLS went from 51 KB to 79 KB
- Serial: irc start / say / dump

Verified on the device against irc.libera.chat:6697: certificate
checked, joined #roro9stack-test, sent a message, quit cleanly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
This commit is contained in:
2026-10-02 15:10:03 +02:00
co-authored by Claude Opus 5.5
parent 311eb38723
commit 74a341418e
11 changed files with 357 additions and 4 deletions
+27
View File
@@ -10,7 +10,9 @@
#include "key_mapper.h"
#include "platform/nvs_store.h"
#include "service_manager.h"
#include "platform/identity.h"
#include "services/battery_service.h"
#include "services/irc_service.h"
#include "services/clock_service.h"
#include "services/power_service.h"
#include "services/storage_service.h"
@@ -37,6 +39,7 @@ static PowerService* power;
static ClockService* clockService;
static SavedNetworks* savedNetworks;
static WifiService* wifi;
static IrcService* irc;
static Notifier* notifier;
static LauncherApp launcher;
static AppManager* apps;
@@ -66,6 +69,7 @@ static StatusInfo currentStatus() {
s.sdPresent = storageState.present;
s.sdLevel = storageState.level;
s.compose = keyMapper.pendingCompose();
s.unread = irc->totalUnread();
using WifiState = WifiController::State;
switch (wifi->state()) {
case WifiState::Connected: {
@@ -97,6 +101,7 @@ void setup() {
savedNetworks = new SavedNetworks(nvs);
savedNetworks->load();
wifi = new WifiService(settings, *savedNetworks, *clockService);
irc = new IrcService(nvs, "roro_" + identity::defaultShortName(), *wifi, *storageService, *clockService, bus);
notifier = new Notifier(bus, settings);
notifier->onShow = [](uint32_t now, uint32_t until) { power->onNotification(now, until); };
services.add(*power);
@@ -104,6 +109,7 @@ void setup() {
services.add(*battery);
services.add(*storageService);
services.add(*wifi);
services.add(*irc);
apps = new AppManager(launcher);
launcher.setManager(*apps);
@@ -172,6 +178,27 @@ static void serialCommands() {
storageService->requestListing();
listingWanted = true;
}
if (line == "irc start") irc->connect();
if (line.startsWith("irc say ")) { // irc say <buffer index> <text>
String rest = line.substring(8);
int space = rest.indexOf(' ');
int b = rest.substring(0, space).toInt();
std::string text = rest.substring(space + 1).c_str();
irc->withSession([&](IrcSession& s) { s.input(b, text, clockService->utcNow()); });
}
if (line == "irc dump") {
Serial.printf("irc: status %d, unread %d, heap %u min %u\n", (int)irc->status(), irc->totalUnread(),
ESP.getFreeHeap(), ESP.getMinFreeHeap());
irc->withSession([](IrcSession& s) {
for (int i = 0; i < s.bufferCount(); i++) {
const auto& b = s.buffer(i);
Serial.printf("irc buffer %d %s unread %d%s\n", i, b.name.c_str(), b.unread, b.joined ? " joined" : "");
size_t from = b.lines.size() > 6 ? b.lines.size() - 6 : 0;
for (size_t j = from; j < b.lines.size(); j++)
Serial.printf(" <%s> %s\n", b.lines[j].nick.c_str(), b.lines[j].text.c_str());
}
});
}
if (line == "wifi status")
Serial.printf("wifi: state %d ssid '%s' rssi %d clock %s heap %u min %u\n", (int)wifi->state(),
wifi->ssid().c_str(), wifi->rssi(), clockService->displayTime().c_str(), ESP.getFreeHeap(),