feat: expand companion platform, device controls, and memoir workflows
ci / verify (push) Canceled after 0s

Add customer and operator login, BLE provisioning, device fleet controls, call memory, memoir generation, and refreshed family and maintenance portals. Include API contracts, migrations, firmware sources, tests, assets, and deployment documentation.
This commit is contained in:
Codex Local
2026-10-08 10:06:44 +08:00
parent 98fb2965d0
commit bf1233d5df
861 changed files with 94614 additions and 3209 deletions
@@ -0,0 +1,100 @@
#include "device_screen_wake.h"
#include "device_power.h"
#include "call_client.h"
#include "esp_random.h"
#include "nvs.h"
#include <stdatomic.h>
#include <stdio.h>
#include <string.h>
typedef struct { uint32_t version; int64_t revision; char id[37]; } wake_event_t;
static wake_event_t pending;
static atomic_bool requested;
static bool awake;
static esp_err_t save_pending(bool keep)
{
nvs_handle_t n;
esp_err_t err = nvs_open("control", NVS_READWRITE, &n);
if (err != ESP_OK) return err;
err = keep ? nvs_set_blob(n, "screen_wake", &pending, sizeof(pending))
: nvs_erase_key(n, "screen_wake");
if (err == ESP_ERR_NVS_NOT_FOUND) err = ESP_OK;
if (err == ESP_OK) err = nvs_commit(n);
nvs_close(n);
return err;
}
esp_err_t device_screen_wake_init(void)
{
memset(&pending, 0, sizeof(pending));
awake = false;
atomic_store(&requested, false);
nvs_handle_t n;
esp_err_t err = nvs_open("control", NVS_READONLY, &n);
if (err == ESP_ERR_NVS_NOT_FOUND) return ESP_OK;
if (err != ESP_OK) return err;
size_t size = sizeof(pending);
err = nvs_get_blob(n, "screen_wake", &pending, &size);
nvs_close(n);
if (err == ESP_ERR_NVS_NOT_FOUND) { memset(&pending, 0, sizeof(pending)); return ESP_OK; }
if (err != ESP_OK) return err;
if (size != sizeof(pending) || pending.version != 1 || pending.revision <= 0 ||
pending.id[36] != 0 || strlen(pending.id) != 36) return ESP_ERR_INVALID_STATE;
return ESP_OK;
}
void device_screen_wake_request(void) { atomic_store(&requested, true); }
esp_err_t device_screen_wake_poll(int64_t applied_revision)
{
if (atomic_exchange(&requested, false) && !pending.version && call_client_standby()) {
// Retain a tap during initial configuration until its revision is known.
// An offline tap after initialization needs no network access.
if (applied_revision <= 0) { atomic_store(&requested, true); return ESP_OK; }
uint32_t r[4];
esp_fill_random(r, sizeof(r));
pending.version = 1;
pending.revision = applied_revision;
snprintf(pending.id, sizeof(pending.id), "%08lx-%04x-4%03x-%04x-%04x%08lx",
(unsigned long)r[0], (unsigned)(r[1] >> 16), (unsigned)(r[1] & 0xfff),
(unsigned)((r[2] >> 16 & 0x3fff) | 0x8000), (unsigned)(r[2] & 0xffff),
(unsigned long)r[3]);
esp_err_t err = save_pending(true);
if (err != ESP_OK) {
memset(&pending, 0, sizeof(pending));
atomic_store(&requested, true);
return err;
}
}
if (pending.version && !awake) {
esp_err_t err = device_power_apply("active", false);
if (err != ESP_OK) return err;
awake = true;
}
return ESP_OK;
}
void device_screen_wake_add_report(cJSON *report)
{
if (!pending.version || !awake) return;
cJSON *event = cJSON_AddObjectToObject(report, "screen_wake");
cJSON_AddStringToObject(event, "id", pending.id);
cJSON_AddNumberToObject(event, "revision", pending.revision);
}
esp_err_t device_screen_wake_ack(const cJSON *response)
{
if (!pending.version || !awake) return ESP_OK;
const cJSON *event = cJSON_GetObjectItemCaseSensitive(response, "screen_wake");
const cJSON *id = cJSON_GetObjectItemCaseSensitive(event, "id");
const cJSON *revision = cJSON_GetObjectItemCaseSensitive(event, "revision");
const cJSON *result = cJSON_GetObjectItemCaseSensitive(event, "result");
if (!cJSON_IsString(id) || strcmp(id->valuestring, pending.id) ||
!cJSON_IsNumber(revision) || revision->valuedouble != pending.revision ||
!cJSON_IsString(result) || (strcmp(result->valuestring, "applied") &&
strcmp(result->valuestring, "superseded"))) return ESP_ERR_INVALID_RESPONSE;
esp_err_t err = save_pending(false);
if (err == ESP_OK) { memset(&pending, 0, sizeof(pending)); awake = false; }
return err;
}