Work through the remaining review items in TODO.md
Keep the HTTP session alive between weather polls (TODO #1). Re-creating the client each time forced a fresh TLS handshake inside the window where the UI is frozen. Measured on hardware: the fetch went from ~1.3s to ~80ms, so the pause is no longer long enough to matter once thermostat controls exist. The timeout drops from 30s to 10s once a session is warm, since only a cold handshake needs the longer budget. Any transport error or non-200 tears the session down so a half-open connection cannot poison every later poll. The response buffer is now reset per request rather than on HTTP_EVENT_ON_CONNECTED -- a kept-alive connection does not re-fire that event, so the buffer would have accumulated across polls. Indicate stale readings (TODO #2). After two consecutive failures the temperature dims and the status line shows the age. Age comes from tick counts, not weather_data_t.last_update: there is no SNTP client here, so time(NULL) starts at the epoch and any age derived from it is meaningless. Gate the touch readout behind UI_DEBUG_TOUCH, default off (TODO #3). Kept the note about LVGL not bubbling events to parents -- it documents a real trap. Delete main/utils (TODO #4); it had no callers outside itself. Also refreshed project_structure.txt, which was additionally missing i2c_bus and backlight. Lay out two 4MB OTA slots (TODO #5) while no devices are fielded, and document in the README that an OTA download cannot run while the panel is scanning -- the flash write faults the bounce-buffer ISR. Log the truncation warning once per response instead of once per chunk, and record the weather task's stack high-water mark (TODO #6): ~2400 bytes spare of 6144. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
1 parent
5bbb50c4eb
commit
d4b2ab7f99
10 files changed
+222
-101
No files matched your search
+86
-30
@@ -24,19 +24,35 @@ static const char *TAG = "ambient_weather";
|
||||
|
||||
#define WEATHER_URL_MAX 320
|
||||
#define WEATHER_RESP_MAX 4096
|
||||
/* A first TLS handshake on this hardware can take well over 10s. */
|
||||
#define WEATHER_TIMEOUT_MS 30000
|
||||
|
||||
/*
|
||||
* Two timeouts, because the first request costs far more than the rest.
|
||||
*
|
||||
* A cold TLS handshake on this board takes well over 10s. Once the session is
|
||||
* established and kept alive, a poll is just a short request/response. Holding
|
||||
* the cold timeout forever would mean the caller has to assume a 30s stall on
|
||||
* every poll -- and the caller pauses the UI for the duration of this call, so
|
||||
* that assumption is expensive.
|
||||
*/
|
||||
#define WEATHER_TIMEOUT_COLD_MS 30000
|
||||
#define WEATHER_TIMEOUT_WARM_MS 10000
|
||||
|
||||
static char resp_buf[WEATHER_RESP_MAX];
|
||||
static size_t resp_len = 0;
|
||||
static bool resp_truncated = false;
|
||||
|
||||
/*
|
||||
* The client is kept alive between polls. Re-creating it each time forced a
|
||||
* fresh TLS handshake -- seconds of work, all of it inside the window where
|
||||
* the UI is frozen.
|
||||
*/
|
||||
static esp_http_client_handle_t client = NULL;
|
||||
static char url[WEATHER_URL_MAX];
|
||||
static bool session_warm = false;
|
||||
|
||||
static esp_err_t http_event_handler(esp_http_client_event_t *evt)
|
||||
{
|
||||
switch (evt->event_id) {
|
||||
case HTTP_EVENT_ON_CONNECTED:
|
||||
resp_len = 0;
|
||||
break;
|
||||
|
||||
case HTTP_EVENT_ON_DATA:
|
||||
/*
|
||||
* Accumulate into a fixed buffer. The response is chunked, so
|
||||
@@ -46,7 +62,9 @@ static esp_err_t http_event_handler(esp_http_client_event_t *evt)
|
||||
if (resp_len + evt->data_len < sizeof(resp_buf)) {
|
||||
memcpy(resp_buf + resp_len, evt->data, evt->data_len);
|
||||
resp_len += evt->data_len;
|
||||
} else {
|
||||
} else if (!resp_truncated) {
|
||||
/* Once per response, not once per remaining chunk. */
|
||||
resp_truncated = true;
|
||||
ESP_LOGW(TAG, "Response exceeds %d bytes, truncating", WEATHER_RESP_MAX);
|
||||
}
|
||||
break;
|
||||
@@ -58,6 +76,37 @@ static esp_err_t http_event_handler(esp_http_client_event_t *evt)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* Tear the session down so the next poll rebuilds it from scratch. Used after
|
||||
* any transport error -- a half-open connection would fail every subsequent
|
||||
* poll otherwise. */
|
||||
static void client_drop(void)
|
||||
{
|
||||
if (client != NULL) {
|
||||
esp_http_client_cleanup(client);
|
||||
client = NULL;
|
||||
}
|
||||
session_warm = false;
|
||||
}
|
||||
|
||||
static esp_err_t client_ensure(void)
|
||||
{
|
||||
if (client != NULL) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
const esp_http_client_config_t config = {
|
||||
.url = url,
|
||||
.method = HTTP_METHOD_GET,
|
||||
.timeout_ms = WEATHER_TIMEOUT_COLD_MS,
|
||||
.event_handler = http_event_handler,
|
||||
.crt_bundle_attach = esp_crt_bundle_attach,
|
||||
.keep_alive_enable = true,
|
||||
};
|
||||
|
||||
client = esp_http_client_init(&config);
|
||||
return (client != NULL) ? ESP_OK : ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
esp_err_t weather_init(void)
|
||||
{
|
||||
if (AMBIENT_WEATHER_API_KEY[0] == '\0' ||
|
||||
@@ -68,6 +117,17 @@ esp_err_t weather_init(void)
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
/* Credentials are compile-time constants, so the URL never changes. */
|
||||
const int written = snprintf(url, sizeof(url),
|
||||
"https://rt.ambientweather.net/v1/devices/%s"
|
||||
"?apiKey=%s&applicationKey=%s&limit=1",
|
||||
AMBIENT_WEATHER_DEVICE_ID, AMBIENT_WEATHER_API_KEY, AMBIENT_WEATHER_APP_KEY);
|
||||
|
||||
if (written < 0 || written >= (int)sizeof(url)) {
|
||||
ESP_LOGE(TAG, "URL truncated -- check credential lengths");
|
||||
return ESP_ERR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
@@ -102,45 +162,41 @@ esp_err_t weather_update(weather_data_t *data)
|
||||
* partial response cannot leave stale values looking fresh. */
|
||||
memset(data, 0, sizeof(*data));
|
||||
|
||||
char url[WEATHER_URL_MAX];
|
||||
const int written = snprintf(url, sizeof(url),
|
||||
"https://rt.ambientweather.net/v1/devices/%s"
|
||||
"?apiKey=%s&applicationKey=%s&limit=1",
|
||||
AMBIENT_WEATHER_DEVICE_ID, AMBIENT_WEATHER_API_KEY, AMBIENT_WEATHER_APP_KEY);
|
||||
|
||||
if (written < 0 || written >= (int)sizeof(url)) {
|
||||
ESP_LOGE(TAG, "URL truncated -- check credential lengths");
|
||||
return ESP_ERR_INVALID_SIZE;
|
||||
esp_err_t err = client_ensure();
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
const esp_http_client_config_t config = {
|
||||
.url = url,
|
||||
.method = HTTP_METHOD_GET,
|
||||
.timeout_ms = WEATHER_TIMEOUT_MS,
|
||||
.event_handler = http_event_handler,
|
||||
.crt_bundle_attach = esp_crt_bundle_attach,
|
||||
};
|
||||
/*
|
||||
* Reset per request, not on HTTP_EVENT_ON_CONNECTED: a kept-alive session
|
||||
* reuses the connection, so that event does not fire again and the buffer
|
||||
* would accumulate across polls.
|
||||
*/
|
||||
resp_len = 0;
|
||||
resp_truncated = false;
|
||||
|
||||
esp_http_client_handle_t client = esp_http_client_init(&config);
|
||||
if (client == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
esp_http_client_set_timeout_ms(client, session_warm ? WEATHER_TIMEOUT_WARM_MS
|
||||
: WEATHER_TIMEOUT_COLD_MS);
|
||||
|
||||
esp_err_t err = esp_http_client_perform(client);
|
||||
err = esp_http_client_perform(client);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "HTTP request failed: %s", esp_err_to_name(err));
|
||||
esp_http_client_cleanup(client);
|
||||
client_drop();
|
||||
return err;
|
||||
}
|
||||
|
||||
const int status = esp_http_client_get_status_code(client);
|
||||
esp_http_client_cleanup(client);
|
||||
|
||||
if (status != 200) {
|
||||
ESP_LOGE(TAG, "HTTP %d from Ambient Weather", status);
|
||||
/* Could be a stale session the server has since closed. */
|
||||
client_drop();
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
/* Connection is good; subsequent polls skip the handshake. */
|
||||
session_warm = true;
|
||||
|
||||
if (resp_len == 0) {
|
||||
ESP_LOGE(TAG, "Empty response");
|
||||
return ESP_FAIL;
|
||||
|
||||
Reference in new issue
Block a user