bringup and order sensors
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/version.cmake)
|
||||
|
||||
idf_component_register(
|
||||
SRCS main.c log_test.c partition_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 bt_hid.c # list the source files of this component
|
||||
SRCS main.c log_test.c partition_test.c i2c.c rtc.c storage.c uart_comms.c control_fsm.c power_mgmt.c bringup.c rf_433.c rtc.c sensors.c solar.c webserver.c simple_dns_server.c comms.c bt_hid.c # list the source files of this component
|
||||
INCLUDE_DIRS "." "${CMAKE_BINARY_DIR}"
|
||||
PRIV_INCLUDE_DIRS # optional, add here private include directories
|
||||
|
||||
|
||||
672
main/bringup.c
Normal file
672
main/bringup.c
Normal file
@@ -0,0 +1,672 @@
|
||||
/*
|
||||
* bringup.c — manufacturing / bench bring-up procedure over UART.
|
||||
*
|
||||
* Line protocol specified in docs/SC-F001/BRINGUP.md §3.
|
||||
* All responses are written with printf() so they share the UART stream
|
||||
* with uart_comms; keep every line short and grep-friendly.
|
||||
*
|
||||
* Distinct from the per-module POST routines (adc_post, i2c_post, ...) that
|
||||
* run on every boot — those are the actual power-on self-tests.
|
||||
*/
|
||||
|
||||
#include "bringup.h"
|
||||
#include "board_config.h"
|
||||
#include "control_fsm.h"
|
||||
#include "i2c.h"
|
||||
#include "power_mgmt.h"
|
||||
#include "rf_433.h"
|
||||
#include "sensors.h"
|
||||
#include "solar.h"
|
||||
#include "storage.h"
|
||||
#include "version.h"
|
||||
#include "webserver.h"
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_partition.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_task_wdt.h"
|
||||
#include "esp_timer.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/uart.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
|
||||
#define TAG "BRINGUP"
|
||||
|
||||
static bool s_active = false;
|
||||
static int64_t s_start_us = 0;
|
||||
static volatile int s_http_reqs = 0;
|
||||
|
||||
/* -------- state helpers -------- */
|
||||
|
||||
bool bringup_mode_is_active(void) { return s_active; }
|
||||
|
||||
void bringup_notify_http_request(void) { s_http_reqs++; }
|
||||
|
||||
void bringup_mode_enter(void)
|
||||
{
|
||||
extern relay_port_t last_relay_state;
|
||||
s_active = true;
|
||||
s_start_us = esp_timer_get_time();
|
||||
s_http_reqs = 0;
|
||||
/* All bridges off, sensor rail (P10) up — system is still on. */
|
||||
relay_port_t idle = {.bridges = {.SENSORS = 1}};
|
||||
last_relay_state = idle;
|
||||
i2c_relays_idle();
|
||||
}
|
||||
|
||||
void bringup_mode_exit(void)
|
||||
{
|
||||
extern relay_port_t last_relay_state;
|
||||
s_active = false;
|
||||
relay_port_t idle = {.bridges = {.SENSORS = 1}};
|
||||
last_relay_state = idle;
|
||||
i2c_relays_idle();
|
||||
}
|
||||
|
||||
static float elapsed_s(void)
|
||||
{
|
||||
return (esp_timer_get_time() - s_start_us) / 1e6f;
|
||||
}
|
||||
|
||||
/* -------- output helpers -------- */
|
||||
|
||||
__attribute__((format(printf, 2, 3)))
|
||||
static void emit(const char *kind, const char *fmt, ...)
|
||||
{
|
||||
printf("BU.%s ", kind);
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vprintf(fmt, ap);
|
||||
va_end(ap);
|
||||
printf("\n");
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
#define OK(fmt, ...) emit("OK", fmt, ##__VA_ARGS__)
|
||||
#define ERR(fmt, ...) emit("ERR", fmt, ##__VA_ARGS__)
|
||||
#define SKIP(fmt, ...) emit("SKIP", fmt, ##__VA_ARGS__)
|
||||
#define EVT(fmt, ...) emit("EVENT", fmt, ##__VA_ARGS__)
|
||||
|
||||
/* -------- tokenizer -------- */
|
||||
|
||||
/* strsep-style: mutate s in place, return next token (no quotes handled). */
|
||||
static char *next_tok(char **s)
|
||||
{
|
||||
if (!s || !*s) return NULL;
|
||||
while (**s == ' ' || **s == '\t') (*s)++;
|
||||
if (**s == '\0') return NULL;
|
||||
char *start = *s;
|
||||
while (**s && **s != ' ' && **s != '\t') (*s)++;
|
||||
if (**s) { *(*s)++ = '\0'; }
|
||||
return start;
|
||||
}
|
||||
|
||||
static void str_upper(char *s)
|
||||
{
|
||||
for (; *s; s++) if (*s >= 'a' && *s <= 'z') *s -= 32;
|
||||
}
|
||||
|
||||
/* -------- parameter lookup by name -------- */
|
||||
|
||||
static int param_find(const char *name)
|
||||
{
|
||||
for (int i = 0; i < NUM_PARAMS; i++) {
|
||||
if (strcmp(name, get_param_name((param_idx_t)i)) == 0) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* -------- command handlers -------- */
|
||||
|
||||
static void cmd_begin(char *args)
|
||||
{
|
||||
(void)args;
|
||||
bringup_mode_enter();
|
||||
OK("begin fw=%s board=%s t=%.2f",
|
||||
FIRMWARE_VERSION,
|
||||
#ifdef BOARD_V5
|
||||
"V5",
|
||||
#else
|
||||
"V4",
|
||||
#endif
|
||||
elapsed_s());
|
||||
}
|
||||
|
||||
static void cmd_end(char *args)
|
||||
{
|
||||
(void)args;
|
||||
OK("end reboot t=%.2f", elapsed_s());
|
||||
fflush(stdout);
|
||||
vTaskDelay(pdMS_TO_TICKS(200));
|
||||
bringup_mode_exit();
|
||||
esp_restart();
|
||||
}
|
||||
|
||||
static void cmd_info(char *args)
|
||||
{
|
||||
(void)args;
|
||||
esp_reset_reason_t r = esp_reset_reason();
|
||||
const char *rname = "?";
|
||||
switch (r) {
|
||||
case ESP_RST_POWERON: rname = "POWERON"; break;
|
||||
case ESP_RST_EXT: rname = "EXT"; break;
|
||||
case ESP_RST_SW: rname = "SW"; break;
|
||||
case ESP_RST_PANIC: rname = "PANIC"; break;
|
||||
case ESP_RST_INT_WDT: rname = "INT_WDT"; break;
|
||||
case ESP_RST_TASK_WDT: rname = "TASK_WDT";break;
|
||||
case ESP_RST_WDT: rname = "WDT"; break;
|
||||
case ESP_RST_DEEPSLEEP:rname = "DEEPSLEEP";break;
|
||||
case ESP_RST_BROWNOUT: rname = "BROWNOUT";break;
|
||||
default: break;
|
||||
}
|
||||
OK("info reset=%s heap=%u min_heap=%u fw=%s build=%s",
|
||||
rname,
|
||||
(unsigned)esp_get_free_heap_size(),
|
||||
(unsigned)esp_get_minimum_free_heap_size(),
|
||||
FIRMWARE_VERSION, BUILD_DATE);
|
||||
}
|
||||
|
||||
static void cmd_flash(char *args)
|
||||
{
|
||||
(void)args;
|
||||
const esp_partition_t *p = esp_partition_find_first(
|
||||
ESP_PARTITION_TYPE_DATA, 0x42, "post_test");
|
||||
if (!p) { ERR("flash reason=\"no post_test partition\""); return; }
|
||||
|
||||
/* Erase, write pattern, read back, compare. */
|
||||
const size_t N = 64;
|
||||
uint8_t pattern[N], readback[N];
|
||||
for (size_t i = 0; i < N; i++) pattern[i] = (uint8_t)(i ^ 0xA5);
|
||||
|
||||
esp_err_t e = esp_partition_erase_range(p, 0, 4096);
|
||||
if (e != ESP_OK) { ERR("flash stage=erase err=%s", esp_err_to_name(e)); return; }
|
||||
e = esp_partition_write(p, 0, pattern, N);
|
||||
if (e != ESP_OK) { ERR("flash stage=write err=%s", esp_err_to_name(e)); return; }
|
||||
e = esp_partition_read(p, 0, readback, N);
|
||||
if (e != ESP_OK) { ERR("flash stage=read err=%s", esp_err_to_name(e)); return; }
|
||||
if (memcmp(pattern, readback, N) != 0) {
|
||||
ERR("flash stage=compare mismatch");
|
||||
return;
|
||||
}
|
||||
OK("flash post_part=roundtrip log_head=%u log_tail=%u partitions_size=%u",
|
||||
(unsigned)log_get_head(), (unsigned)log_get_tail(),
|
||||
(unsigned)(p->address));
|
||||
}
|
||||
|
||||
static void cmd_i2c(char *args)
|
||||
{
|
||||
(void)args;
|
||||
/* i2c_post re-probes TCA9555 by reading its input register. */
|
||||
esp_err_t e = i2c_post();
|
||||
if (e != ESP_OK) { ERR("i2c tca9555=nack err=%s", esp_err_to_name(e)); return; }
|
||||
OK("i2c tca9555=ack");
|
||||
}
|
||||
|
||||
static void cmd_led(char *args)
|
||||
{
|
||||
/* BU.LED <mask 0..7> [on|off] — just writes mask if given; ignores the
|
||||
* optional second token and uses it when present to set/clear. */
|
||||
char *s = args;
|
||||
char *tok = next_tok(&s);
|
||||
if (!tok) { ERR("led reason=\"missing mask\""); return; }
|
||||
unsigned mask = (unsigned)strtoul(tok, NULL, 0);
|
||||
if (mask > 7) mask = 7;
|
||||
i2c_set_led1((uint8_t)mask);
|
||||
OK("led mask=%u", mask);
|
||||
}
|
||||
|
||||
static void cmd_adc_once(void)
|
||||
{
|
||||
int bat_mv = get_bat_raw_mv();
|
||||
float bat_V = get_battery_V();
|
||||
#ifdef BOARD_V5
|
||||
int isens_mv = get_isens_raw_mv();
|
||||
float isens_A = -(isens_mv - 1650.0f) / 13.2f;
|
||||
int voc_mv = get_voc_raw_mv();
|
||||
int fault = get_hw_overcurrent_fault() ? 1 : 0;
|
||||
OK("adc bat_mv=%d bat_V=%.3f isens_mv=%d isens_A=%+.2f voc_mv=%d fault=%d",
|
||||
bat_mv, bat_V, isens_mv, isens_A, voc_mv, fault);
|
||||
#else
|
||||
OK("adc bat_mv=%d bat_V=%.3f", bat_mv, bat_V);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void cmd_adc(char *args)
|
||||
{
|
||||
(void)args;
|
||||
cmd_adc_once();
|
||||
}
|
||||
|
||||
static void cmd_adc_stream(char *args)
|
||||
{
|
||||
char *s = args;
|
||||
char *t = next_tok(&s);
|
||||
int sec = t ? atoi(t) : 5;
|
||||
if (sec < 1) sec = 1;
|
||||
if (sec > 60) sec = 60;
|
||||
|
||||
int64_t end_us = esp_timer_get_time() + (int64_t)sec * 1000000;
|
||||
while (esp_timer_get_time() < end_us) {
|
||||
#ifdef BOARD_V5
|
||||
int bat_mv = get_bat_raw_mv();
|
||||
int isens_mv = get_isens_raw_mv();
|
||||
EVT("adc t=%.2f bat_mv=%d isens_mv=%d", elapsed_s(), bat_mv, isens_mv);
|
||||
#else
|
||||
int bat_mv = get_bat_raw_mv();
|
||||
EVT("adc t=%.2f bat_mv=%d", elapsed_s(), bat_mv);
|
||||
#endif
|
||||
esp_task_wdt_reset();
|
||||
vTaskDelay(pdMS_TO_TICKS(200));
|
||||
}
|
||||
OK("adc.stream sec=%d", sec);
|
||||
}
|
||||
|
||||
static void cmd_sensors_watch(char *args)
|
||||
{
|
||||
/* BU.SENSORS.WATCH [sec]
|
||||
* sec omitted or 0 → watch indefinitely; exit when any byte arrives
|
||||
* on UART0 (operator hit Enter on the host side).
|
||||
* sec > 0 → watch for that many seconds, then return.
|
||||
*/
|
||||
char *s = args;
|
||||
char *t = next_tok(&s);
|
||||
int sec = t ? atoi(t) : 0;
|
||||
bool indefinite = (sec <= 0);
|
||||
if (!indefinite && sec > 600) sec = 600;
|
||||
|
||||
static const char *names[N_SENSORS] = {"SAFETY", "DRIVE", "JACK", "AUX"};
|
||||
bool last_state[N_SENSORS];
|
||||
bool make_seen[N_SENSORS] = {false};
|
||||
bool break_seen[N_SENSORS] = {false};
|
||||
/* Read the GPIO directly — the normal sensor pipeline runs in the FSM
|
||||
* task (sensors_check()), which is paused while bring-up is active, so
|
||||
* get_sensor() returns stale state. Active-low → inverted. */
|
||||
#define _SENS_RAW(i) (!gpio_get_level(sensor_pins[i]))
|
||||
extern uint8_t sensor_pins[N_SENSORS];
|
||||
for (int i = 0; i < N_SENSORS; i++) last_state[i] = _SENS_RAW(i);
|
||||
|
||||
int64_t end_us = esp_timer_get_time() + (int64_t)sec * 1000000;
|
||||
int64_t next_snapshot_us = esp_timer_get_time();
|
||||
while (indefinite || esp_timer_get_time() < end_us) {
|
||||
/* Abort on any UART input. */
|
||||
size_t available = 0;
|
||||
if (uart_get_buffered_data_len(UART_NUM_0, &available) == ESP_OK
|
||||
&& available > 0) {
|
||||
uint8_t drain[64];
|
||||
while (available > 0) {
|
||||
int n = uart_read_bytes(UART_NUM_0, drain, sizeof(drain), 0);
|
||||
if (n <= 0) break;
|
||||
available = (size_t)n < available ? available - (size_t)n : 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
for (int i = 0; i < N_SENSORS; i++) {
|
||||
bool now = _SENS_RAW(i);
|
||||
if (now != last_state[i]) {
|
||||
const char *edge = now ? "make" : "break";
|
||||
if (now) make_seen[i] = true; else break_seen[i] = true;
|
||||
EVT("sensor name=%s edge=%s t=%.2f",
|
||||
names[i], edge, elapsed_s());
|
||||
last_state[i] = now;
|
||||
}
|
||||
}
|
||||
|
||||
/* Periodic state snapshot of all four sensors — includes the
|
||||
* no-connect slot so a floating/misrouted pin is visible. */
|
||||
int64_t now_us = esp_timer_get_time();
|
||||
if (now_us >= next_snapshot_us) {
|
||||
next_snapshot_us = now_us + 250000; /* 250 ms */
|
||||
EVT("state t=%.2f SAFETY=%d DRIVE=%d JACK=%d AUX=%d "
|
||||
"isr_s=%u isr_d=%u isr_j=%u isr_a=%u",
|
||||
elapsed_s(),
|
||||
(int)_SENS_RAW(SENSOR_SAFETY),
|
||||
(int)_SENS_RAW(SENSOR_DRIVE),
|
||||
(int)_SENS_RAW(SENSOR_JACK),
|
||||
(int)_SENS_RAW(SENSOR_AUX2),
|
||||
(unsigned)get_sensor_isr_edges(SENSOR_SAFETY),
|
||||
(unsigned)get_sensor_isr_edges(SENSOR_DRIVE),
|
||||
(unsigned)get_sensor_isr_edges(SENSOR_JACK),
|
||||
(unsigned)get_sensor_isr_edges(SENSOR_AUX2));
|
||||
}
|
||||
|
||||
esp_task_wdt_reset();
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
#undef _SENS_RAW
|
||||
|
||||
/* Summary: which sensors saw both edges. */
|
||||
char buf[128];
|
||||
size_t used = 0;
|
||||
for (int i = 0; i < N_SENSORS; i++) {
|
||||
const char *tag =
|
||||
(make_seen[i] && break_seen[i]) ? "both"
|
||||
: make_seen[i] ? "make_only"
|
||||
: break_seen[i] ? "break_only"
|
||||
: "none";
|
||||
int n = snprintf(buf + used, sizeof(buf) - used,
|
||||
"%s%s=%s", used ? " " : "", names[i], tag);
|
||||
if (n < 0 || (size_t)n >= sizeof(buf) - used) break;
|
||||
used += n;
|
||||
}
|
||||
OK("sensors.watch sec=%d %s", indefinite ? -1 : sec, buf);
|
||||
}
|
||||
|
||||
static bool parse_bridge(const char *s, bridge_t *out)
|
||||
{
|
||||
if (strcasecmp(s, "DRIVE") == 0) { *out = BRIDGE_DRIVE; return true; }
|
||||
if (strcasecmp(s, "JACK") == 0) { *out = BRIDGE_JACK; return true; }
|
||||
if (strcasecmp(s, "AUX") == 0) { *out = BRIDGE_AUX; return true; }
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool parse_dir(const char *s, uint8_t *out)
|
||||
{
|
||||
if (strcasecmp(s, "FWD") == 0 || strcasecmp(s, "UP") == 0)
|
||||
{ *out = BRIDGE_FWD; return true; }
|
||||
if (strcasecmp(s, "REV") == 0 || strcasecmp(s, "DOWN") == 0)
|
||||
{ *out = BRIDGE_REV; return true; }
|
||||
if (strcasecmp(s, "ON") == 0)
|
||||
{ *out = BRIDGE_ON; return true; }
|
||||
if (strcasecmp(s, "OFF") == 0)
|
||||
{ *out = BRIDGE_OFF; return true; }
|
||||
return false;
|
||||
}
|
||||
|
||||
static void cmd_relay(char *args)
|
||||
{
|
||||
char *s = args;
|
||||
char *t_bridge = next_tok(&s);
|
||||
char *t_dir = next_tok(&s);
|
||||
char *t_ms = next_tok(&s);
|
||||
if (!t_bridge || !t_dir) { ERR("relay reason=\"usage: <bridge> <dir> [ms]\""); return; }
|
||||
|
||||
int ms = t_ms ? atoi(t_ms) : 150;
|
||||
if (ms < 10) ms = 10;
|
||||
if (ms > 2000) ms = 2000;
|
||||
|
||||
if (!get_is_safe()) { SKIP("relay reason=\"safety open\""); return; }
|
||||
|
||||
/* P10 / sensor power rail. Default is ON; pulse it OFF to prove the line
|
||||
* can be driven, then restore. */
|
||||
if (strcasecmp(t_bridge, "SENSORS") == 0) {
|
||||
i2c_relays_sleep(); /* P10 low */
|
||||
vTaskDelay(pdMS_TO_TICKS(ms));
|
||||
i2c_relays_idle(); /* P10 high (restore) */
|
||||
OK("relay bridge=SENSORS ms=%d", ms);
|
||||
return;
|
||||
}
|
||||
|
||||
bridge_t b;
|
||||
uint8_t dir;
|
||||
if (!parse_bridge(t_bridge, &b)) { ERR("relay reason=\"bad bridge\""); return; }
|
||||
if (!parse_dir(t_dir, &dir)) { ERR("relay reason=\"bad dir\""); return; }
|
||||
|
||||
/* Sample current before, pulse, sample at midpoint, release, sample after.
|
||||
* FSM is paused during POST, so we drive process_bridge_current() ourselves
|
||||
* to refresh isens[].current before each read. We also mirror the relay
|
||||
* state into last_relay_state so V5's shared autozero gate (which looks at
|
||||
* last_relay_state to decide if bridges are powered) stays truthful. */
|
||||
extern volatile int64_t fsm_now;
|
||||
extern relay_port_t last_relay_state;
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(50)); /* let things settle */
|
||||
fsm_now = esp_timer_get_time();
|
||||
process_bridge_current(b);
|
||||
float I_before = get_bridge_A(b);
|
||||
|
||||
/* Which sensor to count edges on during the pulse — ISR-level counter,
|
||||
* doesn't depend on sensors_check() running. */
|
||||
sensor_t which_sensor = N_SENSORS;
|
||||
if (b == BRIDGE_DRIVE) which_sensor = SENSOR_DRIVE;
|
||||
else if (b == BRIDGE_JACK) which_sensor = SENSOR_JACK;
|
||||
uint32_t edges_before = (which_sensor < N_SENSORS)
|
||||
? get_sensor_isr_edges(which_sensor) : 0;
|
||||
|
||||
relay_port_t rs = {.raw = 0};
|
||||
switch (b) {
|
||||
case BRIDGE_DRIVE: rs.bridges.DRIVE = dir; break;
|
||||
case BRIDGE_JACK: rs.bridges.JACK = dir; break;
|
||||
case BRIDGE_AUX: rs.bridges.AUX = dir; break;
|
||||
default: ERR("relay reason=\"bad bridge idx\""); return;
|
||||
}
|
||||
rs.bridges.SENSORS = 1;
|
||||
last_relay_state = rs;
|
||||
i2c_set_relays(rs);
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(ms / 2));
|
||||
fsm_now = esp_timer_get_time();
|
||||
process_bridge_current(b);
|
||||
float I_mid = get_bridge_A(b);
|
||||
vTaskDelay(pdMS_TO_TICKS(ms - ms / 2));
|
||||
|
||||
relay_port_t idle = {.bridges = {.SENSORS = 1}};
|
||||
last_relay_state = idle;
|
||||
i2c_relays_idle();
|
||||
vTaskDelay(pdMS_TO_TICKS(50));
|
||||
fsm_now = esp_timer_get_time();
|
||||
process_bridge_current(b);
|
||||
float I_after = get_bridge_A(b);
|
||||
float heat = efuse_get_heat(b);
|
||||
int tripped = efuse_get(b) ? 1 : 0;
|
||||
uint32_t edges_after = (which_sensor < N_SENSORS)
|
||||
? get_sensor_isr_edges(which_sensor) : 0;
|
||||
uint32_t edges = edges_after - edges_before;
|
||||
|
||||
OK("relay bridge=%s dir=%s ms=%d I_before=%+.2f I_mid=%+.2f I_after=%+.2f heat=%.3f tripped=%d edges=%u",
|
||||
t_bridge, t_dir, ms, I_before, I_mid, I_after, heat, tripped, (unsigned)edges);
|
||||
}
|
||||
|
||||
static void cmd_rf_watch(char *args)
|
||||
{
|
||||
/* BU.RF.WATCH [sec]
|
||||
* sec omitted or 0 → watch indefinitely; exit when any byte arrives
|
||||
* on UART0 (operator hit Enter on the host side).
|
||||
*/
|
||||
char *s = args;
|
||||
char *t = next_tok(&s);
|
||||
int sec = t ? atoi(t) : 0;
|
||||
bool indefinite = (sec <= 0);
|
||||
if (!indefinite && sec > 600) sec = 600;
|
||||
|
||||
int64_t end_us = esp_timer_get_time() + (int64_t)sec * 1000000;
|
||||
int count = 0;
|
||||
/* Drain any stale code from before the watch started. */
|
||||
(void)rf_433_peek_latest();
|
||||
while (indefinite || esp_timer_get_time() < end_us) {
|
||||
/* Abort on any UART input. */
|
||||
size_t available = 0;
|
||||
if (uart_get_buffered_data_len(UART_NUM_0, &available) == ESP_OK
|
||||
&& available > 0) {
|
||||
uint8_t drain[64];
|
||||
while (available > 0) {
|
||||
int n = uart_read_bytes(UART_NUM_0, drain, sizeof(drain), 0);
|
||||
if (n <= 0) break;
|
||||
available = (size_t)n < available ? available - (size_t)n : 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
uint32_t code = rf_433_peek_latest();
|
||||
if (code) {
|
||||
EVT("rf code=0x%lX t=%.2f", (unsigned long)code, elapsed_s());
|
||||
count++;
|
||||
}
|
||||
esp_task_wdt_reset();
|
||||
vTaskDelay(pdMS_TO_TICKS(50));
|
||||
}
|
||||
OK("rf.watch sec=%d seen=%d", indefinite ? -1 : sec, count);
|
||||
}
|
||||
|
||||
static void cmd_wifi_start(char *args)
|
||||
{
|
||||
(void)args;
|
||||
esp_err_t e = webserver_init();
|
||||
if (e != ESP_OK) { ERR("wifi.start err=%s", esp_err_to_name(e)); return; }
|
||||
OK("wifi.start mode=AP ssid=\"%s\" ip=192.168.4.1",
|
||||
get_param_string(PARAM_WIFI_SSID));
|
||||
}
|
||||
|
||||
static void cmd_wifi_wait(char *args)
|
||||
{
|
||||
(void)args; /* no timeout — BRINGUP.md §4 Stage 6. Operator aborts via Ctrl+C. */
|
||||
wifi_sta_list_t sta = {0};
|
||||
int last_n = 0;
|
||||
while (1) {
|
||||
if (esp_wifi_ap_get_sta_list(&sta) == ESP_OK && sta.num > last_n) {
|
||||
EVT("wifi.assoc n=%d t=%.2f", sta.num, elapsed_s());
|
||||
last_n = sta.num;
|
||||
}
|
||||
/* Bail when at least one client is associated AND the web UI has
|
||||
* issued at least one request (notified by webserver). */
|
||||
if (last_n > 0 && s_http_reqs > 0) break;
|
||||
esp_task_wdt_reset();
|
||||
vTaskDelay(pdMS_TO_TICKS(200));
|
||||
}
|
||||
OK("wifi.wait clients=%d http_reqs=%d", last_n, s_http_reqs);
|
||||
}
|
||||
|
||||
static void cmd_fsm(char *args)
|
||||
{
|
||||
char *s = args;
|
||||
char *sub = next_tok(&s);
|
||||
if (!sub) sub = "INFO";
|
||||
str_upper(sub);
|
||||
if (strcmp(sub, "INFO") == 0) {
|
||||
OK("fsm state=%d err=%d idle=%d",
|
||||
(int)fsm_get_state(), (int)fsm_get_error(), fsm_is_idle() ? 1 : 0);
|
||||
} else {
|
||||
ERR("fsm reason=\"unknown subcommand\"");
|
||||
}
|
||||
}
|
||||
|
||||
static void cmd_solar_tick(char *args)
|
||||
{
|
||||
(void)args;
|
||||
(void)solar_run_fsm();
|
||||
OK("solar tick=ok chg_bulk=%d",
|
||||
gpio_get_level(GPIO_NUM_26));
|
||||
}
|
||||
|
||||
/* BU.PARAM GET <key> | BU.PARAM SET <key> <value> */
|
||||
static void cmd_param(char *args)
|
||||
{
|
||||
char *s = args;
|
||||
char *op = next_tok(&s);
|
||||
char *key = next_tok(&s);
|
||||
if (!op || !key) { ERR("param reason=\"usage: GET <k> | SET <k> <v>\""); return; }
|
||||
str_upper(op);
|
||||
int idx = param_find(key);
|
||||
if (idx < 0) { ERR("param reason=\"unknown key\" key=%s", key); return; }
|
||||
|
||||
param_type_e type = get_param_type((param_idx_t)idx);
|
||||
|
||||
if (strcmp(op, "GET") == 0) {
|
||||
param_value_t v = get_param_value_t((param_idx_t)idx);
|
||||
switch (type) {
|
||||
case PARAM_TYPE_u16: OK("param key=%s value=%u", key, v.u16); break;
|
||||
case PARAM_TYPE_i16: OK("param key=%s value=%d", key, v.i16); break;
|
||||
case PARAM_TYPE_u32: OK("param key=%s value=%u", key, (unsigned)v.u32); break;
|
||||
case PARAM_TYPE_i32: OK("param key=%s value=%d", key, (int)v.i32); break;
|
||||
case PARAM_TYPE_f32: OK("param key=%s value=%.9g", key, v.f32); break;
|
||||
case PARAM_TYPE_f64: OK("param key=%s value=%.17g",key, v.f64); break;
|
||||
case PARAM_TYPE_str: OK("param key=%s value=\"%s\"", key, get_param_string((param_idx_t)idx)); break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(op, "SET") == 0) {
|
||||
char *val = next_tok(&s);
|
||||
if (!val) { ERR("param reason=\"missing value\""); return; }
|
||||
esp_err_t e = ESP_OK;
|
||||
if (type == PARAM_TYPE_str) {
|
||||
e = set_param_string((param_idx_t)idx, val);
|
||||
} else {
|
||||
param_value_t v = {0};
|
||||
switch (type) {
|
||||
case PARAM_TYPE_u16: v.u16 = (uint16_t)strtoul(val, NULL, 0); break;
|
||||
case PARAM_TYPE_i16: v.i16 = (int16_t)strtol(val, NULL, 0); break;
|
||||
case PARAM_TYPE_u32: v.u32 = (uint32_t)strtoul(val, NULL, 0); break;
|
||||
case PARAM_TYPE_i32: v.i32 = (int32_t)strtol(val, NULL, 0); break;
|
||||
case PARAM_TYPE_f32: v.f32 = strtof(val, NULL); break;
|
||||
case PARAM_TYPE_f64: v.f64 = strtod(val, NULL); break;
|
||||
default: break;
|
||||
}
|
||||
e = set_param_value_t((param_idx_t)idx, v);
|
||||
}
|
||||
if (e != ESP_OK) { ERR("param reason=\"set failed\" err=%s", esp_err_to_name(e)); return; }
|
||||
e = commit_params();
|
||||
if (e != ESP_OK) { ERR("param reason=\"commit failed\" err=%s", esp_err_to_name(e)); return; }
|
||||
OK("param key=%s set=ok committed=yes", key);
|
||||
return;
|
||||
}
|
||||
|
||||
ERR("param reason=\"unknown op\" op=%s", op);
|
||||
}
|
||||
|
||||
/* -------- dispatcher -------- */
|
||||
|
||||
typedef void (*cmd_fn)(char *args);
|
||||
|
||||
struct cmd_entry {
|
||||
const char *name; /* uppercased, no BU. prefix */
|
||||
cmd_fn fn;
|
||||
};
|
||||
|
||||
static const struct cmd_entry CMDS[] = {
|
||||
{ "BEGIN", cmd_begin },
|
||||
{ "END", cmd_end },
|
||||
{ "INFO", cmd_info },
|
||||
{ "FLASH", cmd_flash },
|
||||
{ "I2C", cmd_i2c },
|
||||
{ "LED", cmd_led },
|
||||
{ "ADC", cmd_adc },
|
||||
{ "ADC.STREAM", cmd_adc_stream },
|
||||
{ "SENSORS.WATCH", cmd_sensors_watch},
|
||||
{ "RELAY", cmd_relay },
|
||||
{ "RF.WATCH", cmd_rf_watch },
|
||||
{ "WIFI.START", cmd_wifi_start },
|
||||
{ "WIFI.WAIT", cmd_wifi_wait },
|
||||
{ "FSM", cmd_fsm },
|
||||
{ "SOLAR.TICK", cmd_solar_tick },
|
||||
{ "PARAM", cmd_param },
|
||||
};
|
||||
|
||||
void bringup_handle_line(char *line)
|
||||
{
|
||||
/* Trim leading whitespace. */
|
||||
while (*line == ' ' || *line == '\t') line++;
|
||||
if (*line == '\0') return;
|
||||
|
||||
/* Expect "BU.<CMD> [args]" */
|
||||
if (strncasecmp(line, "BU.", 3) != 0) {
|
||||
ERR("dispatch reason=\"missing BU. prefix\"");
|
||||
return;
|
||||
}
|
||||
line += 3;
|
||||
|
||||
/* Split CMD token from args. */
|
||||
char *sp = line;
|
||||
while (*sp && *sp != ' ' && *sp != '\t') sp++;
|
||||
char *args = sp;
|
||||
if (*sp) { *sp = '\0'; args = sp + 1; }
|
||||
|
||||
str_upper(line);
|
||||
|
||||
for (size_t i = 0; i < sizeof(CMDS)/sizeof(CMDS[0]); i++) {
|
||||
if (strcmp(line, CMDS[i].name) == 0) {
|
||||
CMDS[i].fn(args);
|
||||
return;
|
||||
}
|
||||
}
|
||||
ERR("dispatch reason=\"unknown command\" cmd=%s", line);
|
||||
}
|
||||
35
main/bringup.h
Normal file
35
main/bringup.h
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* bringup.h — manufacturing / bench bring-up protocol over UART.
|
||||
*
|
||||
* See docs/SC-F001/BRINGUP.md for the line protocol and stage procedure.
|
||||
*
|
||||
* While bring-up mode is active:
|
||||
* - uart_comms routes every input line to bringup_handle_line() instead of
|
||||
* the JSON parser.
|
||||
* - control_fsm pauses (stays in STATE_IDLE, skips relay writes).
|
||||
* - BU.* commands drive the hardware directly via i2c_set_relays etc.
|
||||
*
|
||||
* Note: this is distinct from the per-module POST routines (adc_post,
|
||||
* i2c_post, storage_post) which run at normal boot and are genuinely
|
||||
* power-on self-tests.
|
||||
*/
|
||||
|
||||
#ifndef MAIN_BRINGUP_H_
|
||||
#define MAIN_BRINGUP_H_
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
void bringup_mode_enter(void);
|
||||
void bringup_mode_exit(void);
|
||||
bool bringup_mode_is_active(void);
|
||||
|
||||
/* Called by uart_comms when a full line has been received in bring-up mode.
|
||||
* `line` is null-terminated, no trailing \r\n. Response is printed via
|
||||
* printf(); caller doesn't need to capture it. */
|
||||
void bringup_handle_line(char *line);
|
||||
|
||||
/* Counted by webserver when the root page is served. BU.WIFI.WAIT uses it
|
||||
* to confirm that an associated client actually loaded the UI. */
|
||||
void bringup_notify_http_request(void);
|
||||
|
||||
#endif /* MAIN_BRINGUP_H_ */
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "esp_timer.h"
|
||||
#include "i2c.h"
|
||||
#include "power_mgmt.h"
|
||||
#include "bringup.h"
|
||||
#include "rtc_wdt.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "sc_err.h"
|
||||
@@ -65,6 +66,10 @@ fsm_state_t fsm_get_state() {
|
||||
return current_state;
|
||||
}
|
||||
|
||||
bool fsm_is_idle(void) {
|
||||
return current_state == STATE_IDLE;
|
||||
}
|
||||
|
||||
static int64_t timer_end = 0;
|
||||
static int64_t timer_start = 0;
|
||||
static inline void set_timer(uint64_t us) {
|
||||
@@ -186,9 +191,15 @@ void control_task(void *param) {
|
||||
while (enabled) {
|
||||
vTaskDelayUntil(&xLastWakeTime, xFrequency);
|
||||
fsm_now = esp_timer_get_time();
|
||||
|
||||
|
||||
/* Bring-up tool owns the relays and ADCs while active — skip. */
|
||||
if (bringup_mode_is_active()) {
|
||||
esp_task_wdt_reset();
|
||||
continue;
|
||||
}
|
||||
|
||||
bool log = false;
|
||||
|
||||
|
||||
/**** READ INPUTS ****/
|
||||
for (uint8_t i = 0; i < N_BRIDGES; i++) {
|
||||
process_bridge_current(i);
|
||||
|
||||
@@ -104,6 +104,7 @@ void fsm_set_remaining_distance(float x);
|
||||
int8_t fsm_get_current_progress(int8_t remainder);
|
||||
|
||||
fsm_state_t fsm_get_state();
|
||||
bool fsm_is_idle(void);
|
||||
|
||||
int8_t get_bridge_state(bridge_t bridge);
|
||||
|
||||
|
||||
@@ -68,6 +68,14 @@ esp_err_t i2c_set_relays(relay_port_t states) {
|
||||
return tca_write_word_8(TCA_REG_OUTPUT1, states.raw);
|
||||
}
|
||||
|
||||
esp_err_t i2c_relays_idle(void) {
|
||||
return i2c_set_relays((relay_port_t){.bridges = {.SENSORS = 1}});
|
||||
}
|
||||
|
||||
esp_err_t i2c_relays_sleep(void) {
|
||||
return i2c_set_relays((relay_port_t){.raw = 0});
|
||||
}
|
||||
|
||||
esp_err_t i2c_set_led1(uint8_t state) {
|
||||
// push 3 LSB to top
|
||||
return tca_write_word_8(TCA_REG_OUTPUT0, state<<5);
|
||||
|
||||
@@ -55,6 +55,13 @@ esp_err_t i2c_stop(void);
|
||||
esp_err_t i2c_set_relays(relay_port_t states);
|
||||
esp_err_t i2c_set_led1(uint8_t state);
|
||||
|
||||
/* Normal run state: all bridges off, but P10 (sensor rail) held high.
|
||||
* Use whenever "everything off" is really "all motors off, system is still on". */
|
||||
esp_err_t i2c_relays_idle(void);
|
||||
|
||||
/* Sleep state: all bridges off AND P10 low, cutting power to the sensors. */
|
||||
esp_err_t i2c_relays_sleep(void);
|
||||
|
||||
esp_err_t i2c_poll_buttons();
|
||||
|
||||
bool i2c_get_button_tripped(uint8_t button);
|
||||
|
||||
13
main/main.c
13
main/main.c
@@ -18,6 +18,7 @@
|
||||
#include "rf_433.h"
|
||||
#include "bt_hid.h"
|
||||
#include "webserver.h"
|
||||
#include "bringup.h"
|
||||
#include "comms_events.h"
|
||||
#include "version.h"
|
||||
#include <string.h>
|
||||
@@ -160,7 +161,9 @@ void app_main(void) {esp_task_wdt_add(NULL);
|
||||
init_critical("I2C", i2c_init);
|
||||
drive_leds(LED_BOOTING); // LED on ASAP after I2C is up
|
||||
i2c_post(); // verify TCA9555 responds
|
||||
i2c_set_relays((relay_port_t){.raw=0});
|
||||
/* Sensors powered from boot; FSM will keep P10 high on every tick.
|
||||
* Drops back to 0 on soft_idle_enter() (sleep). */
|
||||
i2c_relays_idle();
|
||||
|
||||
if (rtc_xtal_init() != ESP_OK) ESP_LOGE(TAG, "RTC FAILED");
|
||||
|
||||
@@ -321,7 +324,13 @@ void app_main(void) {esp_task_wdt_add(NULL);
|
||||
|
||||
while(true) {
|
||||
vTaskDelayUntil(&xLastWakeTime, xFrequency);
|
||||
|
||||
|
||||
/* Bring-up tool owns the LEDs, buttons, and relays while active. */
|
||||
if (bringup_mode_is_active()) {
|
||||
esp_task_wdt_reset();
|
||||
continue;
|
||||
}
|
||||
|
||||
/* In soft idle: slow poll (5s) via direct GPIO, no I2C. */
|
||||
// TODO: Critique & confirm what we do in idle
|
||||
if (soft_idle_is_active()) {
|
||||
|
||||
@@ -584,6 +584,37 @@ bool get_hw_overcurrent_fault(void)
|
||||
#endif
|
||||
}
|
||||
|
||||
static int read_mv_channel(adc_channel_t ch)
|
||||
{
|
||||
int raw = 0, mv = 0;
|
||||
if (adc_oneshot_read(adc1_handle, ch, &raw) != ESP_OK) return 0;
|
||||
if (adc_cali_raw_to_voltage(adc_cali_handle, raw, &mv) != ESP_OK) return 0;
|
||||
return mv;
|
||||
}
|
||||
|
||||
int get_bat_raw_mv(void)
|
||||
{
|
||||
return read_mv_channel(PIN_V_SENS_BAT);
|
||||
}
|
||||
|
||||
int get_isens_raw_mv(void)
|
||||
{
|
||||
#ifdef BOARD_V5
|
||||
return read_mv_channel(PIN_V_ISENS_MAIN);
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
int get_voc_raw_mv(void)
|
||||
{
|
||||
#ifdef BOARD_V5
|
||||
return read_mv_channel(PIN_V_VOC);
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
efuse_trip_t efuse_get(bridge_t bridge)
|
||||
{
|
||||
if (bridge >= N_BRIDGES) return false;
|
||||
|
||||
@@ -34,6 +34,11 @@ float get_battery_V();
|
||||
// Always false on V4.
|
||||
bool get_hw_overcurrent_fault(void);
|
||||
|
||||
// Raw, unfiltered ADC reads — used by POST. Return 0 on error.
|
||||
int get_bat_raw_mv(void);
|
||||
int get_isens_raw_mv(void); // V5 only — returns 0 on V4
|
||||
int get_voc_raw_mv(void); // V5 only — returns 0 on V4
|
||||
|
||||
void disable_autozero(bridge_t bridge);
|
||||
bool get_bridge_overcurrent(bridge_t bridge, float threshold);
|
||||
bool get_bridge_spike(bridge_t bridge, float threshold);
|
||||
|
||||
@@ -43,6 +43,15 @@ bool controls_enabled = true;
|
||||
// Temporary storage for learned keycodes (not committed to params yet)
|
||||
static int64_t temp_keycodes[NUM_RF_BUTTONS] = {0};
|
||||
|
||||
// Most recently decoded raw code, read-and-clear via rf_433_peek_latest().
|
||||
static volatile uint32_t latest_code = 0;
|
||||
|
||||
uint32_t rf_433_peek_latest(void) {
|
||||
uint32_t c = latest_code;
|
||||
latest_code = 0;
|
||||
return c;
|
||||
}
|
||||
|
||||
// For rmt_rx_register_event_callbacks
|
||||
static bool rfrx_done(rmt_channel_handle_t channel, const rmt_rx_done_event_data_t *edata, void *udata) {
|
||||
BaseType_t high_task_wakeup = pdFALSE;
|
||||
@@ -137,6 +146,7 @@ static void rf_433_receiver_task(void* param) {
|
||||
|
||||
// If we got a valid code, process it
|
||||
if (code) {
|
||||
latest_code = code;
|
||||
ESP_LOGI(TAG, "GOT KEYCODE 0x%lx [%d]", (long) code, len);
|
||||
|
||||
if (learn_flag >= 0) {
|
||||
|
||||
@@ -11,11 +11,14 @@
|
||||
|
||||
#define NUM_RF_BUTTONS 8
|
||||
|
||||
int64_t receive_keycode(void);
|
||||
|
||||
esp_err_t rf_433_init();
|
||||
esp_err_t rf_433_stop();
|
||||
|
||||
/* Consume-once peek of the most recently decoded raw code. Returns 0 if
|
||||
* none has arrived since the last call. Used by the bring-up RF.WATCH
|
||||
* stage; does not interfere with the normal decode/dispatch path. */
|
||||
uint32_t rf_433_peek_latest(void);
|
||||
|
||||
void rf_433_learn_keycode(uint8_t index);
|
||||
void rf_433_cancel_learn_keycode();
|
||||
|
||||
|
||||
@@ -81,10 +81,12 @@ void soft_idle_enter(void)
|
||||
{
|
||||
if (in_soft_idle) return;
|
||||
in_soft_idle = true;
|
||||
ESP_LOGI("RTC", "Entering soft idle (WiFi/BT off, LEDs off)");
|
||||
ESP_LOGI("RTC", "Entering soft idle (WiFi/BT off, LEDs off, sensors off)");
|
||||
webserver_stop();
|
||||
bt_hid_stop();
|
||||
i2c_set_led1(0);
|
||||
/* Drop P10 to kill sensor rail power while we're asleep. */
|
||||
i2c_relays_sleep();
|
||||
}
|
||||
|
||||
bool soft_idle_is_active(void) { return in_soft_idle; }
|
||||
@@ -95,6 +97,8 @@ void soft_idle_exit(void)
|
||||
if (!in_soft_idle) return;
|
||||
in_soft_idle = false;
|
||||
ESP_LOGI("RTC", "Exiting soft idle");
|
||||
/* Bring sensor rail back before anything else tries to read sensors. */
|
||||
i2c_relays_idle();
|
||||
webserver_restart_wifi();
|
||||
bt_hid_resume();
|
||||
rtc_reset_shutdown_timer();
|
||||
|
||||
@@ -17,14 +17,21 @@
|
||||
static const char* TAG = "SENS";
|
||||
|
||||
#ifdef BOARD_V5
|
||||
// V5 labels 2/3/4/1 → IO14/16/19/27 → SAFETY/JACK/DRIVE/nc
|
||||
// V5 physical connectors:
|
||||
// J1 = IO27 → SAFETY
|
||||
// J2 = IO14 → JACK
|
||||
// J3 = IO23 → n/c (AUX) (J3 unreliable on the V5 board, moved DRIVE off)
|
||||
// J4 = IO19 → DRIVE
|
||||
// Array order matches sensor_t: SAFETY, DRIVE, JACK, AUX2
|
||||
uint8_t sensor_pins[N_SENSORS] = {GPIO_NUM_14, GPIO_NUM_19, GPIO_NUM_16, GPIO_NUM_27};
|
||||
uint8_t sensor_pins[N_SENSORS] = {GPIO_NUM_27, GPIO_NUM_19, GPIO_NUM_14, GPIO_NUM_23};
|
||||
#else // BOARD_V4
|
||||
uint8_t sensor_pins[N_SENSORS] = {GPIO_NUM_27, GPIO_NUM_14, GPIO_NUM_16, GPIO_NUM_19};
|
||||
#endif
|
||||
|
||||
volatile int16_t sensor_count[N_SENSORS] = {0};
|
||||
/* Bumped directly in the ISR on every edge — does not require sensors_check()
|
||||
* to run, so it works even while bring-up pauses the FSM task. */
|
||||
volatile uint32_t sensor_isr_edge_count[N_SENSORS] = {0};
|
||||
static volatile uint64_t sensor_last_isr_time[N_SENSORS] = {0};
|
||||
static volatile bool sensor_stable_state[N_SENSORS] = {false};
|
||||
static QueueHandle_t sensor_event_queue = NULL;
|
||||
@@ -59,6 +66,8 @@ static void IRAM_ATTR sensor_isr_handler(void* arg) {
|
||||
uint64_t now = esp_timer_get_time();
|
||||
sensor_last_isr_time[i] = now;
|
||||
|
||||
sensor_isr_edge_count[i]++;
|
||||
|
||||
sensor_event_t evt = {.sensor_id = i, .level = !gpio_get_level(gpio_num)};
|
||||
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
||||
xQueueSendFromISR(sensor_event_queue, &evt, &xHigherPriorityTaskWoken);
|
||||
@@ -69,6 +78,17 @@ esp_err_t sensors_init() {
|
||||
|
||||
uint64_t pin_mask = 0;
|
||||
for (uint8_t i = 0; i < N_SENSORS; i++) pin_mask |= (1ULL << sensor_pins[i]);
|
||||
|
||||
/* Belt-and-suspenders: force each sensor pin into digital-GPIO mode with
|
||||
* pull-up explicitly applied. gpio_config()'s pull_up_en is known to be
|
||||
* shadowed by RTC-subsystem settings on RTC-capable pins (IO27, 32, 33,
|
||||
* 34–39). gpio_reset_pin() detaches any lingering RTC/peripheral mux,
|
||||
* and the explicit gpio_set_pull_mode() call goes through the right
|
||||
* path regardless of which sub-block owns the pin. */
|
||||
for (uint8_t i = 0; i < N_SENSORS; i++) {
|
||||
gpio_reset_pin(sensor_pins[i]);
|
||||
}
|
||||
|
||||
gpio_config_t io_conf = {
|
||||
.pin_bit_mask = pin_mask,
|
||||
.mode = GPIO_MODE_INPUT,
|
||||
@@ -78,6 +98,10 @@ esp_err_t sensors_init() {
|
||||
};
|
||||
ESP_ERROR_CHECK(gpio_config(&io_conf));
|
||||
|
||||
for (uint8_t i = 0; i < N_SENSORS; i++) {
|
||||
ESP_ERROR_CHECK(gpio_set_pull_mode(sensor_pins[i], GPIO_PULLUP_ONLY));
|
||||
}
|
||||
|
||||
sensor_event_queue = xQueueCreate(16, sizeof(sensor_event_t));
|
||||
if (!sensor_event_queue) {
|
||||
ESP_LOGE(TAG, "Failed to create sensor queue");
|
||||
@@ -165,7 +189,9 @@ void sensors_check() {
|
||||
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});
|
||||
/* Kill all bridges but leave the sensor rail up — we still
|
||||
* want to observe the safety input. */
|
||||
i2c_relays_idle();
|
||||
ESP_LOGI(TAG, "SAFETY BREAK - Relays disabled");
|
||||
}
|
||||
}
|
||||
@@ -198,4 +224,9 @@ int16_t get_sensor_counter(sensor_t i) {
|
||||
|
||||
void set_sensor_counter(sensor_t i, int16_t to) {
|
||||
sensor_count[i] = to;
|
||||
}
|
||||
|
||||
uint32_t get_sensor_isr_edges(sensor_t i) {
|
||||
if (i >= N_SENSORS) return 0;
|
||||
return sensor_isr_edge_count[i];
|
||||
}
|
||||
@@ -27,6 +27,11 @@ void reset_sensor_counter(sensor_t i);
|
||||
void set_sensor_counter(sensor_t i, int16_t to);
|
||||
int16_t get_sensor_counter(sensor_t i);
|
||||
|
||||
/* Raw ISR-level edge count. Bumped on every GPIO transition by the
|
||||
* sensor ISR regardless of whether sensors_check() is running; safe to
|
||||
* read during bring-up while the FSM task is paused. */
|
||||
uint32_t get_sensor_isr_edges(sensor_t i);
|
||||
|
||||
bool get_sensor(sensor_t i);
|
||||
bool get_is_safe(void);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "uart_comms.h"
|
||||
#include "comms.h"
|
||||
#include "bringup.h"
|
||||
#include "cJSON.h"
|
||||
#include "driver/uart.h"
|
||||
#include "esp_log.h"
|
||||
@@ -143,12 +144,20 @@ static void process_command(char *cmd) {
|
||||
while (*cmd == ' ' || *cmd == '\t') {
|
||||
cmd++;
|
||||
}
|
||||
|
||||
|
||||
// Ignore empty commands
|
||||
if (*cmd == '\0') {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Route bring-up traffic to the BU parser. BU.BEGIN flips the mode flag
|
||||
// so the subsequent lines in the same session come here too.
|
||||
if (bringup_mode_is_active() ||
|
||||
strncasecmp(cmd, "BU.", 3) == 0) {
|
||||
bringup_handle_line(cmd);
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert to uppercase for command matching
|
||||
char command[16] = {0};
|
||||
int i = 0;
|
||||
@@ -208,36 +217,37 @@ void uart_event_task(void *pvParameters) {
|
||||
if (len > 0) {
|
||||
for (int i = 0; i < len; i++) {
|
||||
char c = (char)data[i];
|
||||
|
||||
// Echo character
|
||||
printf("%c", c);
|
||||
fflush(stdout);
|
||||
|
||||
bool quiet = bringup_mode_is_active();
|
||||
|
||||
// Echo character (suppressed in bring-up mode — machine-parseable output)
|
||||
if (!quiet) { printf("%c", c); fflush(stdout); }
|
||||
|
||||
// Handle backspace
|
||||
if (c == '\b' || c == 127) {
|
||||
if (cmd_pos > 0) {
|
||||
cmd_pos--;
|
||||
printf(" \b"); // Clear character on screen
|
||||
if (!quiet) { printf(" \b"); fflush(stdout); }
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle newline/carriage return
|
||||
if (c == '\n' || c == '\r') {
|
||||
cmd_buffer[cmd_pos] = '\0';
|
||||
if (!quiet) printf("\n");
|
||||
|
||||
if (cmd_pos > 0) {
|
||||
process_command(cmd_buffer);
|
||||
}
|
||||
|
||||
cmd_pos = 0;
|
||||
if (!bringup_mode_is_active()) {
|
||||
printf("\n> ");
|
||||
fflush(stdout);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle newline/carriage return
|
||||
if (c == '\n' || c == '\r') {
|
||||
cmd_buffer[cmd_pos] = '\0';
|
||||
printf("\n");
|
||||
|
||||
if (cmd_pos > 0) {
|
||||
process_command(cmd_buffer);
|
||||
}
|
||||
|
||||
cmd_pos = 0;
|
||||
printf("\n> ");
|
||||
fflush(stdout);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// Add to buffer if not full
|
||||
if (cmd_pos < CMD_MAX_LEN - 1) {
|
||||
cmd_buffer[cmd_pos++] = c;
|
||||
|
||||
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, 0xb5, 0x89, 0xe7, 0x69, 0x02, 0xff, 0xed, 0x3d, 0xfd, 0x57, 0xdb, 0x48,
|
||||
0x1f, 0x8b, 0x08, 0x00, 0xe4, 0x3c, 0xe9, 0x69, 0x02, 0xff, 0xed, 0x3d, 0xfd, 0x57, 0xdb, 0x48,
|
||||
0x92, 0x3f, 0xef, 0xfc, 0x15, 0x4d, 0x92, 0x21, 0x52, 0x10, 0xc2, 0x86, 0x64, 0x66, 0xd6, 0x46,
|
||||
0x66, 0x09, 0x98, 0x0d, 0x93, 0x04, 0x78, 0xd8, 0x24, 0x33, 0xc7, 0xf1, 0x90, 0x6c, 0xb5, 0xb1,
|
||||
0x06, 0x59, 0xf2, 0x4a, 0x32, 0xc4, 0x6b, 0xfc, 0xbf, 0x5f, 0x55, 0x7f, 0x48, 0xad, 0x0f, 0x1b,
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "endian.h"
|
||||
#include "esp_ota_ops.h"
|
||||
#include "esp_timer.h"
|
||||
#include "bringup.h"
|
||||
#include "power_mgmt.h"
|
||||
#include "rf_433.h"
|
||||
#include "rtc.h"
|
||||
@@ -65,11 +66,13 @@ char http_buffer[4096];
|
||||
/* Handler to serve the HTML page */
|
||||
static esp_err_t root_get_handler(httpd_req_t *req) {
|
||||
//ESP_LOGI(TAG, "root_get_handler");
|
||||
|
||||
|
||||
if (req == NULL) {
|
||||
ESP_LOGE(TAG, "Null request pointer");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
bringup_notify_http_request();
|
||||
|
||||
// Send the HTML response
|
||||
esp_err_t err = httpd_resp_set_type(req, "text/html");
|
||||
|
||||
Reference in New Issue
Block a user