Files
lisael-box/main/audio/routine_manifest.c
T
2026-06-21 10:41:50 +02:00

108 lines
3.4 KiB
C

// See routine_manifest.h. The pure parser is host-testable (no ESP-IDF deps);
// the SD loader is compiled only on-target (#ifndef LISAEL_HOST_TEST).
#include "audio/routine_manifest.h"
#include "cJSON.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
int lisael_routine_parse(const char *json, lisael_routine_t *routines, int max_routines)
{
if (!json || !routines || max_routines <= 0) {
return -1;
}
cJSON *root = cJSON_Parse(json);
if (!cJSON_IsObject(root)) {
cJSON_Delete(root);
return -1;
}
cJSON *arr = cJSON_GetObjectItem(root, "routines");
if (!cJSON_IsArray(arr)) {
cJSON_Delete(root);
return -1;
}
int n_routines = 0;
cJSON *jr;
cJSON_ArrayForEach(jr, arr) {
if (n_routines >= max_routines) {
break;
}
cJSON *jid = cJSON_GetObjectItem(jr, "id");
cJSON *jtitle = cJSON_GetObjectItem(jr, "title");
cJSON *jicon = cJSON_GetObjectItem(jr, "icon");
cJSON *jsteps = cJSON_GetObjectItem(jr, "steps");
if (!cJSON_IsString(jid) || !cJSON_IsArray(jsteps)) {
continue;
}
lisael_routine_t *r = &routines[n_routines];
memset(r, 0, sizeof(*r));
snprintf(r->id, sizeof(r->id), "%s", jid->valuestring);
if (cJSON_IsString(jtitle)) {
snprintf(r->title, sizeof(r->title), "%s", jtitle->valuestring);
} else {
snprintf(r->title, sizeof(r->title), "%s", jid->valuestring);
}
if (cJSON_IsString(jicon)) {
snprintf(r->icon, sizeof(r->icon), "%s", jicon->valuestring);
}
cJSON *jstep;
cJSON_ArrayForEach(jstep, jsteps) {
if (r->n_steps >= LISAEL_MAX_STEPS) {
break;
}
cJSON *jtext = cJSON_GetObjectItem(jstep, "text");
cJSON *jsicon = cJSON_GetObjectItem(jstep, "icon");
cJSON *jaudio = cJSON_GetObjectItem(jstep, "audio");
if (!cJSON_IsString(jtext)) {
continue; // a step with no label is useless; skip it
}
lisael_step_t *s = &r->steps[r->n_steps];
snprintf(s->text, sizeof(s->text), "%s", jtext->valuestring);
if (cJSON_IsString(jsicon)) {
snprintf(s->icon, sizeof(s->icon), "%s", jsicon->valuestring);
}
if (cJSON_IsString(jaudio)) {
snprintf(s->audio, sizeof(s->audio), "%s", jaudio->valuestring);
}
r->n_steps++;
}
if (r->n_steps > 0) {
n_routines++; // skip routines with no usable step
}
}
cJSON_Delete(root);
return n_routines;
}
#ifndef LISAEL_HOST_TEST
#include "esp_log.h"
int lisael_routine_load(lisael_routine_t *routines, int max_routines)
{
const char *path = "/sdcard/routines/routines.json";
FILE *f = fopen(path, "r");
if (!f) {
return -1;
}
fseek(f, 0, SEEK_END);
long sz = ftell(f);
fseek(f, 0, SEEK_SET);
if (sz <= 0 || sz > 64 * 1024) {
fclose(f);
return -1;
}
char *buf = malloc((size_t)sz + 1);
if (!buf) {
fclose(f);
return -1;
}
size_t rd = fread(buf, 1, (size_t)sz, f);
buf[rd] = '\0';
fclose(f);
int n = lisael_routine_parse(buf, routines, max_routines);
free(buf);
ESP_LOGI("routine", "loaded %d routine(s) from %s", n, path);
return n;
}
#endif