feat(firmware): M2 WiFi + on-device REST API
Adds the WiFi-to-app sync path and the on-device open REST API implementing the
device paths of api/openapi.yaml, plus persistent config in NVS.
What changed:
- firmware/src/config.{h,cpp}: NVS (Preferences) store - stable device id from MAC,
WiFi SSID/PSK, API bearer token, upload settings. Secrets stay off the SD card.
- firmware/src/net_wifi.{h,cpp}: station join with stored creds; SoftAP provisioning
fallback when no creds / join fails; mDNS openscribe.local; reconnect in loop().
- firmware/src/api_http.{h,cpp}: synchronous WebServer on :80. Routes: GET /device,
GET/PUT /device/config, GET /recordings, GET /recordings/{id}, GET
/recordings/{id}/audio (HTTP Range -> 206 + Content-Range), DELETE /recordings/{id},
POST /record/start|stop. Bearer-token auth on mutating calls (open when no token).
- firmware/src/storage.{h,cpp}: list/read/delete/count helpers + path builders.
- firmware/src/main.cpp: wire config + WiFi + API into setup/loop; bump fw to 0.2.0.
- firmware/platformio.ini: add bblanchon/ArduinoJson@^7 (only new dep).
- state/: ARCHITECTURE, NOTES, TODO updated; security + NTP follow-ups recorded.
Why:
- This is the WiFi transfer channel and the device half of the "completely open API":
clients can list, download (resumable), delete and control without any cloud.
Notes:
- Not compiled locally (no PlatformIO on the dev host) or hardware-verified; CI builds
both board profiles. Security follow-ups logged: API is open when no token is set,
and the SoftAP uses a fixed default passphrase (make user-set at M4/BLE).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
16e3460642
commit
87a00265a3
13 changed files with 510 additions and 20 deletions
|
|
@ -94,4 +94,57 @@ bool writeMetadata(const String &path, const String &json) {
|
|||
return true;
|
||||
}
|
||||
|
||||
String recWavPath(const String &id) { return String(OSC_REC_DIR) + "/" + id + ".wav"; }
|
||||
String recMetaPath(const String &id) { return String(OSC_REC_DIR) + "/" + id + ".json"; }
|
||||
|
||||
String readFile(const String &path) {
|
||||
File f = SD.open(path, FILE_READ);
|
||||
if (!f) return String();
|
||||
String s;
|
||||
s.reserve(f.size() + 1);
|
||||
while (f.available()) s += static_cast<char>(f.read());
|
||||
f.close();
|
||||
return s;
|
||||
}
|
||||
|
||||
bool removeRecording(const String &id) {
|
||||
bool a = SD.remove(recWavPath(id));
|
||||
bool b = SD.remove(recMetaPath(id));
|
||||
return a || b;
|
||||
}
|
||||
|
||||
uint32_t countRecordings() {
|
||||
uint32_t n = 0;
|
||||
File dir = SD.open(OSC_REC_DIR);
|
||||
if (!dir) return 0;
|
||||
for (File f = dir.openNextFile(); f; f = dir.openNextFile()) {
|
||||
if (String(f.name()).endsWith(".wav")) n++;
|
||||
f.close();
|
||||
}
|
||||
dir.close();
|
||||
return n;
|
||||
}
|
||||
|
||||
String listRecordingsJson() {
|
||||
String out = "{\"items\":[";
|
||||
bool first = true;
|
||||
File dir = SD.open(OSC_REC_DIR);
|
||||
if (dir) {
|
||||
for (File f = dir.openNextFile(); f; f = dir.openNextFile()) {
|
||||
if (String(f.name()).endsWith(".json")) {
|
||||
String content;
|
||||
content.reserve(f.size() + 1);
|
||||
while (f.available()) content += static_cast<char>(f.read());
|
||||
if (!first) out += ",";
|
||||
out += content;
|
||||
first = false;
|
||||
}
|
||||
f.close();
|
||||
}
|
||||
dir.close();
|
||||
}
|
||||
out += "],\"next_cursor\":null}";
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace storage
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue