feat(adr-109): /ota/recalibrate + NVS AP-MAC binding for gain-lock
Two FW changes closing both Open Items in ADR-108: 1. POST /ota/recalibrate on port 8032 erases csi_cfg/gl_agc, gl_fft, gl_ap_mac then esp_restart() — operator can force a full re-cal without USB. Reuses ota_check_auth Bearer-token guard. 2. New csi_cfg/gl_ap_mac (6-byte blob) saved alongside AGC/FFT. Boot-time short-circuit compares saved BSSID with current esp_wifi_sta_get_ap_info().bssid; mismatch → discard cache, run full calibration. All-zero (legacy NVS without MAC) treated as wildcard so existing deployments don't re-cal on first upgrade. Verified by OTA-flashing both sensors (192.168.0.100, .101) and calling /ota/recalibrate via curl — both returned the expected JSON and came back online ~15 s later running fresh calibration. Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
parent
4b58e5442a
commit
f92807cdaf
|
|
@ -101,8 +101,15 @@ extern void phy_force_rx_gain(int force_en, int force_value);
|
|||
#define RV_GAIN_NVS_NS "csi_cfg"
|
||||
#define RV_GAIN_NVS_K_AGC "gl_agc"
|
||||
#define RV_GAIN_NVS_K_FFT "gl_fft"
|
||||
/* ADR-111: BSSID of the AP that gain-lock was calibrated against.
|
||||
* 6-byte blob. On boot, if the currently-connected AP MAC differs from
|
||||
* the saved value, the cached AGC/FFT are ignored and a full calibration
|
||||
* runs (gain-lock is tied to a specific AP path; swapping APs invalidates
|
||||
* it). The new MAC is written alongside AGC/FFT after re-calibration. */
|
||||
#define RV_GAIN_NVS_K_AP_MAC "gl_ap_mac"
|
||||
|
||||
static esp_err_t rv_gain_load_from_nvs(uint8_t *agc_out, int8_t *fft_out)
|
||||
static esp_err_t rv_gain_load_from_nvs(uint8_t *agc_out, int8_t *fft_out,
|
||||
uint8_t mac_out[6])
|
||||
{
|
||||
nvs_handle_t h;
|
||||
esp_err_t err = nvs_open(RV_GAIN_NVS_NS, NVS_READONLY, &h);
|
||||
|
|
@ -111,12 +118,22 @@ static esp_err_t rv_gain_load_from_nvs(uint8_t *agc_out, int8_t *fft_out)
|
|||
int8_t fft = 0;
|
||||
err = nvs_get_u8(h, RV_GAIN_NVS_K_AGC, &agc);
|
||||
if (err == ESP_OK) err = nvs_get_i8(h, RV_GAIN_NVS_K_FFT, &fft);
|
||||
/* AP MAC is optional — older NVS blobs predate ADR-111 and have only
|
||||
* AGC+FFT. Treat a missing MAC as a wildcard match so a one-time
|
||||
* upgrade doesn't force every node to do a full re-cal. */
|
||||
if (err == ESP_OK && mac_out != NULL) {
|
||||
size_t want = 6;
|
||||
esp_err_t mac_err = nvs_get_blob(h, RV_GAIN_NVS_K_AP_MAC, mac_out, &want);
|
||||
if (mac_err != ESP_OK || want != 6) {
|
||||
memset(mac_out, 0, 6);
|
||||
}
|
||||
}
|
||||
nvs_close(h);
|
||||
if (err == ESP_OK) { *agc_out = agc; *fft_out = fft; }
|
||||
return err;
|
||||
}
|
||||
|
||||
static void rv_gain_save_to_nvs(uint8_t agc, int8_t fft)
|
||||
static void rv_gain_save_to_nvs(uint8_t agc, int8_t fft, const uint8_t mac[6])
|
||||
{
|
||||
nvs_handle_t h;
|
||||
esp_err_t err = nvs_open(RV_GAIN_NVS_NS, NVS_READWRITE, &h);
|
||||
|
|
@ -127,6 +144,9 @@ static void rv_gain_save_to_nvs(uint8_t agc, int8_t fft)
|
|||
}
|
||||
nvs_set_u8(h, RV_GAIN_NVS_K_AGC, agc);
|
||||
nvs_set_i8(h, RV_GAIN_NVS_K_FFT, fft);
|
||||
if (mac != NULL) {
|
||||
nvs_set_blob(h, RV_GAIN_NVS_K_AP_MAC, mac, 6);
|
||||
}
|
||||
nvs_commit(h);
|
||||
nvs_close(h);
|
||||
}
|
||||
|
|
@ -151,24 +171,53 @@ static void rv_gain_lock_process(const wifi_csi_info_t *info)
|
|||
{
|
||||
if (s_gain_locked || info == NULL) return;
|
||||
|
||||
/* ADR-108: short-circuit calibration if previous values are in NVS. */
|
||||
/* ADR-108: short-circuit calibration if previous values are in NVS.
|
||||
* ADR-111: also compare the saved BSSID with the currently-connected
|
||||
* AP. If they differ, the cached gain is invalid (different AP path
|
||||
* → different multipath, different optimal AGC) — discard it and run
|
||||
* a full calibration against the new AP. */
|
||||
static bool s_nvs_checked = false;
|
||||
if (!s_nvs_checked) {
|
||||
s_nvs_checked = true;
|
||||
uint8_t agc = 0; int8_t fft = 0;
|
||||
if (rv_gain_load_from_nvs(&agc, &fft) == ESP_OK &&
|
||||
uint8_t agc = 0; int8_t fft = 0; uint8_t saved_mac[6] = {0};
|
||||
if (rv_gain_load_from_nvs(&agc, &fft, saved_mac) == ESP_OK &&
|
||||
agc >= RV_GAIN_MIN_SAFE_AGC)
|
||||
{
|
||||
phy_fft_scale_force(true, fft);
|
||||
phy_force_rx_gain(1, (int)agc);
|
||||
s_gain_agc_value = agc;
|
||||
s_gain_fft_value = fft;
|
||||
s_gain_locked = true;
|
||||
ESP_LOGI("csi_collector",
|
||||
"gain-lock RESTORED from NVS: AGC=%u FFT=%d "
|
||||
"(0-packet calibration; clear NVS to recalibrate)",
|
||||
(unsigned)agc, (int)fft);
|
||||
return;
|
||||
/* Read the current AP MAC. If we can't (not connected yet)
|
||||
* the gain-lock callback should not be firing at all — but
|
||||
* be defensive and skip the cache if AP info is unavailable. */
|
||||
wifi_ap_record_t ap;
|
||||
bool ap_ok = (esp_wifi_sta_get_ap_info(&ap) == ESP_OK);
|
||||
bool wildcard = true;
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if (saved_mac[i] != 0) { wildcard = false; break; }
|
||||
}
|
||||
if (ap_ok && (wildcard ||
|
||||
memcmp(saved_mac, ap.bssid, 6) == 0))
|
||||
{
|
||||
phy_fft_scale_force(true, fft);
|
||||
phy_force_rx_gain(1, (int)agc);
|
||||
s_gain_agc_value = agc;
|
||||
s_gain_fft_value = fft;
|
||||
s_gain_locked = true;
|
||||
ESP_LOGI("csi_collector",
|
||||
"gain-lock RESTORED from NVS: AGC=%u FFT=%d "
|
||||
"AP=%02x:%02x:%02x:%02x:%02x:%02x%s",
|
||||
(unsigned)agc, (int)fft,
|
||||
ap.bssid[0], ap.bssid[1], ap.bssid[2],
|
||||
ap.bssid[3], ap.bssid[4], ap.bssid[5],
|
||||
wildcard ? " (legacy NVS, no MAC stored)" : "");
|
||||
return;
|
||||
}
|
||||
if (ap_ok) {
|
||||
ESP_LOGW("csi_collector",
|
||||
"gain-lock NVS MISS: saved AP=%02x:%02x:%02x:%02x:%02x:%02x "
|
||||
"→ current=%02x:%02x:%02x:%02x:%02x:%02x. Re-calibrating.",
|
||||
saved_mac[0], saved_mac[1], saved_mac[2],
|
||||
saved_mac[3], saved_mac[4], saved_mac[5],
|
||||
ap.bssid[0], ap.bssid[1], ap.bssid[2],
|
||||
ap.bssid[3], ap.bssid[4], ap.bssid[5]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -209,10 +258,21 @@ static void rv_gain_lock_process(const wifi_csi_info_t *info)
|
|||
"baseline drift should now collapse.",
|
||||
(unsigned)s_gain_agc_value, (int)s_gain_fft_value,
|
||||
(unsigned)RV_GAIN_CAL_PACKETS);
|
||||
/* ADR-108: persist for next boot — short-circuit calibration. */
|
||||
rv_gain_save_to_nvs(s_gain_agc_value, s_gain_fft_value);
|
||||
ESP_LOGI(TAG, "gain-lock PERSISTED to NVS (%s/%s, %s)",
|
||||
RV_GAIN_NVS_NS, RV_GAIN_NVS_K_AGC, RV_GAIN_NVS_K_FFT);
|
||||
/* ADR-108: persist for next boot — short-circuit calibration.
|
||||
* ADR-111: also persist the AP BSSID this calibration ran against
|
||||
* so the boot-time short-circuit can detect AP swaps and discard
|
||||
* stale gain values. */
|
||||
uint8_t cur_mac[6] = {0};
|
||||
wifi_ap_record_t ap;
|
||||
if (esp_wifi_sta_get_ap_info(&ap) == ESP_OK) {
|
||||
memcpy(cur_mac, ap.bssid, 6);
|
||||
}
|
||||
rv_gain_save_to_nvs(s_gain_agc_value, s_gain_fft_value, cur_mac);
|
||||
ESP_LOGI(TAG,
|
||||
"gain-lock PERSISTED to NVS (AGC=%u FFT=%d AP=%02x:%02x:%02x:%02x:%02x:%02x)",
|
||||
(unsigned)s_gain_agc_value, (int)s_gain_fft_value,
|
||||
cur_mac[0], cur_mac[1], cur_mac[2],
|
||||
cur_mac[3], cur_mac[4], cur_mac[5]);
|
||||
}
|
||||
s_gain_locked = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,6 +96,60 @@ static esp_err_t ota_status_handler(httpd_req_t *req)
|
|||
return ESP_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /ota/recalibrate — clear cached gain-lock NVS keys and reboot.
|
||||
*
|
||||
* ADR-109: lets the operator force a full gain-lock re-calibration from
|
||||
* the server without a USB connection. Erases csi_cfg/gl_agc, gl_fft, and
|
||||
* gl_ap_mac (ADR-111), then calls esp_restart(). Next boot finds no NVS
|
||||
* cache and runs the 300-packet calibration as if it were a fresh device.
|
||||
*/
|
||||
static esp_err_t ota_recalibrate_handler(httpd_req_t *req)
|
||||
{
|
||||
if (!ota_check_auth(req)) {
|
||||
ESP_LOGW(TAG, "/ota/recalibrate rejected: authentication failed");
|
||||
httpd_resp_send_err(req, HTTPD_403_FORBIDDEN,
|
||||
"Authentication required. Use: Authorization: Bearer <psk>");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
nvs_handle_t h;
|
||||
esp_err_t err = nvs_open("csi_cfg", NVS_READWRITE, &h);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "/ota/recalibrate: nvs_open(csi_cfg) failed: %s",
|
||||
esp_err_to_name(err));
|
||||
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR,
|
||||
"NVS open failed");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
/* Erase all three keys defensively — ignore individual ESP_ERR_NVS_NOT_FOUND
|
||||
* (key already absent on a never-calibrated device). */
|
||||
(void)nvs_erase_key(h, "gl_agc");
|
||||
(void)nvs_erase_key(h, "gl_fft");
|
||||
(void)nvs_erase_key(h, "gl_ap_mac");
|
||||
err = nvs_commit(h);
|
||||
nvs_close(h);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "/ota/recalibrate: nvs_commit failed: %s",
|
||||
esp_err_to_name(err));
|
||||
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR,
|
||||
"NVS commit failed");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "/ota/recalibrate: gain-lock NVS cleared; rebooting in 1s");
|
||||
|
||||
const char *resp =
|
||||
"{\"status\":\"ok\",\"message\":\"gain-lock NVS cleared; rebooting\"}";
|
||||
httpd_resp_set_type(req, "application/json");
|
||||
httpd_resp_send(req, resp, strlen(resp));
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
esp_restart();
|
||||
return ESP_OK; /* unreachable */
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /ota — receive and flash firmware binary.
|
||||
*/
|
||||
|
|
@ -249,9 +303,19 @@ static esp_err_t ota_start_server(httpd_handle_t *out_handle)
|
|||
};
|
||||
httpd_register_uri_handler(server, &upload_uri);
|
||||
|
||||
/* ADR-109: REST trigger for full gain-lock re-calibration. */
|
||||
httpd_uri_t recalibrate_uri = {
|
||||
.uri = "/ota/recalibrate",
|
||||
.method = HTTP_POST,
|
||||
.handler = ota_recalibrate_handler,
|
||||
.user_ctx = NULL,
|
||||
};
|
||||
httpd_register_uri_handler(server, &recalibrate_uri);
|
||||
|
||||
ESP_LOGI(TAG, "OTA HTTP server started on port %d", OTA_PORT);
|
||||
ESP_LOGI(TAG, " GET /ota/status — firmware version info");
|
||||
ESP_LOGI(TAG, " POST /ota — upload new firmware binary");
|
||||
ESP_LOGI(TAG, " GET /ota/status — firmware version info");
|
||||
ESP_LOGI(TAG, " POST /ota — upload new firmware binary");
|
||||
ESP_LOGI(TAG, " POST /ota/recalibrate — clear gain-lock NVS + reboot");
|
||||
|
||||
if (out_handle) *out_handle = server;
|
||||
return ESP_OK;
|
||||
|
|
|
|||
Loading…
Reference in New Issue