#include "device_screen_wake.h" #include "device_power.h" #include "call_client.h" #include "esp_random.h" #include "nvs.h" #include #include #include 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; }