Public Access
Stop IRC by hand; remove mDNS
IrcService::disconnect() stops the session from any state: QUIT if connected, then no more retries. /quit goes through it (before, it only stopped a connected session; while waiting for Wi-Fi or retrying it did nothing), and so does the new `irc stop` command. Stopped by hand, opening the IRC App no longer reconnects; typing a line does. mDNS is gone: it never crossed the dev box's routed network, and it cost about 7.5 KB of RAM. Pushes go to the IP shown in Settings -> Firmware, which drops its Name row. OTA Q54 records the change. On the device, Debug Build: 105.6 KB free with Wi-Fi (was 98); with IRC on TLS 54 KB free (was 46); after `irc stop`, back to 99 KB. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
This commit is contained in:
+1
-1
@@ -61,7 +61,7 @@ A Wi-Fi network the device may join on its own (name, password). When several ar
|
|||||||
_Avoid_: profile, known network
|
_Avoid_: profile, known network
|
||||||
|
|
||||||
**IRC Service**:
|
**IRC Service**:
|
||||||
The Service that keeps the IRC connection alive in the background once the IRC App has started it, until the user disconnects. It reconnects after drops, and pauses while the Wi-Fi Service is Monitoring. It does not start by itself after a reboot.
|
The Service that keeps the IRC connection alive in the background once the IRC App has started it, until the user stops it (`/quit`, or `irc stop` on the console). Once stopped by hand, opening the App again doesn't reconnect; typing a line does. It reconnects after drops, and pauses while the Wi-Fi Service is Monitoring. It does not start by itself after a reboot.
|
||||||
_Avoid_: IRC client (that's the App)
|
_Avoid_: IRC client (that's the App)
|
||||||
|
|
||||||
**Buffer**:
|
**Buffer**:
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ To install from the SD card instead, copy the `.ota` file from `.pio/build/cardp
|
|||||||
| `sd list` | Lists the files of each Storage Clean-up category |
|
| `sd list` | Lists the files of each Storage Clean-up category |
|
||||||
| `cat <path>` | Prints the first ~1.2 KB of a file on the SD card |
|
| `cat <path>` | Prints the first ~1.2 KB of a file on the SD card |
|
||||||
| `irc start` | Starts the IRC Service (normally done by opening the IRC App) |
|
| `irc start` | Starts the IRC Service (normally done by opening the IRC App) |
|
||||||
|
| `irc stop` | Stops it, as `/quit` does: QUIT if connected, no more retries, and the App stays disconnected until you type |
|
||||||
| `irc say <buffer> <text>` | Types into a Buffer, commands included (`irc say 0 /join #test`) |
|
| `irc say <buffer> <text>` | Types into a Buffer, commands included (`irc say 0 /join #test`) |
|
||||||
| `irc dump` | Prints IRC status, memory, and the last lines of each Buffer |
|
| `irc dump` | Prints IRC status, memory, and the last lines of each Buffer |
|
||||||
| `wifi status` | Prints Wi-Fi state, network, signal, clock and free heap |
|
| `wifi status` | Prints Wi-Fi state, network, signal, clock and free heap |
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|---|---|
|
|---|---|
|
||||||
| Q52 | Two sources: **push over Wi-Fi** from the PC, and **from the SD card**. Pulling from Gitea releases is deferred. |
|
| Q52 | Two sources: **push over Wi-Fi** from the PC, and **from the SD card**. Pulling from Gitea releases is deferred. |
|
||||||
| Q53 | **Signed Update Files** (ECDSA P-256 over SHA-256). The private key stays in `~/.config/roro9stack/`, and the firmware embeds the public key (ADR 0003). |
|
| Q53 | **Signed Update Files** (ECDSA P-256 over SHA-256). The private key stays in `~/.config/roro9stack/`, and the firmware embeds the public key (ADR 0003). |
|
||||||
| Q54 | The device **always listens** for pushes on the LAN while Wi-Fi is Connected, announced as `roro9stack-<id>.local`. |
|
| Q54 | The device **always listens** for pushes on the LAN while Wi-Fi is Connected. *Revised in M2:* it was announced over mDNS as `roro9stack-<id>.local`; mDNS was removed to save RAM (it never crossed the dev box's routed network anyway). Pushes go to the IP shown in Settings → Firmware. |
|
||||||
| Q55 | New firmware runs on **Probation**. It's confirmed once booted, UI drawn, Services started, 30 s without a crash, and Wi-Fi connected (if configured). Otherwise **Rollback**. A Toast reports either outcome. |
|
| Q55 | New firmware runs on **Probation**. It's confirmed once booted, UI drawn, Services started, 30 s without a crash, and Wi-Fi connected (if configured). Otherwise **Rollback**. A Toast reports either outcome. |
|
||||||
| Q56 | **Downgrades are allowed**, with "older than the installed version" shown. |
|
| Q56 | **Downgrades are allowed**, with "older than the installed version" shown. |
|
||||||
| Q57 | A valid push **installs right away**: progress screen, then reboot. The reboot waits for Text Entry to end, 60 s at most. |
|
| Q57 | A valid push **installs right away**: progress screen, then reboot. The reboot waits for Text Entry to end, 60 s at most. |
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
"""Pushes a signed Update File to a Cardputer over Wi-Fi (TCP 3232) and reports the result.
|
"""Pushes a signed Update File to a Cardputer over Wi-Fi (TCP 3232) and reports the result.
|
||||||
|
|
||||||
Usage: scripts/ota_push.py <file.ota> <host>
|
Usage: scripts/ota_push.py <file.ota> <host>
|
||||||
<host> is the device's IP (shown in Settings > About), or its name when mDNS works on your network.
|
<host> is the device's IP (shown in Settings > Firmware).
|
||||||
"""
|
"""
|
||||||
import socket
|
import socket
|
||||||
import sys
|
import sys
|
||||||
|
|||||||
@@ -78,7 +78,6 @@ void FirmwarePage::draw(Canvas& c) {
|
|||||||
case kVersion: return "Version";
|
case kVersion: return "Version";
|
||||||
case kStatus: return "Status";
|
case kStatus: return "Status";
|
||||||
case kAddress: return "Push to";
|
case kAddress: return "Push to";
|
||||||
case kName: return "Name";
|
|
||||||
case kSdHeader: return listed ? (found.empty() ? "No .ota files in /updates" : "On the SD card:") : "Looking on the SD card...";
|
case kSdHeader: return listed ? (found.empty() ? "No .ota files in /updates" : "On the SD card:") : "Looking on the SD card...";
|
||||||
default: return " " + found[i - kFixed].substr(std::string(kUpdatesFolder).size() + 1);
|
default: return " " + found[i - kFixed].substr(std::string(kUpdatesFolder).size() + 1);
|
||||||
}
|
}
|
||||||
@@ -88,7 +87,6 @@ void FirmwarePage::draw(Canvas& c) {
|
|||||||
case kVersion: return versionString();
|
case kVersion: return versionString();
|
||||||
case kStatus: return update_.onProbation() ? "on probation" : "confirmed";
|
case kStatus: return update_.onProbation() ? "on probation" : "confirmed";
|
||||||
case kAddress: return ip.empty() ? "Wi-Fi not connected" : ip + ":" + std::to_string(UpdateService::kPort);
|
case kAddress: return ip.empty() ? "Wi-Fi not connected" : ip + ":" + std::to_string(UpdateService::kPort);
|
||||||
case kName: return update_.hostname() + ".local";
|
|
||||||
case kSdHeader: return "";
|
case kSdHeader: return "";
|
||||||
default: return "install >";
|
default: return "install >";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ class FirmwarePage {
|
|||||||
void draw(Canvas& c);
|
void draw(Canvas& c);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum Row { kVersion, kStatus, kAddress, kName, kSdHeader, kFixed };
|
enum Row { kVersion, kStatus, kAddress, kSdHeader, kFixed };
|
||||||
|
|
||||||
std::vector<std::string> files();
|
std::vector<std::string> files();
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ std::string lowered(std::string s) {
|
|||||||
void IrcApp::onEnter() {
|
void IrcApp::onEnter() {
|
||||||
page_ = Page::Chat;
|
page_ = Page::Chat;
|
||||||
editing_ = false;
|
editing_ = false;
|
||||||
irc_.connect();
|
if (!irc_.stoppedByUser()) irc_.connect(); // stopped by hand: stay stopped, typing reconnects
|
||||||
view(current_);
|
view(current_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -347,7 +347,7 @@ static const char* const kHelp =
|
|||||||
"coredump erase forget the core dump in flash\n"
|
"coredump erase forget the core dump in flash\n"
|
||||||
"key <name|char> press a key: up down left right select back home del tab, or one character\n"
|
"key <name|char> press a key: up down left right select back home del tab, or one character\n"
|
||||||
"wifi status | wifi add <ssid><TAB><password>\n"
|
"wifi status | wifi add <ssid><TAB><password>\n"
|
||||||
"irc start | irc dump | irc say <buffer> <text>\n"
|
"irc start | irc stop | irc dump | irc say <buffer> <text>\n"
|
||||||
"ls [folder] | rm <path> | install <path.ota> (Update from SD)\n"
|
"ls [folder] | rm <path> | install <path.ota> (Update from SD)\n"
|
||||||
"sd list | cat <path> | log <text> | burst | sound on|off | short | normal\n"
|
"sd list | cat <path> | log <text> | burst | sound on|off | short | normal\n"
|
||||||
#ifdef RORO_DEBUG
|
#ifdef RORO_DEBUG
|
||||||
@@ -463,6 +463,7 @@ static void runCommand(String line) {
|
|||||||
listingWanted = true;
|
listingWanted = true;
|
||||||
}
|
}
|
||||||
if (line == "irc start") irc->connect();
|
if (line == "irc start") irc->connect();
|
||||||
|
if (line == "irc stop") irc->disconnect();
|
||||||
if (line.startsWith("irc say ")) { // irc say <buffer index> <text>
|
if (line.startsWith("irc say ")) { // irc say <buffer index> <text>
|
||||||
String rest = line.substring(8);
|
String rest = line.substring(8);
|
||||||
int space = rest.indexOf(' ');
|
int space = rest.indexOf(' ');
|
||||||
|
|||||||
@@ -37,12 +37,19 @@ void IrcService::start() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void IrcService::connect() {
|
void IrcService::connect() {
|
||||||
|
stoppedByUser_ = false;
|
||||||
if (wanted_) return;
|
if (wanted_) return;
|
||||||
retryAtMs_ = millis();
|
retryAtMs_ = millis();
|
||||||
backoff_.reset();
|
backoff_.reset();
|
||||||
wanted_ = true;
|
wanted_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void IrcService::disconnect() {
|
||||||
|
stoppedByUser_ = true;
|
||||||
|
stopRequested_ = true; // the IRC task says QUIT and closes; nothing to do if it never started
|
||||||
|
if (!task_) wanted_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
int IrcService::totalUnread() {
|
int IrcService::totalUnread() {
|
||||||
Lock l(lock_);
|
Lock l(lock_);
|
||||||
return session_->totalUnread();
|
return session_->totalUnread();
|
||||||
@@ -176,13 +183,10 @@ void IrcService::flushEffects() {
|
|||||||
status_ = Status::Online;
|
status_ = Status::Online;
|
||||||
backoff_.reset();
|
backoff_.reset();
|
||||||
}
|
}
|
||||||
if (quit && open_) {
|
if (quit) { // /quit: QUIT is already on its way if connected; stop in any state
|
||||||
delay(300); // let QUIT reach the server
|
stoppedByUser_ = true;
|
||||||
close("");
|
stopRequested_ = true;
|
||||||
wanted_ = false;
|
quitSent_ = true;
|
||||||
status_ = Status::Stopped;
|
|
||||||
Lock l(lock_);
|
|
||||||
session_->disconnected(clock_.utcNow(), "disconnected");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,6 +196,19 @@ void IrcService::loop() {
|
|||||||
bool wifiUp = wifi_.state() == WifiController::State::Connected;
|
bool wifiUp = wifi_.state() == WifiController::State::Connected;
|
||||||
bool monitoring = wifi_.state() == WifiController::State::Monitoring;
|
bool monitoring = wifi_.state() == WifiController::State::Monitoring;
|
||||||
|
|
||||||
|
if (stopRequested_) {
|
||||||
|
stopRequested_ = false;
|
||||||
|
if (open_) {
|
||||||
|
if (!quitSent_) conn_->print("QUIT :roro9stack\r\n");
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(300)); // let QUIT reach the server
|
||||||
|
close("");
|
||||||
|
}
|
||||||
|
quitSent_ = false;
|
||||||
|
wanted_ = false;
|
||||||
|
status_ = Status::Stopped;
|
||||||
|
Lock l(lock_);
|
||||||
|
session_->disconnected(clock_.utcNow(), "disconnected (Enter reconnects)");
|
||||||
|
}
|
||||||
if (restart_) {
|
if (restart_) {
|
||||||
restart_ = false;
|
restart_ = false;
|
||||||
if (open_) {
|
if (open_) {
|
||||||
|
|||||||
@@ -31,9 +31,12 @@ class IrcService : public Service {
|
|||||||
const char* name() const override { return "irc"; }
|
const char* name() const override { return "irc"; }
|
||||||
void start() override;
|
void start() override;
|
||||||
|
|
||||||
// Starts the session (the IRC App does this when opened); /quit stops it.
|
// Starts the session (the IRC App does this when opened, unless you stopped it).
|
||||||
void connect();
|
void connect();
|
||||||
|
// Stops it, from any state: says QUIT if connected, and stops retrying. /quit, `irc stop`.
|
||||||
|
void disconnect();
|
||||||
bool running() const { return wanted_; }
|
bool running() const { return wanted_; }
|
||||||
|
bool stoppedByUser() const { return stoppedByUser_; }
|
||||||
Status status() const { return status_; }
|
Status status() const { return status_; }
|
||||||
|
|
||||||
// Read or act on the session while holding its lock: withSession([](IrcSession& s) { ... }).
|
// Read or act on the session while holding its lock: withSession([](IrcSession& s) { ... }).
|
||||||
@@ -78,6 +81,9 @@ class IrcService : public Service {
|
|||||||
ReconnectPolicy backoff_;
|
ReconnectPolicy backoff_;
|
||||||
std::string partial_;
|
std::string partial_;
|
||||||
volatile bool wanted_ = false;
|
volatile bool wanted_ = false;
|
||||||
|
volatile bool stoppedByUser_ = false; // a manual stop: opening the App doesn't reconnect
|
||||||
|
volatile bool stopRequested_ = false;
|
||||||
|
bool quitSent_ = false; // /quit already queued its QUIT
|
||||||
volatile bool restart_ = false;
|
volatile bool restart_ = false;
|
||||||
volatile Status status_ = Status::Stopped;
|
volatile Status status_ = Status::Stopped;
|
||||||
bool open_ = false;
|
bool open_ = false;
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
#include "update_service.h"
|
#include "update_service.h"
|
||||||
|
|
||||||
#include <ESPmDNS.h>
|
|
||||||
#include <SD.h>
|
#include <SD.h>
|
||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
#include <esp_ota_ops.h>
|
#include <esp_ota_ops.h>
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include "platform/identity.h"
|
|
||||||
#include "platform/ota_device.h"
|
#include "platform/ota_device.h"
|
||||||
#include "platform/system_info.h"
|
#include "platform/system_info.h"
|
||||||
#include "probation.h"
|
#include "probation.h"
|
||||||
@@ -66,7 +64,6 @@ class FileSource : public UpdateSource {
|
|||||||
UpdateService::UpdateService(KeyValueStore& store, WifiService& wifi, SavedNetworks& saved, StorageService& storage,
|
UpdateService::UpdateService(KeyValueStore& store, WifiService& wifi, SavedNetworks& saved, StorageService& storage,
|
||||||
EventBus& bus, const Settings& settings)
|
EventBus& bus, const Settings& settings)
|
||||||
: store_(store), wifi_(wifi), saved_(saved), storage_(storage), bus_(bus), settings_(settings) {
|
: store_(store), wifi_(wifi), saved_(saved), storage_(storage), bus_(bus), settings_(settings) {
|
||||||
hostname_ = "roro9stack-" + identity::defaultShortName();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void UpdateService::notify(const std::string& text, NotificationLevel level) {
|
void UpdateService::notify(const std::string& text, NotificationLevel level) {
|
||||||
@@ -196,11 +193,9 @@ void UpdateService::listen() {
|
|||||||
bool connected = wifi_.state() == WifiController::State::Connected;
|
bool connected = wifi_.state() == WifiController::State::Connected;
|
||||||
if (connected && !listening) {
|
if (connected && !listening) {
|
||||||
server.begin();
|
server.begin();
|
||||||
if (MDNS.begin(hostname_.c_str())) MDNS.addService("roro9stack", "tcp", kPort);
|
|
||||||
listening = true;
|
listening = true;
|
||||||
} else if (!connected && listening) {
|
} else if (!connected && listening) {
|
||||||
server.end();
|
server.end();
|
||||||
MDNS.end();
|
|
||||||
listening = false;
|
listening = false;
|
||||||
}
|
}
|
||||||
// The main loop restarts into an installed update when it's safe. If it never does (stuck,
|
// The main loop restarts into an installed update when it's safe. If it never does (stuck,
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ class UpdateService : public Service {
|
|||||||
int percent() const { return percent_; }
|
int percent() const { return percent_; }
|
||||||
std::string incomingVersion() const;
|
std::string incomingVersion() const;
|
||||||
bool onProbation() const { return probation_; }
|
bool onProbation() const { return probation_; }
|
||||||
const std::string& hostname() const { return hostname_; }
|
|
||||||
|
|
||||||
// Installs an Update File from the SD card (runs on the storage task).
|
// Installs an Update File from the SD card (runs on the storage task).
|
||||||
void installFromSd(const std::string& path);
|
void installFromSd(const std::string& path);
|
||||||
@@ -58,7 +57,6 @@ class UpdateService : public Service {
|
|||||||
EventBus& bus_;
|
EventBus& bus_;
|
||||||
const Settings& settings_;
|
const Settings& settings_;
|
||||||
TaskHandle_t task_ = nullptr;
|
TaskHandle_t task_ = nullptr;
|
||||||
std::string hostname_;
|
|
||||||
volatile Phase phase_ = Phase::Idle;
|
volatile Phase phase_ = Phase::Idle;
|
||||||
volatile int percent_ = 0;
|
volatile int percent_ = 0;
|
||||||
std::string incoming_;
|
std::string incoming_;
|
||||||
|
|||||||
Reference in New Issue
Block a user