src.nth.io/

summaryrefslogtreecommitdiff
path: root/main/state_client.c
diff options
context:
space:
mode:
Diffstat (limited to 'main/state_client.c')
-rw-r--r--main/state_client.c105
1 files changed, 105 insertions, 0 deletions
diff --git a/main/state_client.c b/main/state_client.c
new file mode 100644
index 0000000..2d55bf9
--- /dev/null
+++ b/main/state_client.c
@@ -0,0 +1,105 @@
+#include "state_client.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include "cJSON.h"
+#include "esp_http_client.h"
+#include "esp_log.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/queue.h"
+#include "freertos/task.h"
+
+#include "viewport_state.h"
+
+static const char *TAG = "state_client";
+
+#define WORKER_STACK 4096
+#define WORKER_PRIO 5
+#define HTTP_TIMEOUT_MS 1000
+#define USER_AGENT "ScryptedViewport/" VIEWPORT_VERSION
+
+// Depth-1 queue holding the next desired state. xQueueOverwrite() gives us
+// the replace-on-full semantics from the spec.
+static QueueHandle_t s_queue;
+
+static void perform_post(viewport_run_state_t state)
+{
+ char url[320];
+ char body[160];
+ char viewport_name[64];
+
+ viewport_state_lock();
+ viewport_state_t *st = viewport_state_get();
+ if (st->scrypted_url[0] == '\0') {
+ viewport_state_unlock();
+ return; // not configured — silently drop
+ }
+ snprintf(url, sizeof(url), "%s/state", st->scrypted_url);
+ strncpy(viewport_name, st->viewport_name, sizeof(viewport_name) - 1);
+ viewport_name[sizeof(viewport_name) - 1] = '\0';
+ viewport_state_unlock();
+
+ const char *state_str = (state == VIEWPORT_STATE_AWAKE) ? "wake" : "sleep";
+ snprintf(body, sizeof(body),
+ "{\"viewport\":\"%s\",\"state\":\"%s\"}",
+ viewport_name, state_str);
+
+ esp_http_client_config_t cfg = {
+ .url = url,
+ .method = HTTP_METHOD_POST,
+ .timeout_ms = HTTP_TIMEOUT_MS,
+ .disable_auto_redirect = true,
+ };
+ esp_http_client_handle_t client = esp_http_client_init(&cfg);
+ if (!client) {
+ viewport_state_lock();
+ viewport_state_get()->state_post_failures++;
+ viewport_state_unlock();
+ return;
+ }
+ esp_http_client_set_header(client, "Content-Type", "application/json");
+ esp_http_client_set_header(client, "User-Agent", USER_AGENT);
+ esp_http_client_set_header(client, "Connection", "close");
+ esp_http_client_set_post_field(client, body, strlen(body));
+
+ esp_err_t err = esp_http_client_perform(client);
+ int status = (err == ESP_OK) ? esp_http_client_get_status_code(client) : -1;
+
+ if (err != ESP_OK || status < 200 || status >= 300) {
+ ESP_LOGW(TAG, "POST %s failed: err=%s status=%d", url, esp_err_to_name(err), status);
+ viewport_state_lock();
+ viewport_state_get()->state_post_failures++;
+ viewport_state_unlock();
+ } else {
+ ESP_LOGI(TAG, "POST %s {state:%s} -> %d", url, state_str, status);
+ }
+ esp_http_client_cleanup(client);
+}
+
+static void worker(void *arg)
+{
+ for (;;) {
+ viewport_run_state_t pending;
+ if (xQueueReceive(s_queue, &pending, portMAX_DELAY) == pdTRUE) {
+ perform_post(pending);
+ }
+ }
+}
+
+esp_err_t state_client_init(void)
+{
+ s_queue = xQueueCreate(1, sizeof(viewport_run_state_t));
+ if (!s_queue) return ESP_ERR_NO_MEM;
+ BaseType_t ok = xTaskCreate(worker, "state_client", WORKER_STACK,
+ NULL, WORKER_PRIO, NULL);
+ return (ok == pdPASS) ? ESP_OK : ESP_FAIL;
+}
+
+void state_client_post(viewport_run_state_t state)
+{
+ if (state != VIEWPORT_STATE_AWAKE && state != VIEWPORT_STATE_ASLEEP) return;
+ // xQueueOverwrite is depth-1 replace-on-full. The in-flight POST in the
+ // worker is unaffected — it finishes naturally, then drains the new entry.
+ xQueueOverwrite(s_queue, &state);
+}