feat(box3,plip): POST /game/cmd — trigger cmd_exec over HTTP #14
@@ -14,6 +14,7 @@
|
||||
#include <unistd.h>
|
||||
|
||||
#include "cJSON.h"
|
||||
#include "cmd_exec.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_http_server.h"
|
||||
#include "esp_log.h"
|
||||
@@ -219,6 +220,38 @@ static esp_err_t handle_scenario_post(httpd_req_t *req) {
|
||||
return send_json(req, "200 OK", buf);
|
||||
}
|
||||
|
||||
// ---------- POST /game/cmd ----------
|
||||
//
|
||||
// WiFi-direct CMD endpoint: receives {op,a} JSON frames (≤512 bytes) and
|
||||
// forwards them to cmd_exec_handle(). Replaces ESP-NOW CMD path for annexes.
|
||||
|
||||
#define MAX_CMD_BYTES 512
|
||||
|
||||
static esp_err_t handle_cmd_post(httpd_req_t *req) {
|
||||
if (req->content_len <= 0 || req->content_len > MAX_CMD_BYTES) {
|
||||
ESP_LOGW(TAG, "POST /game/cmd: bad content_len=%d", (int)req->content_len);
|
||||
return send_error(req, "400 Bad Request", "body must be 1..512 bytes");
|
||||
}
|
||||
char body[MAX_CMD_BYTES + 1];
|
||||
int total = 0;
|
||||
while (total < (int)req->content_len) {
|
||||
int got = httpd_req_recv(req, body + total, req->content_len - total);
|
||||
if (got <= 0) {
|
||||
if (got == HTTPD_SOCK_ERR_TIMEOUT) continue;
|
||||
return send_error(req, "400 Bad Request", "recv failed");
|
||||
}
|
||||
total += got;
|
||||
}
|
||||
body[total] = '\0';
|
||||
|
||||
ESP_LOGI(TAG, "POST /game/cmd (%d B): %.*s", total, total < 80 ? total : 80, body);
|
||||
esp_err_t err = cmd_exec_handle(body, (size_t)total);
|
||||
if (err == ESP_ERR_INVALID_ARG) {
|
||||
return send_error(req, "400 Bad Request", "malformed cmd json or missing op");
|
||||
}
|
||||
return send_json(req, "200 OK", "{\"ok\":true}");
|
||||
}
|
||||
|
||||
// ---------- POST /game/file ----------
|
||||
//
|
||||
// Write binary assets directly to the SD card over HTTP.
|
||||
@@ -326,7 +359,7 @@ esp_err_t scenario_server_start(void) {
|
||||
}
|
||||
httpd_config_t cfg = HTTPD_DEFAULT_CONFIG();
|
||||
cfg.server_port = 80;
|
||||
cfg.max_uri_handlers = 10;
|
||||
cfg.max_uri_handlers = 12;
|
||||
cfg.stack_size = 8192;
|
||||
|
||||
esp_err_t err = httpd_start(&s_server, &cfg);
|
||||
@@ -347,10 +380,15 @@ esp_err_t scenario_server_start(void) {
|
||||
.uri = "/game/file", .method = HTTP_POST,
|
||||
.handler = handle_file_post, .user_ctx = NULL,
|
||||
};
|
||||
static const httpd_uri_t uri_cmd = {
|
||||
.uri = "/game/cmd", .method = HTTP_POST,
|
||||
.handler = handle_cmd_post, .user_ctx = NULL,
|
||||
};
|
||||
httpd_register_uri_handler(s_server, &uri_healthz);
|
||||
httpd_register_uri_handler(s_server, &uri_scenario);
|
||||
httpd_register_uri_handler(s_server, &uri_file);
|
||||
httpd_register_uri_handler(s_server, &uri_cmd);
|
||||
|
||||
ESP_LOGI(TAG, "scenario server up on :80 (GET /healthz, POST /game/scenario, POST /game/file)");
|
||||
ESP_LOGI(TAG, "scenario server up on :80 (GET /healthz, POST /game/scenario, POST /game/file, POST /game/cmd)");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
+40
-6
@@ -24,6 +24,7 @@
|
||||
#include <string.h>
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "esp_spiffs.h"
|
||||
#include "esp_vfs_fat.h"
|
||||
#include "driver/i2s_std.h"
|
||||
#include "driver/sdspi_host.h"
|
||||
@@ -64,6 +65,27 @@ static QueueHandle_t s_queue;
|
||||
static i2s_chan_handle_t s_spk_handle = NULL;
|
||||
static volatile bool s_stop_req = false;
|
||||
static bool s_sd_mounted = false;
|
||||
static bool s_spiffs_mounted = false;
|
||||
|
||||
/* ── SPIFFS lazy mount ───────────────────────────────────────────────────── */
|
||||
|
||||
static void ensure_spiffs_audio(void)
|
||||
{
|
||||
if (s_spiffs_mounted) return;
|
||||
esp_vfs_spiffs_conf_t conf = {
|
||||
.base_path = "/spiffs",
|
||||
.partition_label = "storage",
|
||||
.max_files = 8,
|
||||
.format_if_mount_failed = false,
|
||||
};
|
||||
esp_err_t ret = esp_vfs_spiffs_register(&conf);
|
||||
if (ret == ESP_OK || ret == ESP_ERR_INVALID_STATE /* already mounted */) {
|
||||
s_spiffs_mounted = true;
|
||||
ESP_LOGI(TAG, "SPIFFS mounted at /spiffs");
|
||||
} else {
|
||||
ESP_LOGW(TAG, "SPIFFS mount failed: %s", esp_err_to_name(ret));
|
||||
}
|
||||
}
|
||||
|
||||
/* ── SD card init (best-effort) ──────────────────────────────────────────── */
|
||||
|
||||
@@ -179,14 +201,26 @@ static void play_wav_buf(const uint8_t *buf, size_t len)
|
||||
stream_pcm(buf + wi.data_offset, wi.data_size);
|
||||
}
|
||||
|
||||
/* Play WAV from SD. Falls back to tone on error. */
|
||||
/* Play WAV from filesystem (SPIFFS or SD). Falls back to tone on error.
|
||||
* Paths starting with /spiffs/ are read directly from SPIFFS; all other
|
||||
* paths (relative or /sdcard/…) require the SD card to be mounted first. */
|
||||
static void play_wav_file(const char *path)
|
||||
{
|
||||
ensure_sd_mounted();
|
||||
if (!s_sd_mounted) {
|
||||
ESP_LOGW(TAG, "SD not mounted — beep fallback");
|
||||
audio_play_tone(880.0f, 200);
|
||||
return;
|
||||
bool is_spiffs = (strncmp(path, "/spiffs/", 8) == 0 || strncmp(path, "/spiffs", 7) == 0);
|
||||
if (is_spiffs) {
|
||||
ensure_spiffs_audio();
|
||||
if (!s_spiffs_mounted) {
|
||||
ESP_LOGW(TAG, "SPIFFS not mounted — beep fallback");
|
||||
audio_play_tone(880.0f, 200);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
ensure_sd_mounted();
|
||||
if (!s_sd_mounted) {
|
||||
ESP_LOGW(TAG, "SD not mounted — beep fallback");
|
||||
audio_play_tone(880.0f, 200);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
char full[192];
|
||||
|
||||
+35
-1
@@ -21,6 +21,7 @@
|
||||
#include <sys/stat.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "cmd_exec.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_http_server.h"
|
||||
@@ -221,6 +222,34 @@ static esp_err_t handle_file_post(httpd_req_t *req)
|
||||
return send_json(req, "200 OK", resp);
|
||||
}
|
||||
|
||||
/* ── POST /game/cmd (WiFi-direct CMD endpoint) ───────────────────────────── */
|
||||
|
||||
#define MAX_CMD_BYTES 512
|
||||
|
||||
static esp_err_t handle_cmd_post(httpd_req_t *req)
|
||||
{
|
||||
if (req->content_len <= 0 || req->content_len > MAX_CMD_BYTES)
|
||||
return send_json(req, "400 Bad Request", "{\"error\":\"body must be 1..512 bytes\"}");
|
||||
|
||||
char body[MAX_CMD_BYTES + 1];
|
||||
int total = 0;
|
||||
while (total < (int)req->content_len) {
|
||||
int got = httpd_req_recv(req, body + total, req->content_len - total);
|
||||
if (got <= 0) {
|
||||
if (got == HTTPD_SOCK_ERR_TIMEOUT) continue;
|
||||
return send_json(req, "400 Bad Request", "{\"error\":\"recv failed\"}");
|
||||
}
|
||||
total += got;
|
||||
}
|
||||
body[total] = '\0';
|
||||
|
||||
ESP_LOGI(TAG, "POST /game/cmd (%d B): %.*s", total, total < 80 ? total : 80, body);
|
||||
esp_err_t err = cmd_exec_handle(body, (size_t)total);
|
||||
if (err == ESP_ERR_INVALID_ARG)
|
||||
return send_json(req, "400 Bad Request", "{\"error\":\"malformed cmd json or missing op\"}");
|
||||
return send_json(req, "200 OK", "{\"ok\":true}");
|
||||
}
|
||||
|
||||
/* ── Public API ───────────────────────────────────────────────────────────── */
|
||||
|
||||
bool net_is_connected(void)
|
||||
@@ -314,10 +343,15 @@ esp_err_t net_init(void)
|
||||
.uri = "/game/file", .method = HTTP_POST,
|
||||
.handler = handle_file_post, .user_ctx = NULL,
|
||||
};
|
||||
static const httpd_uri_t uri_cmd = {
|
||||
.uri = "/game/cmd", .method = HTTP_POST,
|
||||
.handler = handle_cmd_post, .user_ctx = NULL,
|
||||
};
|
||||
httpd_register_uri_handler(s_httpd, &uri_status);
|
||||
httpd_register_uri_handler(s_httpd, &uri_scenario);
|
||||
httpd_register_uri_handler(s_httpd, &uri_file);
|
||||
httpd_register_uri_handler(s_httpd, &uri_cmd);
|
||||
|
||||
ESP_LOGI(TAG, "httpd up on :80 (GET /status, POST /game/scenario, POST /game/file)");
|
||||
ESP_LOGI(TAG, "httpd up on :80 (GET /status, POST /game/scenario, POST /game/file, POST /game/cmd)");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user