src.nth.io/

summaryrefslogtreecommitdiff
path: root/main/state_client.c
blob: 2d55bf97c4527dad3449c5b600feb28fd223ffa4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
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);
}