upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/main/mint_health.c
diff options
context:
space:
mode:
Diffstat (limited to 'main/mint_health.c')
-rw-r--r--main/mint_health.c235
1 files changed, 235 insertions, 0 deletions
diff --git a/main/mint_health.c b/main/mint_health.c
new file mode 100644
index 0000000..5853a39
--- /dev/null
+++ b/main/mint_health.c
@@ -0,0 +1,235 @@
1#include "mint_health.h"
2#include "esp_log.h"
3#include "esp_http_client.h"
4#include "esp_crt_bundle.h"
5#include "freertos/FreeRTOS.h"
6#include "freertos/task.h"
7#include "freertos/semphr.h"
8#include <string.h>
9#include <stdlib.h>
10
11static const char *TAG = "mint_health";
12
13static mint_status_t s_mints[MINT_HEALTH_MAX];
14static int s_mint_count = 0;
15static bool s_running = false;
16static TaskHandle_t s_task_handle = NULL;
17static SemaphoreHandle_t s_mutex = NULL;
18
19#define MAX_CALLBACKS 4
20static mint_health_changed_cb s_callbacks[MAX_CALLBACKS];
21static int s_callback_count = 0;
22
23static void fire_callbacks(void)
24{
25 for (int i = 0; i < s_callback_count; i++) {
26 if (s_callbacks[i]) s_callbacks[i]();
27 }
28}
29
30esp_err_t mint_health_init(const char urls[][256], int count)
31{
32 if (count > MINT_HEALTH_MAX) count = MINT_HEALTH_MAX;
33 s_mint_count = count;
34 s_callback_count = 0;
35
36 if (!s_mutex) s_mutex = xSemaphoreCreateMutex();
37
38 memset(s_mints, 0, sizeof(s_mints));
39 for (int i = 0; i < count; i++) {
40 strncpy(s_mints[i].url, urls[i], sizeof(s_mints[i].url) - 1);
41 s_mints[i].reachable = false;
42 s_mints[i].consecutive_successes = 0;
43 s_mints[i].last_probe_ms = 0;
44 s_mints[i].last_http_status = 0;
45 }
46
47 ESP_LOGI(TAG, "Initialized with %d mints", count);
48 return ESP_OK;
49}
50
51static bool probe_mint(const char *url)
52{
53 char probe_url[512];
54 snprintf(probe_url, sizeof(probe_url), "%s/v1/info", url);
55
56 esp_http_client_config_t config = {
57 .url = probe_url,
58 .method = HTTP_METHOD_GET,
59 .timeout_ms = MINT_HEALTH_PROBE_TIMEOUT_MS,
60 .crt_bundle_attach = esp_crt_bundle_attach,
61 };
62 esp_http_client_handle_t client = esp_http_client_init(&config);
63 if (!client) return false;
64
65 esp_err_t err = esp_http_client_open(client, 0);
66 if (err != ESP_OK) {
67 esp_http_client_cleanup(client);
68 return false;
69 }
70
71 int content_length = esp_http_client_fetch_headers(client);
72 int status = esp_http_client_get_status_code(client);
73
74 char *resp = NULL;
75 if (content_length > 0 && content_length < 8192) {
76 resp = malloc(content_length + 1);
77 if (resp) {
78 int read = esp_http_client_read(client, resp, content_length);
79 if (read > 0) resp[read] = '\0';
80 }
81 }
82 if (resp) free(resp);
83
84 esp_http_client_cleanup(client);
85 return (status >= 200 && status < 300);
86}
87
88static void run_probes(void)
89{
90 int old_reachable = 0;
91 int new_reachable = 0;
92
93 if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) return;
94
95 for (int i = 0; i < s_mint_count; i++) {
96 if (s_mints[i].reachable) old_reachable++;
97 }
98
99 for (int i = 0; i < s_mint_count; i++) {
100 bool ok = probe_mint(s_mints[i].url);
101 s_mints[i].last_probe_ms = (int64_t)xTaskGetTickCount() * portTICK_PERIOD_MS;
102 s_mints[i].last_http_status = ok ? 200 : 0;
103
104 if (ok) {
105 s_mints[i].consecutive_successes++;
106 if (s_mints[i].consecutive_successes >= MINT_HEALTH_RECOVERY_THRESHOLD) {
107 if (!s_mints[i].reachable) {
108 ESP_LOGI(TAG, "Mint RECOVERED: %s", s_mints[i].url);
109 }
110 s_mints[i].reachable = true;
111 }
112 } else {
113 if (s_mints[i].reachable) {
114 ESP_LOGW(TAG, "Mint UNREACHABLE: %s", s_mints[i].url);
115 }
116 s_mints[i].reachable = false;
117 s_mints[i].consecutive_successes = 0;
118 }
119
120 if (s_mints[i].reachable) new_reachable++;
121 }
122
123 bool changed = (old_reachable != new_reachable);
124 xSemaphoreGive(s_mutex);
125
126 if (changed) {
127 ESP_LOGI(TAG, "Reachable set changed: %d -> %d", old_reachable, new_reachable);
128 fire_callbacks();
129 }
130}
131
132static void run_initial_probes(void)
133{
134 if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) return;
135
136 for (int i = 0; i < s_mint_count; i++) {
137 bool ok = probe_mint(s_mints[i].url);
138 s_mints[i].last_probe_ms = (int64_t)xTaskGetTickCount() * portTICK_PERIOD_MS;
139 s_mints[i].last_http_status = ok ? 200 : 0;
140
141 if (ok) {
142 s_mints[i].consecutive_successes = MINT_HEALTH_RECOVERY_THRESHOLD;
143 s_mints[i].reachable = true;
144 ESP_LOGI(TAG, "Initial probe OK: %s (reachable)", s_mints[i].url);
145 } else {
146 s_mints[i].consecutive_successes = 0;
147 s_mints[i].reachable = false;
148 ESP_LOGW(TAG, "Initial probe FAIL: %s (unreachable)", s_mints[i].url);
149 }
150 }
151
152 xSemaphoreGive(s_mutex);
153 fire_callbacks();
154}
155
156static void health_task(void *pvParameters)
157{
158 ESP_LOGI(TAG, "Health probe task started, waiting for DNS to stabilize...");
159 vTaskDelay(pdMS_TO_TICKS(5000));
160 run_initial_probes();
161
162 while (s_running) {
163 vTaskDelay(pdMS_TO_TICKS(MINT_HEALTH_PROBE_INTERVAL_S * 1000));
164 if (!s_running) break;
165 run_probes();
166 }
167
168 s_task_handle = NULL;
169 vTaskDelete(NULL);
170}
171
172void mint_health_start(void)
173{
174 if (s_running) return;
175 s_running = true;
176 xTaskCreate(health_task, "mint_health", 16384, NULL, 3, &s_task_handle);
177}
178
179void mint_health_stop(void)
180{
181 s_running = false;
182 if (s_task_handle) {
183 vTaskDelay(pdMS_TO_TICKS(100));
184 }
185}
186
187const mint_status_t *mint_health_get_all(int *out_count)
188{
189 if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) {
190 *out_count = 0;
191 return s_mints;
192 }
193 *out_count = s_mint_count;
194 xSemaphoreGive(s_mutex);
195 return s_mints;
196}
197
198bool mint_health_is_reachable(const char *url)
199{
200 if (!url) return false;
201 if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) return false;
202 bool result = false;
203 for (int i = 0; i < s_mint_count; i++) {
204 if (strcmp(s_mints[i].url, url) == 0 || strstr(url, s_mints[i].url) != NULL) {
205 result = s_mints[i].reachable;
206 break;
207 }
208 }
209 xSemaphoreGive(s_mutex);
210 return result;
211}
212
213void mint_health_mark_unreachable(const char *url)
214{
215 if (!url) return;
216 if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) return;
217 for (int i = 0; i < s_mint_count; i++) {
218 if (strcmp(s_mints[i].url, url) == 0 || strstr(url, s_mints[i].url) != NULL) {
219 if (s_mints[i].reachable) {
220 s_mints[i].reachable = false;
221 s_mints[i].consecutive_successes = 0;
222 ESP_LOGW(TAG, "Reactively marked unreachable: %s", url);
223 }
224 break;
225 }
226 }
227 xSemaphoreGive(s_mutex);
228}
229
230void mint_health_register_callback(mint_health_changed_cb cb)
231{
232 if (s_callback_count < MAX_CALLBACKS && cb) {
233 s_callbacks[s_callback_count++] = cb;
234 }
235}