chore: firmware fixes + agent docs + submodule
- SLIC phone: CORS, AP password, I2S fixes - UI engines: nav fixes for esp8266/rp2040 - ESP32_ZACUS: NPC integration layer (8a83eba) - Agent docs updated
This commit is contained in:
@@ -6,6 +6,39 @@ Project Manager + Tech Lead + QA gatekeeper.
|
||||
## Scope
|
||||
Default rules for the whole repository. Nested `AGENTS.md` files override only local differences.
|
||||
|
||||
## Canonical References
|
||||
- Architecture and system maps: `docs/architecture/index.md`
|
||||
- Runtime contract: `specs/ZACUS_RUNTIME_3_SPEC.md`
|
||||
- AI integration architecture: `specs/AI_INTEGRATION_SPEC.md`
|
||||
- Quickstart and developer commands: `docs/QUICKSTART.md`
|
||||
- Repository structure map: `docs/STRUCTURE.md`
|
||||
- Deployment runbook: `docs/DEPLOYMENT_RUNBOOK.md`
|
||||
- Security practices: `docs/SECURITY.md`
|
||||
|
||||
Use links to these docs instead of duplicating long procedures in agent reports.
|
||||
|
||||
## Project Conventions
|
||||
- Main source of truth for gameplay/story remains `game/scenarios/zacus_v2.yaml`.
|
||||
- Supporting manifests must stay in sync:
|
||||
- `audio/manifests/zacus_v2_audio.yaml`
|
||||
- `printables/manifests/zacus_v2_printables.yaml`
|
||||
- Preferred command entrypoint for day-to-day workflows: `./tools/dev/zacus.sh <action>`.
|
||||
- Keep user-facing conversation in French; keep code/comments/commits in English.
|
||||
- Do not commit generated runtime/log artifacts (`artifacts/`, `logs/`, build outputs).
|
||||
|
||||
## Local Contracts Map
|
||||
Use the nearest contract for scoped work:
|
||||
- `.github/AGENTS.md` for CI/templates
|
||||
- `audio/AGENTS.md` for audio manifests
|
||||
- `docs/AGENTS.md` for documentation updates
|
||||
- `game/AGENTS.md` for scenarios/prompts
|
||||
- `hardware/AGENTS.md` for hardware integration
|
||||
- `hardware/firmware/AGENTS.md` for firmware workflows
|
||||
- `hardware/firmware/tools/dev/AGENTS.md` for firmware tooling scripts
|
||||
- `kit-maitre-du-jeu/AGENTS.md` for GM kit content
|
||||
- `printables/AGENTS.md` for printables manifests and sources
|
||||
- `tools/AGENTS.md` for repository tooling scripts
|
||||
|
||||
## Must
|
||||
- Use `game/scenarios/*.yaml` as single source of truth for story points.
|
||||
- Treat `hardware/firmware/esp32/` as read-only.
|
||||
|
||||
+1
-1
Submodule ESP32_ZACUS updated: cbc80f898f...8a83ebaa0e
@@ -5125,3 +5125,6 @@
|
||||
- `[MAIN] credits boot audio scene=SCENE_CREDITS track=/music/SCENE_WIN.mp3 source=forced prev=/music/boot_radio.mp3`
|
||||
- `AUDIO_STATUS ... track=/music/SCENE_WIN.mp3 ...`
|
||||
- Dernier upload TENTATIVE FAIL: port `/dev/cu.usbmodem5AB90753301` disparu (USB non detecte au moment de la commande).
|
||||
2026-04-02T17:28:02Z plan_runner --agent domains/firmware-tooling - executed 3 commands
|
||||
2026-04-02T17:28:03Z plan_runner --agent domains/docs - executed 2 commands
|
||||
[20260402-172814] Run artefacts: /Users/electron/Documents/Projets_Creatifs/le-mystere-professeur-zacus/hardware/firmware/artifacts/rc_live/freenove_esp32s3_esp8266_oled_20260402-172814, logs: /Users/electron/Documents/Projets_Creatifs/le-mystere-professeur-zacus/hardware/firmware/logs/rc_live, summary: /Users/electron/Documents/Projets_Creatifs/le-mystere-professeur-zacus/hardware/firmware/artifacts/rc_live/freenove_esp32s3_esp8266_oled_20260402-172814/summary.md
|
||||
|
||||
@@ -66,8 +66,37 @@ void UiNavEngine::update() {
|
||||
}
|
||||
|
||||
void UiNavEngine::triggerAction(const UiScreenAction& action) {
|
||||
if (!action.condition.isEmpty()) {
|
||||
Serial.printf("[UI_NAV] condition=%s (evaluation deferred)\n", action.condition.c_str());
|
||||
}
|
||||
if (!action.audio.isEmpty()) {
|
||||
Serial.printf("[UI_NAV] play_audio=%s\n", action.audio.c_str());
|
||||
}
|
||||
if (!action.popup.isEmpty()) {
|
||||
Serial.printf("[UI_NAV] show_popup=%s\n", action.popup.c_str());
|
||||
}
|
||||
if (!action.led.isEmpty()) {
|
||||
Serial.printf("[UI_NAV] set_led=%s\n", action.led.c_str());
|
||||
}
|
||||
if (!action.sync.isEmpty()) {
|
||||
Serial.printf("[UI_NAV] sync=%s\n", action.sync.c_str());
|
||||
}
|
||||
const bool hasReset = !action.reset.isEmpty() && action.reset != "0" && action.reset != "false";
|
||||
if (hasReset) {
|
||||
Serial.printf("[UI_NAV] reset=%s\n", action.reset.c_str());
|
||||
if (!current_.id.isEmpty()) {
|
||||
loadScreen(current_.id.c_str());
|
||||
return;
|
||||
}
|
||||
}
|
||||
const bool hasEnd = !action.end.isEmpty() && action.end != "0" && action.end != "false";
|
||||
if (hasEnd) {
|
||||
Serial.printf("[UI_NAV] end=%s\n", action.end.c_str());
|
||||
timeoutMs_ = 0;
|
||||
current_.actions.clear();
|
||||
return;
|
||||
}
|
||||
if (!action.goto_id.isEmpty()) {
|
||||
loadScreen(action.goto_id.c_str());
|
||||
}
|
||||
// TODO: gérer play_audio, show_popup, set_led, sync, reset, end, etc.
|
||||
}
|
||||
|
||||
@@ -66,8 +66,37 @@ void UiNavEngine::update() {
|
||||
}
|
||||
|
||||
void UiNavEngine::triggerAction(const UiScreenAction& action) {
|
||||
if (!action.condition.isEmpty()) {
|
||||
Serial.printf("[UI_NAV] condition=%s (evaluation deferred)\n", action.condition.c_str());
|
||||
}
|
||||
if (!action.audio.isEmpty()) {
|
||||
Serial.printf("[UI_NAV] play_audio=%s\n", action.audio.c_str());
|
||||
}
|
||||
if (!action.popup.isEmpty()) {
|
||||
Serial.printf("[UI_NAV] show_popup=%s\n", action.popup.c_str());
|
||||
}
|
||||
if (!action.led.isEmpty()) {
|
||||
Serial.printf("[UI_NAV] set_led=%s\n", action.led.c_str());
|
||||
}
|
||||
if (!action.sync.isEmpty()) {
|
||||
Serial.printf("[UI_NAV] sync=%s\n", action.sync.c_str());
|
||||
}
|
||||
const bool hasReset = !action.reset.isEmpty() && action.reset != "0" && action.reset != "false";
|
||||
if (hasReset) {
|
||||
Serial.printf("[UI_NAV] reset=%s\n", action.reset.c_str());
|
||||
if (!current_.id.isEmpty()) {
|
||||
loadScreen(current_.id.c_str());
|
||||
return;
|
||||
}
|
||||
}
|
||||
const bool hasEnd = !action.end.isEmpty() && action.end != "0" && action.end != "false";
|
||||
if (hasEnd) {
|
||||
Serial.printf("[UI_NAV] end=%s\n", action.end.c_str());
|
||||
timeoutMs_ = 0;
|
||||
current_.actions.clear();
|
||||
return;
|
||||
}
|
||||
if (!action.goto_id.isEmpty()) {
|
||||
loadScreen(action.goto_id.c_str());
|
||||
}
|
||||
// TODO: gérer play_audio, show_popup, set_led, sync, reset, end, etc.
|
||||
}
|
||||
|
||||
@@ -1124,6 +1124,18 @@ bool startAudioFromFsAsync(fs::FS& storage,
|
||||
return false;
|
||||
}
|
||||
|
||||
fs::File audioFile = storage.open(path, FILE_READ);
|
||||
if (!audioFile) {
|
||||
Serial.printf("[AUDIO_ASYNC] %s cannot open file: %s\n", source, path);
|
||||
return false;
|
||||
}
|
||||
const size_t sizeBytes = static_cast<size_t>(audioFile.size());
|
||||
audioFile.close();
|
||||
if (sizeBytes == 0U) {
|
||||
Serial.printf("[AUDIO_ASYNC] %s empty file rejected: %s\n", source, path);
|
||||
return false;
|
||||
}
|
||||
|
||||
setBootAudioPaEnabled(true, source);
|
||||
printBootAudioOutputInfo(source);
|
||||
if (!audioService().startBaseFs(storage, path, gain, maxDurationMs, source)) {
|
||||
@@ -1297,12 +1309,9 @@ void startBootAudioLoopCycle(uint32_t nowMs, const char* source) {
|
||||
stopBootRadioScan("boot_proto_cycle");
|
||||
audioService().stopBase("boot_proto_cycle");
|
||||
|
||||
// TODO: Boot audio disabled temporarily due to corrupted MP3 in LittleFS
|
||||
// This prevents crashes from loop budget saturation during heavy I/O
|
||||
bool startedAudio = false; // DISABLED: startRandomTokenFxAsync("BOOT", source, false);
|
||||
bool startedAudio = startBootAudioPrimaryFxAsync(source);
|
||||
if (!startedAudio) {
|
||||
// DISABLED: startedAudio = startBootAudioPrimaryFxAsync(source);
|
||||
Serial.println("[BOOT_PROTO] Boot audio disabled (corrupted file recovery).");
|
||||
Serial.println("[BOOT_PROTO] Audio replay unavailable, fallback radio scan.");
|
||||
}
|
||||
if (!g_bootAudioProtocol.active) {
|
||||
Serial.printf("[BOOT_PROTO] LOOP aborted after key action (%s)\n", source);
|
||||
@@ -3331,7 +3340,11 @@ void app_orchestrator::setup() {
|
||||
setupInternalLittleFs();
|
||||
storyFsManager().init();
|
||||
g_wifi.begin("uson-esp32");
|
||||
g_web.begin(&g_wifi, nullptr, &g_mp3, 8080U, nullptr);
|
||||
WebUiService::Config webConfig = {};
|
||||
webConfig.authEnabled = config::kWebUiAuthEnabled;
|
||||
snprintf(webConfig.user, sizeof(webConfig.user), "%s", config::kWebUiAuthUser);
|
||||
snprintf(webConfig.pass, sizeof(webConfig.pass), "%s", config::kWebUiAuthPass);
|
||||
g_web.begin(&g_wifi, nullptr, &g_mp3, 8080U, &webConfig);
|
||||
g_web.setStoryContext(&storyV2Controller(), &storyFsManager());
|
||||
g_radioRuntime.begin(config::kEnableRadioRuntimeTasks, &g_wifi, nullptr, &g_web);
|
||||
g_web.setRuntime(&g_radioRuntime);
|
||||
|
||||
@@ -26,6 +26,26 @@
|
||||
#define USON_ENABLE_CODEC_OPUS 0
|
||||
#endif
|
||||
|
||||
#ifndef USON_AP_FALLBACK_SSID
|
||||
#define USON_AP_FALLBACK_SSID "U-SON-RADIO"
|
||||
#endif
|
||||
|
||||
#ifndef USON_AP_FALLBACK_PASSWORD
|
||||
#define USON_AP_FALLBACK_PASSWORD ""
|
||||
#endif
|
||||
|
||||
#ifndef USON_WEBUI_AUTH_ENABLE
|
||||
#define USON_WEBUI_AUTH_ENABLE 1
|
||||
#endif
|
||||
|
||||
#ifndef USON_WEBUI_AUTH_USER
|
||||
#define USON_WEBUI_AUTH_USER "admin"
|
||||
#endif
|
||||
|
||||
#ifndef USON_WEBUI_AUTH_PASS
|
||||
#define USON_WEBUI_AUTH_PASS ""
|
||||
#endif
|
||||
|
||||
namespace config {
|
||||
|
||||
constexpr uint8_t kPinLedR = 16;
|
||||
@@ -96,6 +116,11 @@ constexpr bool kPreferLittleFsBootFx = false; // DISABLED: temp fix for audio i
|
||||
constexpr char kBootFxLittleFsPath[] = ""; // EMPTY: disable boot audio file entirely
|
||||
constexpr float kBootFxLittleFsGain = 0.24f;
|
||||
constexpr uint32_t kBootFxLittleFsMaxDurationMs = 22000;
|
||||
constexpr char kApFallbackSsid[] = USON_AP_FALLBACK_SSID;
|
||||
constexpr char kApFallbackPassword[] = USON_AP_FALLBACK_PASSWORD;
|
||||
constexpr bool kWebUiAuthEnabled = (USON_WEBUI_AUTH_ENABLE != 0);
|
||||
constexpr char kWebUiAuthUser[] = USON_WEBUI_AUTH_USER;
|
||||
constexpr char kWebUiAuthPass[] = USON_WEBUI_AUTH_PASS;
|
||||
|
||||
// UI link v2: UART bidirectionnel ESP32 <-> UI (OLED/TFT) en 3.3V TTL.
|
||||
// On garde ces valeurs stables pour permettre le hot-swap UI sans reflasher l'ESP32.
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
#include <ArduinoJson.h>
|
||||
#include <WiFi.h>
|
||||
#include <cstring>
|
||||
|
||||
#include "../../config.h"
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -131,7 +134,12 @@ void WifiService::update(uint32_t nowMs) {
|
||||
|
||||
if (apAutoFallback_ && WiFi.status() != WL_CONNECTED && !snap_.apEnabled &&
|
||||
static_cast<int32_t>(nowMs - lastStaAttemptMs_) >= static_cast<int32_t>(kApFallbackDelayMs)) {
|
||||
enableAp("U-SON-RADIO", "zacus-radio-42", "AP_FALLBACK");
|
||||
if (strlen(config::kApFallbackPassword) < 8U) {
|
||||
setError("AP_PASS_UNSET");
|
||||
setEvent("AP_FALLBACK_DISABLED");
|
||||
} else {
|
||||
enableAp(config::kApFallbackSsid, config::kApFallbackPassword, "AP_FALLBACK");
|
||||
}
|
||||
}
|
||||
|
||||
updateSnapshot(nowMs);
|
||||
@@ -164,8 +172,13 @@ bool WifiService::connectSta(const char* ssid, const char* pass, const char* rea
|
||||
}
|
||||
|
||||
bool WifiService::enableAp(const char* ssid, const char* pass, const char* reason) {
|
||||
const char* apSsid = (ssid != nullptr && ssid[0] != '\0') ? ssid : "U-SON-RADIO";
|
||||
const char* apPass = (pass != nullptr) ? pass : "zacus-radio-42";
|
||||
const char* apSsid = (ssid != nullptr && ssid[0] != '\0') ? ssid : config::kApFallbackSsid;
|
||||
const char* apPass = (pass != nullptr && pass[0] != '\0') ? pass : config::kApFallbackPassword;
|
||||
|
||||
if (apPass == nullptr || strlen(apPass) < 8U) {
|
||||
setError("AP_PASS_SHORT");
|
||||
return false;
|
||||
}
|
||||
|
||||
WiFi.mode(WIFI_AP_STA);
|
||||
const bool ok = WiFi.softAP(apSsid, apPass);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
#include "../../config.h"
|
||||
#include "serial_dispatch.h"
|
||||
#include "../network/wifi_service.h"
|
||||
#include "../radio/radio_service.h"
|
||||
@@ -267,7 +268,9 @@ bool serialProcessRadioCommand(const SerialCommand& cmd,
|
||||
char ssid[33] = {};
|
||||
char pass[65] = {};
|
||||
if (args[0] == '\0') {
|
||||
const bool ok = ctx.wifi->enableAp("U-SON-RADIO", "usonradio", "serial_ap_on");
|
||||
const bool ok = ctx.wifi->enableAp(config::kApFallbackSsid,
|
||||
config::kApFallbackPassword,
|
||||
"serial_ap_on");
|
||||
serialDispatchReply(out, "WIFI", ok ? SerialDispatchResult::kOk : SerialDispatchResult::kBusy, "ap_on");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "../../runtime/radio_runtime.h"
|
||||
#include "fs/story_fs_manager.h"
|
||||
#include "generated/scenarios_gen.h"
|
||||
#include "../../config.h"
|
||||
#include "../network/wifi_service.h"
|
||||
#include "../radio/radio_service.h"
|
||||
#include "../serial/serial_commands_story.h"
|
||||
@@ -156,6 +157,12 @@ void WebUiService::begin(WifiService* wifi,
|
||||
if (cfg != nullptr) {
|
||||
config_ = *cfg;
|
||||
}
|
||||
if (config_.authEnabled && config_.pass[0] == '\0') {
|
||||
const uint32_t chipSuffix = static_cast<uint32_t>(ESP.getEfuseMac() & 0x00FFFFFFULL);
|
||||
snprintf(config_.pass, sizeof(config_.pass), "esp32-%06lx", static_cast<unsigned long>(chipSuffix));
|
||||
setError("AUTH_PASS_AUTO");
|
||||
Serial.printf("[WEB] Auth enabled with auto-generated password for user '%s'.\n", config_.user);
|
||||
}
|
||||
snap_ = Snapshot();
|
||||
snap_.port = port;
|
||||
|
||||
@@ -609,8 +616,8 @@ void WebUiService::setupRoutes() {
|
||||
request->send(200, "application/json", "{\"ok\":true}");
|
||||
return;
|
||||
}
|
||||
String ssid = "U-SON-RADIO";
|
||||
String pass = "usonradio";
|
||||
String ssid = config::kApFallbackSsid;
|
||||
String pass = config::kApFallbackPassword;
|
||||
if (request->hasParam("ssid")) {
|
||||
ssid = request->getParam("ssid")->value();
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ class WebUiService {
|
||||
struct Config {
|
||||
bool authEnabled = false;
|
||||
char user[33] = "admin";
|
||||
char pass[65] = "usonradio";
|
||||
char pass[65] = "";
|
||||
};
|
||||
|
||||
struct Snapshot {
|
||||
|
||||
Reference in New Issue
Block a user