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);
}
|