safety interlocking and a bunch of other fun stuff
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/version.cmake)
|
||||
|
||||
idf_component_register(
|
||||
SRCS main.c i2c.c rtc.c storage.c uart_comms.c control_fsm.c power_mgmt.c rf_433.c rtc.c sensors.c solar.c webserver.c simple_dns_server.c # list the source files of this component
|
||||
SRCS main.c i2c.c rtc.c storage.c uart_comms.c control_fsm.c power_mgmt.c rf_433.c rtc.c sensors.c solar.c webserver.c simple_dns_server.c comms.c # list the source files of this component
|
||||
INCLUDE_DIRS "." "${CMAKE_BINARY_DIR}"
|
||||
PRIV_INCLUDE_DIRS # optional, add here private include directories
|
||||
|
||||
|
||||
458
main/comms.c
Normal file
458
main/comms.c
Normal file
@@ -0,0 +1,458 @@
|
||||
#include "comms.h"
|
||||
#include "cJSON.h"
|
||||
#include "control_fsm.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_system.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "power_mgmt.h"
|
||||
#include "rf_433.h"
|
||||
#include "rtc.h"
|
||||
#include "storage.h"
|
||||
#include "version.h"
|
||||
#include <string.h>
|
||||
|
||||
static const char *TAG = "COMMS";
|
||||
|
||||
/**
|
||||
* Build a JSON object containing complete system status
|
||||
*/
|
||||
cJSON* comms_handle_get(void) {
|
||||
ESP_LOGI(TAG, "GET request");
|
||||
|
||||
rtc_reset_shutdown_timer();
|
||||
|
||||
// Create root JSON object
|
||||
cJSON *root = cJSON_CreateObject();
|
||||
if (root == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to create JSON object");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Add basic system info
|
||||
cJSON_AddStringToObject(root, "build_version", FIRMWARE_VERSION);
|
||||
cJSON_AddStringToObject(root, "build_date", BUILD_DATE);
|
||||
cJSON_AddNumberToObject(root, "time", (double)rtc_get_s());
|
||||
cJSON_AddBoolToObject(root, "rtc_set", rtc_is_set());
|
||||
cJSON_AddNumberToObject(root, "state", fsm_get_state());
|
||||
cJSON_AddNumberToObject(root, "voltage", get_battery_V());
|
||||
cJSON_AddNumberToObject(root, "remaining_dist", fsm_get_remaining_distance());
|
||||
cJSON_AddNumberToObject(root, "next_alarm", (double)rtc_get_next_alarm_s());
|
||||
|
||||
cJSON *msg_array = cJSON_CreateArray();
|
||||
if (msg_array == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to create msg array");
|
||||
cJSON_Delete(root);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Add state message
|
||||
switch(fsm_get_state()) {
|
||||
case STATE_IDLE:
|
||||
cJSON_AddItemToArray(msg_array, cJSON_CreateString("IDLE"));
|
||||
break;
|
||||
case STATE_UNDO_JACK:
|
||||
case STATE_UNDO_JACK_START:
|
||||
cJSON_AddItemToArray(msg_array, cJSON_CreateString("CANCELLING MOVE"));
|
||||
break;
|
||||
default:
|
||||
cJSON_AddItemToArray(msg_array, cJSON_CreateString("MOVING..."));
|
||||
break;
|
||||
}
|
||||
|
||||
// Add warning/error messages
|
||||
if (fsm_get_remaining_distance() <= 0) {
|
||||
cJSON_AddItemToArray(msg_array, cJSON_CreateString("DISTANCE LIMIT HIT"));
|
||||
}
|
||||
if (efuse_is_tripped(BRIDGE_AUX)) {
|
||||
cJSON_AddItemToArray(msg_array, cJSON_CreateString("AUX EFUSE TRIP"));
|
||||
}
|
||||
if (efuse_is_tripped(BRIDGE_JACK)) {
|
||||
cJSON_AddItemToArray(msg_array, cJSON_CreateString("JACK EFUSE TRIP"));
|
||||
}
|
||||
if (efuse_is_tripped(BRIDGE_DRIVE)) {
|
||||
cJSON_AddItemToArray(msg_array, cJSON_CreateString("DRIVE EFUSE TRIP"));
|
||||
}
|
||||
if (!rtc_is_set()) {
|
||||
cJSON_AddItemToArray(msg_array, cJSON_CreateString("CLOCK NOT SET"));
|
||||
}
|
||||
|
||||
cJSON_AddItemToObject(root, "msg", msg_array);
|
||||
|
||||
// Add parameters object
|
||||
cJSON *parameters = cJSON_CreateObject();
|
||||
if (parameters == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to create parameters object");
|
||||
cJSON_Delete(root);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Add all parameters
|
||||
for (param_idx_t i = 0; i < NUM_PARAMS; i++) {
|
||||
const char *name = get_param_name(i);
|
||||
param_value_t value = get_param_value_t(i);
|
||||
|
||||
switch (get_param_type(i)) {
|
||||
case PARAM_TYPE_f32:
|
||||
cJSON_AddNumberToObject(parameters, name, value.f32);
|
||||
break;
|
||||
case PARAM_TYPE_f64:
|
||||
cJSON_AddNumberToObject(parameters, name, value.f64);
|
||||
break;
|
||||
case PARAM_TYPE_i32:
|
||||
cJSON_AddNumberToObject(parameters, name, value.i32);
|
||||
break;
|
||||
case PARAM_TYPE_i16:
|
||||
cJSON_AddNumberToObject(parameters, name, value.i16);
|
||||
break;
|
||||
case PARAM_TYPE_u32:
|
||||
cJSON_AddNumberToObject(parameters, name, value.u32);
|
||||
break;
|
||||
case PARAM_TYPE_u16:
|
||||
cJSON_AddNumberToObject(parameters, name, value.u16);
|
||||
break;
|
||||
case PARAM_TYPE_str:
|
||||
cJSON_AddStringToObject(parameters, name, get_param_string(i));
|
||||
break;
|
||||
default:
|
||||
cJSON_AddNullToObject(parameters, name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
cJSON_AddItemToObject(root, "parameters", parameters);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a POST request with JSON data
|
||||
*/
|
||||
esp_err_t comms_handle_post(cJSON *root, cJSON **response_json) {
|
||||
ESP_LOGI(TAG, "POST request");
|
||||
|
||||
if (root == NULL || response_json == NULL) {
|
||||
ESP_LOGE(TAG, "Invalid arguments to comms_handle_post");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
rtc_reset_shutdown_timer();
|
||||
|
||||
bool cmd_executed = false;
|
||||
bool sleep_requested = false;
|
||||
bool reboot_requested = false;
|
||||
const char *error_msg = NULL;
|
||||
int params_updated = 0;
|
||||
int params_failed = 0;
|
||||
|
||||
// Process time if present
|
||||
cJSON *time = cJSON_GetObjectItem(root, "time");
|
||||
if (cJSON_IsNumber(time)) {
|
||||
int64_t new_time = (int64_t)cJSON_GetNumberValue(time);
|
||||
ESP_LOGI(TAG, "Setting time to %lld", new_time);
|
||||
rtc_set_s(new_time);
|
||||
}
|
||||
|
||||
// Process remaining_dist if present
|
||||
cJSON *remaining_dist = cJSON_GetObjectItem(root, "remaining_dist");
|
||||
if (cJSON_IsNumber(remaining_dist)) {
|
||||
int64_t new_dist = (int64_t)cJSON_GetNumberValue(remaining_dist);
|
||||
ESP_LOGI(TAG, "Setting remaining_dist to %lld", new_dist);
|
||||
fsm_set_remaining_distance(new_dist);
|
||||
}
|
||||
|
||||
// Process command if present
|
||||
cJSON *cmd = cJSON_GetObjectItem(root, "cmd");
|
||||
if (cJSON_IsString(cmd)) {
|
||||
const char *cmd_str = cmd->valuestring;
|
||||
ESP_LOGI(TAG, "Executing command: %s", cmd_str);
|
||||
|
||||
if (strcmp(cmd_str, "start") == 0) {
|
||||
fsm_request(FSM_CMD_START);
|
||||
cmd_executed = true;
|
||||
}
|
||||
else if (strcmp(cmd_str, "stop") == 0) {
|
||||
fsm_request(FSM_CMD_STOP);
|
||||
cmd_executed = true;
|
||||
}
|
||||
else if (strcmp(cmd_str, "undo") == 0) {
|
||||
fsm_request(FSM_CMD_UNDO);
|
||||
cmd_executed = true;
|
||||
}
|
||||
else if (strcmp(cmd_str, "fwd") == 0) {
|
||||
pulseOverride(RELAY_A1);
|
||||
pulseOverride(RELAY_A3);
|
||||
cmd_executed = true;
|
||||
}
|
||||
else if (strcmp(cmd_str, "rev") == 0) {
|
||||
pulseOverride(RELAY_B1);
|
||||
pulseOverride(RELAY_A3);
|
||||
cmd_executed = true;
|
||||
}
|
||||
else if (strcmp(cmd_str, "up") == 0) {
|
||||
pulseOverride(RELAY_A2);
|
||||
cmd_executed = true;
|
||||
}
|
||||
else if (strcmp(cmd_str, "down") == 0) {
|
||||
pulseOverride(RELAY_B2);
|
||||
cmd_executed = true;
|
||||
}
|
||||
else if (strcmp(cmd_str, "aux") == 0) {
|
||||
pulseOverride(RELAY_A3);
|
||||
cmd_executed = true;
|
||||
}
|
||||
else if (strcmp(cmd_str, "reboot") == 0) {
|
||||
reboot_requested = true;
|
||||
cmd_executed = true;
|
||||
}
|
||||
else if (strcmp(cmd_str, "sleep") == 0) {
|
||||
sleep_requested = true;
|
||||
cmd_executed = true;
|
||||
}
|
||||
else if (strcmp(cmd_str, "rf_clear_temp") == 0) {
|
||||
rf_433_clear_temp_keycodes();
|
||||
cmd_executed = true;
|
||||
}
|
||||
else if (strcmp(cmd_str, "rf_disable") == 0) {
|
||||
rf_433_disable_controls();
|
||||
cmd_executed = true;
|
||||
}
|
||||
else if (strcmp(cmd_str, "rf_enable") == 0) {
|
||||
rf_433_enable_controls();
|
||||
cmd_executed = true;
|
||||
}
|
||||
else if (strcmp(cmd_str, "rf_learn") == 0) {
|
||||
// Start learning for a specific channel
|
||||
cJSON *channel = cJSON_GetObjectItem(root, "channel");
|
||||
if (cJSON_IsNumber(channel)) {
|
||||
int ch = (int)cJSON_GetNumberValue(channel);
|
||||
if (ch >= 0 && ch < NUM_RF_BUTTONS) {
|
||||
rf_433_learn_keycode(ch);
|
||||
cmd_executed = true;
|
||||
} else if (ch == -1) {
|
||||
rf_433_cancel_learn_keycode();
|
||||
cmd_executed = true;
|
||||
} else {
|
||||
error_msg = "Invalid channel number";
|
||||
}
|
||||
} else {
|
||||
error_msg = "rf_learn requires channel parameter";
|
||||
}
|
||||
}
|
||||
else if (strcmp(cmd_str, "rf_set_temp") == 0) {
|
||||
cJSON *index = cJSON_GetObjectItem(root, "index");
|
||||
cJSON *code = cJSON_GetObjectItem(root, "code");
|
||||
if (cJSON_IsNumber(index) && cJSON_IsNumber(code)) {
|
||||
int idx = (int)cJSON_GetNumberValue(index);
|
||||
int32_t rf_code = (int32_t)cJSON_GetNumberValue(code);
|
||||
rf_433_set_temp_keycode(idx, rf_code);
|
||||
cmd_executed = true;
|
||||
} else {
|
||||
error_msg = "rf_set_temp requires index and code parameters";
|
||||
}
|
||||
}
|
||||
else if (strcmp(cmd_str, "rf_status") == 0) {
|
||||
// Return current temp keycodes
|
||||
cJSON *response = cJSON_CreateObject();
|
||||
cJSON *codes_array = cJSON_CreateArray();
|
||||
|
||||
for (int i = 0; i < 4; i++) { // Only return first 4 for web UI
|
||||
int32_t code = rf_433_get_temp_keycode(i);
|
||||
cJSON_AddItemToArray(codes_array, cJSON_CreateNumber(code));
|
||||
}
|
||||
|
||||
cJSON_AddItemToObject(response, "codes", codes_array);
|
||||
*response_json = response;
|
||||
return ESP_OK;
|
||||
}
|
||||
else if (strcmp(cmd_str, "cal_jack_start") == 0) {
|
||||
fsm_request(FSM_CMD_CALIBRATE_JACK_PREP);
|
||||
ESP_LOGI(TAG, "FSM_CMD_CALIBRATE_JACK_PREP");
|
||||
cmd_executed = true;
|
||||
}
|
||||
else if (strcmp(cmd_str, "cal_jack_finish") == 0) {
|
||||
cJSON *amt = cJSON_GetObjectItem(root, "amt");
|
||||
if (cJSON_IsNumber(amt) && amt->valuedouble >= 0 && amt->valuedouble < 8) {
|
||||
ESP_LOGI(TAG, "FSM_CMD_CALIBRATE_JACK_FINISH");
|
||||
fsm_set_cal_val(amt->valuedouble);
|
||||
fsm_request(FSM_CMD_CALIBRATE_JACK_FINISH);
|
||||
cmd_executed = true;
|
||||
} else {
|
||||
error_msg = "cal_jack_finish requires amt parameter (0-8)";
|
||||
}
|
||||
}
|
||||
else if (strcmp(cmd_str, "cal_drive_start") == 0) {
|
||||
fsm_request(FSM_CMD_CALIBRATE_DRIVE_PREP);
|
||||
ESP_LOGI(TAG, "FSM_CMD_CALIBRATE_DRIVE_PREP");
|
||||
cmd_executed = true;
|
||||
}
|
||||
else if (strcmp(cmd_str, "cal_drive_finish") == 0) {
|
||||
cJSON *amt = cJSON_GetObjectItem(root, "amt");
|
||||
if (cJSON_IsNumber(amt) && amt->valuedouble >= 0 && amt->valuedouble < 8) {
|
||||
ESP_LOGI(TAG, "FSM_CMD_CALIBRATE_DRIVE_FINISH");
|
||||
fsm_set_cal_val(amt->valuedouble);
|
||||
fsm_request(FSM_CMD_CALIBRATE_DRIVE_FINISH);
|
||||
cmd_executed = true;
|
||||
} else {
|
||||
error_msg = "cal_drive_finish requires amt parameter (0-8)";
|
||||
}
|
||||
}
|
||||
else if (strcmp(cmd_str, "cal_get") == 0) {
|
||||
ESP_LOGI(TAG, "CAL_GET");
|
||||
|
||||
// Build JSON response with calibration values
|
||||
cJSON *response = cJSON_CreateObject();
|
||||
cJSON_AddItemToObject(response, "e", cJSON_CreateNumber(fsm_get_cal_e()));
|
||||
cJSON_AddItemToObject(response, "t", cJSON_CreateNumber(fsm_get_cal_t()));
|
||||
|
||||
*response_json = response;
|
||||
return ESP_OK;
|
||||
}
|
||||
else {
|
||||
ESP_LOGW(TAG, "Unknown command: %s", cmd_str);
|
||||
error_msg = "Unknown command";
|
||||
}
|
||||
}
|
||||
|
||||
// Process batch parameter updates if present
|
||||
cJSON *parameters = cJSON_GetObjectItem(root, "parameters");
|
||||
if (cJSON_IsObject(parameters)) {
|
||||
// Process individual parameter updates (direct key-value pairs)
|
||||
cJSON *item = NULL;
|
||||
cJSON_ArrayForEach(item, parameters) {
|
||||
const char *key = item->string;
|
||||
ESP_LOGI(TAG, "Processing parameter: %s", key);
|
||||
|
||||
// Find parameter index by name
|
||||
param_idx_t param_idx = NUM_PARAMS;
|
||||
for (param_idx_t i = 0; i < NUM_PARAMS; i++) {
|
||||
if (strcmp(get_param_name(i), key) == 0) {
|
||||
param_idx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (param_idx == NUM_PARAMS) {
|
||||
ESP_LOGW(TAG, "Unknown parameter: %s", key);
|
||||
params_failed++;
|
||||
continue;
|
||||
}
|
||||
|
||||
cJSON *value_json = cJSON_GetObjectItem(parameters, key);
|
||||
|
||||
// Set parameter value based on type
|
||||
switch (get_param_type(param_idx)) {
|
||||
case PARAM_TYPE_f32:
|
||||
if (cJSON_IsNumber(value_json)) {
|
||||
set_param_value_t(param_idx, (param_value_t){.f32 = value_json->valuedouble});
|
||||
params_updated++;
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Type mismatch for parameter: %s", key);
|
||||
params_failed++;
|
||||
}
|
||||
break;
|
||||
case PARAM_TYPE_f64:
|
||||
if (cJSON_IsNumber(value_json)) {
|
||||
set_param_value_t(param_idx, (param_value_t){.f64 = value_json->valuedouble});
|
||||
params_updated++;
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Type mismatch for parameter: %s", key);
|
||||
params_failed++;
|
||||
}
|
||||
break;
|
||||
case PARAM_TYPE_i32:
|
||||
if (cJSON_IsNumber(value_json)) {
|
||||
set_param_value_t(param_idx, (param_value_t){.i32 = value_json->valueint});
|
||||
params_updated++;
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Type mismatch for parameter: %s", key);
|
||||
params_failed++;
|
||||
}
|
||||
break;
|
||||
case PARAM_TYPE_i16:
|
||||
if (cJSON_IsNumber(value_json)) {
|
||||
set_param_value_t(param_idx, (param_value_t){.i16 = value_json->valueint});
|
||||
params_updated++;
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Type mismatch for parameter: %s", key);
|
||||
params_failed++;
|
||||
}
|
||||
break;
|
||||
case PARAM_TYPE_u32:
|
||||
if (cJSON_IsNumber(value_json)) {
|
||||
set_param_value_t(param_idx, (param_value_t){.u32 = value_json->valueint});
|
||||
params_updated++;
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Type mismatch for parameter: %s", key);
|
||||
params_failed++;
|
||||
}
|
||||
break;
|
||||
case PARAM_TYPE_u16:
|
||||
if (cJSON_IsNumber(value_json)) {
|
||||
set_param_value_t(param_idx, (param_value_t){.u16 = value_json->valueint});
|
||||
params_updated++;
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Type mismatch for parameter: %s", key);
|
||||
params_failed++;
|
||||
}
|
||||
break;
|
||||
case PARAM_TYPE_str:
|
||||
if (cJSON_IsString(value_json)) {
|
||||
set_param_string(param_idx, value_json->valuestring);
|
||||
params_updated++;
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Type mismatch for parameter: %s", key);
|
||||
params_failed++;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ESP_LOGW(TAG, "Unknown type for parameter: %s", key);
|
||||
params_failed++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (params_updated > 0) {
|
||||
rtc_schedule_next_alarm();
|
||||
commit_params();
|
||||
}
|
||||
}
|
||||
|
||||
// Build response
|
||||
cJSON *response = cJSON_CreateObject();
|
||||
if (response == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to create response JSON");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
if (reboot_requested) {
|
||||
cJSON_AddStringToObject(response, "status", "ok");
|
||||
cJSON_AddStringToObject(response, "message", "Rebooting...");
|
||||
cJSON_AddBoolToObject(response, "reboot", true);
|
||||
*response_json = response;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
if (sleep_requested) {
|
||||
cJSON_AddStringToObject(response, "status", "ok");
|
||||
cJSON_AddStringToObject(response, "message", "Sleeping...");
|
||||
cJSON_AddBoolToObject(response, "sleep", true);
|
||||
*response_json = response;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
if (error_msg != NULL) {
|
||||
cJSON_AddStringToObject(response, "status", "error");
|
||||
cJSON_AddStringToObject(response, "message", error_msg);
|
||||
*response_json = response;
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
cJSON_AddStringToObject(response, "status", "ok");
|
||||
cJSON_AddNumberToObject(response, "params_updated", params_updated);
|
||||
cJSON_AddNumberToObject(response, "params_failed", params_failed);
|
||||
cJSON_AddBoolToObject(response, "cmd_executed", cmd_executed);
|
||||
|
||||
*response_json = response;
|
||||
return ESP_OK;
|
||||
}
|
||||
34
main/comms.h
Normal file
34
main/comms.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#ifndef COMMS_H
|
||||
#define COMMS_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "esp_err.h"
|
||||
#include "cJSON.h"
|
||||
|
||||
/**
|
||||
* Unified communications module
|
||||
*
|
||||
* This module provides the core GET/POST logic that can be called from
|
||||
* either the webserver (HTTP) or UART (serial) interfaces.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Process a GET request - returns complete system status as JSON
|
||||
*
|
||||
* @return cJSON object containing system status, or NULL on error
|
||||
* Caller is responsible for deleting the returned object with cJSON_Delete()
|
||||
*/
|
||||
cJSON* comms_handle_get(void);
|
||||
|
||||
/**
|
||||
* Process a POST request - handles commands, parameter updates, time updates
|
||||
*
|
||||
* @param request_json The parsed JSON request object
|
||||
* @param response_json Pointer to store the response JSON object
|
||||
* @return ESP_OK on success, error code otherwise
|
||||
* Caller is responsible for deleting the response_json with cJSON_Delete()
|
||||
*/
|
||||
esp_err_t comms_handle_post(cJSON *request_json, cJSON **response_json);
|
||||
|
||||
|
||||
#endif // COMMS_H
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "power_mgmt.h"
|
||||
#include "rtc_wdt.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "sc_err.h"
|
||||
#include "storage.h"
|
||||
#include "rtc.h"
|
||||
#include "sensors.h"
|
||||
@@ -25,6 +26,10 @@
|
||||
#define TAG "FSM"
|
||||
|
||||
static QueueHandle_t fsm_cmd_queue = NULL;
|
||||
|
||||
RTC_DATA_ATTR esp_err_t error = ESP_OK;
|
||||
esp_err_t fsm_get_error() { return error; }
|
||||
void fsm_clear_error() { error = ESP_OK; }
|
||||
|
||||
// map from relay number to bridge
|
||||
bridge_t bridge_map[] = {
|
||||
@@ -171,19 +176,41 @@ void control_task(void *param) {
|
||||
|
||||
fsm_cmd_t cmd;
|
||||
while (xQueueReceive(fsm_cmd_queue, &cmd, 0) == pdTRUE) {
|
||||
// if (error != ESP_OK) continue; // don't do anything until error is cleared
|
||||
|
||||
switch (cmd) {
|
||||
case FSM_CMD_START:
|
||||
if (current_state == STATE_IDLE) {
|
||||
// Check if we have remaining distance before starting
|
||||
if (remaining_distance > 0.0f
|
||||
&& !efuse_is_tripped(BRIDGE_DRIVE)
|
||||
&& !efuse_is_tripped(BRIDGE_JACK)
|
||||
&& !efuse_is_tripped(BRIDGE_AUX)) {
|
||||
current_state = STATE_MOVE_START_DELAY;
|
||||
set_timer(TRANSITION_DELAY_US);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Cannot start move: no remaining distance (%.2f)", remaining_distance);
|
||||
if (remaining_distance <= 0.0f) {
|
||||
error = SC_ERR_LEASH_HIT;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (get_battery_V() > get_param_value_t(PARAM_LOW_PROTECTION_V).f32) {
|
||||
error = SC_ERR_LOW_BATTERY;
|
||||
continue;
|
||||
}
|
||||
if (get_safety_sensor()) {
|
||||
error = SC_ERR_SAFETY_TRIP;
|
||||
continue;
|
||||
}
|
||||
if (efuse_is_tripped(BRIDGE_DRIVE)) {
|
||||
error = SC_ERR_EFUSE_TRIP_1;
|
||||
continue;
|
||||
}
|
||||
if (efuse_is_tripped(BRIDGE_JACK)) {
|
||||
error = SC_ERR_EFUSE_TRIP_2;
|
||||
continue;
|
||||
}
|
||||
if (efuse_is_tripped(BRIDGE_AUX)) {
|
||||
error = SC_ERR_EFUSE_TRIP_3;
|
||||
continue;
|
||||
}
|
||||
|
||||
error = ESP_OK; // if everything is OK now, we're OK.
|
||||
current_state = STATE_MOVE_START_DELAY;
|
||||
set_timer(TRANSITION_DELAY_US);
|
||||
}
|
||||
break;
|
||||
case FSM_CMD_STOP:
|
||||
@@ -202,14 +229,16 @@ void control_task(void *param) {
|
||||
|
||||
case FSM_CMD_CALIBRATE_JACK_PREP:
|
||||
ESP_LOGI(TAG, "FSM_CMD_CALIBRATE_JACK_PREP");
|
||||
if (current_state == STATE_IDLE) {
|
||||
if (current_state == STATE_IDLE
|
||||
&& get_battery_V() > get_param_value_t(PARAM_LOW_PROTECTION_V).f32) {
|
||||
current_state = STATE_CALIBRATE_JACK_DELAY;
|
||||
}
|
||||
break;
|
||||
|
||||
case FSM_CMD_CALIBRATE_JACK_START:
|
||||
ESP_LOGI(TAG, "FSM_CMD_CALIBRATE_JACK_START");
|
||||
if (current_state == STATE_CALIBRATE_JACK_DELAY) {
|
||||
if (current_state == STATE_CALIBRATE_JACK_DELAY
|
||||
&& get_battery_V() > get_param_value_t(PARAM_LOW_PROTECTION_V).f32) {
|
||||
current_state = STATE_CALIBRATE_JACK_MOVE;
|
||||
set_timer(CALIBRATE_JACK_MAX_TIME);
|
||||
}
|
||||
@@ -231,14 +260,16 @@ void control_task(void *param) {
|
||||
|
||||
case FSM_CMD_CALIBRATE_DRIVE_PREP:
|
||||
ESP_LOGI(TAG, "FSM_CMD_CALIBRATE_DRIVE_PREP");
|
||||
if (current_state == STATE_IDLE) {
|
||||
if (current_state == STATE_IDLE
|
||||
&& get_battery_V() > get_param_value_t(PARAM_LOW_PROTECTION_V).f32) {
|
||||
current_state = STATE_CALIBRATE_DRIVE_DELAY;
|
||||
}
|
||||
break;
|
||||
|
||||
case FSM_CMD_CALIBRATE_DRIVE_START:
|
||||
ESP_LOGI(TAG, "FSM_CMD_CALIBRATE_DRIVE_START");
|
||||
if (current_state == STATE_CALIBRATE_DRIVE_DELAY) {
|
||||
if (current_state == STATE_CALIBRATE_DRIVE_DELAY
|
||||
&& get_battery_V() > get_param_value_t(PARAM_LOW_PROTECTION_V).f32) {
|
||||
current_state = STATE_CALIBRATE_DRIVE_MOVE;
|
||||
set_timer(CALIBRATE_DRIVE_MAX_TIME);
|
||||
set_sensor_counter(SENSOR_DRIVE, 0);
|
||||
@@ -306,6 +337,10 @@ void control_task(void *param) {
|
||||
}
|
||||
break;
|
||||
case STATE_MOVE_START_DELAY:
|
||||
if (!get_safety_sensor()) {
|
||||
error = SC_ERR_SAFETY_TRIP;
|
||||
current_state = STATE_IDLE;
|
||||
}
|
||||
if (timer_done()) {
|
||||
current_state = STATE_JACK_UP_START;
|
||||
set_timer(JACK_TIME / 2); // First phase is half of total jack time
|
||||
@@ -313,6 +348,11 @@ void control_task(void *param) {
|
||||
}
|
||||
break;
|
||||
case STATE_JACK_UP_START:
|
||||
if (!get_safety_sensor()) {
|
||||
error = SC_ERR_SAFETY_TRIP;
|
||||
current_state = STATE_UNDO_JACK_START;
|
||||
}
|
||||
|
||||
{
|
||||
// Track elapsed time
|
||||
int64_t elapsed = current_time - timer_start;
|
||||
@@ -332,14 +372,19 @@ void control_task(void *param) {
|
||||
}
|
||||
}
|
||||
|
||||
// E-fuse trip should still cause undo
|
||||
// E-fuse trip
|
||||
if (efuse_is_tripped(BRIDGE_JACK)) {
|
||||
ESP_LOGI(TAG, "START->UP BY TIME");
|
||||
current_state = STATE_UNDO_JACK_START;
|
||||
error = SC_ERR_EFUSE_TRIP_2;
|
||||
current_state = STATE_IDLE;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case STATE_JACK_UP:
|
||||
if (!get_safety_sensor()) {
|
||||
error = SC_ERR_SAFETY_TRIP;
|
||||
current_state = STATE_UNDO_JACK_START;
|
||||
}
|
||||
|
||||
{
|
||||
if (timer_done() || efuse_is_tripped(BRIDGE_JACK)) {
|
||||
// Track total time including first phase
|
||||
@@ -348,12 +393,16 @@ void control_task(void *param) {
|
||||
set_timer(TRANSITION_DELAY_US);
|
||||
}
|
||||
if (efuse_is_tripped(BRIDGE_JACK)) {
|
||||
ESP_LOGE(TAG, "JACK TRIPPED EFUSE");
|
||||
current_state = STATE_UNDO_JACK_START;
|
||||
error = SC_ERR_EFUSE_TRIP_2;
|
||||
current_state = STATE_IDLE;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case STATE_DRIVE_START_DELAY:
|
||||
if (!get_safety_sensor()) {
|
||||
error = SC_ERR_SAFETY_TRIP;
|
||||
current_state = STATE_UNDO_JACK_START;
|
||||
}
|
||||
if (timer_done()) {
|
||||
current_state = STATE_DRIVE;
|
||||
set_timer(DRIVE_TIME);
|
||||
@@ -366,6 +415,10 @@ void control_task(void *param) {
|
||||
}
|
||||
break;
|
||||
case STATE_DRIVE:
|
||||
if (!get_safety_sensor()) {
|
||||
error = SC_ERR_SAFETY_TRIP;
|
||||
current_state = STATE_UNDO_JACK_START;
|
||||
}
|
||||
{
|
||||
int32_t current_encoder = get_sensor_counter(SENSOR_DRIVE);
|
||||
int32_t ticks_traveled = current_encoder - move_start_encoder;
|
||||
@@ -407,17 +460,27 @@ void control_task(void *param) {
|
||||
if (remaining_distance < 0.0f) remaining_distance = 0.0f;
|
||||
ESP_LOGW(TAG, "Drive fault: traveled %.2f, old_remaining %.2f, new_remaining %.2f",
|
||||
distance_traveled, old_remaining, remaining_distance);
|
||||
|
||||
error = SC_ERR_EFUSE_TRIP_1;
|
||||
current_state = STATE_UNDO_JACK_START;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case STATE_DRIVE_END_DELAY:
|
||||
if (!get_safety_sensor()) {
|
||||
error = SC_ERR_SAFETY_TRIP;
|
||||
current_state = STATE_UNDO_JACK_START;
|
||||
}
|
||||
if (timer_done()) {
|
||||
current_state = STATE_JACK_DOWN;
|
||||
set_timer(jack_up_total_time); // Use the tracked jack up time
|
||||
}
|
||||
break;
|
||||
case STATE_JACK_DOWN:
|
||||
if (!get_safety_sensor()) {
|
||||
error = SC_ERR_SAFETY_TRIP;
|
||||
current_state = STATE_UNDO_JACK_START;
|
||||
}
|
||||
{
|
||||
// Get current sensing parameters
|
||||
int64_t delay = get_param_value_t(PARAM_EFUSE_INRUSH_US).u32;
|
||||
@@ -474,7 +537,6 @@ void control_task(void *param) {
|
||||
break;
|
||||
case STATE_CALIBRATE_JACK_MOVE:
|
||||
if (timer_done()) {
|
||||
ESP_LOGI(TAG, "STATE_CALIBRATE_JACK_END");
|
||||
current_state = STATE_IDLE;
|
||||
fsm_cal_t = current_time - timer_start;
|
||||
}
|
||||
@@ -485,8 +547,7 @@ void control_task(void *param) {
|
||||
// no way out of this except a command
|
||||
break;
|
||||
case STATE_CALIBRATE_DRIVE_MOVE:
|
||||
if (timer_done()) {
|
||||
ESP_LOGI(TAG, "STATE_CALIBRATE_DRIVE_END");
|
||||
if (!get_safety_sensor() || timer_done()) {
|
||||
current_state = STATE_IDLE;
|
||||
fsm_cal_t = current_time - timer_start;
|
||||
fsm_cal_e = get_sensor_counter(SENSOR_DRIVE);
|
||||
|
||||
@@ -44,7 +44,7 @@ typedef enum {
|
||||
} fsm_state_t;
|
||||
|
||||
typedef enum {
|
||||
RELAY_NONE = 0,
|
||||
RELAY_SENSORS = 0,
|
||||
RELAY_C3,
|
||||
RELAY_B3,
|
||||
RELAY_A3,
|
||||
@@ -75,6 +75,9 @@ int64_t fsm_get_cal_t();
|
||||
int64_t fsm_get_cal_e();
|
||||
void fsm_request(fsm_cmd_t cmd);
|
||||
|
||||
esp_err_t fsm_get_error();
|
||||
void fsm_clear_error();
|
||||
|
||||
|
||||
float fsm_get_remaining_distance(void);
|
||||
void fsm_set_remaining_distance(float x);
|
||||
|
||||
33
main/i2c.c
33
main/i2c.c
@@ -28,8 +28,12 @@
|
||||
#define REPEAT_MS 200
|
||||
#define REPEAT_START_MS 700
|
||||
|
||||
#define SAFETY_MASK 0b00111111
|
||||
|
||||
// Static Variables
|
||||
static bool i2c_initted = false;
|
||||
static bool safety_ok = false; // Safety interlock
|
||||
static uint8_t last_relay_request = 0; // Track last relay request
|
||||
|
||||
// === I2C LOW-LEVEL ===
|
||||
static esp_err_t tca_write_word_8(uint8_t reg, uint8_t value) {
|
||||
@@ -64,12 +68,37 @@ esp_err_t i2c_init(void) {
|
||||
ESP_ERROR_CHECK(tca_write_word_8(TCA_REG_CONFIG1, 0b00000000));
|
||||
|
||||
i2c_initted = true;
|
||||
safety_ok = false; // Start with safety not OK
|
||||
last_relay_request = 0;
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t i2c_set_relays(uint8_t states) {
|
||||
return tca_write_word_8(TCA_REG_OUTPUT1, states);
|
||||
void i2c_set_safety_status(bool safe) {
|
||||
safety_ok = safe;
|
||||
|
||||
if (!safe) {
|
||||
// Safety tripped - immediately turn off all relays
|
||||
ESP_LOGW("I2C", "Safety interlock activated");
|
||||
tca_write_word_8(TCA_REG_OUTPUT1, last_relay_request & SAFETY_MASK);
|
||||
} else {
|
||||
// Safety cleared - restore last requested relay state
|
||||
ESP_LOGI("I2C", "Safety interlock cleared");
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t i2c_set_relays(uint8_t states) {
|
||||
last_relay_request = states; // Always track the request
|
||||
|
||||
if (!safety_ok) {
|
||||
// Safety interlock active - refuse to energize relays
|
||||
ESP_LOGW("I2C", "Main relay operation blocked by safety interlock");
|
||||
return tca_write_word_8(TCA_REG_OUTPUT1, states & SAFETY_MASK);
|
||||
}
|
||||
|
||||
return tca_write_word_8(TCA_REG_OUTPUT1, states);
|
||||
}
|
||||
|
||||
esp_err_t i2c_set_led1(uint8_t state) {
|
||||
// push 3 LSB to top
|
||||
return tca_write_word_8(TCA_REG_OUTPUT0, state<<5);
|
||||
|
||||
@@ -11,6 +11,7 @@ esp_err_t i2c_stop(void);
|
||||
|
||||
esp_err_t i2c_set_relays(uint8_t states);
|
||||
esp_err_t i2c_set_led1(uint8_t state);
|
||||
void i2c_set_safety_status(bool safe);
|
||||
|
||||
esp_err_t i2c_poll_buttons();
|
||||
|
||||
|
||||
@@ -124,7 +124,7 @@ void app_main(void) {
|
||||
ESP_LOGI(TAG, "Firmware: %s", FIRMWARE_STRING);
|
||||
ESP_LOGI(TAG, "Version: %s", FIRMWARE_VERSION);
|
||||
ESP_LOGI(TAG, "Branch: %s", FIRMWARE_BRANCH);
|
||||
ESP_LOGI(TAG, "Built: %s", BUILD_DATE);
|
||||
ESP_LOGI(TAG, "Built: %s", BUILD_DATE);
|
||||
|
||||
|
||||
// Check for factory reset condition: Cold boot + button held
|
||||
@@ -240,12 +240,13 @@ void app_main(void) {
|
||||
driveLEDs(LED_STATE_START2);
|
||||
} else if (i2c_get_button_ms(0) > 100){
|
||||
driveLEDs(LED_STATE_START1);
|
||||
} else{
|
||||
} else {
|
||||
if (
|
||||
rtc_is_set() &&
|
||||
!efuse_is_tripped(BRIDGE_JACK) &&
|
||||
!efuse_is_tripped(BRIDGE_AUX) &&
|
||||
!efuse_is_tripped(BRIDGE_DRIVE)
|
||||
!efuse_is_tripped(BRIDGE_DRIVE) &&
|
||||
fsm_get_error() == ESP_OK
|
||||
) {
|
||||
driveLEDs(LED_STATE_AWAKE);
|
||||
} else {
|
||||
|
||||
26
main/sc_err.h
Normal file
26
main/sc_err.h
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* sc_errors.h
|
||||
*
|
||||
* Created on: Jan 5, 2026
|
||||
* Author: Thad
|
||||
*/
|
||||
|
||||
#ifndef MAIN_SC_ERR_H_
|
||||
#define MAIN_SC_ERR_H_
|
||||
|
||||
// from esp_err.h, from 0 -> 0x100 is clear
|
||||
// as is 0x10D -> 0x3000
|
||||
|
||||
#define SC_ERR_EFUSE_TRIP_1 0x201
|
||||
#define SC_ERR_EFUSE_TRIP_2 0x202
|
||||
#define SC_ERR_EFUSE_TRIP_3 0x203
|
||||
|
||||
#define SC_ERR_SAFETY_TRIP 0x210
|
||||
|
||||
#define SC_ERR_LEASH_HIT 0x211
|
||||
|
||||
#define SC_ERR_RTC_NOT_SET 0x220
|
||||
#define SC_ERR_LOW_BATTERY 0x230
|
||||
|
||||
|
||||
#endif /* MAIN_SC_ERR_H_ */
|
||||
107
main/sensors.c
107
main/sensors.c
@@ -1,10 +1,12 @@
|
||||
#include "sensors.h"
|
||||
#include "i2c.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_task_wdt.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "storage.h"
|
||||
|
||||
static const char* TAG = "SENS";
|
||||
|
||||
@@ -15,6 +17,13 @@ static volatile uint64_t sensor_last_isr_time[N_SENSORS] = {0};
|
||||
static volatile bool sensor_stable_state[N_SENSORS] = {false};
|
||||
static QueueHandle_t sensor_event_queue = NULL;
|
||||
|
||||
// Safety sensor debouncing
|
||||
static volatile bool safety_tripped = false;
|
||||
static volatile uint64_t safety_low_start_time = 0;
|
||||
static volatile uint64_t safety_high_start_time = 0;
|
||||
#define SAFETY_TRIP_DEBOUNCE_US get_param_value_t(PARAM_SAFETY_BREAK_US).u32
|
||||
#define SAFETY_UNTRIP_DEBOUNCE_US get_param_value_t(PARAM_SAFETY_BREAK_US).u32
|
||||
|
||||
#define DEBOUNCE_TIME_US 2000 // 2 ms debounce (adjust per switch)
|
||||
#define DEBOUNCE_TICKS pdMS_TO_TICKS(DEBOUNCE_TIME_MS)
|
||||
|
||||
@@ -23,7 +32,7 @@ typedef struct {
|
||||
bool level;
|
||||
} sensor_event_t;
|
||||
|
||||
// ISR: Minimal work — just record timestamp and forward to queue
|
||||
// ISR: Minimal work – just record timestamp and forward to queue
|
||||
static void IRAM_ATTR sensor_isr_handler(void* arg) {
|
||||
uint32_t gpio_num = (uint32_t)arg;
|
||||
uint8_t i;
|
||||
@@ -56,62 +65,72 @@ static void sensor_debounce_task(void* param) {
|
||||
last_processed_time[i] = esp_timer_get_time();
|
||||
}
|
||||
|
||||
// Initialize safety sensor
|
||||
bool safety_current = !gpio_get_level(sensor_pins[SENSOR_SAFETY]);
|
||||
if (safety_current) {
|
||||
safety_low_start_time = esp_timer_get_time();
|
||||
safety_high_start_time = 0;
|
||||
} else {
|
||||
safety_low_start_time = 0;
|
||||
safety_high_start_time = esp_timer_get_time();
|
||||
}
|
||||
|
||||
uint8_t i = 0;
|
||||
//int64_t now = -1;
|
||||
uint8_t i = 0;
|
||||
|
||||
while (1) {
|
||||
if (xQueueReceive(sensor_event_queue, &evt, pdMS_TO_TICKS(100)) == pdTRUE) {
|
||||
if (xQueueReceive(sensor_event_queue, &evt, pdMS_TO_TICKS(10)) == pdTRUE) {
|
||||
i = evt.sensor_id;
|
||||
|
||||
ESP_LOGI("SENS", "EVENT %d", i);
|
||||
|
||||
bool current_raw = !gpio_get_level(sensor_pins[i]);
|
||||
|
||||
|
||||
sensor_stable_state[i] = current_raw;
|
||||
sensor_stable_state[i] = current_raw;
|
||||
|
||||
if (current_raw && !last_raw_state[i]){
|
||||
ESP_LOGI("SENS", "FALLING");
|
||||
sensor_count[i]++;
|
||||
}
|
||||
if (current_raw && !last_raw_state[i]){
|
||||
ESP_LOGI("SENS", "FALLING");
|
||||
sensor_count[i]++;
|
||||
}
|
||||
if (!current_raw && last_raw_state[i]){
|
||||
ESP_LOGI("SENS", "RISING");
|
||||
sensor_count[i]++;
|
||||
}
|
||||
|
||||
last_raw_state[i] = current_raw;
|
||||
ESP_LOGI("SENS", "RISING");
|
||||
sensor_count[i]++;
|
||||
}
|
||||
|
||||
last_raw_state[i] = current_raw;
|
||||
}
|
||||
|
||||
// Handle safety sensor debouncing with asymmetric timing
|
||||
bool safety_current = !gpio_get_level(sensor_pins[SENSOR_SAFETY]);
|
||||
uint64_t now = esp_timer_get_time();
|
||||
|
||||
//now = esp_timer_get_time();
|
||||
if (safety_current) {
|
||||
// Safety sensor is LOW (active)
|
||||
if (safety_low_start_time == 0) {
|
||||
// First time going low, start timing
|
||||
safety_low_start_time = now;
|
||||
safety_high_start_time = 0;
|
||||
ESP_LOGI(TAG, "Safety sensor went LOW, starting trip timer");
|
||||
} else if (!safety_tripped && (now - safety_low_start_time >= SAFETY_TRIP_DEBOUNCE_US)) {
|
||||
// Been low for 200ms, trip the safety
|
||||
safety_tripped = true;
|
||||
i2c_set_safety_status(false);
|
||||
ESP_LOGW(TAG, "SAFETY TRIPPED - Relays disabled");
|
||||
}
|
||||
} else {
|
||||
// Safety sensor is HIGH (inactive)
|
||||
if (safety_high_start_time == 0) {
|
||||
// First time going high, start timing
|
||||
safety_high_start_time = now;
|
||||
safety_low_start_time = 0;
|
||||
ESP_LOGI(TAG, "Safety sensor went HIGH, starting un-trip timer");
|
||||
} else if (safety_tripped && (now - safety_high_start_time >= SAFETY_UNTRIP_DEBOUNCE_US)) {
|
||||
// Been high for 300ms, un-trip the safety
|
||||
safety_tripped = false;
|
||||
i2c_set_safety_status(true);
|
||||
ESP_LOGI(TAG, "SAFETY CLEARED - Relays enabled");
|
||||
}
|
||||
}
|
||||
|
||||
/*// Wait for debounce period since last ISR
|
||||
if (now - sensor_last_isr_time[i] >= (DEBOUNCE_TIME_US)) {
|
||||
bool current_raw = !gpio_get_level(sensor_pins[i]);
|
||||
|
||||
// Only update if stable and different from last stable
|
||||
if (current_raw != sensor_stable_state[i]) {
|
||||
bool was_high = sensor_stable_state[i];
|
||||
|
||||
// Count rising OR falling edges
|
||||
if (current_raw && !sensor_stable_state[i]){
|
||||
ESP_LOGI("SENS", "FALLING");
|
||||
sensor_count[i]++;
|
||||
}
|
||||
if (!current_raw && sensor_stable_state[i]){
|
||||
ESP_LOGI("SENS", "RISING");
|
||||
sensor_count[i]++;
|
||||
}
|
||||
|
||||
|
||||
sensor_stable_state[i] = current_raw;
|
||||
|
||||
last_raw_state[i] = current_raw;
|
||||
last_processed_time[i] = now;
|
||||
}
|
||||
}*/
|
||||
|
||||
esp_task_wdt_reset();
|
||||
}
|
||||
}
|
||||
@@ -160,6 +179,10 @@ bool get_sensor(sensor_t i) {
|
||||
return sensor_stable_state[i];
|
||||
}
|
||||
|
||||
bool get_safety_sensor(void) {
|
||||
return !safety_tripped; // Returns true if safe, false if tripped
|
||||
}
|
||||
|
||||
int32_t get_sensor_counter(sensor_t i) {
|
||||
return sensor_count[i];
|
||||
}
|
||||
|
||||
@@ -26,8 +26,9 @@ void set_sensor_counter(sensor_t i, int32_t to);
|
||||
int32_t get_sensor_counter(sensor_t i);
|
||||
|
||||
bool get_sensor(sensor_t i);
|
||||
bool get_safety_sensor(void);
|
||||
|
||||
esp_err_t sensors_init();
|
||||
esp_err_t sensors_stop();
|
||||
|
||||
#endif /* MAIN_SENSORS_H_ */
|
||||
#endif /* MAIN_SENSORS_H_ */
|
||||
@@ -120,6 +120,7 @@ static inline uint8_t param_type_size(param_type_e type) {
|
||||
PARAM_DEF(JACK_I_DOWN, f32, 8.0, "A") \
|
||||
PARAM_DEF(V_SENS_K, f32, 0.00766666666, "V/mV") \
|
||||
PARAM_DEF(BUILD_VERSION, str, "undefined", "") \
|
||||
PARAM_DEF(SAFETY_BREAK_US, u32, 200000, "") \
|
||||
|
||||
|
||||
// Generate enum for parameter indices
|
||||
|
||||
@@ -1,329 +1,139 @@
|
||||
#include "uart_comms.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "esp_task_wdt.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "comms.h"
|
||||
#include "cJSON.h"
|
||||
#include "driver/uart.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_system.h"
|
||||
#include "storage.h"
|
||||
#include <errno.h>
|
||||
#include <ctype.h>
|
||||
#include "rf_433.h"
|
||||
#include "esp_task_wdt.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "rtc.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#define TAG "UART"
|
||||
|
||||
#define UART_NUM UART_NUM_0
|
||||
#define BUF_SIZE (1024)
|
||||
#define CMD_MAX_LEN (256)
|
||||
#define CMD_MAX_LEN (2048)
|
||||
|
||||
static char cmd_buffer[CMD_MAX_LEN];
|
||||
static int cmd_pos = 0;
|
||||
static TaskHandle_t uart_task_handle = NULL;
|
||||
|
||||
// TODO: Set Time
|
||||
// TODO: Pair Remote
|
||||
// TODO: Command Move
|
||||
// TODO: Show current sensor values
|
||||
|
||||
// Parse value as either decimal or hex (0x prefix)
|
||||
static bool parse_uint64(const char *str, uint64_t *result) {
|
||||
char *endptr;
|
||||
|
||||
if (str == NULL || result == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for hex prefix
|
||||
if (strlen(str) > 2 && str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) {
|
||||
*result = strtoull(str, &endptr, 16);
|
||||
} else {
|
||||
*result = strtoull(str, &endptr, 10);
|
||||
}
|
||||
|
||||
// Check if conversion was successful (endptr should point to null terminator)
|
||||
return (*endptr == '\0' && endptr != str);
|
||||
}
|
||||
|
||||
// Format parameter value for display based on its type
|
||||
static void print_param_value(param_idx_t id, param_value_t val) {
|
||||
param_type_e type = get_param_type(id);
|
||||
|
||||
char sbuf[9] = {0};
|
||||
|
||||
switch (type) {
|
||||
case PARAM_TYPE_u16:
|
||||
printf("%u (0x%04X)\n",
|
||||
val.u16, val.u16);
|
||||
break;
|
||||
|
||||
case PARAM_TYPE_i16:
|
||||
printf("%d (0x%04X)\n",
|
||||
val.i16, (uint16_t)val.i16);
|
||||
break;
|
||||
|
||||
case PARAM_TYPE_u32:
|
||||
printf("%lu (0x%08lX)\n",
|
||||
(unsigned long)val.u32, (unsigned long)val.u32);
|
||||
break;
|
||||
|
||||
case PARAM_TYPE_i32:
|
||||
printf("%ld (0x%08lX)\n",
|
||||
(long)val.i32, (unsigned long)val.i32);
|
||||
break;
|
||||
|
||||
case PARAM_TYPE_f32:
|
||||
printf("%.6f (0x%08lX as bits)\n",
|
||||
val.f32, (unsigned long)val.u32);
|
||||
break;
|
||||
|
||||
|
||||
case PARAM_TYPE_f64:
|
||||
printf("%.6f (0x%016llX as bits)\n",
|
||||
val.f64, (unsigned long long)val.f64);
|
||||
break;
|
||||
|
||||
case PARAM_TYPE_str:
|
||||
memcpy(val.str, sbuf, 8);
|
||||
sbuf[8] = '\0';
|
||||
printf("\"%s\"", sbuf);
|
||||
break;
|
||||
|
||||
default:
|
||||
printf("UNKNOWN TYPE\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static esp_err_t parse_param_value(const char *orig_str, param_type_e type, param_value_t *val) {
|
||||
const char *str = orig_str;
|
||||
// Skip leading whitespace
|
||||
while (isspace((unsigned char)*str)) str++;
|
||||
|
||||
// Check for negative sign on unsigned integer types
|
||||
bool is_unsigned_int = (type == PARAM_TYPE_u16 || type == PARAM_TYPE_u32);
|
||||
if (is_unsigned_int && *str == '-') {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
char *endptr;
|
||||
errno = 0;
|
||||
|
||||
switch (type) {
|
||||
case PARAM_TYPE_u16:
|
||||
val->u16 = strtoull(str, &endptr, 0);
|
||||
break;
|
||||
case PARAM_TYPE_u32:
|
||||
val->u32 = strtoull(str, &endptr, 0);
|
||||
break;
|
||||
|
||||
case PARAM_TYPE_i16:
|
||||
val->i16 = strtoll(str, &endptr, 0);
|
||||
break;
|
||||
case PARAM_TYPE_i32:
|
||||
val->i32 = strtoll(str, &endptr, 0);
|
||||
break;
|
||||
|
||||
case PARAM_TYPE_f32:
|
||||
val->f32 = strtof(str, &endptr);
|
||||
break;
|
||||
|
||||
case PARAM_TYPE_f64:
|
||||
val->f64 = strtod(str, &endptr);
|
||||
break;
|
||||
|
||||
default:
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
if (errno == ERANGE || endptr == str || *endptr != '\0') {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// Process set parameter command: sp <id> <value>
|
||||
static void cmd_set_param(char *args) {
|
||||
char *id_str = strtok(args, " \t");
|
||||
char *val_str = strtok(NULL, " \t");
|
||||
|
||||
if (id_str == NULL || val_str == NULL) {
|
||||
printf("ERROR: Usage: sp <id> <value>\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse parameter ID
|
||||
uint64_t id_u64;
|
||||
if (!parse_uint64(id_str, &id_u64)) {
|
||||
printf("ERROR: Invalid parameter ID\n");
|
||||
return;
|
||||
}
|
||||
|
||||
param_idx_t id = (param_idx_t)id_u64;
|
||||
if (id >= NUM_PARAMS) {
|
||||
printf("ERROR: Parameter ID %u out of range (max %d)\n",
|
||||
id, NUM_PARAMS - 1);
|
||||
return;
|
||||
}
|
||||
|
||||
param_value_t param_val = {0};
|
||||
param_type_e type = get_param_type(id);
|
||||
esp_err_t parse_err = parse_param_value(val_str, type, ¶m_val);
|
||||
if (parse_err != ESP_OK) {
|
||||
printf("ERROR: Invalid value\n");
|
||||
return;
|
||||
}
|
||||
|
||||
esp_err_t err = set_param_value_t(id, param_val);
|
||||
if (err == ESP_OK) {
|
||||
printf("OK: Parameter %u (%s) set to ",
|
||||
id, get_param_name(id));
|
||||
print_param_value(id, param_val);
|
||||
printf("(Not committed - use 'cp' to save)\n");
|
||||
} else {
|
||||
printf("ERROR: Failed to set parameter\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Process get parameter command: gp <id>
|
||||
static void cmd_get_param(char *args) {
|
||||
char *id_str = strtok(args, " \t");
|
||||
|
||||
if (id_str == NULL) {
|
||||
printf("ERROR: Usage: gp <id>\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse parameter ID
|
||||
uint64_t id_u64;
|
||||
if (!parse_uint64(id_str, &id_u64)) {
|
||||
printf("ERROR: Invalid parameter ID\n");
|
||||
return;
|
||||
}
|
||||
|
||||
param_idx_t id = (param_idx_t)id_u64;
|
||||
if (id >= NUM_PARAMS) {
|
||||
printf("ERROR: Parameter ID %u out of range (max %d)\n",
|
||||
id, NUM_PARAMS - 1);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get parameter
|
||||
param_value_t val = get_param_value_t(id);
|
||||
printf("Parameter %u (%s) = ", id, get_param_name(id));
|
||||
print_param_value(id, val);
|
||||
}
|
||||
|
||||
// Process commit parameters command: cp
|
||||
static void cmd_commit_params(char *args) {
|
||||
(void)args; // Unused
|
||||
|
||||
printf("Committing parameters to flash...\n");
|
||||
commit_params();
|
||||
printf("OK: Parameters committed\n");
|
||||
}
|
||||
|
||||
// Process reset parameter command: rp <id>
|
||||
static void cmd_reset_param(char *args) {
|
||||
char *id_str = strtok(args, " \t");
|
||||
|
||||
if (id_str == NULL) {
|
||||
printf("ERROR: Usage: rp <id>\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse parameter ID
|
||||
uint64_t id_u64;
|
||||
if (!parse_uint64(id_str, &id_u64)) {
|
||||
printf("ERROR: Invalid parameter ID\n");
|
||||
return;
|
||||
}
|
||||
|
||||
param_idx_t id = (param_idx_t)id_u64;
|
||||
if (id >= NUM_PARAMS) {
|
||||
printf("ERROR: Parameter ID %u out of range (max %d)\n",
|
||||
id, NUM_PARAMS - 1);
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset to default
|
||||
param_value_t default_val = get_param_default(id);
|
||||
esp_err_t err = set_param_value_t(id, default_val);
|
||||
|
||||
if (err == ESP_OK) {
|
||||
printf("OK: Parameter %u (%s) reset to default: ",
|
||||
id, get_param_name(id));
|
||||
print_param_value(id, default_val);
|
||||
printf("(Not committed - use 'cp' to save)\n");
|
||||
} else {
|
||||
printf("ERROR: Failed to reset parameter\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Process list parameters command: lp
|
||||
static void cmd_list_params(char *args) {
|
||||
(void)args; // Unused
|
||||
|
||||
printf("\n=== Parameter List ===\n");
|
||||
printf("ID | Name | Type | Value\n");
|
||||
printf("----+-------------------+------+------------------\n");
|
||||
|
||||
const char* type_names[] = {
|
||||
"u8 ", "i8 ", "u16", "i16", "u32", "i32", "u64", "i64", "f32", "f64"
|
||||
};
|
||||
|
||||
for (int i = 0; i < NUM_PARAMS; i++) {
|
||||
param_value_t val = get_param_value_t(i);
|
||||
param_type_e type = get_param_type(i);
|
||||
|
||||
printf("%-3d | %-17s | %-4s | ", i, get_param_name(i), type_names[type]);
|
||||
print_param_value(i, val);
|
||||
}
|
||||
// Process GET command
|
||||
static void cmd_get(void) {
|
||||
printf("\n");
|
||||
|
||||
cJSON *response = comms_handle_get();
|
||||
if (response == NULL) {
|
||||
printf("ERROR: Failed to generate GET response\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Pretty print the JSON
|
||||
char *json_str = cJSON_Print(response);
|
||||
cJSON_Delete(response);
|
||||
|
||||
if (json_str == NULL) {
|
||||
printf("ERROR: Failed to serialize JSON\n");
|
||||
return;
|
||||
}
|
||||
|
||||
printf("%s\n", json_str);
|
||||
free(json_str);
|
||||
}
|
||||
|
||||
// Process POST command with JSON data
|
||||
static void cmd_post(char *json_data) {
|
||||
printf("\n");
|
||||
|
||||
// Parse the JSON input
|
||||
cJSON *request = cJSON_Parse(json_data);
|
||||
if (request == NULL) {
|
||||
const char *error_ptr = cJSON_GetErrorPtr();
|
||||
if (error_ptr != NULL) {
|
||||
printf("ERROR: JSON parse error before: %s\n", error_ptr);
|
||||
} else {
|
||||
printf("ERROR: Failed to parse JSON\n");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Call the unified POST handler
|
||||
cJSON *response = NULL;
|
||||
esp_err_t err = comms_handle_post(request, &response);
|
||||
cJSON_Delete(request);
|
||||
|
||||
if (response == NULL) {
|
||||
printf("ERROR: Failed to generate POST response\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Pretty print the response
|
||||
char *json_str = cJSON_Print(response);
|
||||
|
||||
// Check for special response flags before deleting
|
||||
cJSON *reboot_flag = cJSON_GetObjectItem(response, "reboot");
|
||||
cJSON *sleep_flag = cJSON_GetObjectItem(response, "sleep");
|
||||
bool should_reboot = cJSON_IsTrue(reboot_flag);
|
||||
bool should_sleep = cJSON_IsTrue(sleep_flag);
|
||||
|
||||
cJSON_Delete(response);
|
||||
|
||||
if (json_str == NULL) {
|
||||
printf("ERROR: Failed to serialize JSON\n");
|
||||
return;
|
||||
}
|
||||
|
||||
printf("%s\n", json_str);
|
||||
free(json_str);
|
||||
|
||||
// Handle special actions
|
||||
if (should_reboot) {
|
||||
printf("\nRebooting in 2 seconds...\n");
|
||||
fflush(stdout);
|
||||
vTaskDelay(pdMS_TO_TICKS(2000));
|
||||
esp_restart();
|
||||
}
|
||||
|
||||
if (should_sleep) {
|
||||
printf("\nEntering deep sleep in 2 seconds...\n");
|
||||
fflush(stdout);
|
||||
vTaskDelay(pdMS_TO_TICKS(2000));
|
||||
rtc_enter_deep_sleep();
|
||||
}
|
||||
}
|
||||
|
||||
// Process help command
|
||||
static void cmd_help(char *args) {
|
||||
(void)args; // Unused
|
||||
|
||||
printf("\n=== Available Commands ===\n");
|
||||
printf("sp <id> <value> - Set parameter (e.g., sp 0 42 or sp 14 0xDEADBEEF)\n");
|
||||
printf("gp <id> - Get parameter (e.g., gp 0)\n");
|
||||
printf("rp <id> - Reset parameter to default (e.g., rp 0)\n");
|
||||
printf("cp - Commit parameters to flash\n");
|
||||
printf("lp - List all parameters\n");
|
||||
printf("help - Show this help\n");
|
||||
printf("\nNotes:\n");
|
||||
printf("- Values can be decimal (123) or hex (0xABC)\n");
|
||||
printf("- Changes are not saved until you run 'cp'\n");
|
||||
printf("- Parameter IDs range from 0 to %d\n\n", NUM_PARAMS - 1);
|
||||
}
|
||||
|
||||
static void cmd_rf_learn(char *args) {
|
||||
char *id_str = strtok(args, " \t");
|
||||
|
||||
if (id_str == NULL) {
|
||||
rf_433_cancel_learn_keycode();
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse parameter ID
|
||||
uint64_t id_u64;
|
||||
if (!parse_uint64(id_str, &id_u64)) {
|
||||
printf("ERROR: Invalid parameter ID\n");
|
||||
return;
|
||||
}
|
||||
|
||||
param_idx_t id = (param_idx_t)id_u64;
|
||||
if (id < 8) {
|
||||
printf("Listening for keycode for slot %d\n", id);
|
||||
rf_433_learn_keycode(id);
|
||||
return;
|
||||
}
|
||||
printf("ERROR: Keycode slot index out of bounds.\n");
|
||||
static void cmd_help(void) {
|
||||
printf("\n=== UART JSON Interface ===\n");
|
||||
printf("\nCommands:\n");
|
||||
printf(" GET - Get complete system status (JSON)\n");
|
||||
printf(" POST: {json} - Send command or update parameters (JSON)\n");
|
||||
printf(" HELP - Show this help\n");
|
||||
printf("\nPOST JSON Format:\n");
|
||||
printf("{\n");
|
||||
printf(" \"time\": 1234567, // Set RTC time (optional)\n");
|
||||
printf(" \"remaining_dist\": 100, // Set remaining distance (optional)\n");
|
||||
printf(" \"cmd\": \"start\", // Execute command (optional)\n");
|
||||
printf(" \"parameters\": { // Update parameters (optional)\n");
|
||||
printf(" \"PARAM_NAME\": value,\n");
|
||||
printf(" \"PARAM_NAME2\": value\n");
|
||||
printf(" }\n");
|
||||
printf("}\n");
|
||||
printf("\nAvailable Commands:\n");
|
||||
printf(" start, stop, undo, fwd, rev, up, down, aux\n");
|
||||
printf(" reboot, sleep\n");
|
||||
printf(" rf_learn, rf_clear_temp, rf_disable, rf_enable, rf_status\n");
|
||||
printf(" cal_jack_start, cal_jack_finish, cal_drive_start, cal_drive_finish, cal_get\n");
|
||||
printf("\nExamples:\n");
|
||||
printf(" GET\n");
|
||||
printf(" POST: {\"cmd\":\"start\"}\n");
|
||||
printf(" POST: {\"time\":1704067200}\n");
|
||||
printf(" POST: {\"parameters\":{\"WIFI_SSID\":\"MyNetwork\",\"WIFI_CHANNEL\":6}}\n");
|
||||
printf(" POST: {\"cmd\":\"rf_learn\",\"channel\":0}\n");
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
// Parse and execute command
|
||||
@@ -338,49 +148,47 @@ static void process_command(char *cmd) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Extract command
|
||||
char *space = strchr(cmd, ' ');
|
||||
char *tab = strchr(cmd, '\t');
|
||||
char *delim = NULL;
|
||||
|
||||
if (space && tab) {
|
||||
delim = (space < tab) ? space : tab;
|
||||
} else if (space) {
|
||||
delim = space;
|
||||
} else if (tab) {
|
||||
delim = tab;
|
||||
}
|
||||
|
||||
// Convert to uppercase for command matching
|
||||
char command[16] = {0};
|
||||
if (delim) {
|
||||
int cmd_len = delim - cmd;
|
||||
if (cmd_len >= sizeof(command)) {
|
||||
cmd_len = sizeof(command) - 1;
|
||||
}
|
||||
strncpy(command, cmd, cmd_len);
|
||||
cmd = delim + 1; // Point to arguments
|
||||
} else {
|
||||
strncpy(command, cmd, sizeof(command) - 1);
|
||||
cmd = cmd + strlen(cmd); // Point to empty string
|
||||
int i = 0;
|
||||
while (cmd[i] && cmd[i] != ' ' && cmd[i] != '\t' && cmd[i] != ':' && i < 15) {
|
||||
command[i] = (cmd[i] >= 'a' && cmd[i] <= 'z') ? (cmd[i] - 32) : cmd[i];
|
||||
i++;
|
||||
}
|
||||
command[i] = '\0';
|
||||
|
||||
// Execute command
|
||||
if (strcmp(command, "sp") == 0) {
|
||||
cmd_set_param(cmd);
|
||||
} else if (strcmp(command, "gp") == 0) {
|
||||
cmd_get_param(cmd);
|
||||
} else if (strcmp(command, "rp") == 0) {
|
||||
cmd_reset_param(cmd);
|
||||
} else if (strcmp(command, "cp") == 0) {
|
||||
cmd_commit_params(cmd);
|
||||
} else if (strcmp(command, "lp") == 0) {
|
||||
cmd_list_params(cmd);
|
||||
} else if (strcmp(command, "help") == 0) {
|
||||
cmd_help(cmd);
|
||||
} else if (strcmp(command, "rfl") == 0) {
|
||||
cmd_rf_learn(cmd);
|
||||
} else {
|
||||
printf("ERROR: Unknown command '%s' (type 'help' for commands)\n", command);
|
||||
if (strcmp(command, "GET") == 0) {
|
||||
cmd_get();
|
||||
}
|
||||
else if (strcmp(command, "POST") == 0) {
|
||||
// Find the start of JSON data (after ':')
|
||||
char *json_start = strchr(cmd, ':');
|
||||
if (json_start == NULL) {
|
||||
printf("ERROR: POST command requires JSON data after ':'\n");
|
||||
printf("Usage: POST: {json data}\n");
|
||||
return;
|
||||
}
|
||||
json_start++; // Skip the ':'
|
||||
|
||||
// Trim whitespace
|
||||
while (*json_start == ' ' || *json_start == '\t') {
|
||||
json_start++;
|
||||
}
|
||||
|
||||
if (*json_start == '\0') {
|
||||
printf("ERROR: No JSON data provided\n");
|
||||
return;
|
||||
}
|
||||
|
||||
cmd_post(json_start);
|
||||
}
|
||||
else if (strcmp(command, "HELP") == 0) {
|
||||
cmd_help();
|
||||
}
|
||||
else {
|
||||
printf("ERROR: Unknown command '%s'\n", command);
|
||||
printf("Type 'HELP' for available commands\n");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -390,7 +198,7 @@ void uart_event_task(void *pvParameters) {
|
||||
uint8_t data[BUF_SIZE];
|
||||
|
||||
while (1) {
|
||||
esp_task_wdt_reset();
|
||||
esp_task_wdt_reset();
|
||||
int len = uart_read_bytes(UART_NUM, data, BUF_SIZE - 1, 20 / portTICK_PERIOD_MS);
|
||||
|
||||
if (len > 0) {
|
||||
@@ -421,7 +229,7 @@ void uart_event_task(void *pvParameters) {
|
||||
}
|
||||
|
||||
cmd_pos = 0;
|
||||
printf("> ");
|
||||
printf("\n> ");
|
||||
fflush(stdout);
|
||||
continue;
|
||||
}
|
||||
@@ -435,7 +243,7 @@ void uart_event_task(void *pvParameters) {
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t uart_init() {
|
||||
esp_err_t uart_init(void) {
|
||||
// Configure UART
|
||||
uart_config_t uart_config = {
|
||||
.baud_rate = 115200,
|
||||
@@ -450,13 +258,14 @@ esp_err_t uart_init() {
|
||||
ESP_ERROR_CHECK(uart_param_config(UART_NUM, &uart_config));
|
||||
|
||||
// Print startup message
|
||||
/*printf("\n\n");
|
||||
printf("=================================\n");
|
||||
printf(" ESP32 Parameter Manager\n");
|
||||
printf("=================================\n");
|
||||
printf("Type 'help' for available commands\n\n");
|
||||
printf("> ");
|
||||
fflush(stdout);*/
|
||||
printf("\n\n");
|
||||
printf("========================================\n");
|
||||
printf(" UART JSON Interface Ready\n");
|
||||
printf("========================================\n");
|
||||
printf("Type 'HELP' for available commands\n");
|
||||
printf("Type 'GET' to see system status\n");
|
||||
printf("\n> ");
|
||||
fflush(stdout);
|
||||
|
||||
// Create UART task
|
||||
xTaskCreate(uart_event_task, "uart_event_task", 4096, NULL, 12, &uart_task_handle);
|
||||
@@ -465,7 +274,7 @@ esp_err_t uart_init() {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t uart_stop() {
|
||||
esp_err_t uart_stop(void) {
|
||||
if (uart_task_handle == NULL) {
|
||||
ESP_LOGW(TAG, "UART task not running");
|
||||
return ESP_OK;
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
/*
|
||||
* uart_comms.h
|
||||
*
|
||||
* Created on: Dec 13, 2025
|
||||
* Author: Thad
|
||||
*/
|
||||
|
||||
#ifndef MAIN_UART_COMMS_H_
|
||||
#define MAIN_UART_COMMS_H_
|
||||
#ifndef UART_COMMS_H
|
||||
#define UART_COMMS_H
|
||||
|
||||
#include "esp_err.h"
|
||||
|
||||
esp_err_t uart_init();
|
||||
esp_err_t uart_stop();
|
||||
/**
|
||||
* Initialize UART communication interface
|
||||
* Provides JSON-based GET/POST commands over serial
|
||||
*
|
||||
* Commands:
|
||||
* GET - Returns complete system status as pretty-printed JSON
|
||||
* POST: {json data} - Sends JSON command/parameter update
|
||||
*/
|
||||
esp_err_t uart_init(void);
|
||||
|
||||
#endif /* MAIN_UART_COMMS_H_ */
|
||||
/**
|
||||
* Stop UART communication interface
|
||||
*/
|
||||
esp_err_t uart_stop(void);
|
||||
|
||||
#endif // UART_COMMS_H
|
||||
File diff suppressed because one or more lines are too long
@@ -333,6 +333,7 @@ black: #2f2f2f
|
||||
let paramTableCreated = false; // Track if param table has been created
|
||||
let pollInterval = null; // Store interval ID for polling
|
||||
let modalResolve = null;
|
||||
let modalDismissed = false; // Track if out-of-distance modal was dismissed
|
||||
|
||||
const ge = (id) => document.getElementById(id);
|
||||
|
||||
@@ -834,6 +835,11 @@ black: #2f2f2f
|
||||
remainingDistInput.value = data.remaining_dist.toFixed(1);
|
||||
}
|
||||
|
||||
// Check if we should show the "out of distance" modal
|
||||
if (!modalDismissed && (data.remaining_dist === undefined || data.remaining_dist === null || data.remaining_dist <= 0)) {
|
||||
showOutOfDistanceModal();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1282,6 +1288,34 @@ black: #2f2f2f
|
||||
}
|
||||
}
|
||||
|
||||
async function showOutOfDistanceModal() {
|
||||
const distance = await modalPrompt(
|
||||
'Remaining distance is 0 or less. Would you like to add more distance?',
|
||||
'Add Distance (feet)'
|
||||
);
|
||||
|
||||
if (distance === null || distance === '') {
|
||||
// User clicked cancel or entered nothing - mark as dismissed
|
||||
modalDismissed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const dist = parseFloat(distance);
|
||||
if (isNaN(dist) || dist <= 0) {
|
||||
await modalAlert('Invalid distance. Modal dismissed.');
|
||||
modalDismissed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the remaining distance input and commit
|
||||
const remainingDistInput = ge('UX_REM_DIST');
|
||||
if (remainingDistInput) {
|
||||
remainingDistInput.value = dist.toFixed(1);
|
||||
markChanged(remainingDistInput);
|
||||
await commitParams();
|
||||
}
|
||||
}
|
||||
|
||||
// Start automatic polling
|
||||
function startPolling() {
|
||||
// Initial fetch
|
||||
@@ -138,7 +138,7 @@ def print_compression_stats(original_size, minified_size, gzip_size, brotli_size
|
||||
|
||||
|
||||
def main():
|
||||
input_file = "landingpage.html"
|
||||
input_file = "webpage.html"
|
||||
|
||||
# Check if input file exists
|
||||
if not Path(input_file).exists():
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include <Arduino.h>
|
||||
|
||||
const unsigned char PROGMEM html_content_gz[] = {
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x38, 0xff, 0x5b, 0x69, 0x02, 0xff, 0xed, 0x3d, 0x6b, 0x57, 0xdb, 0x48,
|
||||
0x1f, 0x8b, 0x08, 0x00, 0xdb, 0x47, 0x5c, 0x69, 0x02, 0xff, 0xed, 0x3d, 0x6b, 0x57, 0xdb, 0x48,
|
||||
0xb2, 0x9f, 0x77, 0x7e, 0x45, 0xc3, 0x24, 0x8c, 0x94, 0x08, 0xd9, 0x06, 0x32, 0x0f, 0x1b, 0x99,
|
||||
0x25, 0xe0, 0x4c, 0x32, 0x09, 0x8f, 0xc3, 0x23, 0x33, 0x7b, 0x59, 0x0e, 0x92, 0xad, 0x36, 0xd6,
|
||||
0x20, 0x4b, 0x1a, 0x49, 0x86, 0x30, 0xc2, 0xff, 0x7d, 0xab, 0xfa, 0x21, 0xb5, 0x64, 0xd9, 0x98,
|
||||
|
||||
567
main/webserver.c
567
main/webserver.c
@@ -8,6 +8,7 @@
|
||||
*/
|
||||
|
||||
#include "cJSON.h"
|
||||
#include "comms.h"
|
||||
#include "control_fsm.h"
|
||||
#include "endian.h"
|
||||
#include "esp_ota_ops.h"
|
||||
@@ -332,6 +333,9 @@ static esp_err_t log_handler(httpd_req_t *req) {
|
||||
* ... other parameters as direct key-value pairs
|
||||
* }
|
||||
*/
|
||||
/**
|
||||
* Unified GET handler - returns complete system status
|
||||
*/
|
||||
static esp_err_t get_handler(httpd_req_t *req) {
|
||||
ESP_LOGI(TAG, "get_handler");
|
||||
|
||||
@@ -340,139 +344,43 @@ static esp_err_t get_handler(httpd_req_t *req) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
rtc_reset_shutdown_timer();
|
||||
|
||||
int head = 0;
|
||||
|
||||
// Start JSON object
|
||||
head += sprintf(httpBuffer+head, "{");
|
||||
head += sprintf(httpBuffer+head, "\"build_version\":\"%s\",", FIRMWARE_VERSION);
|
||||
head += sprintf(httpBuffer+head, "\"build_date\":\"%s\",", BUILD_DATE);
|
||||
head += sprintf(httpBuffer+head, "\"time\":%lld,", (long long)rtc_get_s());
|
||||
head += sprintf(httpBuffer+head, "\"rtc_set\":%s,", rtc_is_set() ? "true" : "false");
|
||||
head += sprintf(httpBuffer+head, "\"state\":%d,", fsm_get_state());
|
||||
head += sprintf(httpBuffer+head, "\"voltage\":%.3f,", get_battery_V());
|
||||
head += sprintf(httpBuffer+head, "\"remaining_dist\":%.3f,", fsm_get_remaining_distance());
|
||||
head += sprintf(httpBuffer+head, "\"next_alarm\":%lld,", rtc_get_next_alarm_s());
|
||||
|
||||
head += sprintf(httpBuffer+head, "\"msg\":\"");
|
||||
|
||||
switch(fsm_get_state()) {
|
||||
case STATE_IDLE:
|
||||
head += sprintf(httpBuffer+head, "IDLE");
|
||||
break;
|
||||
case STATE_UNDO_JACK:
|
||||
case STATE_UNDO_JACK_START:
|
||||
head += sprintf(httpBuffer+head, "CANCELLING MOVE");
|
||||
break;
|
||||
default:
|
||||
head += sprintf(httpBuffer+head, "MOVING...");
|
||||
break;
|
||||
}
|
||||
|
||||
if (fsm_get_remaining_distance()<=0) {
|
||||
head += sprintf(httpBuffer+head, " | DISTANCE LIMIT HIT");
|
||||
}
|
||||
if (efuse_is_tripped(BRIDGE_AUX)) head += sprintf(httpBuffer+head, " | AUX EFUSE TRIP");
|
||||
if (efuse_is_tripped(BRIDGE_JACK)) head += sprintf(httpBuffer+head, " | JACK EFUSE TRIP");
|
||||
if (efuse_is_tripped(BRIDGE_DRIVE)) head += sprintf(httpBuffer+head, " | DRIVE EFUSE TRIP");
|
||||
if (!rtc_is_set()) {
|
||||
head += sprintf(httpBuffer+head, " | CLOCK NOT SET");
|
||||
}
|
||||
|
||||
// Add parameters metadata object
|
||||
head += sprintf(httpBuffer+head, "\",\"parameters\":{");
|
||||
|
||||
// Values array
|
||||
//head += sprintf(httpBuffer+head, "\"names\":[");
|
||||
for (param_idx_t i = 0; i < NUM_PARAMS; i++) {
|
||||
if (i > 0) {
|
||||
head += sprintf(httpBuffer+head, ",");
|
||||
}
|
||||
|
||||
head += sprintf(httpBuffer+head, "\"%s\":", get_param_name(i));
|
||||
|
||||
param_value_t value = get_param_value_t(i);
|
||||
|
||||
switch (get_param_type(i)) {
|
||||
case PARAM_TYPE_f32:
|
||||
head += sprintf(httpBuffer+head, "%.4f", value.f32);
|
||||
break;
|
||||
case PARAM_TYPE_f64:
|
||||
head += sprintf(httpBuffer+head, "%.4f", value.f64);
|
||||
break;
|
||||
case PARAM_TYPE_i32:
|
||||
head += sprintf(httpBuffer+head, "%ld", (long)value.i32);
|
||||
break;
|
||||
case PARAM_TYPE_i16:
|
||||
head += sprintf(httpBuffer+head, "%d", value.i16);
|
||||
break;
|
||||
case PARAM_TYPE_u32:
|
||||
head += sprintf(httpBuffer+head, "%lu", (long)value.u32);
|
||||
break;
|
||||
case PARAM_TYPE_u16:
|
||||
head += sprintf(httpBuffer+head, "%u", value.u16);
|
||||
break;
|
||||
case PARAM_TYPE_str:
|
||||
head += sprintf(httpBuffer+head, "\"%s\"", get_param_string(i));
|
||||
break;
|
||||
default:
|
||||
head += sprintf(httpBuffer+head, "null");
|
||||
break;
|
||||
}
|
||||
}
|
||||
/*head += sprintf(httpBuffer+head, "],");
|
||||
|
||||
// Names array
|
||||
head += sprintf(httpBuffer+head, "\"names\":[");
|
||||
for (param_idx_t i = 0; i < NUM_PARAMS; i++) {
|
||||
if (i > 0) {
|
||||
head += sprintf(httpBuffer+head, ",");
|
||||
}
|
||||
head += sprintf(httpBuffer+head, "\"%s\"", get_param_name(i));
|
||||
}
|
||||
head += sprintf(httpBuffer+head, "],");
|
||||
|
||||
// Units array
|
||||
head += sprintf(httpBuffer+head, "\"units\":[");
|
||||
for (param_idx_t i = 0; i < NUM_PARAMS; i++) {
|
||||
if (i > 0) {
|
||||
head += sprintf(httpBuffer+head, ",");
|
||||
}
|
||||
head += sprintf(httpBuffer+head, "\"%s\"", get_param_unit(i));
|
||||
}
|
||||
head += sprintf(httpBuffer+head, "]");*/
|
||||
|
||||
// Close parameters object
|
||||
head += sprintf(httpBuffer+head, "}");
|
||||
|
||||
// Close main JSON object
|
||||
head += sprintf(httpBuffer+head, "}");
|
||||
|
||||
// Check if buffer might overflow
|
||||
if (head >= (int)(sizeof(httpBuffer) - 100)) {
|
||||
ESP_LOGE(TAG, "GET response buffer near overflow");
|
||||
// Call unified GET handler
|
||||
cJSON *response = comms_handle_get();
|
||||
if (response == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to generate GET response");
|
||||
return httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR,
|
||||
"Status data too large");
|
||||
"Failed to generate response");
|
||||
}
|
||||
|
||||
|
||||
// Convert to string (not pretty printed for web - save bandwidth)
|
||||
char *json_str = cJSON_PrintUnformatted(response);
|
||||
cJSON_Delete(response);
|
||||
|
||||
if (json_str == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to serialize JSON");
|
||||
return httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR,
|
||||
"Failed to serialize response");
|
||||
}
|
||||
|
||||
// Send response
|
||||
esp_err_t err = httpd_resp_set_type(req, "application/json");
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to set response type: %s", esp_err_to_name(err));
|
||||
free(json_str);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = httpd_resp_send(req, httpBuffer, head);
|
||||
err = httpd_resp_send(req, json_str, strlen(json_str));
|
||||
free(json_str);
|
||||
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to send status response: %s", esp_err_to_name(err));
|
||||
ESP_LOGE(TAG, "Failed to send response: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
err = httpd_resp_set_hdr(req, "Connection", "close");
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to set connection header: %s", esp_err_to_name(err));
|
||||
// Continue anyway
|
||||
ESP_LOGW(TAG, "Failed to set connection header: %s", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
return err;
|
||||
@@ -492,6 +400,9 @@ static esp_err_t get_handler(httpd_req_t *req) {
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
/**
|
||||
* Unified POST handler - handles commands, parameter updates, time updates
|
||||
*/
|
||||
static esp_err_t post_handler(httpd_req_t *req) {
|
||||
ESP_LOGI(TAG, "post_handler");
|
||||
|
||||
@@ -500,8 +411,6 @@ static esp_err_t post_handler(httpd_req_t *req) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
rtc_reset_shutdown_timer();
|
||||
|
||||
// Receive POST data
|
||||
int ret = httpd_req_recv(req, httpBuffer, sizeof(httpBuffer));
|
||||
if (ret <= 0) {
|
||||
@@ -532,390 +441,63 @@ static esp_err_t post_handler(httpd_req_t *req) {
|
||||
return httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON");
|
||||
}
|
||||
|
||||
bool cmd_executed = false;
|
||||
bool sleep_requested = false;
|
||||
bool reboot_requested = false;
|
||||
const char *error_msg = NULL;
|
||||
int params_updated = 0;
|
||||
int params_failed = 0;
|
||||
|
||||
// Process time if present
|
||||
cJSON *time = cJSON_GetObjectItem(root, "time");
|
||||
if (cJSON_IsNumber(time)) {
|
||||
int64_t new_time = (int64_t)cJSON_GetNumberValue(time);
|
||||
ESP_LOGI(TAG, "Setting time to %lld", new_time);
|
||||
rtc_set_s(new_time);
|
||||
}
|
||||
|
||||
|
||||
cJSON *remaining_dist = cJSON_GetObjectItem(root, "remaining_dist");
|
||||
if (cJSON_IsNumber(remaining_dist)) {
|
||||
int64_t new_dist = (int64_t)cJSON_GetNumberValue(remaining_dist);
|
||||
ESP_LOGI(TAG, "Setting time to %lld", new_dist);
|
||||
fsm_set_remaining_distance(new_dist);
|
||||
}
|
||||
|
||||
// WE DO NOT PROCESS STATE. STATE IS A READ-ONLY.
|
||||
|
||||
// Process command if present
|
||||
cJSON *cmd = cJSON_GetObjectItem(root, "cmd");
|
||||
if (cJSON_IsString(cmd)) {
|
||||
const char *cmd_str = cmd->valuestring;
|
||||
ESP_LOGI(TAG, "Executing command: %s", cmd_str);
|
||||
|
||||
/*
|
||||
// Claude made this, wtf?
|
||||
if (strcmp(cmd_str, "trigger") == 0) {
|
||||
// Trigger RF433 transmitter
|
||||
rf_433_transmit();
|
||||
cmd_executed = true;
|
||||
} else */
|
||||
if (strcmp(cmd_str, "start") == 0) {
|
||||
// Start operation - transition FSM to running state
|
||||
//fsm_set_state(FSM_STATE_RUNNING);
|
||||
fsm_request(FSM_CMD_START);
|
||||
cmd_executed = true;
|
||||
}
|
||||
else if (strcmp(cmd_str, "stop") == 0) {
|
||||
// Stop operation - transition FSM to idle state
|
||||
//fsm_set_state(FSM_STATE_IDLE);
|
||||
fsm_request(FSM_CMD_STOP);
|
||||
cmd_executed = true;
|
||||
}
|
||||
else if (strcmp(cmd_str, "undo") == 0) {
|
||||
// Stop operation - transition FSM to idle state
|
||||
//fsm_set_state(FSM_STATE_IDLE);
|
||||
fsm_request(FSM_CMD_UNDO);
|
||||
cmd_executed = true;
|
||||
}
|
||||
else if (strcmp(cmd_str, "fwd") == 0) {
|
||||
pulseOverride(RELAY_A1); pulseOverride(RELAY_A3);
|
||||
cmd_executed = true;
|
||||
}
|
||||
else if (strcmp(cmd_str, "rev") == 0) {
|
||||
pulseOverride(RELAY_B1); pulseOverride(RELAY_A3);
|
||||
cmd_executed = true;
|
||||
}
|
||||
else if (strcmp(cmd_str, "up") == 0) {
|
||||
pulseOverride(RELAY_A2);
|
||||
cmd_executed = true;
|
||||
}
|
||||
else if (strcmp(cmd_str, "down") == 0) {
|
||||
pulseOverride(RELAY_B2);
|
||||
cmd_executed = true;
|
||||
}
|
||||
else if (strcmp(cmd_str, "aux") == 0) {
|
||||
pulseOverride(RELAY_A3);
|
||||
cmd_executed = true;
|
||||
}
|
||||
else if (strcmp(cmd_str, "reboot") == 0) {
|
||||
reboot_requested = true;
|
||||
cmd_executed = true;
|
||||
}
|
||||
else if (strcmp(cmd_str, "sleep") == 0) {
|
||||
sleep_requested = true;
|
||||
cmd_executed = true;
|
||||
}
|
||||
/*else if (strcmp(cmd_str, "rfp") == 0) {
|
||||
// RF programming command - get channel parameter
|
||||
cJSON *channel = cJSON_GetObjectItem(root, "channel");
|
||||
if (cJSON_IsNumber(channel)) {
|
||||
int ch = (int)cJSON_GetNumberValue(channel);
|
||||
ESP_LOGI(TAG, "RF programming channel: %d", ch);
|
||||
rf_433_learn(ch);
|
||||
cmd_executed = true;
|
||||
} else {
|
||||
ESP_LOGW(TAG, "rfp command missing or invalid channel parameter");
|
||||
error_msg = "rfp requires channel parameter";
|
||||
}
|
||||
}*/
|
||||
else if (strcmp(cmd_str, "rf_clear_temp") == 0) {
|
||||
rf_433_clear_temp_keycodes();
|
||||
cmd_executed = true;
|
||||
}
|
||||
else if (strcmp(cmd_str, "rf_disable") == 0) {
|
||||
rf_433_disable_controls();
|
||||
cmd_executed = true;
|
||||
}
|
||||
else if (strcmp(cmd_str, "rf_enable") == 0) {
|
||||
rf_433_enable_controls();
|
||||
cmd_executed = true;
|
||||
}
|
||||
else if (strcmp(cmd_str, "rf_learn") == 0) {
|
||||
// Start learning for a specific channel
|
||||
cJSON *channel = cJSON_GetObjectItem(root, "channel");
|
||||
if (cJSON_IsNumber(channel)) {
|
||||
int ch = (int)cJSON_GetNumberValue(channel);
|
||||
if (ch >= 0 && ch < NUM_RF_BUTTONS) {
|
||||
rf_433_learn_keycode(ch);
|
||||
cmd_executed = true;
|
||||
} else if (ch == -1) {
|
||||
rf_433_cancel_learn_keycode();
|
||||
cmd_executed = true;
|
||||
} else {
|
||||
error_msg = "Invalid channel number";
|
||||
}
|
||||
} else {
|
||||
error_msg = "rf_learn requires channel parameter";
|
||||
}
|
||||
}
|
||||
else if (strcmp(cmd_str, "rf_set_temp") == 0) {
|
||||
cJSON *index = cJSON_GetObjectItem(root, "index");
|
||||
cJSON *code = cJSON_GetObjectItem(root, "code");
|
||||
if (cJSON_IsNumber(index) && cJSON_IsNumber(code)) {
|
||||
int idx = (int)cJSON_GetNumberValue(index);
|
||||
int32_t rf_code = (int32_t)cJSON_GetNumberValue(code);
|
||||
rf_433_set_temp_keycode(idx, rf_code);
|
||||
cmd_executed = true;
|
||||
} else {
|
||||
error_msg = "rf_set_temp requires index and code parameters";
|
||||
}
|
||||
}
|
||||
else if (strcmp(cmd_str, "rf_status") == 0) {
|
||||
// Return current temp keycodes
|
||||
cJSON *response = cJSON_CreateObject();
|
||||
cJSON *codes_array = cJSON_CreateArray();
|
||||
|
||||
for (int i = 0; i < 4; i++) { // Only return first 4 for web UI
|
||||
int32_t code = rf_433_get_temp_keycode(i);
|
||||
cJSON_AddItemToArray(codes_array, cJSON_CreateNumber(code));
|
||||
}
|
||||
|
||||
cJSON_AddItemToObject(response, "codes", codes_array);
|
||||
|
||||
char *json_str = cJSON_Print(response);
|
||||
cJSON_Delete(response);
|
||||
cJSON_Delete(root);
|
||||
|
||||
esp_err_t err = httpd_resp_set_type(req, "application/json");
|
||||
if (err == ESP_OK) {
|
||||
err = httpd_resp_send(req, json_str, strlen(json_str));
|
||||
}
|
||||
free(json_str);
|
||||
return err;
|
||||
}
|
||||
|
||||
else if (strcmp(cmd_str, "cal_jack_start") == 0) {
|
||||
fsm_request(FSM_CMD_CALIBRATE_JACK_PREP);
|
||||
ESP_LOGI(TAG, "FSM_CMD_CALIBRATE_JACK_PREP");
|
||||
cmd_executed = true;
|
||||
}
|
||||
else if (strcmp(cmd_str, "cal_jack_finish") == 0) {
|
||||
cJSON *i = cJSON_GetObjectItem(root, "amt");
|
||||
if (cJSON_IsNumber(i) && i->valuedouble >= 0 && i->valuedouble < 8) {
|
||||
ESP_LOGI(TAG, "FSM_CMD_CALIBRATE_JACK_FINISH");
|
||||
fsm_set_cal_val(i->valuedouble);
|
||||
fsm_request(FSM_CMD_CALIBRATE_JACK_FINISH);
|
||||
cmd_executed = true;
|
||||
} else {
|
||||
error_msg = "cal_jack_finish requires amt parameter (0-8)";
|
||||
}
|
||||
}
|
||||
else if (strcmp(cmd_str, "cal_drive_start") == 0) {
|
||||
fsm_request(FSM_CMD_CALIBRATE_DRIVE_PREP);
|
||||
ESP_LOGI(TAG, "FSM_CMD_CALIBRATE_DRIVE_PREP");
|
||||
cmd_executed = true;
|
||||
}
|
||||
else if (strcmp(cmd_str, "cal_drive_finish") == 0) {
|
||||
cJSON *i = cJSON_GetObjectItem(root, "amt");
|
||||
if (cJSON_IsNumber(i) && i->valuedouble >= 0 && i->valuedouble < 8) {
|
||||
ESP_LOGI(TAG, "FSM_CMD_CALIBRATE_DRIVE_FINISH");
|
||||
fsm_set_cal_val(i->valuedouble);
|
||||
fsm_request(FSM_CMD_CALIBRATE_DRIVE_FINISH);
|
||||
cmd_executed = true;
|
||||
} else {
|
||||
error_msg = "cal_drive_finish requires amt parameter (0-8)";
|
||||
}
|
||||
}
|
||||
|
||||
else if (strcmp(cmd_str, "cal_get") == 0) {
|
||||
ESP_LOGI(TAG, "CAL_GET");
|
||||
|
||||
// Build JSON response with calibration values
|
||||
cJSON *response = cJSON_CreateObject();
|
||||
cJSON_AddItemToObject(response, "e", cJSON_CreateNumber(fsm_get_cal_e()));
|
||||
cJSON_AddItemToObject(response, "t", cJSON_CreateNumber(fsm_get_cal_t()));
|
||||
|
||||
char *json_str = cJSON_Print(response);
|
||||
cJSON_Delete(response);
|
||||
cJSON_Delete(root);
|
||||
|
||||
esp_err_t err = httpd_resp_set_type(req, "application/json");
|
||||
if (err == ESP_OK) {
|
||||
err = httpd_resp_send(req, json_str, strlen(json_str));
|
||||
}
|
||||
free(json_str);
|
||||
return err;
|
||||
}
|
||||
|
||||
else {
|
||||
ESP_LOGW(TAG, "Unknown command: %s", cmd_str);
|
||||
error_msg = "Unknown command";
|
||||
}
|
||||
}
|
||||
|
||||
// Process batch parameter updates if present
|
||||
cJSON *parameters = cJSON_GetObjectItem(root, "parameters");
|
||||
if (cJSON_IsObject(parameters)) {
|
||||
// Process individual parameter updates (direct key-value pairs)
|
||||
cJSON *item = NULL;
|
||||
cJSON_ArrayForEach(item, parameters) {
|
||||
const char *key = item->string;
|
||||
ESP_LOGW(TAG, "PARAMETER %s", key);
|
||||
|
||||
// Find parameter index by name
|
||||
param_idx_t param_idx = NUM_PARAMS;
|
||||
for (param_idx_t i = 0; i < NUM_PARAMS; i++) {
|
||||
if (strcmp(get_param_name(i), key) == 0) {
|
||||
param_idx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (param_idx == NUM_PARAMS) {
|
||||
ESP_LOGW(TAG, "UNKNOWN PARAMETER %s", key);
|
||||
// Not a known parameter, skip silently
|
||||
continue;
|
||||
}
|
||||
|
||||
cJSON *value_json = cJSON_GetObjectItem(parameters, key);
|
||||
|
||||
// Set parameter value based on type
|
||||
switch (get_param_type(param_idx)) {
|
||||
case PARAM_TYPE_f32:
|
||||
if (cJSON_IsNumber(value_json)) {
|
||||
set_param_value_t(param_idx,
|
||||
(param_value_t){.f32=value_json->valueint});
|
||||
params_updated++;
|
||||
} else { ESP_LOGW(TAG, "PARAM TYPE MISMATCH FOR %s", key); }
|
||||
break;
|
||||
case PARAM_TYPE_f64:
|
||||
if (cJSON_IsNumber(value_json)) {
|
||||
set_param_value_t(param_idx,
|
||||
(param_value_t){.f64=value_json->valueint});
|
||||
params_updated++;
|
||||
} else { ESP_LOGW(TAG, "PARAM TYPE MISMATCH FOR %s", key); }
|
||||
break;
|
||||
case PARAM_TYPE_i32:
|
||||
if (cJSON_IsNumber(value_json)) {
|
||||
set_param_value_t(param_idx,
|
||||
(param_value_t){.i32=value_json->valueint});
|
||||
params_updated++;
|
||||
} else { ESP_LOGW(TAG, "PARAM TYPE MISMATCH FOR %s", key); }
|
||||
break;
|
||||
case PARAM_TYPE_i16:
|
||||
if (cJSON_IsNumber(value_json)) {
|
||||
set_param_value_t(param_idx,
|
||||
(param_value_t){.i16=value_json->valueint});
|
||||
params_updated++;
|
||||
} else { ESP_LOGW(TAG, "PARAM TYPE MISMATCH FOR %s", key); }
|
||||
break;
|
||||
case PARAM_TYPE_u32:
|
||||
if (cJSON_IsNumber(value_json)) {
|
||||
set_param_value_t(param_idx,
|
||||
(param_value_t){.u32=value_json->valueint});
|
||||
params_updated++;
|
||||
} else { ESP_LOGW(TAG, "PARAM TYPE MISMATCH FOR %s", key); }
|
||||
break;
|
||||
case PARAM_TYPE_u16:
|
||||
if (cJSON_IsNumber(value_json)) {
|
||||
set_param_value_t(param_idx,
|
||||
(param_value_t){.u16=value_json->valueint});
|
||||
params_updated++;
|
||||
} else { ESP_LOGW(TAG, "PARAM TYPE MISMATCH FOR %s", key); }
|
||||
break;
|
||||
case PARAM_TYPE_str:
|
||||
if (cJSON_IsString(value_json)) {
|
||||
set_param_string(param_idx, value_json->valuestring);
|
||||
params_updated++;
|
||||
} else { ESP_LOGW(TAG, "PARAM TYPE MISMATCH FOR %s", key); }
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (params_updated > 0) {
|
||||
rtc_schedule_next_alarm();
|
||||
commit_params();
|
||||
}
|
||||
}
|
||||
|
||||
// Call unified POST handler
|
||||
cJSON *response = NULL;
|
||||
esp_err_t err = comms_handle_post(root, &response);
|
||||
cJSON_Delete(root);
|
||||
|
||||
if (response == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to generate POST response");
|
||||
return httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR,
|
||||
"Failed to generate response");
|
||||
}
|
||||
|
||||
// Check for special response flags
|
||||
cJSON *reboot_flag = cJSON_GetObjectItem(response, "reboot");
|
||||
cJSON *sleep_flag = cJSON_GetObjectItem(response, "sleep");
|
||||
bool should_reboot = cJSON_IsTrue(reboot_flag);
|
||||
bool should_sleep = cJSON_IsTrue(sleep_flag);
|
||||
|
||||
// Convert response to string
|
||||
char *json_str = cJSON_PrintUnformatted(response);
|
||||
cJSON_Delete(response);
|
||||
|
||||
if (json_str == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to serialize JSON");
|
||||
return httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR,
|
||||
"Failed to serialize response");
|
||||
}
|
||||
|
||||
// Send response
|
||||
esp_err_t err;
|
||||
char response[256];
|
||||
int response_len;
|
||||
|
||||
if (reboot_requested) {
|
||||
// Special handling for reboot
|
||||
response_len = snprintf(response, sizeof(response),
|
||||
"{\"status\":\"ok\",\"message\":\"Rebooting...\"}");
|
||||
|
||||
if (response_len > 0 && response_len < (int)sizeof(response)) {
|
||||
err = httpd_resp_set_type(req, "application/json");
|
||||
if (err == ESP_OK) {
|
||||
err = httpd_resp_send(req, response, response_len);
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Rebooting in 2 seconds...");
|
||||
vTaskDelay(pdMS_TO_TICKS(2000));
|
||||
esp_restart();
|
||||
return ESP_OK; // Never reached
|
||||
}
|
||||
|
||||
if (sleep_requested) {
|
||||
// Special handling for sleep
|
||||
response_len = snprintf(response, sizeof(response),
|
||||
"{\"status\":\"ok\",\"message\":\"Sleeping...\"}");
|
||||
|
||||
if (response_len > 0 && response_len < (int)sizeof(response)) {
|
||||
err = httpd_resp_set_type(req, "application/json");
|
||||
if (err == ESP_OK) {
|
||||
err = httpd_resp_send(req, response, response_len);
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Sleeping in 2 seconds...");
|
||||
vTaskDelay(pdMS_TO_TICKS(2000));
|
||||
rtc_enter_deep_sleep();
|
||||
return ESP_OK; // Never reached
|
||||
}
|
||||
|
||||
if (error_msg != NULL) {
|
||||
err = httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, error_msg);
|
||||
} else {
|
||||
response_len = snprintf(response, sizeof(response),
|
||||
"{\"status\":\"ok\",\"params_updated\":%d,\"params_failed\":%d,\"cmd_executed\":%s}",
|
||||
params_updated, params_failed, cmd_executed ? "true" : "false");
|
||||
|
||||
if (response_len < 0 || response_len >= (int)sizeof(response)) {
|
||||
ESP_LOGE(TAG, "Failed to format response");
|
||||
return httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR,
|
||||
"Internal error");
|
||||
}
|
||||
|
||||
err = httpd_resp_set_type(req, "application/json");
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to set response type: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
err = httpd_resp_send(req, response, response_len);
|
||||
err = httpd_resp_set_type(req, "application/json");
|
||||
if (err == ESP_OK) {
|
||||
err = httpd_resp_send(req, json_str, strlen(json_str));
|
||||
}
|
||||
free(json_str);
|
||||
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to send response: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
// Handle special actions after response is sent
|
||||
if (should_reboot) {
|
||||
ESP_LOGI(TAG, "Rebooting in 2 seconds...");
|
||||
vTaskDelay(pdMS_TO_TICKS(2000));
|
||||
esp_restart();
|
||||
return ESP_OK; // Never reached
|
||||
}
|
||||
|
||||
if (should_sleep) {
|
||||
ESP_LOGI(TAG, "Sleeping in 2 seconds...");
|
||||
vTaskDelay(pdMS_TO_TICKS(2000));
|
||||
rtc_enter_deep_sleep();
|
||||
return ESP_OK; // Never reached
|
||||
}
|
||||
|
||||
err = httpd_resp_set_hdr(req, "Connection", "close");
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to set connection header: %s", esp_err_to_name(err));
|
||||
// Continue anyway
|
||||
ESP_LOGW(TAG, "Failed to set connection header: %s", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
return err;
|
||||
@@ -1412,6 +994,7 @@ static esp_err_t launchSoftAp(void) {
|
||||
esp_err_t webserver_init(void) {
|
||||
ESP_LOGI(TAG, "Initializing webserver...");
|
||||
|
||||
// Initialize comms module
|
||||
esp_err_t err = launchSoftAp();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to launch SoftAP: %s", esp_err_to_name(err));
|
||||
|
||||
Reference in New Issue
Block a user