logging testing. logging works. e-fusing algo works right for jack. jack timing works.
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 comms.c # list the source files of this component
|
||||
SRCS main.c log_test.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
|
||||
|
||||
|
||||
25
main/comms.c
25
main/comms.c
@@ -19,7 +19,7 @@ static const char *TAG = "COMMS";
|
||||
* Build a JSON object containing complete system status
|
||||
*/
|
||||
cJSON* comms_handle_get(void) {
|
||||
ESP_LOGI(TAG, "GET request");
|
||||
//ESP_LOGI(TAG, "GET request");
|
||||
|
||||
rtc_reset_shutdown_timer();
|
||||
|
||||
@@ -52,7 +52,6 @@ cJSON* comms_handle_get(void) {
|
||||
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;
|
||||
@@ -65,13 +64,13 @@ cJSON* comms_handle_get(void) {
|
||||
if (fsm_get_remaining_distance() <= 0) {
|
||||
cJSON_AddItemToArray(msg_array, cJSON_CreateString("DISTANCE LIMIT HIT"));
|
||||
}
|
||||
if (efuse_is_tripped(BRIDGE_AUX)) {
|
||||
if (efuse_get(BRIDGE_AUX)) {
|
||||
cJSON_AddItemToArray(msg_array, cJSON_CreateString("AUX EFUSE TRIP"));
|
||||
}
|
||||
if (efuse_is_tripped(BRIDGE_JACK)) {
|
||||
if (efuse_get(BRIDGE_JACK)) {
|
||||
cJSON_AddItemToArray(msg_array, cJSON_CreateString("JACK EFUSE TRIP"));
|
||||
}
|
||||
if (efuse_is_tripped(BRIDGE_DRIVE)) {
|
||||
if (efuse_get(BRIDGE_DRIVE)) {
|
||||
cJSON_AddItemToArray(msg_array, cJSON_CreateString("DRIVE EFUSE TRIP"));
|
||||
}
|
||||
if (!rtc_is_set()) {
|
||||
@@ -135,7 +134,7 @@ cJSON* comms_handle_get(void) {
|
||||
* Process a POST request with JSON data
|
||||
*/
|
||||
esp_err_t comms_handle_post(cJSON *root, cJSON **response_json) {
|
||||
ESP_LOGI(TAG, "POST request");
|
||||
//ESP_LOGI(TAG, "POST request");
|
||||
|
||||
if (root == NULL || response_json == NULL) {
|
||||
ESP_LOGE(TAG, "Invalid arguments to comms_handle_post");
|
||||
@@ -171,7 +170,7 @@ esp_err_t comms_handle_post(cJSON *root, cJSON **response_json) {
|
||||
cJSON *cmd = cJSON_GetObjectItem(root, "cmd");
|
||||
if (cJSON_IsString(cmd)) {
|
||||
const char *cmd_str = cmd->valuestring;
|
||||
ESP_LOGI(TAG, "Executing command: %s", cmd_str);
|
||||
// ESP_LOGI(TAG, "Executing command: %s", cmd_str);
|
||||
|
||||
if (strcmp(cmd_str, "start") == 0) {
|
||||
fsm_request(FSM_CMD_START);
|
||||
@@ -186,25 +185,23 @@ esp_err_t comms_handle_post(cJSON *root, cJSON **response_json) {
|
||||
cmd_executed = true;
|
||||
}
|
||||
else if (strcmp(cmd_str, "fwd") == 0) {
|
||||
pulseOverride(RELAY_A1);
|
||||
pulseOverride(RELAY_A3);
|
||||
pulseOverride(FSM_OVERRIDE_DRIVE_FWD);
|
||||
cmd_executed = true;
|
||||
}
|
||||
else if (strcmp(cmd_str, "rev") == 0) {
|
||||
pulseOverride(RELAY_B1);
|
||||
pulseOverride(RELAY_A3);
|
||||
pulseOverride(FSM_OVERRIDE_DRIVE_REV);
|
||||
cmd_executed = true;
|
||||
}
|
||||
else if (strcmp(cmd_str, "up") == 0) {
|
||||
pulseOverride(RELAY_A2);
|
||||
pulseOverride(FSM_OVERRIDE_JACK_UP);
|
||||
cmd_executed = true;
|
||||
}
|
||||
else if (strcmp(cmd_str, "down") == 0) {
|
||||
pulseOverride(RELAY_B2);
|
||||
pulseOverride(FSM_OVERRIDE_JACK_DOWN);
|
||||
cmd_executed = true;
|
||||
}
|
||||
else if (strcmp(cmd_str, "aux") == 0) {
|
||||
pulseOverride(RELAY_A3);
|
||||
pulseOverride(FSM_OVERRIDE_AUX);
|
||||
cmd_executed = true;
|
||||
}
|
||||
else if (strcmp(cmd_str, "reboot") == 0) {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "rtc.h"
|
||||
#include "sensors.h"
|
||||
#include "esp_log.h"
|
||||
#include <string.h>
|
||||
#include <sys/param.h>
|
||||
|
||||
#define TRANSITION_DELAY_US 1000000
|
||||
@@ -28,24 +29,14 @@
|
||||
|
||||
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; }
|
||||
RTC_DATA_ATTR esp_err_t fsm_error = ESP_OK;
|
||||
esp_err_t fsm_get_error() { return fsm_error; }
|
||||
void fsm_clear_error() { fsm_error = ESP_OK; }
|
||||
|
||||
// map from relay number to bridge
|
||||
bridge_t bridge_map[] = {
|
||||
BRIDGE_AUX,
|
||||
BRIDGE_AUX,
|
||||
BRIDGE_AUX,
|
||||
BRIDGE_AUX,
|
||||
BRIDGE_JACK,
|
||||
BRIDGE_JACK,
|
||||
BRIDGE_DRIVE,
|
||||
BRIDGE_DRIVE };
|
||||
|
||||
bool relay_states[8] = {false};
|
||||
int64_t override_times[8] = {-1};
|
||||
int64_t override_cooldown[8] = {-1};
|
||||
int64_t override_time = -1;
|
||||
fsm_override_t override_cmd;
|
||||
//int64_t override_cooldown[8] = {-1};
|
||||
bool enabled = false;
|
||||
|
||||
float this_move_dist = 0.0f;
|
||||
@@ -57,40 +48,14 @@ void fsm_set_remaining_distance(float x) { remaining_distance = x;}
|
||||
static int32_t move_start_encoder = 0;
|
||||
|
||||
// Track total jack up time to use for jack down duration
|
||||
static int64_t jack_up_total_time = 0;
|
||||
static int64_t jack_start_us = 0;
|
||||
static int64_t jack_trans_us = 0;
|
||||
static int64_t jack_finish_us = 0;
|
||||
|
||||
volatile fsm_state_t current_state = STATE_IDLE;
|
||||
volatile int64_t current_time = 0;
|
||||
volatile int64_t fsm_now = 0;
|
||||
volatile bool start_running_request = false;
|
||||
|
||||
void setRelay(int8_t relay, bool state) {
|
||||
relay_states[relay] = state;
|
||||
}
|
||||
|
||||
bool isRunning() {
|
||||
for (int i=0;i<8;i++) {
|
||||
if (relay_states[i]) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void driveRelays() {
|
||||
uint8_t state = 0x00;
|
||||
//relay_states[0] = (current_time / 1000000) % 2; // for testing purposes
|
||||
|
||||
for (uint8_t i=0; i<8; i++) {
|
||||
// if we command and efuse permits it set the relay
|
||||
if (relay_states[i] && !efuse_is_tripped(bridge_map[i])) {
|
||||
state |= 0x01<<i;
|
||||
set_autozero(bridge_map[i]);
|
||||
}
|
||||
}
|
||||
|
||||
//ESP_LOGI(TAG, "RELAY STATE: %x", state);
|
||||
i2c_set_relays(state);
|
||||
}
|
||||
|
||||
|
||||
|
||||
fsm_state_t fsm_get_state() {
|
||||
return current_state;
|
||||
@@ -99,28 +64,18 @@ fsm_state_t fsm_get_state() {
|
||||
static int64_t timer_end = 0;
|
||||
static int64_t timer_start = 0;
|
||||
static inline void set_timer(uint64_t us) {
|
||||
timer_end = current_time + us;
|
||||
timer_start = current_time;
|
||||
timer_end = fsm_now + us;
|
||||
timer_start = fsm_now;
|
||||
}
|
||||
static inline bool timer_done() { return current_time >= timer_end; }
|
||||
static inline bool timer_done() { return fsm_now >= timer_end; }
|
||||
|
||||
void pulseOverride(relay_t relay) {
|
||||
void pulseOverride(fsm_override_t cmd) {
|
||||
if (current_state == STATE_IDLE) {
|
||||
// Check if this relay is in cooldown
|
||||
if (override_cooldown[relay] > current_time) {
|
||||
// Still cooling down, ignore the command
|
||||
return;
|
||||
}
|
||||
override_times[relay] = current_time + get_param_value_t(PARAM_RF_PULSE_LENGTH).u32;
|
||||
override_cmd = cmd;
|
||||
override_time = fsm_now + get_param_value_t(PARAM_RF_PULSE_LENGTH).u32;
|
||||
}
|
||||
}
|
||||
|
||||
/*void fsm_begin_auto_move() {
|
||||
if (current_state == STATE_IDLE)
|
||||
current_state = STATE_MOVE_START_DELAY;
|
||||
set_timer(TRANSITION_DELAY_US);
|
||||
}*/
|
||||
|
||||
int64_t fsm_cal_t, fsm_cal_e;
|
||||
float fsm_cal_val;
|
||||
void fsm_set_cal_val(float v) {fsm_cal_val = v;}
|
||||
@@ -143,9 +98,8 @@ int8_t fsm_get_current_progress(int8_t denominator) {
|
||||
case STATE_MOVE_START_DELAY:
|
||||
case STATE_DRIVE_START_DELAY:
|
||||
case STATE_DRIVE_END_DELAY:
|
||||
case STATE_UNDO_JACK:
|
||||
if (timer_end != timer_start)
|
||||
x = (current_time-timer_start)*denominator/(timer_end-timer_start);
|
||||
x = (fsm_now-timer_start)*denominator/(timer_end-timer_start);
|
||||
break;
|
||||
case STATE_UNDO_JACK_START:
|
||||
x = 0;
|
||||
@@ -160,9 +114,56 @@ int8_t fsm_get_current_progress(int8_t denominator) {
|
||||
|
||||
|
||||
#define JACK_TIME get_param_value_t(PARAM_JACK_KT).f32 * get_param_value_t(PARAM_JACK_DIST ).f32
|
||||
#define JACK_DOWN_TIME (jack_finish_us - jack_start_us) * 105/100
|
||||
#define DRIVE_TIME get_param_value_t(PARAM_DRIVE_KT).f32 * this_move_dist
|
||||
#define DRIVE_DIST get_param_value_t(PARAM_DRIVE_KE).f32 * this_move_dist
|
||||
|
||||
int64_t last_log_time = 0;
|
||||
#define LOGSIZE 39
|
||||
esp_err_t send_fsm_log() {
|
||||
if(!rtc_is_set()) return ESP_OK;
|
||||
|
||||
uint8_t entry[LOGSIZE] = {};
|
||||
|
||||
// Pack 64-bit timestamp into bytes 1-8
|
||||
uint64_t be_timestamp = rtc_get_ms();
|
||||
memcpy(&entry[0], &be_timestamp, 8);
|
||||
|
||||
// Pack 32-bit voltages/currents into bytes 9-24
|
||||
float be_voltage = get_battery_V();
|
||||
memcpy(&entry[8], &be_voltage, 4);
|
||||
float be_current1 = get_bridge_raw_A(BRIDGE_DRIVE);
|
||||
memcpy(&entry[12], &be_current1, 4);
|
||||
float be_current2 = get_bridge_raw_A(BRIDGE_JACK);
|
||||
memcpy(&entry[16], &be_current2, 4);
|
||||
float be_current3 = get_bridge_raw_A(BRIDGE_AUX);
|
||||
memcpy(&entry[20], &be_current3, 4);
|
||||
|
||||
int16_t be_counter = get_sensor_counter(SENSOR_DRIVE);
|
||||
memcpy(&entry[24], &be_counter, 2);
|
||||
|
||||
entry[26] = pack_sensors();
|
||||
|
||||
|
||||
|
||||
|
||||
float heat1 = efuse_get_heat(BRIDGE_DRIVE);
|
||||
memcpy(&entry[27], &heat1, 4);
|
||||
float heat2 = efuse_get_heat(BRIDGE_JACK);
|
||||
memcpy(&entry[31], &heat2, 4);
|
||||
float heat3 = efuse_get_heat(BRIDGE_AUX);
|
||||
memcpy(&entry[35], &heat3, 4);
|
||||
|
||||
last_log_time = esp_timer_get_time();
|
||||
|
||||
|
||||
log_write(entry, LOGSIZE, fsm_get_state());
|
||||
|
||||
//ESP_LOGI(TAG, "WROTE LOG; %lld / %ld/%ld; %5.2f %5.2f %5.2f", (long long)rtc_get_ms(), (unsigned long)log_get_tail(), (unsigned long)log_get_head(), heat1, heat2, heat3);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void control_task(void *param) {
|
||||
esp_task_wdt_add(NULL);
|
||||
|
||||
@@ -170,11 +171,22 @@ void control_task(void *param) {
|
||||
const TickType_t xFrequency = pdMS_TO_TICKS(20);
|
||||
enabled = true;
|
||||
|
||||
sensors_init();
|
||||
|
||||
while (enabled) {
|
||||
vTaskDelayUntil(&xLastWakeTime, xFrequency);
|
||||
current_time = esp_timer_get_time();
|
||||
fsm_now = esp_timer_get_time();
|
||||
|
||||
bool log = false;
|
||||
|
||||
/**** READ INPUTS ****/
|
||||
for (uint8_t i = 0; i < N_BRIDGES; i++) {
|
||||
process_bridge_current(i);
|
||||
}
|
||||
process_battery_voltage();
|
||||
sensors_check();
|
||||
|
||||
/**** LISTEN TO COMMANDS ****/
|
||||
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
|
||||
@@ -184,7 +196,8 @@ void control_task(void *param) {
|
||||
// Check if we have remaining distance before starting
|
||||
if (remaining_distance <= 0.0f) {
|
||||
ESP_LOGI(TAG, "FAILED TO START; NO REMAINING DISTANCE");
|
||||
error = SC_ERR_LEASH_HIT;
|
||||
fsm_error = SC_ERR_LEASH_HIT;
|
||||
log = true;
|
||||
continue;
|
||||
}
|
||||
this_move_dist = MIN(get_param_value_t(PARAM_DRIVE_DIST).f32, remaining_distance);
|
||||
@@ -194,33 +207,34 @@ void control_task(void *param) {
|
||||
|
||||
if (get_battery_V() < get_param_value_t(PARAM_LOW_PROTECTION_V).f32) {
|
||||
ESP_LOGI(TAG, "FAILED TO START; INSUFFICIENT VOLTAGE");
|
||||
error = SC_ERR_LOW_BATTERY;
|
||||
fsm_error = SC_ERR_LOW_BATTERY;
|
||||
continue;
|
||||
}
|
||||
if (!get_is_safe()) {
|
||||
ESP_LOGI(TAG, "FAILED TO START; SAFETY NOT SET");
|
||||
error = SC_ERR_SAFETY_TRIP;
|
||||
fsm_error = SC_ERR_SAFETY_TRIP;
|
||||
continue;
|
||||
}
|
||||
if (efuse_is_tripped(BRIDGE_DRIVE)) {
|
||||
if (efuse_get(BRIDGE_DRIVE)) {
|
||||
ESP_LOGI(TAG, "FAILED TO START; EFUSE 1 TRIP");
|
||||
error = SC_ERR_EFUSE_TRIP_1;
|
||||
fsm_error = SC_ERR_EFUSE_TRIP_1;
|
||||
continue;
|
||||
}
|
||||
if (efuse_is_tripped(BRIDGE_JACK)) {
|
||||
if (efuse_get(BRIDGE_JACK)) {
|
||||
ESP_LOGI(TAG, "FAILED TO START; EFUSE 2 TRIP");
|
||||
error = SC_ERR_EFUSE_TRIP_2;
|
||||
fsm_error = SC_ERR_EFUSE_TRIP_2;
|
||||
continue;
|
||||
}
|
||||
if (efuse_is_tripped(BRIDGE_AUX)) {
|
||||
if (efuse_get(BRIDGE_AUX)) {
|
||||
ESP_LOGI(TAG, "FAILED TO START; EFUSE 3 TRIP");
|
||||
error = SC_ERR_EFUSE_TRIP_3;
|
||||
fsm_error = SC_ERR_EFUSE_TRIP_3;
|
||||
continue;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "STARTING");
|
||||
error = ESP_OK; // if everything is OK now, we're OK.
|
||||
fsm_error = ESP_OK; // if everything is OK now, we're OK.
|
||||
current_state = STATE_MOVE_START_DELAY;
|
||||
log = true;
|
||||
set_timer(TRANSITION_DELAY_US);
|
||||
}
|
||||
break;
|
||||
@@ -229,9 +243,9 @@ void control_task(void *param) {
|
||||
break;
|
||||
case FSM_CMD_UNDO:
|
||||
if (current_state != STATE_IDLE &&
|
||||
current_state != STATE_UNDO_JACK_START &&
|
||||
current_state != STATE_UNDO_JACK) {
|
||||
current_state != STATE_UNDO_JACK_START) {
|
||||
current_state = STATE_UNDO_JACK_START;
|
||||
log = true;
|
||||
}
|
||||
break;
|
||||
case FSM_CMD_SHUTDOWN:
|
||||
@@ -243,6 +257,7 @@ void control_task(void *param) {
|
||||
if (current_state == STATE_IDLE
|
||||
&& get_battery_V() > get_param_value_t(PARAM_LOW_PROTECTION_V).f32) {
|
||||
current_state = STATE_CALIBRATE_JACK_DELAY;
|
||||
log = true;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -251,14 +266,16 @@ void control_task(void *param) {
|
||||
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;
|
||||
log = true;
|
||||
set_timer(CALIBRATE_JACK_MAX_TIME);
|
||||
}
|
||||
break;
|
||||
case FSM_CMD_CALIBRATE_JACK_END:
|
||||
ESP_LOGI(TAG, "FSM_CMD_CALIBRATE_JACK_END");
|
||||
if (current_state == STATE_CALIBRATE_JACK_MOVE) {
|
||||
fsm_cal_t = current_time - timer_start;
|
||||
fsm_cal_t = fsm_now - timer_start;
|
||||
current_state = STATE_IDLE;
|
||||
log = true;
|
||||
}
|
||||
break;
|
||||
case FSM_CMD_CALIBRATE_JACK_FINISH:
|
||||
@@ -274,6 +291,7 @@ void control_task(void *param) {
|
||||
if (current_state == STATE_IDLE
|
||||
&& get_battery_V() > get_param_value_t(PARAM_LOW_PROTECTION_V).f32) {
|
||||
current_state = STATE_CALIBRATE_DRIVE_DELAY;
|
||||
log = true;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -282,6 +300,7 @@ void control_task(void *param) {
|
||||
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;
|
||||
log = true;
|
||||
set_timer(CALIBRATE_DRIVE_MAX_TIME);
|
||||
set_sensor_counter(SENSOR_DRIVE, 0);
|
||||
}
|
||||
@@ -289,9 +308,10 @@ void control_task(void *param) {
|
||||
case FSM_CMD_CALIBRATE_DRIVE_END:
|
||||
ESP_LOGI(TAG, "FSM_CMD_CALIBRATE_DRIVE_END");
|
||||
if (current_state == STATE_CALIBRATE_DRIVE_MOVE) {
|
||||
fsm_cal_t = current_time - timer_start;
|
||||
fsm_cal_t = fsm_now - timer_start;
|
||||
fsm_cal_e = get_sensor_counter(SENSOR_DRIVE);
|
||||
current_state = STATE_IDLE;
|
||||
log = true;
|
||||
}
|
||||
break;
|
||||
case FSM_CMD_CALIBRATE_DRIVE_FINISH:
|
||||
@@ -307,115 +327,81 @@ void control_task(void *param) {
|
||||
}
|
||||
|
||||
if (!enabled) break;
|
||||
|
||||
|
||||
|
||||
// State transitions
|
||||
/**** STATE TRANSITIONS ****/
|
||||
switch (current_state) {
|
||||
case STATE_IDLE:
|
||||
//ESP_LOGI("FSM", "IDLE @ %lld", current_time);
|
||||
for (uint8_t i = 0; i < N_RELAYS; ++i) {
|
||||
//ESP_LOGI("FSM", "t[%d] %lld", i, override_times[i]);
|
||||
bool active = override_times[i] > current_time;
|
||||
if (active) rtc_reset_shutdown_timer();
|
||||
|
||||
// Current limiting for manual jack down override (RELAY_B2)
|
||||
if (i == RELAY_B2 && active) {
|
||||
int64_t elapsed = current_time - (override_times[i] - get_param_value_t(PARAM_RF_PULSE_LENGTH).u32);
|
||||
int64_t delay = get_param_value_t(PARAM_EFUSE_INRUSH_US).u32;
|
||||
|
||||
// After inrush delay, check for current spike
|
||||
if (elapsed > delay) {
|
||||
float current = get_bridge_A(BRIDGE_JACK);
|
||||
float threshold = get_param_value_t(PARAM_JACK_I_DOWN).f32;
|
||||
|
||||
if (current > threshold) {
|
||||
// Current spike detected - stop jacking down and start cooldown
|
||||
override_times[i] = -1;
|
||||
override_cooldown[i] = current_time + get_param_value_t(PARAM_EFUSE_TCOOL).u32;
|
||||
active = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// prohibit movement past jack limit switch
|
||||
//if (i == BRIDGE_JACK*2+(bridge_polarities[BRIDGE_JACK]>0?0:1) && get_sensor(SENSOR_JACK))
|
||||
// setRelay(i, false);
|
||||
//else
|
||||
setRelay(i, active);
|
||||
//if (active) ESP_LOGI("FSM", "RUN CHANNEL %d (%lld %c %lld)", i, (long long) override_times[i], active ? '>':'<', (long long) current_time);
|
||||
|
||||
}
|
||||
break;
|
||||
case STATE_MOVE_START_DELAY:
|
||||
if (!get_is_safe()) {
|
||||
error = SC_ERR_SAFETY_TRIP;
|
||||
fsm_error = SC_ERR_SAFETY_TRIP;
|
||||
current_state = STATE_IDLE;
|
||||
}
|
||||
if (timer_done()) {
|
||||
log = true;
|
||||
} else if (timer_done()) {
|
||||
current_state = STATE_JACK_UP_START;
|
||||
set_timer(JACK_TIME / 2); // First phase is half of total jack time
|
||||
jack_up_total_time = 0; // Reset jack up time tracker
|
||||
jack_start_us = fsm_now;
|
||||
}
|
||||
break;
|
||||
case STATE_JACK_UP_START:
|
||||
if (!get_is_safe()) {
|
||||
error = SC_ERR_SAFETY_TRIP;
|
||||
fsm_error = SC_ERR_SAFETY_TRIP;
|
||||
current_state = STATE_UNDO_JACK_START;
|
||||
}
|
||||
|
||||
{
|
||||
// Track elapsed time
|
||||
int64_t elapsed = current_time - timer_start;
|
||||
jack_up_total_time = elapsed;
|
||||
jack_finish_us = fsm_now;
|
||||
log = true;
|
||||
} else {
|
||||
|
||||
if (efuse_get(BRIDGE_JACK)) {
|
||||
ESP_LOGI(TAG, "START->UP BY EFUSE");
|
||||
current_state = STATE_JACK_UP;
|
||||
jack_trans_us = fsm_now;
|
||||
log = true;
|
||||
set_timer(JACK_TIME);
|
||||
}
|
||||
|
||||
// Get current sensing parameters
|
||||
int64_t delay = get_param_value_t(PARAM_EFUSE_INRUSH_US).u32;
|
||||
float current = get_bridge_A(BRIDGE_JACK);
|
||||
float threshold = get_param_value_t(PARAM_JACK_I_UP).f32;
|
||||
|
||||
// After inrush delay, check for current spike OR half-time timeout
|
||||
if (elapsed > delay) {
|
||||
if (current > threshold || timer_done()) {
|
||||
ESP_LOGI(TAG, "START->UP BY CURRENT");
|
||||
current_state = STATE_JACK_UP;
|
||||
set_timer(JACK_TIME); // Second phase is also half of total jack time
|
||||
}
|
||||
if (get_bridge_overcurrent(BRIDGE_JACK, get_param_value_t(PARAM_JACK_I_UP).f32)) {
|
||||
ESP_LOGI(TAG, "START->UP BY CURRENT");
|
||||
current_state = STATE_JACK_UP;
|
||||
jack_trans_us = fsm_now;
|
||||
log = true;
|
||||
set_timer(JACK_TIME);
|
||||
}
|
||||
|
||||
// E-fuse trip
|
||||
if (efuse_is_tripped(BRIDGE_JACK)) {
|
||||
error = SC_ERR_EFUSE_TRIP_2;
|
||||
current_state = STATE_IDLE;
|
||||
if (timer_done()) {
|
||||
ESP_LOGI(TAG, "START->UP BY TIME");
|
||||
current_state = STATE_JACK_UP;
|
||||
jack_trans_us = fsm_now;
|
||||
log = true;
|
||||
set_timer(JACK_TIME);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case STATE_JACK_UP:
|
||||
if (!get_is_safe()) {
|
||||
error = SC_ERR_SAFETY_TRIP;
|
||||
fsm_error = SC_ERR_SAFETY_TRIP;
|
||||
current_state = STATE_UNDO_JACK_START;
|
||||
}
|
||||
|
||||
{
|
||||
if (timer_done() || efuse_is_tripped(BRIDGE_JACK)) {
|
||||
jack_finish_us = fsm_now;
|
||||
set_timer(JACK_DOWN_TIME);
|
||||
log = true;
|
||||
} else {
|
||||
if (timer_done() || efuse_get(BRIDGE_JACK)) {
|
||||
// Track total time including first phase
|
||||
jack_up_total_time += current_time - timer_start;
|
||||
current_state = STATE_DRIVE_START_DELAY;
|
||||
current_state = STATE_DRIVE_START_DELAY;
|
||||
jack_finish_us = fsm_now;
|
||||
log = true;
|
||||
set_timer(TRANSITION_DELAY_US);
|
||||
}
|
||||
if (efuse_is_tripped(BRIDGE_JACK)) {
|
||||
error = SC_ERR_EFUSE_TRIP_2;
|
||||
current_state = STATE_IDLE;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case STATE_DRIVE_START_DELAY:
|
||||
if (!get_is_safe()) {
|
||||
error = SC_ERR_SAFETY_TRIP;
|
||||
fsm_error = SC_ERR_SAFETY_TRIP;
|
||||
current_state = STATE_UNDO_JACK_START;
|
||||
}
|
||||
if (timer_done()) {
|
||||
set_timer(JACK_DOWN_TIME);
|
||||
log = true;
|
||||
} else if (timer_done()) {
|
||||
current_state = STATE_DRIVE;
|
||||
log = true;
|
||||
set_timer(DRIVE_TIME);
|
||||
// Set the encoder counter to track remaining distance in this move
|
||||
set_sensor_counter(SENSOR_DRIVE, -DRIVE_DIST);
|
||||
@@ -425,10 +411,11 @@ void control_task(void *param) {
|
||||
break;
|
||||
case STATE_DRIVE:
|
||||
if (!get_is_safe()) {
|
||||
error = SC_ERR_SAFETY_TRIP;
|
||||
fsm_error = SC_ERR_SAFETY_TRIP;
|
||||
current_state = STATE_UNDO_JACK_START;
|
||||
}
|
||||
{
|
||||
set_timer(JACK_DOWN_TIME);
|
||||
log = true;
|
||||
} else {
|
||||
int32_t current_encoder = get_sensor_counter(SENSOR_DRIVE);
|
||||
int32_t ticks_traveled = current_encoder - move_start_encoder;
|
||||
float ke = get_param_value_t(PARAM_DRIVE_KE).f32;
|
||||
@@ -443,81 +430,80 @@ void control_task(void *param) {
|
||||
// remaining_distance -= distance_traveled;
|
||||
|
||||
current_state = STATE_DRIVE_END_DELAY;
|
||||
log = true;
|
||||
set_timer(TRANSITION_DELAY_US);
|
||||
}
|
||||
|
||||
if (efuse_is_tripped(BRIDGE_DRIVE)) {
|
||||
if (efuse_get(BRIDGE_DRIVE)) {
|
||||
// Update remaining distance even on fault
|
||||
remaining_distance -= distance_traveled;
|
||||
if (remaining_distance < 0.0f) remaining_distance = 0.0f;
|
||||
|
||||
error = SC_ERR_EFUSE_TRIP_1;
|
||||
fsm_error = SC_ERR_EFUSE_TRIP_1;
|
||||
current_state = STATE_UNDO_JACK_START;
|
||||
set_timer(JACK_DOWN_TIME);
|
||||
log = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case STATE_DRIVE_END_DELAY:
|
||||
if (!get_is_safe()) {
|
||||
error = SC_ERR_SAFETY_TRIP;
|
||||
fsm_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
|
||||
log = true;
|
||||
} else if (timer_done()) {
|
||||
current_state = STATE_UNDO_JACK_START;
|
||||
log = true;
|
||||
}
|
||||
break;
|
||||
case STATE_JACK_DOWN:
|
||||
if (!get_is_safe()) {
|
||||
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;
|
||||
int64_t elapsed = current_time - timer_start;
|
||||
|
||||
// After inrush delay, check for current spike
|
||||
if (elapsed > delay) {
|
||||
float current = get_bridge_A(BRIDGE_JACK);
|
||||
float threshold = get_param_value_t(PARAM_JACK_I_DOWN).f32;
|
||||
|
||||
if (current > threshold) {
|
||||
ESP_LOGI(TAG, "DOWN->IDLE BY CURRENT");
|
||||
// Current spike detected - we've hit the ground
|
||||
current_state = STATE_IDLE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (efuse_get(BRIDGE_JACK)) {
|
||||
|
||||
// Timeout - finished jacking down
|
||||
if (timer_done()) {
|
||||
ESP_LOGI(TAG, "DOWN->IDLE BY TIME");
|
||||
current_state = STATE_IDLE;
|
||||
}
|
||||
ESP_LOGI(TAG, "DOWN->IDLE BY EFUSE");
|
||||
// Current spike detected
|
||||
current_state = STATE_IDLE;
|
||||
log = true;
|
||||
break;
|
||||
|
||||
// E-fuse trip - assume we hit something hard
|
||||
if (efuse_is_tripped(BRIDGE_JACK)) {
|
||||
current_state = STATE_IDLE;
|
||||
}
|
||||
}
|
||||
|
||||
if (get_bridge_overcurrent(BRIDGE_JACK, get_param_value_t(PARAM_JACK_I_DOWN).f32)) {
|
||||
|
||||
ESP_LOGI(TAG, "DOWN->IDLE BY OVERCURRENT");
|
||||
// Current spike detected
|
||||
current_state = STATE_IDLE;
|
||||
log = true;
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
if (get_bridge_spike(BRIDGE_JACK, get_param_value_t(PARAM_JACK_IS_DOWN).f32)) {
|
||||
|
||||
ESP_LOGI(TAG, "DOWN->IDLE BY SPIKE");
|
||||
// Current spike detected
|
||||
current_state = STATE_IDLE;
|
||||
log = true;
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
if (timer_done() ) {
|
||||
ESP_LOGI(TAG, "DOWN->IDLE BY TIME");
|
||||
current_state = STATE_IDLE;
|
||||
log = true;
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
|
||||
case STATE_UNDO_JACK_START:
|
||||
// wait for e-fuse to un-trip
|
||||
if (!efuse_is_tripped(BRIDGE_JACK)) {
|
||||
current_state = STATE_UNDO_JACK;
|
||||
set_timer(JACK_TIME);
|
||||
}
|
||||
break;
|
||||
case STATE_UNDO_JACK:
|
||||
if (timer_done()){ // || get_sensor(SENSOR_JACK)) {
|
||||
current_state = STATE_IDLE;
|
||||
}
|
||||
|
||||
// assume we are jacked up all the way (e.g. sensor broke) and should stop
|
||||
if (efuse_is_tripped(BRIDGE_JACK)) {
|
||||
current_state = STATE_IDLE;
|
||||
if (!efuse_get(BRIDGE_JACK)) {
|
||||
set_timer(JACK_DOWN_TIME);
|
||||
current_state = STATE_JACK_DOWN;
|
||||
log = true;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -528,7 +514,7 @@ void control_task(void *param) {
|
||||
case STATE_CALIBRATE_JACK_MOVE:
|
||||
if (timer_done()) {
|
||||
current_state = STATE_IDLE;
|
||||
fsm_cal_t = current_time - timer_start;
|
||||
fsm_cal_t = fsm_now - timer_start;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -539,7 +525,7 @@ void control_task(void *param) {
|
||||
case STATE_CALIBRATE_DRIVE_MOVE:
|
||||
if (!get_is_safe() || timer_done()) {
|
||||
current_state = STATE_IDLE;
|
||||
fsm_cal_t = current_time - timer_start;
|
||||
fsm_cal_t = fsm_now - timer_start;
|
||||
fsm_cal_e = get_sensor_counter(SENSOR_DRIVE);
|
||||
}
|
||||
break;
|
||||
@@ -547,110 +533,169 @@ void control_task(void *param) {
|
||||
default: break;
|
||||
}
|
||||
|
||||
|
||||
//int64_t elapsed_t = (current_time-timer_start);
|
||||
//int64_t total_t = (timer_end-timer_start);
|
||||
//int32_t ticks = get_sensor_counter(SENSOR_DRIVE);
|
||||
//ESP_LOGI("FSM", "[%d] %lld / %lld ms, %ld ticks", current_state, (long long) elapsed_t, (long long) total_t, (long) ticks);
|
||||
|
||||
// Output control
|
||||
/**** SET OUTPUTS ****/
|
||||
switch (current_state) {
|
||||
case STATE_IDLE:
|
||||
//ESP_LOGI("FSM", "IDLE @ %lld", current_time);
|
||||
for (uint8_t i = 0; i < N_RELAYS; ++i) {
|
||||
//ESP_LOGI("FSM", "t[%d] %lld", i, override_times[i]);
|
||||
bool active = override_times[i] > current_time;
|
||||
if (active) rtc_reset_shutdown_timer();
|
||||
|
||||
// Current limiting for manual jack down override (RELAY_B2)
|
||||
if (i == RELAY_B2 && active) {
|
||||
int64_t elapsed = current_time - (override_times[i] - get_param_value_t(PARAM_RF_PULSE_LENGTH).u32);
|
||||
int64_t delay = get_param_value_t(PARAM_EFUSE_INRUSH_US).u32;
|
||||
|
||||
// After inrush delay, check for current spike
|
||||
if (elapsed > delay) {
|
||||
float current = get_bridge_A(BRIDGE_JACK);
|
||||
float threshold = get_param_value_t(PARAM_JACK_I_DOWN).f32;
|
||||
|
||||
if (current > threshold) {
|
||||
// Current spike detected - stop jacking down
|
||||
override_times[i] = -1;
|
||||
active = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// prohibit movement past jack limit switch
|
||||
//if (i == BRIDGE_JACK*2+(bridge_polarities[BRIDGE_JACK]>0?0:1) && get_sensor(SENSOR_JACK))
|
||||
// setRelay(i, false);
|
||||
//else
|
||||
setRelay(i, active);
|
||||
//if (active) ESP_LOGI("FSM", "RUN CHANNEL %d (%lld %c %lld)", i, (long long) override_times[i], active ? '>':'<', (long long) current_time);
|
||||
|
||||
}
|
||||
// In idle we still accept override commands
|
||||
if (override_time > fsm_now) {
|
||||
switch(override_cmd) {
|
||||
case FSM_OVERRIDE_DRIVE_FWD:
|
||||
if (efuse_get(BRIDGE_DRIVE)){
|
||||
driveRelays((relay_port_t){.bridges = {
|
||||
.DRIVE=BRIDGE_OFF,
|
||||
.JACK=BRIDGE_OFF,
|
||||
.AUX=BRIDGE_OFF
|
||||
}});
|
||||
} else {
|
||||
driveRelays((relay_port_t){.bridges = {
|
||||
.DRIVE=BRIDGE_FWD,
|
||||
.JACK=BRIDGE_OFF,
|
||||
.AUX=BRIDGE_FWD
|
||||
}});
|
||||
}
|
||||
break;
|
||||
|
||||
case FSM_OVERRIDE_DRIVE_REV:
|
||||
if (efuse_get(BRIDGE_DRIVE)){
|
||||
driveRelays((relay_port_t){.bridges = {
|
||||
.DRIVE=BRIDGE_OFF,
|
||||
.JACK=BRIDGE_OFF,
|
||||
.AUX=BRIDGE_OFF
|
||||
}});
|
||||
} else {
|
||||
driveRelays((relay_port_t){.bridges = {
|
||||
.DRIVE=BRIDGE_REV,
|
||||
.JACK=BRIDGE_OFF,
|
||||
.AUX=BRIDGE_OFF
|
||||
}});
|
||||
}
|
||||
break;
|
||||
case FSM_OVERRIDE_JACK_UP:
|
||||
if (efuse_get(BRIDGE_JACK)){
|
||||
driveRelays((relay_port_t){.bridges = {
|
||||
.DRIVE=BRIDGE_OFF,
|
||||
.JACK=BRIDGE_OFF,
|
||||
.AUX=BRIDGE_OFF
|
||||
}});
|
||||
} else {
|
||||
driveRelays((relay_port_t){.bridges = {
|
||||
.DRIVE=BRIDGE_OFF,
|
||||
.JACK=BRIDGE_FWD,
|
||||
.AUX=BRIDGE_OFF
|
||||
}});
|
||||
}
|
||||
break;
|
||||
case FSM_OVERRIDE_JACK_DOWN:
|
||||
if (get_bridge_overcurrent(BRIDGE_JACK, get_param_value_t(PARAM_JACK_I_DOWN).f32) ||
|
||||
get_bridge_spike(BRIDGE_JACK, get_param_value_t(PARAM_JACK_IS_DOWN).f32))
|
||||
efuse_set(BRIDGE_JACK, EFUSE_OVERCURRENT);
|
||||
|
||||
if (efuse_get(BRIDGE_JACK)) {
|
||||
driveRelays((relay_port_t){.bridges = {
|
||||
.DRIVE=BRIDGE_OFF,
|
||||
.JACK=BRIDGE_OFF,
|
||||
.AUX=BRIDGE_OFF
|
||||
}});
|
||||
} else {
|
||||
driveRelays((relay_port_t){.bridges = {
|
||||
.DRIVE=BRIDGE_OFF,
|
||||
.JACK=BRIDGE_REV,
|
||||
.AUX=BRIDGE_OFF
|
||||
}});
|
||||
}
|
||||
break;
|
||||
case FSM_OVERRIDE_AUX:
|
||||
if (efuse_get(BRIDGE_AUX)){
|
||||
driveRelays((relay_port_t){.bridges = {
|
||||
.DRIVE=BRIDGE_OFF,
|
||||
.JACK=BRIDGE_OFF,
|
||||
.AUX=BRIDGE_OFF
|
||||
}});
|
||||
} else {
|
||||
driveRelays((relay_port_t){.bridges = {
|
||||
.DRIVE=BRIDGE_OFF,
|
||||
.JACK=BRIDGE_OFF,
|
||||
.AUX=BRIDGE_FWD
|
||||
}});
|
||||
}
|
||||
break;
|
||||
default: // should never hit here but just in case...
|
||||
driveRelays((relay_port_t){.bridges = {
|
||||
.DRIVE=BRIDGE_OFF,
|
||||
.JACK=BRIDGE_OFF,
|
||||
.AUX=BRIDGE_OFF
|
||||
}});
|
||||
break;
|
||||
}
|
||||
rtc_reset_shutdown_timer();
|
||||
log = true;
|
||||
} else {
|
||||
driveRelays((relay_port_t){.bridges = {
|
||||
.DRIVE=BRIDGE_OFF,
|
||||
.JACK=BRIDGE_OFF,
|
||||
.AUX=BRIDGE_OFF
|
||||
}});
|
||||
}
|
||||
break;
|
||||
case STATE_CALIBRATE_JACK_MOVE:
|
||||
case STATE_JACK_UP_START:
|
||||
case STATE_JACK_UP:
|
||||
// jack up and fluff
|
||||
setRelay(RELAY_A1, false);
|
||||
setRelay(RELAY_B1, false);
|
||||
|
||||
setRelay(RELAY_A2, true);
|
||||
setRelay(RELAY_B2, false);
|
||||
|
||||
setRelay(RELAY_A3, true);
|
||||
driveRelays((relay_port_t){.bridges = {
|
||||
.DRIVE=BRIDGE_OFF,
|
||||
.JACK=BRIDGE_FWD,
|
||||
.AUX=BRIDGE_FWD
|
||||
}});
|
||||
rtc_reset_shutdown_timer();
|
||||
log = true;
|
||||
break;
|
||||
case STATE_CALIBRATE_DRIVE_MOVE:
|
||||
case STATE_DRIVE:
|
||||
// drive and fluff
|
||||
setRelay(RELAY_A1, true);
|
||||
setRelay(RELAY_B1, false);
|
||||
|
||||
setRelay(RELAY_A2, false);
|
||||
setRelay(RELAY_B2, false);
|
||||
|
||||
setRelay(RELAY_A3, true);
|
||||
driveRelays((relay_port_t){.bridges = {
|
||||
.DRIVE=BRIDGE_FWD,
|
||||
.JACK=BRIDGE_OFF,
|
||||
.AUX=BRIDGE_FWD
|
||||
}});
|
||||
rtc_reset_shutdown_timer();
|
||||
log = true;
|
||||
break;
|
||||
case STATE_UNDO_JACK:
|
||||
case STATE_JACK_DOWN:
|
||||
// jack down and fluffer
|
||||
setRelay(RELAY_A1, false);
|
||||
setRelay(RELAY_B1, false);
|
||||
|
||||
setRelay(RELAY_A2, false);
|
||||
setRelay(RELAY_B2, true);
|
||||
|
||||
setRelay(RELAY_A3, true);
|
||||
driveRelays((relay_port_t){.bridges = {
|
||||
.DRIVE=BRIDGE_OFF,
|
||||
.JACK=BRIDGE_REV,
|
||||
.AUX=BRIDGE_OFF
|
||||
}});
|
||||
rtc_reset_shutdown_timer();
|
||||
log = true;
|
||||
break;
|
||||
case STATE_UNDO_JACK_START:
|
||||
case STATE_DRIVE_START_DELAY:
|
||||
case STATE_DRIVE_END_DELAY:
|
||||
// only fluffer
|
||||
setRelay(RELAY_A1, false);
|
||||
setRelay(RELAY_B1, false);
|
||||
|
||||
setRelay(RELAY_A2, false);
|
||||
setRelay(RELAY_B2, false);
|
||||
|
||||
setRelay(RELAY_A3, true);
|
||||
driveRelays((relay_port_t){.bridges = {
|
||||
.DRIVE=BRIDGE_OFF,
|
||||
.JACK=BRIDGE_OFF,
|
||||
.AUX=BRIDGE_FWD
|
||||
}});
|
||||
rtc_reset_shutdown_timer();
|
||||
log = true;
|
||||
break;
|
||||
case STATE_CALIBRATE_JACK_DELAY:
|
||||
default:
|
||||
// invalid state; turn all relays off
|
||||
setRelay(RELAY_A1, false);
|
||||
setRelay(RELAY_B1, false);
|
||||
setRelay(RELAY_A2, false);
|
||||
setRelay(RELAY_B2, false);
|
||||
setRelay(RELAY_A3, false);
|
||||
driveRelays((relay_port_t){.bridges = {
|
||||
.DRIVE=BRIDGE_OFF,
|
||||
.JACK=BRIDGE_OFF,
|
||||
.AUX=BRIDGE_OFF
|
||||
}});
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
/**** LOGGING ****/
|
||||
if (log) send_fsm_log();
|
||||
|
||||
driveRelays();
|
||||
|
||||
esp_task_wdt_reset();
|
||||
}
|
||||
@@ -665,7 +710,7 @@ esp_err_t fsm_init() {
|
||||
if (fsm_cmd_queue == NULL) {
|
||||
fsm_cmd_queue = xQueueCreate(8, sizeof(fsm_cmd_t));
|
||||
}
|
||||
xTaskCreate(control_task, "FSM", 4096, NULL, 5, NULL);
|
||||
xTaskCreate(control_task, TAG, 4096, NULL, 10, NULL);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
@@ -34,7 +34,6 @@ typedef enum {
|
||||
STATE_DRIVE,
|
||||
STATE_DRIVE_END_DELAY,
|
||||
STATE_JACK_DOWN,
|
||||
STATE_UNDO_JACK,
|
||||
STATE_UNDO_JACK_START,
|
||||
|
||||
STATE_CALIBRATE_JACK_DELAY,
|
||||
@@ -43,6 +42,7 @@ typedef enum {
|
||||
STATE_CALIBRATE_DRIVE_DELAY,
|
||||
STATE_CALIBRATE_DRIVE_MOVE
|
||||
} fsm_state_t;
|
||||
#define LOG_TYPE_BAT 100
|
||||
|
||||
typedef enum {
|
||||
RELAY_SENSORS = 0,
|
||||
@@ -59,18 +59,32 @@ typedef enum {
|
||||
BRIDGE_AUX = 2,
|
||||
BRIDGE_JACK = 1,
|
||||
BRIDGE_DRIVE = 0,
|
||||
NUM_BRIDGES = 3,
|
||||
} bridge_t;
|
||||
|
||||
typedef enum {
|
||||
BRIDGE_FWD = 0b10,
|
||||
BRIDGE_REV = 0b01,
|
||||
BRIDGE_OFF = 0b00,
|
||||
BRIDGE_ON = 0b11
|
||||
} bridge_dir_t;
|
||||
|
||||
typedef enum {
|
||||
FSM_OVERRIDE_DRIVE_FWD,
|
||||
FSM_OVERRIDE_DRIVE_REV,
|
||||
FSM_OVERRIDE_JACK_UP,
|
||||
FSM_OVERRIDE_JACK_DOWN,
|
||||
FSM_OVERRIDE_AUX
|
||||
} fsm_override_t;
|
||||
|
||||
#define N_RELAYS 8
|
||||
#define N_BRIDGES 3
|
||||
|
||||
void pulseOverride(relay_t relay/*, int64_t pulse*/);
|
||||
void pulseOverride(fsm_override_t cmd);
|
||||
|
||||
esp_err_t fsm_init();
|
||||
esp_err_t fsm_stop();
|
||||
|
||||
bool isRunning();
|
||||
|
||||
void fsm_set_cal_val(float v);
|
||||
int64_t fsm_get_cal_t();
|
||||
int64_t fsm_get_cal_e();
|
||||
|
||||
36
main/i2c.c
36
main/i2c.c
@@ -8,30 +8,6 @@
|
||||
#include "esp_rom_sys.h"
|
||||
#include "sensors.h"
|
||||
|
||||
#define I2C_PORT I2C_NUM_0
|
||||
#define TCA_ADDR_READ 0x21
|
||||
#define TCA_ADDR_WRITE 0x21
|
||||
#define I2C_PULLUP GPIO_PULLUP_DISABLE
|
||||
#define I2C_FREQUENCY 400000
|
||||
|
||||
// TCA9555 Registers
|
||||
#define TCA_REG_INPUT0 0x00
|
||||
#define TCA_REG_INPUT1 0x01
|
||||
#define TCA_REG_OUTPUT0 0x02
|
||||
#define TCA_REG_OUTPUT1 0x03
|
||||
#define TCA_REG_POLARITY0 0x04
|
||||
#define TCA_REG_POLARITY1 0x05
|
||||
#define TCA_REG_CONFIG0 0x06
|
||||
#define TCA_REG_CONFIG1 0x07
|
||||
|
||||
// Debounce & Repeat Settings
|
||||
#define DEBOUNCE_MS 50
|
||||
#define REPEAT_MS 200
|
||||
#define REPEAT_START_MS 700
|
||||
|
||||
#define SAFETY_MASK 0b00110001 // & permissible channels (jack and sensors)
|
||||
#define SENSOR_EN_MASK 0b00000001 // | need forced high
|
||||
|
||||
// Static Variables
|
||||
static bool i2c_initted = false;
|
||||
//static bool safety_ok = false; // Safety interlock
|
||||
@@ -76,16 +52,8 @@ esp_err_t i2c_init(void) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t i2c_set_relays(uint8_t states) {
|
||||
last_relay_request = states; // Always track the request
|
||||
|
||||
if (!get_is_safe()) {
|
||||
// Safety interlock active - refuse to energize relays
|
||||
if (states!=0) ESP_LOGW("I2C", "Main relay operation blocked by safety interlock");
|
||||
return tca_write_word_8(TCA_REG_OUTPUT1, (states | SENSOR_EN_MASK) & SAFETY_MASK);
|
||||
}
|
||||
|
||||
return tca_write_word_8(TCA_REG_OUTPUT1, states | SENSOR_EN_MASK);
|
||||
esp_err_t i2c_set_relays(relay_port_t states) {
|
||||
return tca_write_word_8(TCA_REG_OUTPUT1, states.raw);
|
||||
}
|
||||
|
||||
esp_err_t i2c_set_led1(uint8_t state) {
|
||||
|
||||
44
main/i2c.h
44
main/i2c.h
@@ -5,11 +5,53 @@
|
||||
#include <stdbool.h>
|
||||
#include "esp_err.h"
|
||||
|
||||
#define I2C_PORT I2C_NUM_0
|
||||
#define TCA_ADDR_READ 0x21
|
||||
#define TCA_ADDR_WRITE 0x21
|
||||
#define I2C_PULLUP GPIO_PULLUP_DISABLE
|
||||
#define I2C_FREQUENCY 400000
|
||||
|
||||
// TCA9555 Registers
|
||||
#define TCA_REG_INPUT0 0x00
|
||||
#define TCA_REG_INPUT1 0x01
|
||||
#define TCA_REG_OUTPUT0 0x02
|
||||
#define TCA_REG_OUTPUT1 0x03
|
||||
#define TCA_REG_POLARITY0 0x04
|
||||
#define TCA_REG_POLARITY1 0x05
|
||||
#define TCA_REG_CONFIG0 0x06
|
||||
#define TCA_REG_CONFIG1 0x07
|
||||
|
||||
// Debounce & Repeat Settings
|
||||
#define DEBOUNCE_MS 50
|
||||
#define REPEAT_MS 200
|
||||
#define REPEAT_START_MS 700
|
||||
|
||||
typedef union {
|
||||
uint8_t raw;
|
||||
struct {
|
||||
uint8_t SENSORS : 1; // [0]
|
||||
uint8_t C3 : 1; // [1]
|
||||
uint8_t B3 : 1; // [2]
|
||||
uint8_t A3 : 1; // [3]
|
||||
uint8_t B2 : 1; // [4]
|
||||
uint8_t A2 : 1; // [5]
|
||||
uint8_t B1 : 1; // [6]
|
||||
uint8_t A1 : 1; // [7] MSB
|
||||
} bits;
|
||||
struct {
|
||||
uint8_t SENSORS : 1;
|
||||
uint8_t C3 : 1;
|
||||
uint8_t AUX : 2;
|
||||
uint8_t JACK : 2;
|
||||
uint8_t DRIVE : 2;
|
||||
} bridges;
|
||||
} relay_port_t;
|
||||
|
||||
// Public Functions
|
||||
esp_err_t i2c_init(void);
|
||||
esp_err_t i2c_stop(void);
|
||||
|
||||
esp_err_t i2c_set_relays(uint8_t states);
|
||||
esp_err_t i2c_set_relays(relay_port_t states);
|
||||
esp_err_t i2c_set_led1(uint8_t state);
|
||||
|
||||
esp_err_t i2c_poll_buttons();
|
||||
|
||||
1180
main/log_test.c
Normal file
1180
main/log_test.c
Normal file
File diff suppressed because it is too large
Load Diff
47
main/log_test.h
Normal file
47
main/log_test.h
Normal file
@@ -0,0 +1,47 @@
|
||||
#ifndef LOG_TEST_H
|
||||
#define LOG_TEST_H
|
||||
|
||||
#include "freertos/FreeRTOS.h" // Must be FIRST
|
||||
#include "freertos/projdefs.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
|
||||
#include "esp_err.h"
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
// Test result structure
|
||||
typedef struct {
|
||||
const char* test_name;
|
||||
bool passed;
|
||||
const char* error_msg;
|
||||
} test_result_t;
|
||||
|
||||
// Main test suite runner
|
||||
esp_err_t run_all_log_tests(void);
|
||||
|
||||
// Individual test functions
|
||||
bool test_log_integrity_basic(void);
|
||||
bool test_log_integrity_wraparound(void);
|
||||
bool test_log_head_tail_recovery_empty(void);
|
||||
bool test_log_head_tail_recovery_partial_sector(void);
|
||||
bool test_log_head_tail_recovery_full_sectors(void);
|
||||
bool test_log_head_tail_recovery_wraparound(void);
|
||||
bool test_log_head_equals_tail(void);
|
||||
bool test_log_head_zero_tail_zero(void);
|
||||
bool test_log_head_greater_than_tail(void);
|
||||
bool test_log_head_less_than_tail(void);
|
||||
bool test_log_wraparound_multiple_times(void);
|
||||
bool test_log_sector_boundary_conditions(void);
|
||||
bool test_log_maximum_payload(void);
|
||||
bool test_log_minimum_payload(void);
|
||||
bool test_log_corrupted_entry_recovery(void);
|
||||
bool test_log_full_partition(void);
|
||||
bool test_log_read_after_write(void);
|
||||
bool test_log_multiple_types(void);
|
||||
|
||||
// Helper functions for testing
|
||||
void print_test_results(test_result_t* results, int num_tests);
|
||||
int count_passed_tests(test_result_t* results, int num_tests);
|
||||
|
||||
#endif // LOG_TEST_H
|
||||
86
main/main.c
86
main/main.c
@@ -1,5 +1,6 @@
|
||||
#include "esp_task_wdt.h"
|
||||
#include "i2c.h"
|
||||
#include "log_test.h"
|
||||
#include "storage.h"
|
||||
#include "uart_comms.h"
|
||||
#include "esp_err.h"
|
||||
@@ -17,49 +18,25 @@
|
||||
|
||||
#define TAG "MAIN"
|
||||
|
||||
int64_t last_log_time = 0;
|
||||
#define LOGSIZE 40
|
||||
esp_err_t send_log() {
|
||||
uint8_t entry[LOGSIZE] = {};
|
||||
int64_t last_bat_log_time = 0;
|
||||
esp_err_t send_bat_log() {
|
||||
if(!rtc_is_set()) return ESP_OK;
|
||||
|
||||
uint8_t entry[12] = {};
|
||||
|
||||
|
||||
entry[0] = fsm_get_state();
|
||||
|
||||
// Pack 64-bit timestamp into bytes 1-8
|
||||
uint64_t be_timestamp = rtc_get_ms();
|
||||
memcpy(&entry[1], &be_timestamp, 8);
|
||||
memcpy(&entry[0], &be_timestamp, 8);
|
||||
|
||||
// Pack 32-bit voltages/currents into bytes 9-24
|
||||
float be_voltage = get_battery_V();
|
||||
memcpy(&entry[9], &be_voltage, 4);
|
||||
float be_current1 = get_bridge_A(BRIDGE_DRIVE);
|
||||
memcpy(&entry[13], &be_current1, 4);
|
||||
float be_current2 = get_bridge_A(BRIDGE_JACK);
|
||||
memcpy(&entry[17], &be_current2, 4);
|
||||
float be_current3 = get_bridge_A(BRIDGE_AUX);
|
||||
memcpy(&entry[21], &be_current3, 4);
|
||||
memcpy(&entry[8], &be_voltage, 4);
|
||||
|
||||
int16_t be_counter = get_sensor_counter(SENSOR_DRIVE);
|
||||
memcpy(&entry[25], &be_counter, 2);
|
||||
last_bat_log_time = esp_timer_get_time();
|
||||
|
||||
entry[27] = pack_sensors();
|
||||
|
||||
|
||||
|
||||
|
||||
float heat1 = get_bridge_heat(BRIDGE_DRIVE);
|
||||
memcpy(&entry[28], &heat1, 4);
|
||||
float heat2 = get_bridge_heat(BRIDGE_JACK);
|
||||
memcpy(&entry[32], &heat2, 4);
|
||||
float heat3 = get_bridge_heat(BRIDGE_AUX);
|
||||
memcpy(&entry[36], &heat3, 4);
|
||||
|
||||
last_log_time = esp_timer_get_time();
|
||||
|
||||
|
||||
log_write(entry, LOGSIZE);
|
||||
|
||||
ESP_LOGI(TAG, "WROTE LOG; %lld / %ld/%ld; %5.2f %5.2f %5.2f", (long long)rtc_get_ms(), (unsigned long)log_get_tail(), (unsigned long)log_get_head(), heat1, heat2, heat3);
|
||||
log_write(entry, 12, LOG_TYPE_BAT);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -126,15 +103,16 @@ void driveLEDs(led_state_t state) {
|
||||
|
||||
RTC_DATA_ATTR bool first_boot = true;
|
||||
|
||||
void app_main(void) {
|
||||
esp_task_wdt_add(NULL);
|
||||
void app_main(void) {esp_task_wdt_add(NULL);
|
||||
|
||||
//run_all_log_tests();
|
||||
|
||||
if (rtc_xtal_init() != ESP_OK) ESP_LOGE(TAG, "RTC FAILED");
|
||||
|
||||
// Say hello; turn on the lights
|
||||
esp_sleep_wakeup_cause_t cause = rtc_wakeup_cause();
|
||||
if (i2c_init() != ESP_OK) ESP_LOGE(TAG, "I2C FAILED");
|
||||
i2c_set_relays(0);
|
||||
i2c_set_relays((relay_port_t){.raw=0});
|
||||
driveLEDs(LED_STATE_BOOTING);
|
||||
|
||||
ESP_LOGI(TAG, "Firmware: %s", FIRMWARE_STRING);
|
||||
@@ -188,8 +166,6 @@ void app_main(void) {
|
||||
esp_restart();
|
||||
}
|
||||
|
||||
first_boot = false;
|
||||
|
||||
// Every boot we load parameters and monitor solar, no matter what
|
||||
if (adc_init() != ESP_OK) ESP_LOGE(TAG, "ADC FAILED");
|
||||
if (storage_init() != ESP_OK) ESP_LOGE(TAG, "STORAGE FAILED");
|
||||
@@ -197,8 +173,10 @@ void app_main(void) {
|
||||
if (solar_run_fsm() != ESP_OK) ESP_LOGE(TAG, "SOLAR FAILED");
|
||||
// TODO: Do a 12V check and enter deep sleep if there's a problem
|
||||
|
||||
send_bat_log();
|
||||
|
||||
send_log();
|
||||
|
||||
//send_log();
|
||||
|
||||
//write_dummy_log_1();
|
||||
|
||||
@@ -214,17 +192,19 @@ void app_main(void) {
|
||||
} else */if (cause == ESP_SLEEP_WAKEUP_EXT0) {
|
||||
ESP_LOGI("MAIN", "Woke from button press");
|
||||
} else {
|
||||
if (!rtc_alarm_tripped()) {
|
||||
//enter_deep_sleep();
|
||||
if (!rtc_alarm_tripped() && !first_boot) {
|
||||
rtc_enter_deep_sleep();
|
||||
}
|
||||
}
|
||||
|
||||
first_boot = false;
|
||||
|
||||
/*** FULL BOOT ***/
|
||||
//if (uart_init() != ESP_OK) ESP_LOGE(TAG, "UART FAILED");
|
||||
if (power_init() != ESP_OK) ESP_LOGE(TAG, "POWER FAILED");
|
||||
if (uart_init() != ESP_OK) ESP_LOGE(TAG, "UART FAILED");
|
||||
//if (power_init() != ESP_OK) ESP_LOGE(TAG, "POWER FAILED");
|
||||
if (rf_433_init() != ESP_OK) ESP_LOGE(TAG, "RF FAILED");
|
||||
if (fsm_init() != ESP_OK) ESP_LOGE(TAG, "FSM FAILED");
|
||||
if (sensors_init() != ESP_OK) ESP_LOGE(TAG, "SENSORS FAILED");
|
||||
//if (sensors_init() != ESP_OK) ESP_LOGE(TAG, "SENSORS FAILED");
|
||||
if (webserver_init() != ESP_OK) ESP_LOGE(TAG, "WEBSERVER FAILED");
|
||||
|
||||
/*** MAIN LOOP ***/
|
||||
@@ -276,16 +256,16 @@ void app_main(void) {
|
||||
}
|
||||
|
||||
// when not actively moving we log at a low frequency
|
||||
if (isRunning() || (esp_timer_get_time() > last_log_time + 3000000)) //DEEP_SLEEP_US))
|
||||
send_log();
|
||||
if ((esp_timer_get_time() > last_bat_log_time + DEEP_SLEEP_US))
|
||||
send_bat_log();
|
||||
|
||||
if(i2c_get_button_ms(0) > 2100)
|
||||
fsm_request(FSM_CMD_START);
|
||||
break;
|
||||
case STATE_UNDO_JACK:
|
||||
//case STATE_UNDO_JACK:
|
||||
case STATE_UNDO_JACK_START:
|
||||
// it's running the jack, but undoing
|
||||
send_log();
|
||||
//send_log();
|
||||
driveLEDs(LED_STATE_CANCELLING);
|
||||
if (i2c_get_button_tripped(0)) {
|
||||
ESP_LOGI(TAG, "AAAAH STOP!!!");
|
||||
@@ -294,31 +274,31 @@ void app_main(void) {
|
||||
break;
|
||||
|
||||
case STATE_CALIBRATE_JACK_DELAY:
|
||||
send_log();
|
||||
//send_log();
|
||||
if (i2c_get_button_tripped(0))
|
||||
fsm_request(FSM_CMD_CALIBRATE_JACK_START);
|
||||
break;
|
||||
case STATE_CALIBRATE_JACK_MOVE:
|
||||
send_log();
|
||||
//send_log();
|
||||
if (i2c_get_button_tripped(0))
|
||||
fsm_request(FSM_CMD_CALIBRATE_JACK_END);
|
||||
break;
|
||||
|
||||
|
||||
case STATE_CALIBRATE_DRIVE_DELAY:
|
||||
send_log();
|
||||
//send_log();
|
||||
if (i2c_get_button_tripped(0))
|
||||
fsm_request(FSM_CMD_CALIBRATE_DRIVE_START);
|
||||
break;
|
||||
case STATE_CALIBRATE_DRIVE_MOVE:
|
||||
send_log();
|
||||
//send_log();
|
||||
if (i2c_get_button_tripped(0))
|
||||
fsm_request(FSM_CMD_CALIBRATE_DRIVE_END);
|
||||
break;
|
||||
|
||||
default:
|
||||
// it's running in every other case
|
||||
send_log();
|
||||
//send_log();
|
||||
driveLEDs(LED_STATE_DRIVING);
|
||||
if (i2c_get_button_tripped(0)) {
|
||||
fsm_request(FSM_CMD_UNDO);
|
||||
|
||||
@@ -28,6 +28,8 @@
|
||||
#include "esp_timer.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "control_fsm.h"
|
||||
#include "i2c.h"
|
||||
#include "sensors.h"
|
||||
#include "soc/rtc_io_reg.h"
|
||||
#include "power_mgmt.h"
|
||||
|
||||
@@ -43,32 +45,104 @@
|
||||
#define PIN_V_BATTERY ADC_CHANNEL_3 // GPIO39 / VN
|
||||
#define PIN_V_SENS_BAT PIN_V_BATTERY
|
||||
|
||||
// map from relay number to bridge
|
||||
/*bridge_t bridge_map[] = {
|
||||
-1,
|
||||
BRIDGE_AUX,
|
||||
BRIDGE_AUX,
|
||||
BRIDGE_AUX,
|
||||
BRIDGE_JACK,
|
||||
BRIDGE_JACK,
|
||||
BRIDGE_DRIVE,
|
||||
BRIDGE_DRIVE };*/
|
||||
|
||||
// update time
|
||||
#define UPDATE_MS 20
|
||||
#define UPDATE_S 0.02f
|
||||
|
||||
int64_t now; // us
|
||||
extern int64_t fsm_now; // us
|
||||
|
||||
// E-fuse data
|
||||
typedef struct {
|
||||
int64_t az_enable_time; // Timestamp to enable autozeroing at (negative to disable)
|
||||
float az_offset; // Accumulated zero offset
|
||||
bool az_initialized; // First valid zero established
|
||||
|
||||
float raw_current;
|
||||
|
||||
bool ema_init;
|
||||
float ema_current;
|
||||
|
||||
float current; // with all the corrections applied
|
||||
float current_spike;
|
||||
|
||||
float heat;
|
||||
bool tripped;
|
||||
efuse_trip_t tripped;
|
||||
int64_t trip_time;
|
||||
|
||||
// Inrush tolerance tracking
|
||||
int64_t inrush_start_time; // When instantaneous overcurrent first detected (0 = not in overcurrent)
|
||||
int64_t on_us;
|
||||
int64_t off_us;
|
||||
} isens_channel_t;
|
||||
static isens_channel_t isens[N_BRIDGES] = {0};
|
||||
|
||||
|
||||
/**** DRIVE RELAYS ****/
|
||||
bool relay_states[8] = {false};
|
||||
|
||||
//int64_t bridge_transitions_on[NUM_BRIDGES] = {-1}; // last time relay turned on (used to ignore inrush)
|
||||
//int64_t bridge_transitions_off[NUM_BRIDGES] = {-1}; // last time relay turned off (used to enable autozero)
|
||||
|
||||
relay_port_t last_relay_state;
|
||||
|
||||
// actually write relay states, taking note of transitions, and debouncing transitions to on.
|
||||
#define BRIDGE_TRANSITION_LOGIC(BRIDGE_NAME) \
|
||||
if (relay_state.bridges.BRIDGE_NAME == last_relay_state.bridges.BRIDGE_NAME) { \
|
||||
/* no change; no need to do anything */ \
|
||||
if(false) if (BRIDGE_##BRIDGE_NAME == BRIDGE_JACK) ESP_LOGI(TAG, "NO CHANGE"); \
|
||||
} \
|
||||
else if (last_relay_state.bridges.BRIDGE_NAME != BRIDGE_OFF && relay_state.bridges.BRIDGE_NAME == BRIDGE_OFF) { \
|
||||
isens[BRIDGE_##BRIDGE_NAME].off_us = fsm_now; \
|
||||
if(false) if (BRIDGE_##BRIDGE_NAME == BRIDGE_JACK) ESP_LOGI(TAG, "ON -> OFF"); \
|
||||
} \
|
||||
else if (last_relay_state.bridges.BRIDGE_NAME == BRIDGE_OFF && relay_state.bridges.BRIDGE_NAME != BRIDGE_OFF) { \
|
||||
if (fsm_now > isens[BRIDGE_##BRIDGE_NAME].off_us + 2*get_param_value_t(PARAM_EFUSE_INRUSH_US).u32) { \
|
||||
isens[BRIDGE_##BRIDGE_NAME].on_us = fsm_now; \
|
||||
if(false) if (BRIDGE_##BRIDGE_NAME == BRIDGE_JACK) ESP_LOGI(TAG, "OFF -> ON"); \
|
||||
} else { \
|
||||
relay_state.bridges.BRIDGE_NAME = BRIDGE_OFF; \
|
||||
if(false) if (BRIDGE_##BRIDGE_NAME == BRIDGE_JACK) ESP_LOGI(TAG, "NOT YET; -> OFF"); \
|
||||
} \
|
||||
} \
|
||||
else { \
|
||||
if(false) if (BRIDGE_##BRIDGE_NAME == BRIDGE_JACK) ESP_LOGE(TAG, "TOO FAST OF TRANSITION"); \
|
||||
isens[BRIDGE_##BRIDGE_NAME].off_us = fsm_now; \
|
||||
relay_state.bridges.BRIDGE_NAME = BRIDGE_OFF; \
|
||||
}
|
||||
|
||||
esp_err_t driveRelays(relay_port_t relay_state) {
|
||||
// Four types of transitions.
|
||||
// Not a transition: this does nothing
|
||||
// Anything -> off: always allowed. Record the transition time
|
||||
// off -> anything: has debouncing; set & record transition if fsm_now > bridge_transitions_off + debounce, otherwise keep bridge off.
|
||||
// fwd/rev/on -> fwd/rev/on: not allowed. Actually go to 0. Record the transition time.
|
||||
|
||||
BRIDGE_TRANSITION_LOGIC(DRIVE)
|
||||
BRIDGE_TRANSITION_LOGIC(JACK)
|
||||
BRIDGE_TRANSITION_LOGIC(AUX)
|
||||
|
||||
relay_state.bridges.SENSORS = 1;
|
||||
|
||||
if (!get_is_safe())
|
||||
relay_state.bridges.DRIVE = 0;
|
||||
|
||||
last_relay_state = relay_state;
|
||||
|
||||
//ESP_LOGI(TAG, "RELAY STATE: %x", state);
|
||||
return i2c_set_relays(relay_state);
|
||||
}
|
||||
|
||||
/**** CURRENT / VOLTAGE MONITORING ****/
|
||||
|
||||
// === ADC Handles ===
|
||||
static adc_oneshot_unit_handle_t adc1_handle = NULL;
|
||||
static adc_cali_handle_t adc_cali_handle = NULL;
|
||||
@@ -141,13 +215,28 @@ esp_err_t process_battery_voltage(void)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void set_autozero(bridge_t bridge) {
|
||||
void disable_autozero(bridge_t bridge) {
|
||||
// enable autozeroing for this bridge 1 second from now
|
||||
isens[bridge].az_enable_time = now+1000000;
|
||||
isens[bridge].az_enable_time = fsm_now+1000000;
|
||||
//ESP_LOGI(TAG, "KILLING BRIDGE %d; %lld -> %lld", bridge, (long long int) now, (long long int) isens[bridge].az_enable_time);
|
||||
}
|
||||
|
||||
bool get_bridge_overcurrent(bridge_t bridge, float threshold) {
|
||||
if (bridge < 0 || bridge>=NUM_BRIDGES) return true; // I GUESS?
|
||||
if (fsm_now < isens[bridge].on_us + get_param_value_t(PARAM_EFUSE_INRUSH_US).u32) return false;
|
||||
if (isens[bridge].raw_current < threshold) return false;
|
||||
return true;
|
||||
}
|
||||
bool get_bridge_spike(bridge_t bridge, float threshold) {
|
||||
if (bridge < 0 || bridge>=NUM_BRIDGES) return true; // I GUESS?
|
||||
if (fsm_now < isens[bridge].on_us + get_param_value_t(PARAM_EFUSE_INRUSH_US).u32) return false;
|
||||
if (isens[bridge].current_spike < threshold) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
esp_err_t process_bridge_current(bridge_t bridge) {
|
||||
if (bridge < 0 || bridge >= NUM_BRIDGES) return ESP_ERR_INVALID_ARG;
|
||||
|
||||
int adc_raw = 0;
|
||||
int voltage_mv = 0;
|
||||
|
||||
@@ -158,7 +247,7 @@ esp_err_t process_bridge_current(bridge_t bridge) {
|
||||
case BRIDGE_DRIVE: pin = PIN_V_ISENS1; break;
|
||||
case BRIDGE_JACK: pin = PIN_V_ISENS2; break;
|
||||
case BRIDGE_AUX: pin = PIN_V_ISENS3; break;
|
||||
default: return -42069; // lol
|
||||
default: return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (adc_oneshot_read(adc1_handle, pin, &adc_raw) != ESP_OK) {
|
||||
@@ -168,39 +257,41 @@ esp_err_t process_bridge_current(bridge_t bridge) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
float raw_a = NAN;
|
||||
float last_current = channel->raw_current;
|
||||
channel->raw_current = NAN;
|
||||
|
||||
switch (bridge) {
|
||||
case BRIDGE_JACK:
|
||||
case BRIDGE_AUX:
|
||||
// ACS37042KLHBLT-030B3 is 30A capable and 44 mV/A
|
||||
raw_a = (voltage_mv - 1650.0f) / 44.0f;
|
||||
channel->raw_current = (voltage_mv - 1650.0f) / 44.0f;
|
||||
break;
|
||||
case BRIDGE_DRIVE:
|
||||
// ACS37220LEZATR-100B3 is 100A capable and 13.2 mV/A
|
||||
raw_a = -(voltage_mv - 1650.0f) / 13.2f;
|
||||
channel->raw_current = -(voltage_mv - 1650.0f) / 13.2f;
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
if (!channel->ema_init) {
|
||||
channel->ema_current = (float)raw_a;
|
||||
channel->ema_current = channel->raw_current;
|
||||
channel->ema_init = true;
|
||||
} else {
|
||||
float alpha = get_param_value_t(PARAM_ADC_ALPHA_ISENS).f32;
|
||||
if (isnan(raw_a)) {
|
||||
if (isnan(channel->raw_current)) {
|
||||
//ESP_LOGI(TAG, "RAW BATTERY IS NAN");
|
||||
channel->ema_current = NAN;
|
||||
} else {
|
||||
if (isnan(ema_battery) || isnan(alpha)) {
|
||||
channel->ema_current = raw_a;
|
||||
channel->ema_current = channel->raw_current;
|
||||
} else {
|
||||
channel->ema_current = alpha * raw_a + (1.0f - alpha) * channel->ema_current;
|
||||
channel->ema_current = alpha * channel->raw_current + (1.0f - alpha) * channel->ema_current;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// === AUTO-ZERO LEARNING PHASE ===
|
||||
if (now > channel->az_enable_time) {
|
||||
if (fsm_now > channel->az_enable_time) {
|
||||
//ESP_LOGI(TAG, "AZING %d", bridge);
|
||||
float db = get_param_value_t(PARAM_ADC_DB_IAZ).f32;
|
||||
if (isnan(db) || fabsf(channel->ema_current) <= db) {
|
||||
@@ -210,7 +301,7 @@ esp_err_t process_bridge_current(bridge_t bridge) {
|
||||
channel->az_initialized = true;
|
||||
} else {
|
||||
float alpha = get_param_value_t(PARAM_ADC_ALPHA_IAZ).f32;
|
||||
if (isnan(raw_a)) {
|
||||
if (isnan(channel->raw_current)) {
|
||||
//ESP_LOGI(TAG, "RAW BATTERY IS NAN");
|
||||
} else {
|
||||
if (isnan(ema_battery) || isnan(alpha)) {
|
||||
@@ -225,7 +316,9 @@ esp_err_t process_bridge_current(bridge_t bridge) {
|
||||
}
|
||||
|
||||
// Apply the offset
|
||||
channel->current = channel->ema_current - channel->az_offset;
|
||||
channel->current = channel->raw_current - channel->az_offset;
|
||||
channel->raw_current = channel->raw_current - channel->az_offset;
|
||||
channel->current_spike = channel->raw_current - last_current;
|
||||
|
||||
|
||||
// PARAMETERS FOR E-FUSING ALGORITHM
|
||||
@@ -245,31 +338,21 @@ esp_err_t process_bridge_current(bridge_t bridge) {
|
||||
case BRIDGE_AUX:
|
||||
I_nominal = get_param_value_t(PARAM_EFUSE_INOM_3).f32;
|
||||
break;
|
||||
}
|
||||
|
||||
// Normalize the current as a fraction of rated current
|
||||
default: break;
|
||||
}
|
||||
|
||||
// Normalize the current as a fraction of rated current
|
||||
float I_norm = fabsf(channel->current / I_nominal);
|
||||
|
||||
// Instant trip on extreme overcurrent - but with inrush tolerance
|
||||
if (I_norm >= get_param_value_t(PARAM_EFUSE_KINST).f32) {
|
||||
// Start tracking if this is the first time we've seen overcurrent
|
||||
if (channel->inrush_start_time == 0) {
|
||||
channel->inrush_start_time = now;
|
||||
}
|
||||
|
||||
// Instant trip on extreme overcurrent
|
||||
if (fsm_now > channel->on_us + get_param_value_t(PARAM_EFUSE_INRUSH_US).u32
|
||||
&& I_norm >= get_param_value_t(PARAM_EFUSE_KINST).f32) {
|
||||
// Check if overcurrent has persisted long enough
|
||||
int64_t inrush_duration = now - channel->inrush_start_time;
|
||||
if (inrush_duration >= get_param_value_t(PARAM_EFUSE_INRUSH_US).u32) {
|
||||
channel->tripped = true;
|
||||
channel->trip_time = now;
|
||||
channel->inrush_start_time = 0; // Reset for next time
|
||||
//ESP_LOGI(TAG, "FUSE TRIP: Inom: %+.5f HEAT:%+2.5f", I_norm, channel->heat);
|
||||
return ESP_OK; // no more processing, if we're over, we're over
|
||||
}
|
||||
channel->tripped = true;
|
||||
channel->trip_time = fsm_now;
|
||||
//ESP_LOGI(TAG, "FUSE TRIP: Inom: %+.5f HEAT:%+2.5f", I_norm, channel->heat);
|
||||
return ESP_OK; // no more processing, if we're over, we're over
|
||||
// Still in overcurrent but within inrush tolerance window - don't trip yet
|
||||
} else {
|
||||
// Current dropped below threshold - reset inrush timer
|
||||
channel->inrush_start_time = 0;
|
||||
}
|
||||
|
||||
// Accumulate heat
|
||||
@@ -289,19 +372,20 @@ esp_err_t process_bridge_current(bridge_t bridge) {
|
||||
// Ergo, heat is measured in seconds
|
||||
if (channel->heat > get_param_value_t(PARAM_EFUSE_HEAT_THRESH).f32) {
|
||||
channel->tripped = true;
|
||||
channel->trip_time = now;
|
||||
channel->trip_time = fsm_now;
|
||||
|
||||
// If we're not overheated
|
||||
// And enough time has passed
|
||||
// Go ahead and reset the e-fuse
|
||||
} else if (channel->tripped &&
|
||||
(now - channel->trip_time) > get_param_value_t(PARAM_EFUSE_TCOOL).u32) {
|
||||
(fsm_now - channel->trip_time) > get_param_value_t(PARAM_EFUSE_TCOOL).u32) {
|
||||
channel->tripped = false;
|
||||
// channel.heat = 0.0f // I think we should wait for the e-fuse to catch up
|
||||
}
|
||||
|
||||
//if (bridge == BRIDGE_JACK)
|
||||
//ESP_LOGI(TAG, "FUSE: raw_a: %+.4f cur: %+.4f Inorm: %+.5f HEAT:%+2.5f", raw_a, channel->current, I_norm, channel->heat);
|
||||
//if (bridge == BRIDGE_JACK) ESP_LOGI(TAG, "TIME: %lld", (long long) fsm_now);
|
||||
|
||||
//if (bridge == BRIDGE_JACK) ESP_LOGI(TAG, "FUSE: trip [%d] %lld, raw_a: %+.4f cur: %+.4f Inorm: %+.5f HEAT:%+2.5f", channel->tripped, channel->trip_time, channel->raw_current, channel->current, I_norm, channel->heat);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -313,9 +397,13 @@ float get_bridge_A(bridge_t bridge)
|
||||
if (bridge >= N_BRIDGES) return NAN;
|
||||
return isens[bridge].current;
|
||||
}
|
||||
float get_bridge_raw_A(bridge_t bridge)
|
||||
{
|
||||
if (bridge >= N_BRIDGES) return NAN;
|
||||
return isens[bridge].raw_current;
|
||||
}
|
||||
|
||||
|
||||
float get_bridge_heat(bridge_t bridge) {
|
||||
float efuse_get_heat(bridge_t bridge) {
|
||||
if (bridge >= N_BRIDGES) return NAN;
|
||||
return isens[bridge].heat;
|
||||
}
|
||||
@@ -327,57 +415,14 @@ float get_battery_V(void)
|
||||
return get_raw_battery_voltage();
|
||||
}
|
||||
|
||||
// === Public E-Fuse Controls ===
|
||||
/*void efuse_reset_all(void)
|
||||
{
|
||||
for (uint8_t i = 0; i < N_BRIDGES; i++) {
|
||||
isens[i].heat = 0.0f;
|
||||
isens[i].tripped = false;
|
||||
}
|
||||
}*/
|
||||
|
||||
bool efuse_is_tripped(bridge_t bridge)
|
||||
efuse_trip_t efuse_get(bridge_t bridge)
|
||||
{
|
||||
if (bridge >= N_BRIDGES) return false;
|
||||
return isens[bridge].tripped;
|
||||
}
|
||||
|
||||
// === Power Management Task ===
|
||||
void power_mgmt_task(void *param) {
|
||||
esp_task_wdt_add(NULL);
|
||||
|
||||
|
||||
|
||||
TickType_t xLastWakeTime = xTaskGetTickCount();
|
||||
const TickType_t xFrequency = pdMS_TO_TICKS(UPDATE_MS);
|
||||
|
||||
while (1) {
|
||||
vTaskDelayUntil(&xLastWakeTime, xFrequency);
|
||||
now = esp_timer_get_time(); // us
|
||||
|
||||
/*if (now - last_wake_time < period) {
|
||||
uint32_t delay_us = (period - (now - last_wake_time)) / 1000;
|
||||
if (delay_us > 0) vTaskDelay(pdMS_TO_TICKS(delay_us));
|
||||
continue;
|
||||
}
|
||||
last_wake_time = now;*/
|
||||
|
||||
// Sample currents
|
||||
for (uint8_t i = 0; i < N_BRIDGES; i++) {
|
||||
process_bridge_current(i);
|
||||
}
|
||||
|
||||
process_battery_voltage();
|
||||
esp_task_wdt_reset();
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t power_init() {
|
||||
xTaskCreate(power_mgmt_task, "PWR", 4096, NULL, 5, NULL);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t power_stop() {
|
||||
return ESP_OK;
|
||||
void efuse_set(bridge_t bridge, efuse_trip_t state)
|
||||
{
|
||||
if (bridge >= N_BRIDGES) return;
|
||||
isens[bridge].tripped = state;
|
||||
isens[bridge].trip_time = fsm_now;
|
||||
}
|
||||
@@ -12,19 +12,36 @@
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include "esp_err.h"
|
||||
#include "i2c.h"
|
||||
|
||||
typedef enum {
|
||||
EFUSE_OK = 0,
|
||||
EFUSE_OVERCURRENT = 1,
|
||||
EFUSE_OVERHEAT = 2,
|
||||
} efuse_trip_t;
|
||||
|
||||
//void efuse_reset_all(void); // Clear all trip states (manual/programmatic reset)
|
||||
bool efuse_is_tripped(bridge_t bridge); // Query if bridge is currently faulted
|
||||
void efuse_clear (bridge_t bridge);
|
||||
efuse_trip_t efuse_get (bridge_t bridge); // Query if bridge is currently faulted
|
||||
float efuse_get_heat(bridge_t bridge);
|
||||
void efuse_set(bridge_t bridge, efuse_trip_t state);
|
||||
|
||||
float get_bridge_A(bridge_t bridge);
|
||||
float get_bridge_raw_A(bridge_t bridge);
|
||||
float get_battery_V();
|
||||
float get_bridge_heat(bridge_t bridge);
|
||||
|
||||
void set_autozero(bridge_t bridge);
|
||||
void disable_autozero(bridge_t bridge);
|
||||
bool get_bridge_overcurrent(bridge_t bridge, float threshold);
|
||||
bool get_bridge_spike(bridge_t bridge, float threshold);
|
||||
|
||||
esp_err_t process_bridge_current(bridge_t bridge);
|
||||
esp_err_t process_battery_voltage();
|
||||
|
||||
esp_err_t adc_init();
|
||||
esp_err_t power_init();
|
||||
esp_err_t power_stop();
|
||||
|
||||
|
||||
esp_err_t driveRelays(relay_port_t relay_state);
|
||||
|
||||
#endif /* MAIN_POWER_MGMT_H_ */
|
||||
@@ -155,10 +155,10 @@ static void rf_433_receiver_task(void* param) {
|
||||
// Compare just the code (lower 32 bits)
|
||||
if ((uint32_t)match == code && code!=0) {
|
||||
switch (i) {
|
||||
case 0: pulseOverride(RELAY_A1); pulseOverride(RELAY_A3); break;
|
||||
case 1: pulseOverride(RELAY_B1); pulseOverride(RELAY_A3); break;
|
||||
case 2: pulseOverride(RELAY_A2); break;
|
||||
case 3: pulseOverride(RELAY_B2); break;
|
||||
case 0: pulseOverride(FSM_OVERRIDE_DRIVE_FWD); break;
|
||||
case 1: pulseOverride(FSM_OVERRIDE_DRIVE_REV); break;
|
||||
case 2: pulseOverride(FSM_OVERRIDE_JACK_UP); break;
|
||||
case 3: pulseOverride(FSM_OVERRIDE_JACK_DOWN); break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
@@ -210,7 +210,7 @@ static void rf_433_receiver_task(void* param) {
|
||||
}
|
||||
|
||||
esp_err_t rf_433_init() {
|
||||
xTaskCreate(rf_433_receiver_task, TAG, 4096, NULL, 10, NULL);
|
||||
xTaskCreate(rf_433_receiver_task, TAG, 4096, NULL, 5, NULL);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
|
||||
137
main/sensors.c
137
main/sensors.c
@@ -58,10 +58,31 @@ static void IRAM_ATTR sensor_isr_handler(void* arg) {
|
||||
if (xHigherPriorityTaskWoken) portYIELD_FROM_ISR();
|
||||
}
|
||||
|
||||
// Debounce task: Processes queue, updates state & count
|
||||
static void sensor_debounce_task(void* param) {
|
||||
esp_task_wdt_add(NULL);
|
||||
sensor_event_t evt;
|
||||
esp_err_t sensors_init() {
|
||||
|
||||
gpio_config_t io_conf = {
|
||||
.pin_bit_mask = (1ULL << sensor_pins[0]) | (1ULL << sensor_pins[1]),
|
||||
.mode = GPIO_MODE_INPUT,
|
||||
.pull_up_en = GPIO_PULLUP_ENABLE,
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.intr_type = GPIO_INTR_ANYEDGE,
|
||||
};
|
||||
ESP_ERROR_CHECK(gpio_config(&io_conf));
|
||||
|
||||
sensor_event_queue = xQueueCreate(16, sizeof(sensor_event_t));
|
||||
if (!sensor_event_queue) {
|
||||
ESP_LOGE(TAG, "Failed to create sensor queue");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
// Install ISR service
|
||||
ESP_ERROR_CHECK(gpio_install_isr_service(0));
|
||||
|
||||
for (uint8_t i = 0; i < N_SENSORS; i++) {
|
||||
ESP_ERROR_CHECK(gpio_isr_handler_add(sensor_pins[i], sensor_isr_handler, INT2VOIDP(sensor_pins[i])));
|
||||
sensor_stable_state[i] = !gpio_get_level(sensor_pins[i]);
|
||||
}
|
||||
|
||||
//static uint64_t last_processed_time[N_SENSORS] = {0};
|
||||
|
||||
// Initialize stable state
|
||||
@@ -82,61 +103,65 @@ static void sensor_debounce_task(void* param) {
|
||||
safety_high_start_time = esp_timer_get_time();
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void sensors_check() {
|
||||
sensor_event_t evt;
|
||||
|
||||
uint8_t i = 0;
|
||||
|
||||
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]);
|
||||
|
||||
while (1) {
|
||||
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;
|
||||
|
||||
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;
|
||||
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", "RISING");
|
||||
sensor_count[i]++;
|
||||
}
|
||||
|
||||
// Handle safety sensor debouncing with asymmetric timing
|
||||
bool safety_current = !gpio_get_level(sensor_pins[SENSOR_SAFETY]);
|
||||
uint64_t 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 make timer");
|
||||
} else if (!is_safe && (now - safety_low_start_time >= SAFETY_MAKE_DEBOUNCE_US)) {
|
||||
is_safe = true;
|
||||
ESP_LOGW(TAG, "SAFETY MADE - Relays enabled");
|
||||
}
|
||||
} 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 break timer");
|
||||
} else if (is_safe && (now - safety_high_start_time >= SAFETY_BREAK_DEBOUNCE_US)) {
|
||||
is_safe = false;
|
||||
i2c_set_relays(0);
|
||||
ESP_LOGI(TAG, "SAFETY BREAK - Relays disabled");
|
||||
}
|
||||
}
|
||||
|
||||
esp_task_wdt_reset();
|
||||
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();
|
||||
|
||||
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 make timer");
|
||||
} else if (!is_safe && (now - safety_low_start_time >= SAFETY_MAKE_DEBOUNCE_US)) {
|
||||
is_safe = true;
|
||||
ESP_LOGW(TAG, "SAFETY MADE - Relays enabled");
|
||||
}
|
||||
} 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 break timer");
|
||||
} else if (is_safe && (now - safety_high_start_time >= SAFETY_BREAK_DEBOUNCE_US)) {
|
||||
is_safe = false;
|
||||
i2c_set_relays((relay_port_t){.raw=0});
|
||||
ESP_LOGI(TAG, "SAFETY BREAK - Relays disabled");
|
||||
}
|
||||
}
|
||||
|
||||
esp_task_wdt_reset();
|
||||
}
|
||||
|
||||
int8_t pack_sensors() {
|
||||
@@ -148,7 +173,7 @@ int8_t pack_sensors() {
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t sensors_init() {
|
||||
/*esp_err_t sensors_init() {
|
||||
gpio_config_t io_conf = {
|
||||
.pin_bit_mask = (1ULL << sensor_pins[0]) | (1ULL << sensor_pins[1]),
|
||||
.mode = GPIO_MODE_INPUT,
|
||||
@@ -185,7 +210,7 @@ esp_err_t sensors_stop() {
|
||||
vQueueDelete(sensor_event_queue);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
}*/
|
||||
|
||||
// Public API
|
||||
bool get_sensor(sensor_t i) {
|
||||
|
||||
@@ -33,6 +33,7 @@ bool get_is_safe(void);
|
||||
int8_t pack_sensors();
|
||||
|
||||
esp_err_t sensors_init();
|
||||
esp_err_t sensors_stop();
|
||||
void sensors_check();
|
||||
//esp_err_t sensors_stop();
|
||||
|
||||
#endif /* MAIN_SENSORS_H_ */
|
||||
511
main/storage.c
511
main/storage.c
@@ -29,8 +29,9 @@ static bool log_task_running = false;
|
||||
|
||||
// Log queue entry structure
|
||||
typedef struct {
|
||||
uint8_t data[LOG_MAX_PAYLOAD + 1]; // +1 for length byte
|
||||
uint8_t data[LOG_MAX_PAYLOAD]; // +1 for length byte
|
||||
uint8_t len;
|
||||
uint8_t type;
|
||||
} log_queue_entry_t;
|
||||
|
||||
// ============================================================================
|
||||
@@ -48,9 +49,6 @@ typedef struct {
|
||||
// Helper macro to check if a byte is a valid log type
|
||||
#define IS_VALID_LOG_TYPE(x) ((x) >= 0xC0 && (x) <= 0xCF)
|
||||
|
||||
// Maximum payload size per log entry (255 max due to 1-byte size field)
|
||||
#define LOG_MAX_PAYLOAD 255
|
||||
|
||||
// ============================================================================
|
||||
// PARAMETER TABLE GENERATION
|
||||
// ============================================================================
|
||||
@@ -498,32 +496,50 @@ static inline uint32_t log_sector_end(uint32_t x) {
|
||||
}
|
||||
|
||||
|
||||
// Helper function to check if a sector is erased (all 0xFF)
|
||||
static bool is_sector_erased(uint32_t sector_offset) {
|
||||
uint8_t buf[256];
|
||||
esp_err_t err = esp_partition_read(storage_partition, sector_offset, buf, 256);
|
||||
// Helper function to check if a sector is erased (starts with 0xFF)
|
||||
static bool is_sector_erased(uint32_t x) {
|
||||
uint8_t buf; //[256];
|
||||
esp_err_t err = esp_partition_read(storage_partition, LOG_START_OFFSET + x * FLASH_SECTOR_SIZE, &buf, 1);
|
||||
if (err != ESP_OK) return false;
|
||||
if (buf == 0xFF) return true;
|
||||
|
||||
for (int i = 0; i < 256; i++) {
|
||||
/*for (int i = 0; i < 256; i++) {
|
||||
if (buf[i] != 0xFF) return false;
|
||||
}
|
||||
}*/
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool is_sector_full(uint32_t x) {
|
||||
uint8_t buf; //[256];
|
||||
esp_err_t err = esp_partition_read(storage_partition, LOG_START_OFFSET + (x+1) * FLASH_SECTOR_SIZE - 1, &buf, 1);
|
||||
if (err != ESP_OK) return false;
|
||||
if (buf == 0xFF) return false;
|
||||
|
||||
/*for (int i = 0; i < 256; i++) {
|
||||
if (buf[i] != 0xFF) return false;
|
||||
}*/
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline void find_head_tail(int32_t num_sectors, int32_t *head, int32_t *tail) {
|
||||
|
||||
}
|
||||
|
||||
// Helper function to check if a sector has data (contains non-0xFF, non-0x00 bytes)
|
||||
static bool sector_has_data(uint32_t sector_offset) {
|
||||
uint8_t buf[256];
|
||||
esp_err_t err = esp_partition_read(storage_partition, sector_offset, buf, 256);
|
||||
/*static bool sector_has_data(uint32_t sector_offset) {
|
||||
uint8_t buf; //[256];
|
||||
esp_err_t err = esp_partition_read(storage_partition, sector_offset, &buf, 256);
|
||||
if (err != ESP_OK) return false;
|
||||
if (buf )
|
||||
|
||||
for (int i = 0; i < 256; i++) {
|
||||
if (buf[i] != 0xFF && buf[i] != 0x00) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}*/
|
||||
|
||||
// Replace log_write with this non-blocking version:
|
||||
esp_err_t log_write(uint8_t* buf, uint8_t len) {
|
||||
esp_err_t log_write(uint8_t* buf, uint8_t len, uint8_t type) {
|
||||
if (!log_initialized || storage_partition == NULL) {
|
||||
ESP_LOGE(TAG, "Logging not initialized");
|
||||
return ESP_FAIL;
|
||||
@@ -539,9 +555,15 @@ esp_err_t log_write(uint8_t* buf, uint8_t len) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
if (type == 0xFF) {
|
||||
ESP_LOGE(TAG, "Attempt to log with type=0xFF; not allowed");
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
// Create queue entry
|
||||
log_queue_entry_t entry;
|
||||
entry.len = len;
|
||||
entry.type = type;
|
||||
memcpy(entry.data, buf, len);
|
||||
|
||||
// Try to send to queue (non-blocking)
|
||||
@@ -554,7 +576,7 @@ esp_err_t log_write(uint8_t* buf, uint8_t len) {
|
||||
}
|
||||
|
||||
// The actual blocking write function (called by the task)
|
||||
static esp_err_t log_write_blocking(uint8_t* buf, uint8_t len) {
|
||||
static esp_err_t log_write_blocking(uint8_t* buf, uint8_t len, uint8_t type) {
|
||||
if (!log_initialized || storage_partition == NULL) {
|
||||
ESP_LOGE(TAG, "Logging not initialized");
|
||||
return ESP_FAIL;
|
||||
@@ -568,8 +590,8 @@ static esp_err_t log_write_blocking(uint8_t* buf, uint8_t len) {
|
||||
if (log_mutex) xSemaphoreTake(log_mutex, portMAX_DELAY);
|
||||
|
||||
// check if we will overrun the sector
|
||||
if (log_head_offset + len+1 >= log_sector_end(log_head_offset)) {
|
||||
ESP_LOGI(TAG, "WILL OVERRUN (%ld >= %ld)", (long)log_head_offset + len+1, (long)log_sector_end(log_head_offset));
|
||||
if (log_head_offset + len+2 >= log_sector_end(log_head_offset)) {
|
||||
//ESP_LOGI(TAG, "WILL OVERRUN (%ld >= %ld)", (long)log_head_offset + len+2, (long)log_sector_end(log_head_offset));
|
||||
// zero the rest of sector
|
||||
char zeros[256] = {0};
|
||||
esp_partition_write(storage_partition,
|
||||
@@ -588,7 +610,7 @@ static esp_err_t log_write_blocking(uint8_t* buf, uint8_t len) {
|
||||
|
||||
// Erase the next sector
|
||||
if (err == ESP_OK && check_byte != 0xFF) {
|
||||
ESP_LOGI(TAG, "Erasing sector %lu for log", (unsigned long)log_head_offset);
|
||||
//ESP_LOGI(TAG, "Erasing sector %lu for log", (unsigned long)log_head_offset);
|
||||
err = esp_partition_erase_range(storage_partition,
|
||||
log_head_offset,
|
||||
FLASH_SECTOR_SIZE);
|
||||
@@ -606,13 +628,15 @@ static esp_err_t log_write_blocking(uint8_t* buf, uint8_t len) {
|
||||
if (log_tail_offset >= storage_partition->size)
|
||||
log_tail_offset = LOG_START_OFFSET;
|
||||
|
||||
ESP_LOGI(TAG, "Tail/Head are now %lu/%lu",
|
||||
ESP_LOGI(TAG, "Erased; Tail/Head are now %lu/%lu",
|
||||
(unsigned long)log_tail_offset, (unsigned long)log_head_offset);
|
||||
}
|
||||
len++; // account for type bit
|
||||
|
||||
|
||||
esp_partition_write(storage_partition, log_head_offset, &len, 1);
|
||||
esp_partition_write(storage_partition, log_head_offset+1, buf, len);
|
||||
esp_partition_write(storage_partition, log_head_offset+1, buf, len-1);
|
||||
esp_partition_write(storage_partition, log_head_offset+len, &type, 1);
|
||||
|
||||
log_head_offset+=len+1;
|
||||
ESP_LOGI(TAG, "Wrote; Tail/Head are now %lu/%lu",
|
||||
@@ -629,17 +653,20 @@ static void log_writer_task(void *pvParameters) {
|
||||
ESP_LOGI(TAG, "Log writer task started");
|
||||
|
||||
while (log_task_running) {
|
||||
// Feed watchdog
|
||||
//esp_task_wdt_reset();
|
||||
// Wait for log entry (with timeout to check running flag)
|
||||
if (xQueueReceive(log_queue, &entry, pdMS_TO_TICKS(100)) == pdTRUE) {
|
||||
// Write the log entry (blocking)
|
||||
esp_err_t err = log_write_blocking(entry.data, entry.len);
|
||||
esp_err_t err = log_write_blocking(entry.data, entry.len, entry.type);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to write log entry: %s", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
//esp_task_wdt_reset();
|
||||
}
|
||||
|
||||
// Feed watchdog
|
||||
esp_task_wdt_reset();
|
||||
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Log writer task stopping");
|
||||
@@ -664,28 +691,50 @@ esp_err_t log_init() {
|
||||
log_head_offset = LOG_START_OFFSET;
|
||||
log_tail_offset = LOG_START_OFFSET;
|
||||
|
||||
// ... [INCLUDE THE BISECTION ALGORITHM FROM EARLIER HERE] ...
|
||||
// Binary search to find the first erased sector
|
||||
int32_t left = 0;
|
||||
int32_t right = num_sectors - 1;
|
||||
int32_t head_sector = -1;
|
||||
// Binary search for the first non-full sector
|
||||
int32_t l = 0;
|
||||
int32_t r = num_sectors - 1;
|
||||
int32_t head_sector = 0;
|
||||
int32_t tail_sector = 0;
|
||||
|
||||
while (left <= right) {
|
||||
int32_t mid = left + (right - left) / 2;
|
||||
uint32_t sector_offset = LOG_START_OFFSET + mid * FLASH_SECTOR_SIZE;
|
||||
|
||||
esp_task_wdt_reset();
|
||||
|
||||
bool erased = is_sector_erased(sector_offset);
|
||||
bool prev_has_data = (mid > 0) ? sector_has_data(LOG_START_OFFSET + (mid-1) * FLASH_SECTOR_SIZE) : false;
|
||||
|
||||
if (erased && (mid == 0 || prev_has_data)) {
|
||||
head_sector = mid;
|
||||
break;
|
||||
} else if (erased) {
|
||||
right = mid - 1;
|
||||
while (l < r) {
|
||||
int32_t m = (l + r) / 2;
|
||||
if (is_sector_full(m)) {
|
||||
l = m + 1;
|
||||
} else {
|
||||
left = mid + 1;
|
||||
r = m;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t first_non_full = l;
|
||||
|
||||
// Determine head based on what the first non-full sector is
|
||||
if (first_non_full >= num_sectors) {
|
||||
// All sectors are full (wraparound case)
|
||||
head_sector = 0;
|
||||
} else if (is_sector_erased(first_non_full)) {
|
||||
// First non-full is erased -> head is this erased sector
|
||||
head_sector = first_non_full;
|
||||
} else {
|
||||
// First non-full is partial -> head is this partial sector
|
||||
head_sector = first_non_full;
|
||||
}
|
||||
|
||||
// Binary search for tail: first full sector after head (with wraparound)
|
||||
// Search from head+1 to head+num_sectors-1 (wrapping around)
|
||||
l = 1;
|
||||
r = num_sectors - 1;
|
||||
|
||||
while (l <= r) {
|
||||
int32_t m = (l + r) / 2;
|
||||
int32_t sector = (head_sector + m) % num_sectors;
|
||||
|
||||
if (is_sector_full(sector)) {
|
||||
// Found a full sector, but need the FIRST one
|
||||
tail_sector = sector;
|
||||
r = m - 1;
|
||||
} else {
|
||||
l = m + 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -737,31 +786,6 @@ esp_err_t log_init() {
|
||||
log_head_offset = cursor;
|
||||
}
|
||||
|
||||
// Find tail
|
||||
int32_t tail_sector = -1;
|
||||
|
||||
for (int32_t i = head_sector + 1; i < num_sectors; i++) {
|
||||
uint32_t sector_offset = LOG_START_OFFSET + i * FLASH_SECTOR_SIZE;
|
||||
esp_task_wdt_reset();
|
||||
|
||||
if (sector_has_data(sector_offset)) {
|
||||
tail_sector = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (tail_sector == -1 && head_sector > 0) {
|
||||
for (int32_t i = 0; i < head_sector; i++) {
|
||||
uint32_t sector_offset = LOG_START_OFFSET + i * FLASH_SECTOR_SIZE;
|
||||
esp_task_wdt_reset();
|
||||
|
||||
if (sector_has_data(sector_offset)) {
|
||||
tail_sector = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tail_sector >= 0) {
|
||||
log_tail_offset = LOG_START_OFFSET + tail_sector * FLASH_SECTOR_SIZE;
|
||||
} else {
|
||||
@@ -800,8 +824,10 @@ esp_err_t log_init() {
|
||||
}
|
||||
|
||||
// Add the task to watchdog
|
||||
esp_task_wdt_add(log_task_handle);
|
||||
|
||||
//if (esp_task_wdt_add(log_task_handle) != ESP_OK) {
|
||||
// ESP_LOGW(TAG, "Log task already subscribed to watchdog or failed to add");
|
||||
//}
|
||||
|
||||
ESP_LOGI(TAG, "Log writer task created successfully");
|
||||
|
||||
if (log_mutex) xSemaphoreGive(log_mutex);
|
||||
@@ -811,17 +837,17 @@ esp_err_t log_init() {
|
||||
// Update storage_deinit to stop the task
|
||||
void storage_deinit(void) {
|
||||
// Stop the log writer task
|
||||
if (log_task_running) {
|
||||
log_task_running = false;
|
||||
|
||||
// Wait a bit for task to finish
|
||||
vTaskDelay(pdMS_TO_TICKS(200));
|
||||
|
||||
if (log_task_handle != NULL) {
|
||||
esp_task_wdt_delete(log_task_handle);
|
||||
log_task_handle = NULL;
|
||||
}
|
||||
}
|
||||
if (log_task_running) {
|
||||
log_task_running = false;
|
||||
vTaskDelay(pdMS_TO_TICKS(200));
|
||||
|
||||
if (log_task_handle != NULL) {
|
||||
// Don't try to delete from watchdog - task was never added
|
||||
// esp_task_wdt_delete(log_task_handle); // <-- REMOVE THIS LINE
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
log_task_handle = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// Delete the queue
|
||||
if (log_queue != NULL) {
|
||||
@@ -836,3 +862,324 @@ void storage_deinit(void) {
|
||||
log_mutex = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* ADDITIONS TO storage.c
|
||||
*
|
||||
* Add these functions to storage.c to support the test suite.
|
||||
* These provide the ability to erase all log sectors, simulate power cycles,
|
||||
* and read back log entries for verification.
|
||||
*/
|
||||
|
||||
// Add these function declarations to storage.h:
|
||||
/*
|
||||
esp_err_t log_read(uint8_t* len, uint8_t* buf, uint8_t* type);
|
||||
void log_read_reset(void);
|
||||
esp_err_t log_erase_all_sectors(void);
|
||||
esp_err_t log_simulate_power_cycle(void);
|
||||
*/
|
||||
|
||||
// Add this static variable near the top of storage.c with other log static variables
|
||||
// (around line 118, after log_tail_offset and log_initialized):
|
||||
|
||||
static uint32_t log_read_cursor = 0;
|
||||
|
||||
|
||||
// Add these functions to storage.c (after the existing log functions):
|
||||
|
||||
/*
|
||||
* ADDITIONS TO storage.c
|
||||
*
|
||||
* Add these functions to storage.c to support the test suite.
|
||||
* These provide the ability to erase all log sectors, simulate power cycles,
|
||||
* and read back log entries for verification.
|
||||
*/
|
||||
|
||||
// Add these function declarations to storage.h:
|
||||
/*
|
||||
esp_err_t log_read(uint8_t* len, uint8_t* buf, uint8_t* type);
|
||||
void log_read_reset(void);
|
||||
esp_err_t log_erase_all_sectors(void);
|
||||
esp_err_t log_simulate_power_cycle(void);
|
||||
*/
|
||||
|
||||
// Add this static variable near the top of storage.c with other log static variables
|
||||
// (around line 118, after log_tail_offset and log_initialized):
|
||||
/*
|
||||
static uint32_t log_read_cursor = 0;
|
||||
*/
|
||||
|
||||
// Add these functions to storage.c (after the existing log functions):
|
||||
|
||||
/**
|
||||
* @brief Read a log entry from the current read cursor position
|
||||
*
|
||||
* Reads a log entry from flash starting at the current read cursor and advances
|
||||
* the cursor to the next entry. The read cursor is independent of tail - reading
|
||||
* does NOT affect the tail pointer (which marks the boundary of valid data).
|
||||
*
|
||||
* The tail is only moved by the log write system when sectors need to be erased
|
||||
* during wraparound. Reading is completely non-destructive.
|
||||
*
|
||||
* This is a test/debug function - the production logging system is write-only.
|
||||
*
|
||||
* Log entry format in flash:
|
||||
* [1 byte: length] [length-1 bytes: data] [1 byte: type]
|
||||
*
|
||||
* @param len Pointer to store the entry length (data length, not including type byte)
|
||||
* @param buf Buffer to store the entry data (must be at least LOG_MAX_PAYLOAD bytes)
|
||||
* @param type Pointer to store the entry type
|
||||
* @return ESP_OK on success, ESP_ERR_NOT_FOUND if no more entries, error code otherwise
|
||||
*/
|
||||
esp_err_t log_read(uint8_t* len, uint8_t* buf, uint8_t* type) {
|
||||
// NOTE: log_read_cursor must be declared as a file-scope static variable
|
||||
// Add this declaration near the other log static variables in storage.c:
|
||||
// static uint32_t log_read_cursor = 0;
|
||||
|
||||
if (!log_initialized || storage_partition == NULL) {
|
||||
ESP_LOGE(TAG, "Logging not initialized");
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
if (len == NULL || buf == NULL || type == NULL) {
|
||||
ESP_LOGE(TAG, "NULL pointer passed to log_read");
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (log_mutex) xSemaphoreTake(log_mutex, portMAX_DELAY);
|
||||
|
||||
// Initialize read cursor to tail on first read (when cursor is 0)
|
||||
if (log_read_cursor == 0) {
|
||||
log_read_cursor = log_tail_offset;
|
||||
}
|
||||
|
||||
// Check if we've caught up to head (no more entries to read)
|
||||
if (log_read_cursor == log_head_offset) {
|
||||
if (log_mutex) xSemaphoreGive(log_mutex);
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
// Read the length byte
|
||||
uint8_t entry_len;
|
||||
esp_err_t err = esp_partition_read(storage_partition, log_read_cursor, &entry_len, 1);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to read entry length at offset %lu", (unsigned long)log_read_cursor);
|
||||
if (log_mutex) xSemaphoreGive(log_mutex);
|
||||
return err;
|
||||
}
|
||||
|
||||
// Check for erased flash (0xFF) - means we've reached the end
|
||||
if (entry_len == 0xFF) {
|
||||
if (log_mutex) xSemaphoreGive(log_mutex);
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
// Check for sector padding (0x00) - skip to next sector
|
||||
if (entry_len == 0x00) {
|
||||
// Move to next sector
|
||||
uint32_t next_sector = ((log_read_cursor / FLASH_SECTOR_SIZE) + 1) * FLASH_SECTOR_SIZE;
|
||||
|
||||
// Handle wraparound
|
||||
if (next_sector >= storage_partition->size) {
|
||||
log_read_cursor = LOG_START_OFFSET;
|
||||
} else {
|
||||
log_read_cursor = next_sector;
|
||||
}
|
||||
|
||||
if (log_mutex) xSemaphoreGive(log_mutex);
|
||||
return log_read(len, buf, type); // Recursive call to read from next sector
|
||||
}
|
||||
|
||||
// Validate length
|
||||
if (entry_len > LOG_MAX_PAYLOAD + 1) { // +1 for type byte included in length
|
||||
ESP_LOGE(TAG, "Invalid entry length %d at offset %lu", entry_len, (unsigned long)log_read_cursor);
|
||||
if (log_mutex) xSemaphoreGive(log_mutex);
|
||||
return ESP_ERR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
// Read the data (length-1 bytes, since length includes the type byte)
|
||||
uint8_t data_len = entry_len - 1;
|
||||
err = esp_partition_read(storage_partition, log_read_cursor + 1, buf, data_len);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to read entry data at offset %lu", (unsigned long)(log_read_cursor + 1));
|
||||
if (log_mutex) xSemaphoreGive(log_mutex);
|
||||
return err;
|
||||
}
|
||||
|
||||
// Read the type byte
|
||||
uint8_t entry_type;
|
||||
err = esp_partition_read(storage_partition, log_read_cursor + entry_len, &entry_type, 1);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to read entry type at offset %lu", (unsigned long)(log_read_cursor + entry_len));
|
||||
if (log_mutex) xSemaphoreGive(log_mutex);
|
||||
return err;
|
||||
}
|
||||
|
||||
// Validate type
|
||||
if (!IS_VALID_LOG_TYPE(entry_type)) {
|
||||
ESP_LOGW(TAG, "Invalid log type 0x%02X at offset %lu", entry_type, (unsigned long)(log_read_cursor + entry_len));
|
||||
// Continue anyway - might be valid data with unknown type
|
||||
}
|
||||
|
||||
// Set output parameters
|
||||
*len = data_len;
|
||||
*type = entry_type;
|
||||
|
||||
// Advance read cursor (NOT tail - tail is only moved by writes during wraparound)
|
||||
log_read_cursor += entry_len + 1; // +1 for the type byte after data
|
||||
|
||||
// Handle wraparound
|
||||
if (log_read_cursor >= storage_partition->size) {
|
||||
log_read_cursor = LOG_START_OFFSET;
|
||||
}
|
||||
|
||||
if (log_mutex) xSemaphoreGive(log_mutex);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reset the log read cursor to start reading from the beginning
|
||||
*
|
||||
* Resets the internal read cursor so that the next call to log_read()
|
||||
* will start reading from the tail (oldest valid entry).
|
||||
* This is useful for tests that need to re-read the log.
|
||||
*
|
||||
* IMPORTANT: This does NOT affect the tail pointer. The tail marks the
|
||||
* boundary of valid data and is only moved by the write system.
|
||||
*/
|
||||
void log_read_reset(void) {
|
||||
if (log_mutex) xSemaphoreTake(log_mutex, portMAX_DELAY);
|
||||
log_read_cursor = 0; // Reset to 0 so next read starts from tail
|
||||
if (log_mutex) xSemaphoreGive(log_mutex);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Erase all sectors in the log area
|
||||
*
|
||||
* This function erases the entire log area of the flash partition,
|
||||
* resetting it to a clean state. Useful for testing.
|
||||
*
|
||||
* @return ESP_OK on success, error code otherwise
|
||||
*/
|
||||
esp_err_t log_erase_all_sectors(void) {
|
||||
if (storage_partition == NULL) {
|
||||
ESP_LOGE(TAG, "Storage partition not initialized");
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
// Stop the log writer task
|
||||
if (log_task_running) {
|
||||
log_task_running = false;
|
||||
vTaskDelay(pdMS_TO_TICKS(200));
|
||||
|
||||
if (log_task_handle != NULL) {
|
||||
// Don't try to delete from watchdog - task was never added
|
||||
// esp_task_wdt_delete(log_task_handle); // <-- REMOVE THIS LINE
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
log_task_handle = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the queue
|
||||
if (log_queue != NULL) {
|
||||
xQueueReset(log_queue);
|
||||
}
|
||||
|
||||
if (log_mutex) xSemaphoreTake(log_mutex, portMAX_DELAY);
|
||||
|
||||
uint32_t log_area_size = storage_partition->size - LOG_START_OFFSET;
|
||||
|
||||
ESP_LOGI(TAG, "Erasing all log sectors (%lu bytes)...", (unsigned long)log_area_size);
|
||||
|
||||
esp_err_t err = esp_partition_erase_range(storage_partition,
|
||||
LOG_START_OFFSET,
|
||||
log_area_size);
|
||||
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to erase log area: %s", esp_err_to_name(err));
|
||||
if (log_mutex) xSemaphoreGive(log_mutex);
|
||||
return err;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "All log sectors erased successfully");
|
||||
|
||||
if (log_mutex) xSemaphoreGive(log_mutex);
|
||||
|
||||
// Reinitialize
|
||||
log_initialized = false;
|
||||
log_read_cursor = 0;
|
||||
|
||||
err = log_init();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to reinitialize log after erase");
|
||||
return err;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Simulate a power cycle by re-running log initialization
|
||||
*
|
||||
* This function simulates what happens when the device powers off and back on:
|
||||
* - Clears the in-memory head/tail tracking (simulated by marking as uninitialized)
|
||||
* - Re-runs log_init() to scan flash and recover head/tail positions
|
||||
*
|
||||
* This is crucial for testing that the log system can correctly recover its
|
||||
* state from flash after a power loss.
|
||||
*
|
||||
* @return ESP_OK on success, error code otherwise
|
||||
*/
|
||||
esp_err_t log_simulate_power_cycle(void) {
|
||||
ESP_LOGI(TAG, "Simulating power cycle...");
|
||||
// Stop the log writer task
|
||||
if (log_task_running) {
|
||||
log_task_running = false;
|
||||
vTaskDelay(pdMS_TO_TICKS(200));
|
||||
|
||||
if (log_task_handle != NULL) {
|
||||
// Don't try to delete from watchdog - task was never added
|
||||
// esp_task_wdt_delete(log_task_handle); // <-- REMOVE THIS LINE
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
log_task_handle = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the queue
|
||||
if (log_queue != NULL) {
|
||||
xQueueReset(log_queue);
|
||||
}
|
||||
|
||||
// Mark log as uninitialized (simulates losing RAM state)
|
||||
log_initialized = false;
|
||||
|
||||
// Reset read cursor (simulates losing RAM state)
|
||||
log_read_cursor = 0;
|
||||
|
||||
// Re-run log initialization to scan flash and recover state
|
||||
esp_err_t err = log_init();
|
||||
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to reinitialize log after simulated power cycle");
|
||||
return err;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Power cycle simulation complete. Head=%lu, Tail=%lu",
|
||||
(unsigned long)log_head_offset, (unsigned long)log_tail_offset);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t log_write_blocking_test(uint8_t* buf, uint8_t len, uint8_t type) {
|
||||
// Check queue space - wait if nearly full
|
||||
while (uxQueueSpacesAvailable(log_queue) < 2) {
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
esp_task_wdt_reset();
|
||||
}
|
||||
|
||||
return log_write(buf, len, type);
|
||||
}
|
||||
@@ -28,7 +28,7 @@
|
||||
// 0xCA-0xCF reserved for future use
|
||||
|
||||
// Maximum payload size per log entry (255 max due to 1-byte size field)
|
||||
#define LOG_MAX_PAYLOAD 255
|
||||
#define LOG_MAX_PAYLOAD 200
|
||||
|
||||
// Helper macro to check if a byte is a valid log type
|
||||
#define IS_VALID_LOG_TYPE(x) ((x) >= 0xC0 && (x) <= 0xCF)
|
||||
@@ -75,10 +75,10 @@ typedef struct {
|
||||
PARAM_DEF(ADC_ALPHA_IAZ, f32, 0.005, "-") \
|
||||
PARAM_DEF(ADC_DB_IAZ, f32, 5.0, "A") \
|
||||
PARAM_DEF(EFUSE_INOM_1, f32, 40.0, "A") \
|
||||
PARAM_DEF(EFUSE_INOM_2, f32, 6.0, "A") \
|
||||
PARAM_DEF(EFUSE_INOM_2, f32, 14.0, "A") \
|
||||
PARAM_DEF(EFUSE_INOM_3, f32, 4.0, "A") \
|
||||
PARAM_DEF(EFUSE_HEAT_THRESH, f32, 60.0, "i/i^2-s") \
|
||||
PARAM_DEF(EFUSE_KINST, f32, 5.0, "i/i") \
|
||||
PARAM_DEF(EFUSE_KINST, f32, 2.0, "i/i") \
|
||||
PARAM_DEF(EFUSE_TAUCOOL, f32, 0.2, "i") \
|
||||
PARAM_DEF(EFUSE_TCOOL, u32, 5000000, "us") \
|
||||
PARAM_DEF(LOW_PROTECTION_V, f32, 10.0, "V") \
|
||||
@@ -91,13 +91,14 @@ typedef struct {
|
||||
PARAM_DEF(WIFI_CHANNEL, u16, 6, "") \
|
||||
PARAM_DEF(WIFI_SSID, str, "sc.local", "") \
|
||||
PARAM_DEF(WIFI_PASS, str, "password", "") \
|
||||
PARAM_DEF(EFUSE_INRUSH_US, u32, 300000, "us") \
|
||||
PARAM_DEF(JACK_I_UP, f32, 5.0, "A") \
|
||||
PARAM_DEF(JACK_I_DOWN, f32, 8.0, "A") \
|
||||
PARAM_DEF(EFUSE_INRUSH_US, u32, 250000, "us") \
|
||||
PARAM_DEF(JACK_I_UP, f32, 8.0, "A") \
|
||||
PARAM_DEF(JACK_I_DOWN, f32, 15.0, "A") \
|
||||
PARAM_DEF(V_SENS_K, f32, 0.00766666666, "V/mV") \
|
||||
PARAM_DEF(BUILD_VERSION, str, "undefined", "") \
|
||||
PARAM_DEF(SAFETY_BREAK_US, u32, 300000, "") \
|
||||
PARAM_DEF(SAFETY_MAKE_US, u32, 1000000, "") \
|
||||
PARAM_DEF(JACK_IS_DOWN, f32, 8.0, "A") \
|
||||
|
||||
// Generate enum for parameter indices
|
||||
#define PARAM_DEF(name, type, default_val, unit) PARAM_##name,
|
||||
@@ -163,7 +164,7 @@ esp_err_t commit_params(void);
|
||||
|
||||
// Logging functions
|
||||
esp_err_t log_init(void);
|
||||
esp_err_t log_write(uint8_t* buf, uint8_t len);
|
||||
esp_err_t log_write(uint8_t* buf, uint8_t len, uint8_t type);
|
||||
uint32_t log_get_head(void);
|
||||
uint32_t log_get_tail(void);
|
||||
uint32_t log_get_offset(void);
|
||||
@@ -176,4 +177,9 @@ esp_err_t write_dummy_log_1(void);
|
||||
esp_err_t write_dummy_log_2(void);
|
||||
esp_err_t write_dummy_log_3(void);
|
||||
|
||||
esp_err_t log_read(uint8_t* len, uint8_t* buf, uint8_t* type);
|
||||
esp_err_t log_erase_all_sectors(void);
|
||||
esp_err_t log_simulate_power_cycle(void);
|
||||
void log_read_reset(void);
|
||||
esp_err_t log_write_blocking_test(uint8_t* buf, uint8_t len, uint8_t type);
|
||||
#endif // STORAGE_H
|
||||
File diff suppressed because one or more lines are too long
@@ -4,7 +4,7 @@
|
||||
#include <Arduino.h>
|
||||
|
||||
const unsigned char PROGMEM html_content_gz[] = {
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x68, 0x76, 0x5c, 0x69, 0x02, 0xff, 0xed, 0x3d, 0x6b, 0x57, 0xdb, 0xc6,
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x11, 0xd0, 0x6b, 0x69, 0x02, 0xff, 0xed, 0x3d, 0x6b, 0x57, 0xdb, 0xc6,
|
||||
0xb6, 0x9f, 0x4f, 0x7f, 0xc5, 0x40, 0x52, 0x2a, 0x25, 0x42, 0xb6, 0x81, 0xf4, 0x61, 0x23, 0x73,
|
||||
0x08, 0x38, 0x6d, 0x9a, 0xf0, 0x58, 0x3c, 0xd2, 0x9e, 0xcb, 0x61, 0x21, 0xd9, 0x1a, 0x63, 0x15,
|
||||
0x59, 0x72, 0x25, 0x19, 0x42, 0x85, 0xff, 0xfb, 0xd9, 0x7b, 0x1e, 0xd2, 0xe8, 0x61, 0x63, 0x92,
|
||||
|
||||
@@ -61,7 +61,7 @@ char httpBuffer[4096];
|
||||
|
||||
/* Handler to serve the HTML page */
|
||||
static esp_err_t root_get_handler(httpd_req_t *req) {
|
||||
ESP_LOGI(TAG, "root_get_handler");
|
||||
//ESP_LOGI(TAG, "root_get_handler");
|
||||
|
||||
if (req == NULL) {
|
||||
ESP_LOGE(TAG, "Null request pointer");
|
||||
@@ -199,9 +199,9 @@ static esp_err_t log_handler(httpd_req_t *req) {
|
||||
// Total size: 4 (json length) + json + 8 (head/tail) + log_data
|
||||
uint32_t total_size = 4 + json_len + 8 + log_data_size;
|
||||
|
||||
ESP_LOGI(TAG, "Log request: tail=%ld, head=%ld, json_len=%lu, log_size=%ld, total=%lu",
|
||||
(long)tail, (long)head, (unsigned long)json_len, (long)log_data_size,
|
||||
(unsigned long)total_size);
|
||||
//ESP_LOGI(TAG, "Log request: tail=%ld, head=%ld, json_len=%lu, log_size=%ld, total=%lu",
|
||||
// (long)tail, (long)head, (unsigned long)json_len, (long)log_data_size,
|
||||
// (unsigned long)total_size);
|
||||
|
||||
// Send HTTP headers
|
||||
char len_str[16];
|
||||
@@ -368,7 +368,7 @@ static esp_err_t log_handler(httpd_req_t *req) {
|
||||
* Unified GET handler - returns complete system status
|
||||
*/
|
||||
static esp_err_t get_handler(httpd_req_t *req) {
|
||||
ESP_LOGI(TAG, "get_handler");
|
||||
//ESP_LOGI(TAG, "get_handler");
|
||||
|
||||
if (req == NULL) {
|
||||
ESP_LOGE(TAG, "Null request pointer");
|
||||
@@ -692,7 +692,7 @@ static esp_err_t ota_post_handler(httpd_req_t *req) {
|
||||
|
||||
|
||||
static esp_err_t catchall_handler(httpd_req_t *req) {
|
||||
ESP_LOGI(TAG, "catchall_handler; %s", req->uri);
|
||||
//ESP_LOGI(TAG, "catchall_handler; %s", req->uri);
|
||||
const char *uri = req->uri;
|
||||
|
||||
// Windows NCSI
|
||||
@@ -868,6 +868,9 @@ static void wifi_event_handler(void* arg, esp_event_base_t event_base,
|
||||
static esp_err_t launchSoftAp(void) {
|
||||
esp_err_t err;
|
||||
|
||||
|
||||
ESP_LOGI(TAG, "AP LAUNCHING");
|
||||
|
||||
err = nvs_flash_init();
|
||||
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
// NVS partition was truncated and needs to be erased
|
||||
@@ -880,11 +883,17 @@ static esp_err_t launchSoftAp(void) {
|
||||
// Retry init after erase
|
||||
err = nvs_flash_init();
|
||||
}
|
||||
|
||||
|
||||
ESP_LOGI(TAG, "AP LAUNCHING...");
|
||||
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to initialize NVS: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "HI THERE");
|
||||
|
||||
err = esp_netif_init();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to initialize netif: %s", esp_err_to_name(err));
|
||||
@@ -1032,6 +1041,8 @@ esp_err_t webserver_init(void) {
|
||||
return err;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "AP LAUNCHED");
|
||||
|
||||
err = startHttpServer();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to start HTTP server: %s", esp_err_to_name(err));
|
||||
|
||||
Reference in New Issue
Block a user