Compare commits
22
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ce9d729027 | ||
|
|
06acd84928 | ||
|
|
cfd047a0ac | ||
|
|
a3830a58cb | ||
|
|
3785f07189 | ||
|
|
37b772218b | ||
|
|
8f7db974fa | ||
|
|
02d226b949 | ||
|
|
d71e28c85d | ||
|
|
adff1d963b | ||
|
|
d84116c45b | ||
|
|
1ade64b2b2 | ||
|
|
20535ceb14 | ||
|
|
76ef75da29 | ||
|
|
d70df219f8 | ||
|
|
7e854e10f0 | ||
|
|
b6c385e09c | ||
|
|
a9f76ea058 | ||
|
|
c40c714028 | ||
|
|
f87ab0c65f | ||
|
|
b4230b2a0e | ||
|
|
04e1ed0f16 |
@@ -4,6 +4,7 @@
|
|||||||
.vscode/launch.json
|
.vscode/launch.json
|
||||||
.vscode/ipch
|
.vscode/ipch
|
||||||
build/
|
build/
|
||||||
|
build_host/
|
||||||
sdkconfig
|
sdkconfig
|
||||||
sdkconfig.old
|
sdkconfig.old
|
||||||
managed_components/
|
managed_components/
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
idf_component_register(SRCS "app_machine.c"
|
idf_component_register(SRCS "app_machine.c"
|
||||||
INCLUDE_DIRS "include"
|
INCLUDE_DIRS "include"
|
||||||
REQUIRES ui_cmd app_process
|
REQUIRES ui_cmd app_process
|
||||||
PRIV_REQUIRES hal_motor hal_temp hal_audio)
|
PRIV_REQUIRES hal_motor hal_temp hal_audio freertos)
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/queue.h"
|
||||||
|
|
||||||
#include "app_machine.h"
|
#include "app_machine.h"
|
||||||
#include "app_process.h"
|
#include "app_process.h"
|
||||||
#include "hal_motor.h"
|
#include "hal_motor.h"
|
||||||
@@ -5,6 +8,7 @@
|
|||||||
#include "hal_audio.h"
|
#include "hal_audio.h"
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
#define AGITATE_RPM 60u
|
#define AGITATE_RPM 60u
|
||||||
|
|
||||||
@@ -16,20 +20,13 @@ static uint32_t s_deadline_ms;
|
|||||||
static bool s_have_deadline;
|
static bool s_have_deadline;
|
||||||
static bool s_resume_pending;
|
static bool s_resume_pending;
|
||||||
static bool s_auto_advance;
|
static bool s_auto_advance;
|
||||||
static ui_evt_t s_q[UI_EVT_QUEUE_LEN];
|
static QueueHandle_t s_evtq;
|
||||||
static uint8_t s_q_head;
|
|
||||||
static uint8_t s_q_tail;
|
|
||||||
static uint8_t s_q_count;
|
|
||||||
|
|
||||||
static void emit(ui_evt_t ev)
|
static void emit(ui_evt_t ev)
|
||||||
{
|
{
|
||||||
if (s_q_count == UI_EVT_QUEUE_LEN) {
|
if (s_evtq != NULL) {
|
||||||
s_q_head = (uint8_t)((s_q_head + 1u) % UI_EVT_QUEUE_LEN);
|
xQueueSend(s_evtq, &ev, 0);
|
||||||
s_q_count--;
|
|
||||||
}
|
}
|
||||||
s_q[s_q_tail] = ev;
|
|
||||||
s_q_tail = (uint8_t)((s_q_tail + 1u) % UI_EVT_QUEUE_LEN);
|
|
||||||
s_q_count++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void fill_common(ui_evt_t *ev)
|
static void fill_common(ui_evt_t *ev)
|
||||||
@@ -122,14 +119,13 @@ static void enter_armed(void)
|
|||||||
emit_id(EVT_STEP_ARMED);
|
emit_id(EVT_STEP_ARMED);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void start_running(uint32_t now_ms)
|
static void start_running(void)
|
||||||
{
|
{
|
||||||
const process_def_t *p = app_process_get(s_proc);
|
const process_def_t *p = app_process_get(s_proc);
|
||||||
uint16_t t_s = app_process_time_s(s_proc, s_step);
|
uint16_t t_s = app_process_time_s(s_proc, s_step);
|
||||||
s_remaining_ms = (uint32_t)t_s * 1000u;
|
s_remaining_ms = (uint32_t)t_s * 1000u;
|
||||||
s_deadline_ms = now_ms + s_remaining_ms;
|
s_have_deadline = false;
|
||||||
s_have_deadline = true;
|
s_resume_pending = true;
|
||||||
s_resume_pending = false;
|
|
||||||
|
|
||||||
if (t_s == 0 || p == NULL) {
|
if (t_s == 0 || p == NULL) {
|
||||||
hal_motor_enable(false);
|
hal_motor_enable(false);
|
||||||
@@ -198,10 +194,14 @@ void app_machine_init(void)
|
|||||||
s_deadline_ms = 0;
|
s_deadline_ms = 0;
|
||||||
s_have_deadline = false;
|
s_have_deadline = false;
|
||||||
s_resume_pending = false;
|
s_resume_pending = false;
|
||||||
s_auto_advance = false;
|
s_auto_advance = true;
|
||||||
s_q_head = 0;
|
|
||||||
s_q_tail = 0;
|
if (s_evtq == NULL) {
|
||||||
s_q_count = 0;
|
s_evtq = xQueueCreate(UI_EVT_QUEUE_LEN, sizeof(ui_evt_t));
|
||||||
|
} else {
|
||||||
|
xQueueReset(s_evtq);
|
||||||
|
}
|
||||||
|
|
||||||
app_process_init();
|
app_process_init();
|
||||||
hal_motor_init();
|
hal_motor_init();
|
||||||
hal_temp_init();
|
hal_temp_init();
|
||||||
@@ -230,13 +230,13 @@ uint32_t app_machine_remaining_ms(void)
|
|||||||
|
|
||||||
int app_machine_last_event(ui_evt_t *out)
|
int app_machine_last_event(ui_evt_t *out)
|
||||||
{
|
{
|
||||||
if (s_q_count == 0 || out == NULL) {
|
if (s_evtq == NULL || out == NULL) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
*out = s_q[s_q_head];
|
if (xQueueReceive(s_evtq, out, 0) == pdTRUE) {
|
||||||
s_q_head = (uint8_t)((s_q_head + 1u) % UI_EVT_QUEUE_LEN);
|
return 1;
|
||||||
s_q_count--;
|
}
|
||||||
return 1;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void app_machine_on_temp(float c, bool ok)
|
void app_machine_on_temp(float c, bool ok)
|
||||||
@@ -336,7 +336,7 @@ void app_machine_handle_cmd(const ui_cmd_t *cmd)
|
|||||||
enter_armed();
|
enter_armed();
|
||||||
} else if (s_state == ST_ARMED) {
|
} else if (s_state == ST_ARMED) {
|
||||||
apply_step_index(cmd->step_index);
|
apply_step_index(cmd->step_index);
|
||||||
start_running(0);
|
start_running();
|
||||||
} else if (s_state == ST_COMPLETE) {
|
} else if (s_state == ST_COMPLETE) {
|
||||||
arm_or_next_from_complete();
|
arm_or_next_from_complete();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ set(priv_req driver)
|
|||||||
if(IDF_TARGET STREQUAL "esp32")
|
if(IDF_TARGET STREQUAL "esp32")
|
||||||
list(APPEND srcs wroom/hal_audio.c)
|
list(APPEND srcs wroom/hal_audio.c)
|
||||||
list(APPEND priv_req board_wroom)
|
list(APPEND priv_req board_wroom)
|
||||||
|
elseif(IDF_TARGET STREQUAL "esp32s3")
|
||||||
|
list(APPEND srcs s3/hal_audio.c)
|
||||||
|
list(APPEND priv_req board_jc4827w543)
|
||||||
else()
|
else()
|
||||||
list(APPEND srcs stub/hal_audio.c)
|
list(APPEND srcs stub/hal_audio.c)
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -0,0 +1,212 @@
|
|||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "esp_err.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "driver/i2s_std.h"
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
#include "freertos/queue.h"
|
||||||
|
|
||||||
|
#include "board.h"
|
||||||
|
|
||||||
|
#define SPK_SAMPLE_RATE 48000
|
||||||
|
#define TONE_HZ 2000
|
||||||
|
#define TONE_PERIOD_SMP (SPK_SAMPLE_RATE / TONE_HZ) /* 24 samples */
|
||||||
|
#define CHUNK_MS 10
|
||||||
|
#define CHUNK_FRAMES (SPK_SAMPLE_RATE / 1000 * CHUNK_MS) /* 480, multiple of period */
|
||||||
|
#define TONE_AMPLITUDE 12000
|
||||||
|
#define DMA_DESC_NUM 4
|
||||||
|
#define DMA_FRAME_NUM 240
|
||||||
|
|
||||||
|
static const char *TAG = "audio";
|
||||||
|
|
||||||
|
enum {
|
||||||
|
AUDIO_SHORT = 1,
|
||||||
|
AUDIO_ALARM,
|
||||||
|
AUDIO_CANCEL,
|
||||||
|
};
|
||||||
|
|
||||||
|
static QueueHandle_t s_q;
|
||||||
|
static TaskHandle_t s_task;
|
||||||
|
static i2s_chan_handle_t s_tx;
|
||||||
|
static bool s_chan_on;
|
||||||
|
static bool s_inited;
|
||||||
|
|
||||||
|
static int16_t s_tone[CHUNK_FRAMES * 2];
|
||||||
|
static int16_t s_silence[CHUNK_FRAMES * 2];
|
||||||
|
|
||||||
|
static void chan_on(void)
|
||||||
|
{
|
||||||
|
if (!s_chan_on && s_tx != NULL) {
|
||||||
|
i2s_channel_enable(s_tx);
|
||||||
|
s_chan_on = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void chan_off(void)
|
||||||
|
{
|
||||||
|
if (s_chan_on && s_tx != NULL) {
|
||||||
|
size_t w = 0;
|
||||||
|
i2s_channel_write(s_tx, s_silence, sizeof(s_silence), &w, 50);
|
||||||
|
i2s_channel_disable(s_tx);
|
||||||
|
s_chan_on = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool drain_cancel(void)
|
||||||
|
{
|
||||||
|
int msg;
|
||||||
|
while (xQueueReceive(s_q, &msg, 0) == pdTRUE) {
|
||||||
|
if (msg == AUDIO_CANCEL) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Write buf for ~ms wall time. DMA pacing makes each 10 ms write block
|
||||||
|
* in this task only; the UI path never touches I2S. */
|
||||||
|
static bool play_buf_ms(const int16_t *buf, size_t len, uint32_t ms)
|
||||||
|
{
|
||||||
|
TickType_t t0 = xTaskGetTickCount();
|
||||||
|
while ((xTaskGetTickCount() - t0) < pdMS_TO_TICKS(ms)) {
|
||||||
|
if (drain_cancel()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
size_t w = 0;
|
||||||
|
if (i2s_channel_write(s_tx, buf, len, &w, portMAX_DELAY) != ESP_OK) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void play_short(void)
|
||||||
|
{
|
||||||
|
chan_on();
|
||||||
|
play_buf_ms(s_tone, sizeof(s_tone), 100);
|
||||||
|
chan_off();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void play_alarm(void)
|
||||||
|
{
|
||||||
|
chan_on();
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
if (!play_buf_ms(s_tone, sizeof(s_tone), 500)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!play_buf_ms(s_silence, sizeof(s_silence), 250)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chan_off();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void audio_task(void *arg)
|
||||||
|
{
|
||||||
|
(void)arg;
|
||||||
|
int msg;
|
||||||
|
for (;;) {
|
||||||
|
if (xQueueReceive(s_q, &msg, portMAX_DELAY) != pdTRUE) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (msg == AUDIO_SHORT) {
|
||||||
|
play_short();
|
||||||
|
} else if (msg == AUDIO_ALARM) {
|
||||||
|
play_alarm();
|
||||||
|
} else if (msg == AUDIO_CANCEL) {
|
||||||
|
chan_off();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void post(int msg)
|
||||||
|
{
|
||||||
|
if (s_q == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
xQueueSend(s_q, &msg, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void autofilm_audio_task(void *arg)
|
||||||
|
{
|
||||||
|
audio_task(arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void hal_audio_init(void)
|
||||||
|
{
|
||||||
|
if (s_inited) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
gpio_num_t bclk = board_pin_spk_bclk();
|
||||||
|
gpio_num_t lrclk = board_pin_spk_lrclk();
|
||||||
|
gpio_num_t din = board_pin_spk_din();
|
||||||
|
if (bclk == GPIO_NUM_NC || lrclk == GPIO_NUM_NC || din == GPIO_NUM_NC) {
|
||||||
|
ESP_LOGW(TAG, "no speaker pins on this board");
|
||||||
|
s_inited = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < CHUNK_FRAMES; i++) {
|
||||||
|
int16_t v = ((i % TONE_PERIOD_SMP) < (TONE_PERIOD_SMP / 2))
|
||||||
|
? TONE_AMPLITUDE : -TONE_AMPLITUDE;
|
||||||
|
s_tone[i * 2] = v;
|
||||||
|
s_tone[i * 2 + 1] = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER);
|
||||||
|
chan_cfg.dma_desc_num = DMA_DESC_NUM;
|
||||||
|
chan_cfg.dma_frame_num = DMA_FRAME_NUM;
|
||||||
|
if (i2s_new_channel(&chan_cfg, &s_tx, NULL) != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "i2s_new_channel failed");
|
||||||
|
s_inited = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
i2s_std_config_t std_cfg = {
|
||||||
|
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(SPK_SAMPLE_RATE),
|
||||||
|
.slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT,
|
||||||
|
I2S_SLOT_MODE_STEREO),
|
||||||
|
.gpio_cfg = {
|
||||||
|
.mclk = I2S_GPIO_UNUSED,
|
||||||
|
.bclk = bclk,
|
||||||
|
.ws = lrclk,
|
||||||
|
.dout = din,
|
||||||
|
.din = I2S_GPIO_UNUSED,
|
||||||
|
.invert_flags = {
|
||||||
|
.mclk_inv = false,
|
||||||
|
.bclk_inv = false,
|
||||||
|
.ws_inv = false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if (i2s_channel_init_std_mode(s_tx, &std_cfg) != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "i2s_channel_init_std_mode failed");
|
||||||
|
i2s_del_channel(s_tx);
|
||||||
|
s_tx = NULL;
|
||||||
|
s_inited = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
s_q = xQueueCreate(8, sizeof(int));
|
||||||
|
if (s_task == NULL) {
|
||||||
|
xTaskCreate(audio_task, "audio", 3072, NULL, 2, &s_task);
|
||||||
|
}
|
||||||
|
s_inited = true;
|
||||||
|
ESP_LOGI(TAG, "init i2s bclk=%d ws=%d dout=%d", bclk, lrclk, din);
|
||||||
|
}
|
||||||
|
|
||||||
|
void hal_audio_beep_short(void)
|
||||||
|
{
|
||||||
|
post(AUDIO_SHORT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void hal_audio_alarm_complete(void)
|
||||||
|
{
|
||||||
|
post(AUDIO_ALARM);
|
||||||
|
}
|
||||||
|
|
||||||
|
void hal_audio_alarm_cancel(void)
|
||||||
|
{
|
||||||
|
post(AUDIO_CANCEL);
|
||||||
|
}
|
||||||
@@ -3,8 +3,11 @@ set(priv_inc)
|
|||||||
set(priv_req driver)
|
set(priv_req driver)
|
||||||
|
|
||||||
if(IDF_TARGET STREQUAL "esp32")
|
if(IDF_TARGET STREQUAL "esp32")
|
||||||
list(APPEND srcs wroom/hal_motor.c)
|
list(APPEND srcs hal_motor.c)
|
||||||
list(APPEND priv_req board_wroom)
|
list(APPEND priv_req board_wroom)
|
||||||
|
elseif(IDF_TARGET STREQUAL "esp32s3")
|
||||||
|
list(APPEND srcs hal_motor.c)
|
||||||
|
list(APPEND priv_req board_jc4827w543)
|
||||||
else()
|
else()
|
||||||
list(APPEND srcs stub/hal_motor.c)
|
list(APPEND srcs stub/hal_motor.c)
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -10,9 +10,9 @@
|
|||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
#include "freertos/task.h"
|
#include "freertos/task.h"
|
||||||
|
|
||||||
#define PIN_STEP 12
|
#include "board.h"
|
||||||
#define PIN_DIR 14
|
#include "hal_motor.h"
|
||||||
#define PIN_EN 27
|
|
||||||
#define STEPS_PER_REV 4800
|
#define STEPS_PER_REV 4800
|
||||||
#define DEFAULT_RPM 60
|
#define DEFAULT_RPM 60
|
||||||
#define ACCEL 9600
|
#define ACCEL 9600
|
||||||
@@ -23,6 +23,10 @@
|
|||||||
|
|
||||||
static const char *TAG = "motor";
|
static const char *TAG = "motor";
|
||||||
|
|
||||||
|
static gpio_num_t s_pin_en = GPIO_NUM_NC;
|
||||||
|
static gpio_num_t s_pin_dir = GPIO_NUM_NC;
|
||||||
|
static gpio_num_t s_pin_step = GPIO_NUM_NC;
|
||||||
|
static int s_en_disable_level = 1;
|
||||||
static TaskHandle_t s_task;
|
static TaskHandle_t s_task;
|
||||||
static atomic_uint s_stop_req;
|
static atomic_uint s_stop_req;
|
||||||
static float s_cw;
|
static float s_cw;
|
||||||
@@ -35,13 +39,17 @@ static rmt_encoder_handle_t s_enc;
|
|||||||
|
|
||||||
static void en_disable(void)
|
static void en_disable(void)
|
||||||
{
|
{
|
||||||
gpio_set_level((gpio_num_t)PIN_EN, 1);
|
if (s_pin_en != GPIO_NUM_NC) {
|
||||||
|
gpio_set_level(s_pin_en, s_en_disable_level);
|
||||||
|
}
|
||||||
s_enabled = false;
|
s_enabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void en_enable(void)
|
static void en_enable(void)
|
||||||
{
|
{
|
||||||
gpio_set_level((gpio_num_t)PIN_EN, 0);
|
if (s_pin_en != GPIO_NUM_NC) {
|
||||||
|
gpio_set_level(s_pin_en, !s_en_disable_level);
|
||||||
|
}
|
||||||
s_enabled = true;
|
s_enabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,7 +119,7 @@ static bool run_move(long steps)
|
|||||||
if (steps < 0) {
|
if (steps < 0) {
|
||||||
steps = -steps;
|
steps = -steps;
|
||||||
}
|
}
|
||||||
gpio_set_level((gpio_num_t)PIN_DIR, dir);
|
gpio_set_level(s_pin_dir, dir);
|
||||||
|
|
||||||
uint32_t cruise = (s_rpm * STEPS_PER_REV) / 60;
|
uint32_t cruise = (s_rpm * STEPS_PER_REV) / 60;
|
||||||
if (cruise == 0) {
|
if (cruise == 0) {
|
||||||
@@ -189,22 +197,31 @@ void hal_motor_init(void)
|
|||||||
if (s_inited) {
|
if (s_inited) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
s_pin_en = board_pin_motor_en();
|
||||||
|
s_pin_dir = board_pin_motor_dir();
|
||||||
|
s_pin_step = board_pin_motor_step();
|
||||||
|
s_en_disable_level = board_motor_en_disable_level();
|
||||||
|
if (s_pin_en == GPIO_NUM_NC || s_pin_dir == GPIO_NUM_NC || s_pin_step == GPIO_NUM_NC) {
|
||||||
|
ESP_LOGW(TAG, "no motor pins on this board");
|
||||||
|
s_inited = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
gpio_config_t io = {
|
gpio_config_t io = {
|
||||||
.pin_bit_mask = (1ULL << PIN_EN) | (1ULL << PIN_DIR),
|
.pin_bit_mask = (1ULL << s_pin_en) | (1ULL << s_pin_dir),
|
||||||
.mode = GPIO_MODE_OUTPUT,
|
.mode = GPIO_MODE_OUTPUT,
|
||||||
.pull_up_en = GPIO_PULLUP_DISABLE,
|
.pull_up_en = GPIO_PULLUP_DISABLE,
|
||||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||||
.intr_type = GPIO_INTR_DISABLE,
|
.intr_type = GPIO_INTR_DISABLE,
|
||||||
};
|
};
|
||||||
gpio_config(&io);
|
gpio_config(&io);
|
||||||
gpio_set_level((gpio_num_t)PIN_EN, 1);
|
gpio_set_level(s_pin_en, s_en_disable_level);
|
||||||
gpio_set_level((gpio_num_t)PIN_DIR, 0);
|
gpio_set_level(s_pin_dir, 0);
|
||||||
s_enabled = false;
|
s_enabled = false;
|
||||||
atomic_store(&s_stop_req, 0);
|
atomic_store(&s_stop_req, 0);
|
||||||
|
|
||||||
rmt_tx_channel_config_t txcfg = {
|
rmt_tx_channel_config_t txcfg = {
|
||||||
.clk_src = RMT_CLK_SRC_DEFAULT,
|
.clk_src = RMT_CLK_SRC_DEFAULT,
|
||||||
.gpio_num = PIN_STEP,
|
.gpio_num = s_pin_step,
|
||||||
.mem_block_symbols = 64,
|
.mem_block_symbols = 64,
|
||||||
.resolution_hz = RMT_RES_HZ,
|
.resolution_hz = RMT_RES_HZ,
|
||||||
.trans_queue_depth = 4,
|
.trans_queue_depth = 4,
|
||||||
@@ -219,7 +236,8 @@ void hal_motor_init(void)
|
|||||||
configMAX_PRIORITIES - 2, &s_task, 0);
|
configMAX_PRIORITIES - 2, &s_task, 0);
|
||||||
}
|
}
|
||||||
s_inited = true;
|
s_inited = true;
|
||||||
ESP_LOGI(TAG, "init EN=HIGH RMT step=%d dir=%d", PIN_STEP, PIN_DIR);
|
ESP_LOGI(TAG, "init en=%d step=%d dir=%d dis_lvl=%d",
|
||||||
|
s_pin_en, s_pin_step, s_pin_dir, s_en_disable_level);
|
||||||
}
|
}
|
||||||
|
|
||||||
void hal_motor_enable(bool on)
|
void hal_motor_enable(bool on)
|
||||||
@@ -234,8 +252,7 @@ void hal_motor_enable(bool on)
|
|||||||
void hal_motor_request_stop(void)
|
void hal_motor_request_stop(void)
|
||||||
{
|
{
|
||||||
atomic_store(&s_stop_req, 1);
|
atomic_store(&s_stop_req, 1);
|
||||||
gpio_set_level((gpio_num_t)PIN_EN, 1);
|
en_disable();
|
||||||
s_enabled = false;
|
|
||||||
rmt_abort();
|
rmt_abort();
|
||||||
if (s_task != NULL) {
|
if (s_task != NULL) {
|
||||||
xTaskNotifyGive(s_task);
|
xTaskNotifyGive(s_task);
|
||||||
@@ -1,10 +1,13 @@
|
|||||||
set(srcs)
|
set(srcs)
|
||||||
set(priv_inc)
|
set(priv_inc)
|
||||||
set(priv_req driver)
|
set(priv_req driver esp_timer)
|
||||||
|
|
||||||
if(IDF_TARGET STREQUAL "esp32")
|
if(IDF_TARGET STREQUAL "esp32")
|
||||||
list(APPEND srcs wroom/hal_temp.c)
|
list(APPEND srcs hal_temp.c)
|
||||||
list(APPEND priv_req board_wroom onewire_bus)
|
list(APPEND priv_req board_wroom onewire_bus)
|
||||||
|
elseif(IDF_TARGET STREQUAL "esp32s3")
|
||||||
|
list(APPEND srcs hal_temp.c)
|
||||||
|
list(APPEND priv_req board_jc4827w543 onewire_bus)
|
||||||
else()
|
else()
|
||||||
list(APPEND srcs stub/hal_temp.c)
|
list(APPEND srcs stub/hal_temp.c)
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -4,12 +4,12 @@
|
|||||||
|
|
||||||
#include "esp_err.h"
|
#include "esp_err.h"
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
#include "freertos/FreeRTOS.h"
|
#include "esp_timer.h"
|
||||||
#include "freertos/task.h"
|
|
||||||
|
|
||||||
#include "onewire_bus.h"
|
#include "onewire_bus.h"
|
||||||
|
#include "board.h"
|
||||||
|
#include "hal_temp.h"
|
||||||
|
|
||||||
#define PIN_DS 13
|
|
||||||
#define TEMP_OFFSET 0.4f
|
#define TEMP_OFFSET 0.4f
|
||||||
#define CMD_SKIP_ROM 0xCC
|
#define CMD_SKIP_ROM 0xCC
|
||||||
#define CMD_CONVERT_T 0x44
|
#define CMD_CONVERT_T 0x44
|
||||||
@@ -17,10 +17,24 @@
|
|||||||
|
|
||||||
static const char *TAG = "temp";
|
static const char *TAG = "temp";
|
||||||
|
|
||||||
|
#define TEMP_CONV_MS 750
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
T_START,
|
||||||
|
T_WAIT,
|
||||||
|
} temp_state_t;
|
||||||
|
|
||||||
static onewire_bus_handle_t s_bus;
|
static onewire_bus_handle_t s_bus;
|
||||||
static bool s_inited;
|
static bool s_inited;
|
||||||
static float s_last_c;
|
static float s_last_c;
|
||||||
static bool s_last_ok;
|
static bool s_last_ok;
|
||||||
|
static temp_state_t s_state = T_START;
|
||||||
|
static uint32_t s_deadline_ms;
|
||||||
|
|
||||||
|
static uint32_t now_ms(void)
|
||||||
|
{
|
||||||
|
return (uint32_t)(esp_timer_get_time() / 1000ULL);
|
||||||
|
}
|
||||||
|
|
||||||
static bool scratch_valid(const uint8_t *sp)
|
static bool scratch_valid(const uint8_t *sp)
|
||||||
{
|
{
|
||||||
@@ -40,42 +54,59 @@ static bool scratch_valid(const uint8_t *sp)
|
|||||||
return crc == sp[8];
|
return crc == sp[8];
|
||||||
}
|
}
|
||||||
|
|
||||||
void autofilm_temp_tick(void)
|
void hal_temp_tick(void)
|
||||||
{
|
{
|
||||||
if (s_bus == NULL) {
|
if (s_bus == NULL) {
|
||||||
s_last_ok = false;
|
s_last_ok = false;
|
||||||
vTaskDelay(pdMS_TO_TICKS(750));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
uint32_t now = now_ms();
|
||||||
|
if (s_state == T_START) {
|
||||||
|
if (now < s_deadline_ms) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (onewire_bus_reset(s_bus) != ESP_OK) {
|
||||||
|
s_last_ok = false;
|
||||||
|
s_deadline_ms = now + TEMP_CONV_MS;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uint8_t conv[2] = {CMD_SKIP_ROM, CMD_CONVERT_T};
|
||||||
|
if (onewire_bus_write_bytes(s_bus, conv, 2) != ESP_OK) {
|
||||||
|
s_last_ok = false;
|
||||||
|
s_deadline_ms = now + TEMP_CONV_MS;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
s_deadline_ms = now + TEMP_CONV_MS;
|
||||||
|
s_state = T_WAIT;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/* T_WAIT */
|
||||||
|
if (now < s_deadline_ms) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
s_state = T_START;
|
||||||
|
s_deadline_ms = now;
|
||||||
if (onewire_bus_reset(s_bus) != ESP_OK) {
|
if (onewire_bus_reset(s_bus) != ESP_OK) {
|
||||||
s_last_ok = false;
|
s_last_ok = false;
|
||||||
vTaskDelay(pdMS_TO_TICKS(750));
|
s_deadline_ms = now + TEMP_CONV_MS;
|
||||||
return;
|
|
||||||
}
|
|
||||||
uint8_t conv[2] = {CMD_SKIP_ROM, CMD_CONVERT_T};
|
|
||||||
if (onewire_bus_write_bytes(s_bus, conv, 2) != ESP_OK) {
|
|
||||||
s_last_ok = false;
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(750));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(750));
|
|
||||||
if (onewire_bus_reset(s_bus) != ESP_OK) {
|
|
||||||
s_last_ok = false;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
uint8_t rd[2] = {CMD_SKIP_ROM, CMD_READ_SCRATCH};
|
uint8_t rd[2] = {CMD_SKIP_ROM, CMD_READ_SCRATCH};
|
||||||
if (onewire_bus_write_bytes(s_bus, rd, 2) != ESP_OK) {
|
if (onewire_bus_write_bytes(s_bus, rd, 2) != ESP_OK) {
|
||||||
s_last_ok = false;
|
s_last_ok = false;
|
||||||
|
s_deadline_ms = now + TEMP_CONV_MS;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
uint8_t sp[9];
|
uint8_t sp[9];
|
||||||
memset(sp, 0, sizeof(sp));
|
memset(sp, 0, sizeof(sp));
|
||||||
if (onewire_bus_read_bytes(s_bus, sp, sizeof(sp)) != ESP_OK) {
|
if (onewire_bus_read_bytes(s_bus, sp, sizeof(sp)) != ESP_OK) {
|
||||||
s_last_ok = false;
|
s_last_ok = false;
|
||||||
|
s_deadline_ms = now + TEMP_CONV_MS;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!scratch_valid(sp)) {
|
if (!scratch_valid(sp)) {
|
||||||
s_last_ok = false;
|
s_last_ok = false;
|
||||||
|
s_deadline_ms = now + TEMP_CONV_MS;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int16_t raw = (int16_t)((uint16_t)sp[0] | ((uint16_t)sp[1] << 8));
|
int16_t raw = (int16_t)((uint16_t)sp[0] | ((uint16_t)sp[1] << 8));
|
||||||
@@ -84,21 +115,19 @@ void autofilm_temp_tick(void)
|
|||||||
s_last_ok = true;
|
s_last_ok = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void autofilm_temp_task(void *arg)
|
|
||||||
{
|
|
||||||
(void)arg;
|
|
||||||
for (;;) {
|
|
||||||
autofilm_temp_tick();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void hal_temp_init(void)
|
void hal_temp_init(void)
|
||||||
{
|
{
|
||||||
if (s_inited) {
|
if (s_inited) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
gpio_num_t pin = board_pin_temp();
|
||||||
|
if (pin == GPIO_NUM_NC) {
|
||||||
|
ESP_LOGW(TAG, "no temp pin on this board");
|
||||||
|
s_inited = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
onewire_bus_config_t bus_config = {
|
onewire_bus_config_t bus_config = {
|
||||||
.bus_gpio_num = PIN_DS,
|
.bus_gpio_num = pin,
|
||||||
.flags = {
|
.flags = {
|
||||||
.en_pull_up = true,
|
.en_pull_up = true,
|
||||||
},
|
},
|
||||||
@@ -108,12 +137,12 @@ void hal_temp_init(void)
|
|||||||
};
|
};
|
||||||
if (onewire_new_bus_rmt(&bus_config, &rmt_config, &s_bus) != ESP_OK) {
|
if (onewire_new_bus_rmt(&bus_config, &rmt_config, &s_bus) != ESP_OK) {
|
||||||
s_bus = NULL;
|
s_bus = NULL;
|
||||||
ESP_LOGE(TAG, "onewire_bus install failed pin=%d", PIN_DS);
|
ESP_LOGE(TAG, "onewire_bus install failed pin=%d", pin);
|
||||||
}
|
}
|
||||||
s_last_ok = false;
|
s_last_ok = false;
|
||||||
s_last_c = 0.0f;
|
s_last_c = 0.0f;
|
||||||
s_inited = true;
|
s_inited = true;
|
||||||
ESP_LOGI(TAG, "init onewire_bus pin=%d offset=%.1f", PIN_DS, (double)TEMP_OFFSET);
|
ESP_LOGI(TAG, "init onewire_bus pin=%d offset=%.1f", pin, (double)TEMP_OFFSET);
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t hal_temp_read_c(float *out)
|
esp_err_t hal_temp_read_c(float *out)
|
||||||
@@ -7,4 +7,5 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
void hal_temp_init(void);
|
void hal_temp_init(void);
|
||||||
|
void hal_temp_tick(void); /* blocking conversion poll; blocks >=750 ms */
|
||||||
esp_err_t hal_temp_read_c(float *out); /* ESP_FAIL → disconnected */
|
esp_err_t hal_temp_read_c(float *out); /* ESP_FAIL → disconnected */
|
||||||
|
|||||||
@@ -1,9 +1,17 @@
|
|||||||
#include "hal_temp.h"
|
#include "hal_temp.h"
|
||||||
|
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
|
||||||
void hal_temp_init(void)
|
void hal_temp_init(void)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void hal_temp_tick(void)
|
||||||
|
{
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(750));
|
||||||
|
}
|
||||||
|
|
||||||
esp_err_t hal_temp_read_c(float *out)
|
esp_err_t hal_temp_read_c(float *out)
|
||||||
{
|
{
|
||||||
(void)out;
|
(void)out;
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
# Codebase Audit & Remediation Plan (develop branch)
|
||||||
|
|
||||||
|
After a thorough audit of the `develop` branch focusing on the recent modular ESP-IDF port, I've identified several critical defects primarily centered around FreeRTOS concurrency, system timers, and legacy feature regressions.
|
||||||
|
|
||||||
|
Here is the step-by-step remediation plan to address these issues. **No code has been changed yet.**
|
||||||
|
|
||||||
|
## 1. Fix Critical Step Timing Bug in `app_machine.c`
|
||||||
|
**Defect:** `CMD_START_STEP` triggers `start_running(0)`, passing a hardcoded `0` for `now_ms`. The task then calculates `s_deadline_ms = 0 + remaining_ms`. When `app_machine_tick()` runs moments later, it reads the *real* system uptime (e.g., 200,000 ms). If the uptime is larger than the step duration, the step instantly completes.
|
||||||
|
**Remediation Steps:**
|
||||||
|
- Modify `start_running()` to remove the `now_ms` argument.
|
||||||
|
- Instead of calculating `s_deadline_ms` immediately, configure the state machine to defer calculation: set `s_have_deadline = false;` and `s_resume_pending = true;`.
|
||||||
|
- This safely delegates the deadline calculation to the next `app_machine_tick()` cycle, which naturally computes it using the true system `now_ms`.
|
||||||
|
- Update `app_machine_handle_cmd()` to call `start_running()` without arguments.
|
||||||
|
|
||||||
|
## 2. Fix Event Queue Concurrency in `app_machine.c`
|
||||||
|
**Defect:** The system uses a raw array `s_q` (with `s_q_head`, `s_q_tail`, and `s_q_count++`) to pass events from the machine to the UI. However, `emit()` is called by both the `machine_task` and the `temp_task` (via `app_machine_on_temp`), while `app_machine_last_event()` is read by the `ui_task`. These are non-atomic read-modify-write operations across three threads, which will inevitably corrupt the queue and crash the UI.
|
||||||
|
**Remediation Steps:**
|
||||||
|
- Replace the raw ring buffer variables (`s_q`, `s_q_head`, `s_q_tail`, `s_q_count`) with a standard FreeRTOS `QueueHandle_t s_evtq`.
|
||||||
|
- In `app_machine_init()`, initialize the queue: `s_evtq = xQueueCreate(UI_EVT_QUEUE_LEN, sizeof(ui_evt_t));`.
|
||||||
|
- Update `emit()` to use `xQueueSend(s_evtq, &ev, 0);`.
|
||||||
|
- Update `app_machine_last_event()` to use `xQueueReceive(s_evtq, out, 0) == pdTRUE`.
|
||||||
|
|
||||||
|
## 3. Restore Auto-Advance Functionality in `app_machine.c`
|
||||||
|
**Defect:** The legacy Arduino loop automatically chained processing steps together (`run == 1`). The new state machine includes a `maybe_auto_advance()` function guarded by `s_auto_advance`, but `s_auto_advance` is permanently hardcoded to `false` and never toggled. As a result, the machine halts after every single step.
|
||||||
|
**Remediation Steps:**
|
||||||
|
- Initialize `s_auto_advance = true` to match the legacy behavior of chaining steps automatically.
|
||||||
|
- (Optional) Wire up a UI command (`CMD_TOGGLE_AUTO_ADVANCE`) to allow users to turn this off if manual pausing between steps is desired.
|
||||||
|
|
||||||
|
## 4. Fix Task Watchdog Initialization in `main.c`
|
||||||
|
**Defect:** `ui_task`, `machine_task`, `input_task`, and `temp_task` all invoke `esp_task_wdt_add(NULL)`. However, the Task Watchdog Timer (TWDT) is never initialized in `app_main()`. Depending on the ESP-IDF version and `sdkconfig` defaults, this can cause silent failures or panic at boot.
|
||||||
|
**Remediation Steps:**
|
||||||
|
- In `app_main()`, invoke `esp_task_wdt_init(&wdt_config)` *before* creating the FreeRTOS tasks.
|
||||||
|
- Ensure the WDT timeout is set generously (e.g., 3-5 seconds) to accommodate the 1.5-second blocking time in `hal_temp_tick()`.
|
||||||
|
|
||||||
|
## 5. Clean up `hal_temp_tick()` Timing (Low Priority)
|
||||||
|
**Defect:** `hal_temp_tick()` blocks `temp_task` with multiple `vTaskDelay(pdMS_TO_TICKS(750))` calls for the DS18B20 conversion. While safely isolated from the UI, it forces the watchdog timeout to be artificially large and limits responsiveness if multiple sensors were ever added.
|
||||||
|
**Remediation Steps:**
|
||||||
|
- Refactor the 1-Wire sequence into a non-blocking state machine within `temp_task`, or simply keep it as-is but acknowledge the WDT requirement constraint outlined in step 4.
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
# AUDIT MEGAPLAN — develop-branch defect remediation
|
||||||
|
|
||||||
|
Audience: coding agent. One phase = one session. Do not start the next phase in the same session.
|
||||||
|
|
||||||
|
## Source
|
||||||
|
|
||||||
|
Implements `docs/audit_remediation_plan.md` — five defects found in the post-refactor `develop` tree: step deadline computed from a fake `now`, non-thread-safe UI event ring, auto-advance regression, implicit task-watchdog setup, and a blocking DS18B20 conversion in `hal_temp_tick`.
|
||||||
|
|
||||||
|
## Protocol (every session)
|
||||||
|
|
||||||
|
1. `git checkout feature/audit-remediation && git pull --ff-only`
|
||||||
|
2. Read only: `AGENTS.md`, this file (status table), **the assigned phase file**, and the audit doc items it cites. Open `docs/CURRENT_STATE.md` / `docs/TARGET_ARCHITECTURE.md` / `docs/CICD.md` only if the phase `READ:` list says so.
|
||||||
|
3. Execute `IN` only. Honour `OUT` and `FORBIDDEN`.
|
||||||
|
4. Run `VERIFY` exactly. Do not push if any verify item fails. Local verify = host ctest; firmware builds run in Gitea CI on push (`feature/**` is a push trigger — see `.gitea/workflows/ci.yml`).
|
||||||
|
5. Commits: messages listed in the phase. Imperative. One concern per commit.
|
||||||
|
6. Push `origin feature/audit-remediation`. PR target is `develop`, not `main`.
|
||||||
|
7. Set phase `STATUS:` to `DONE` in the phase file **and** this table. One-line `Notes` if you diverged (API name only — do not silently change behaviour).
|
||||||
|
|
||||||
|
If blocked: stop, commit nothing broken, write `BLOCKED:` at top of the phase file with the exact error.
|
||||||
|
|
||||||
|
## Frozen constraints (never reinterpret)
|
||||||
|
|
||||||
|
- Recipe **values** (times s, CW, CCW, temp min/pref/max) for C41 E6 ECN-2 B&W Custom B&WREV = `components/app_process/app_process.c` / `docs/CURRENT_STATE.md`. Changing values requires explicit user order + doc update in the same commit.
|
||||||
|
- Stop while running: Esc `'X'` (keypad r4c4) and touch STOP on S3 → immediate motor **EN disabled**, then Resume or ReturnToStepSelect. `hal_motor_request_stop` runs before any other work on the stop path.
|
||||||
|
- UI never blocks on motor, OneWire, or audio.
|
||||||
|
- No Arduino types in `ui_cmd`, `app_process`, `app_machine` public headers.
|
||||||
|
- Watchdog stays **on** — A03 makes setup explicit, it does not disable anything. No `vTaskDelete` of long-lived workers.
|
||||||
|
- Session time edits ±5 s are RAM overlays only. No NVS profiles. No pumps/valves/heaters.
|
||||||
|
- **Auto-advance**: REFACTOR-MEGAPLAN froze `auto_advance` default `false` "unless a later phase says so" — **A02 is that phase** (audit item 3, owner-approved). Semantics = legacy `run==1` chain: auto-**ARM** the next step, never auto-run; the operator still presses Start per step.
|
||||||
|
- Event queue semantics change (accepted): a full event queue now drops the **newest** event (`xQueueSend` timeout 0); the old ring overwrote the oldest. UI drains at 40 Hz vs ≤~1.3 Hz producers — overflow is not expected.
|
||||||
|
|
||||||
|
## Branch
|
||||||
|
|
||||||
|
`feature/audit-remediation` cut from `develop` → PRs into `develop`. (`feature/**` is required for the CI push trigger.)
|
||||||
|
|
||||||
|
## Working-tree note
|
||||||
|
|
||||||
|
The branch was cut with uncommitted `components/app_machine/app_machine.c` changes present: they implement audit items 1–2 **plus** the item-3 flag flip. A01 lands items 1–2 with `s_auto_advance` reverted to `false`; A02 lands the flag. Do not commit the whole dirty file in one go — split per concern (`git add -p` or re-apply in order).
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
| ID | File | Session goal | STATUS |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| A00 | `audit/A00-docs.md` | Land audit doc + megaplan + phase files | DONE |
|
||||||
|
| A01 | `audit/A01-machine-timing-queue.md` | Deadline fix + FreeRTOS event queue + host queue shim | DONE |
|
||||||
|
| A02 | `audit/A02-auto-advance.md` | Restore auto-arm after step complete + test rework | DONE |
|
||||||
|
| A03 | `audit/A03-task-wdt.md` | Explicit TWDT init/reconfigure + `add()` failure logging | DONE |
|
||||||
|
| A04 | `audit/A04-temp-nonblocking.md` | OPTIONAL: non-blocking DS18B20 conversion | DONE |
|
||||||
|
|
||||||
|
## Dependency
|
||||||
|
|
||||||
|
```
|
||||||
|
A00 → A01 → A02 (same file; A02 builds on A01's queue + tests)
|
||||||
|
A03 is independent of A01/A02 (main.c only) — run any time after A00
|
||||||
|
A04 runs after A03 (shares temp_task in main.c); OPTIONAL — skipping is acceptable
|
||||||
|
```
|
||||||
|
|
||||||
|
## Do not
|
||||||
|
|
||||||
|
- Expand scope: no `CMD_TOGGLE_AUTO_ADVANCE` (rejected by owner), no UI changes, no NVS, no pumps/heater, no recipe edits.
|
||||||
|
- Commit `sdkconfig` (generated), `build/`, `build_host/`, tokens.
|
||||||
|
- Batch unrelated fixes into a phase.
|
||||||
|
- Reduce the TWDT timeout below the blocking bound while `hal_temp_tick` still blocks (~750 ms); A04 removes that constraint.
|
||||||
@@ -36,6 +36,6 @@ If blocked: stop, commit nothing broken, write `BLOCKED:` at top of the phase fi
|
|||||||
| ID | File | Session goal | STATUS |
|
| ID | File | Session goal | STATUS |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| I01 | `integration/I01-board-configs.md` | Board pin allocations & accessors | DONE |
|
| I01 | `integration/I01-board-configs.md` | Board pin allocations & accessors | DONE |
|
||||||
| I02 | `integration/I02-temp-fix.md` | Temp HAL shared & starvation fix | TODO |
|
| I02 | `integration/I02-temp-fix.md` | Temp HAL shared & starvation fix | DONE |
|
||||||
| I03 | `integration/I03-motor-hal.md` | Motor HAL shared across boards | TODO |
|
| I03 | `integration/I03-motor-hal.md` | Motor HAL shared across boards | DONE |
|
||||||
| I04 | `integration/I04-audio-i2s.md` | I2S audio implementation for S3 | TODO |
|
| I04 | `integration/I04-audio-i2s.md` | I2S audio implementation for S3 | DONE |
|
||||||
|
|||||||
@@ -52,6 +52,8 @@ P07 Notes: custom NV3041A QSPI + GT911; shared app_ui text grid + colour softkey
|
|||||||
|
|
||||||
P08 Notes: LEDC audio; RMT STEP + GPIO DIR/EN; onewire_bus DS18B20 +0.4; GPIO keypad; IDF I2C 2004. Arduino-as-component removed. third_party trees unlinked. host ctest green. idf.py not run here (no IDF_PATH). hw unflashed.
|
P08 Notes: LEDC audio; RMT STEP + GPIO DIR/EN; onewire_bus DS18B20 +0.4; GPIO keypad; IDF I2C 2004. Arduino-as-component removed. third_party trees unlinked. host ctest green. idf.py not run here (no IDF_PATH). hw unflashed.
|
||||||
|
|
||||||
|
AUDIT A02 Notes: supersedes the frozen `auto_advance` default-false constraint — `s_auto_advance = true` restores the legacy auto-ARM chain (audit item 3, owner-approved); auto-ARM only, never auto-run.
|
||||||
|
|
||||||
## Dependency
|
## Dependency
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# A00 — land audit docs on the remediation branch
|
||||||
|
|
||||||
|
STATUS: DONE
|
||||||
|
DEPENDS: —
|
||||||
|
Notes: executed by the session that authored the megaplan.
|
||||||
|
|
||||||
|
**READ:** `AGENTS.md`, `docs/megaplans/AUDIT-MEGAPLAN.md`
|
||||||
|
|
||||||
|
**IN:**
|
||||||
|
1. Create `feature/audit-remediation` from `develop`.
|
||||||
|
2. Commit `docs/audit_remediation_plan.md`, `docs/megaplans/AUDIT-MEGAPLAN.md`, `docs/megaplans/audit/*.md`, and the `build_host/` `.gitignore` line.
|
||||||
|
3. Do **not** commit the `components/app_machine/app_machine.c` working-tree changes (A01/A02 own them) or `build_host/` artifacts.
|
||||||
|
|
||||||
|
**OUT:** all code changes.
|
||||||
|
|
||||||
|
**VERIFY:** `git log -1` shows the docs commit; `git status` shows only `app_machine.c` modified.
|
||||||
|
|
||||||
|
**COMMITS:**
|
||||||
|
1. `Add audit remediation megaplan`
|
||||||
|
|
||||||
|
**DoD checkboxes:**
|
||||||
|
- [x] Branch `feature/audit-remediation` exists, cut from `develop`.
|
||||||
|
- [x] Audit doc + megaplan + phase files committed.
|
||||||
|
- [x] `app_machine.c` left uncommitted for A01/A02.
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
# A01 — app_machine deadline fix + FreeRTOS event queue + host queue shim
|
||||||
|
|
||||||
|
STATUS: DONE
|
||||||
|
DEPENDS: A00
|
||||||
|
|
||||||
|
**READ:** this file, `docs/megaplans/AUDIT-MEGAPLAN.md`, `docs/audit_remediation_plan.md` items 1–2, `components/app_machine/app_machine.c`, `tests/host/CMakeLists.txt`
|
||||||
|
|
||||||
|
**Context:** the working tree already carries uncommitted fixes for items 1–2 **and** the item-3 `s_auto_advance = true` flip. This phase lands items 1–2 only; the flag stays `false` until A02. The queue change adds `#include "freertos/queue.h"` to `app_machine.c`, which the host test build cannot resolve — a shim is required.
|
||||||
|
|
||||||
|
**IN:**
|
||||||
|
1. `app_machine.c` deadline fix (keep from working tree): `start_running(void)` takes no `now_ms`; sets `s_remaining_ms = time_s * 1000`, `s_have_deadline = false`, `s_resume_pending = true`. First `app_machine_tick` in `ST_RUNNING` computes `s_deadline_ms = now_ms + s_remaining_ms` from real uptime. `app_machine_handle_cmd` calls `start_running()` with no args.
|
||||||
|
2. `app_machine.c` cleanup: revert `s_auto_advance` to `false`; restore `#define AGITATE_RPM 60u`; **remove** the duplicate `#define UI_EVT_QUEUE_LEN 16` (already defined via `ui_cmd.h`).
|
||||||
|
3. `app_machine.c` event queue (keep from working tree): `static QueueHandle_t s_evtq`; `emit()` does `xQueueSend(s_evtq, &ev, 0)` when non-NULL; `app_machine_last_event()` does `xQueueReceive(s_evtq, out, 0) == pdTRUE`. In `app_machine_init`: `if (s_evtq == NULL) { s_evtq = xQueueCreate(UI_EVT_QUEUE_LEN, sizeof(ui_evt_t)); } else { xQueueReset(s_evtq); }` — host tests re-init repeatedly and stale events must not leak between inits.
|
||||||
|
4. `components/app_machine/CMakeLists.txt`: add `freertos` to `PRIV_REQUIRES` (explicit dependency for `freertos/queue.h`).
|
||||||
|
5. New host shim so `test_machine` keeps compiling the same `app_machine.c`:
|
||||||
|
- `tests/host/freertos/FreeRTOS.h` — `BaseType_t`, `UBaseType_t`, `TickType_t`, `pdTRUE`, `pdFALSE`, `pdPASS`, `pdMS_TO_TICKS`.
|
||||||
|
- `tests/host/freertos/queue.h` — `QueueHandle_t` + prototypes for `xQueueCreate`, `xQueueSend`, `xQueueReceive`, `xQueueReset`.
|
||||||
|
- `tests/host/freertos/queue.c` — malloc'd ring buffer; `xQueueSend` returns `pdFALSE` (drop) when full; `xQueueReceive` returns `pdFALSE` when empty; `xQueueReset` clears.
|
||||||
|
- `tests/host/CMakeLists.txt` — add `freertos/queue.c` to the `test_machine` sources (`tests/host` is already on its include path, so `"freertos/FreeRTOS.h"` resolves to the shim).
|
||||||
|
6. Behaviour must be identical for host tests: all existing `test_machine.c` cases pass **unchanged** (auto_advance still `false`).
|
||||||
|
|
||||||
|
**OUT:** `s_auto_advance = true` (A02), TWDT setup (A03), `hal_temp` internals (A04), any UI change.
|
||||||
|
|
||||||
|
**FORBIDDEN:** committing the auto-advance flag; changing recipe values; Arduino types; deleting or weakening host test assertions.
|
||||||
|
|
||||||
|
**VERIFY:**
|
||||||
|
```
|
||||||
|
cmake -S tests/host -B build/host && cmake --build build/host && ctest --test-dir build/host --output-on-failure
|
||||||
|
```
|
||||||
|
All green. Firmware builds run in CI on push.
|
||||||
|
|
||||||
|
**COMMITS** (order matters — every commit must compile on host):
|
||||||
|
1. `Add FreeRTOS queue shim for host tests` (shim alone; old code doesn't include the headers yet — harmless)
|
||||||
|
2. `Fix step deadline to derive from real uptime`
|
||||||
|
3. `Replace machine event ring with FreeRTOS queue`
|
||||||
|
|
||||||
|
Stage hunks per concern (`git add -p` or re-apply in order) — the dirty file combines all three changes plus the A02 flag.
|
||||||
|
|
||||||
|
**DoD checkboxes:**
|
||||||
|
- [x] `start_running` takes no `now_ms`; deadline set on the first `RUNNING` tick.
|
||||||
|
- [x] `emit`/`app_machine_last_event` go through `s_evtq`; `app_machine_init` creates or resets the queue.
|
||||||
|
- [x] `s_auto_advance` still `false`.
|
||||||
|
- [x] Host ctest green with zero test changes.
|
||||||
|
- [x] STATUS → DONE here and in the megaplan table.
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
# A02 — restore auto-advance (auto-arm next step after complete)
|
||||||
|
|
||||||
|
STATUS: DONE
|
||||||
|
DEPENDS: A01
|
||||||
|
Notes: `stop_disables_motor_and_resume` final assertion updated to `ST_ARMED` — the "unchanged" list cannot hold once auto-advance is on (resume-to-complete auto-arms step 1).
|
||||||
|
|
||||||
|
**READ:** this file, `docs/megaplans/AUDIT-MEGAPLAN.md`, `docs/audit_remediation_plan.md` item 3, `docs/CURRENT_STATE.md` §Runtime behaviour, `components/app_machine/app_machine.c`, `tests/host/test_machine.c`
|
||||||
|
|
||||||
|
**Semantics (implement exactly):** legacy `run==1` in `src/menu.cpp` chained `startProcessing()`, which displayed `Ent:start Esc:quit` for the next step — i.e. **auto-ARM, never auto-run**. `maybe_auto_advance()` already implements this: on the completing tick it moves to `ST_ARMED` for `s_step+1` (emitting `EVT_STEP_ARMED`), or `ST_IDLE` + `EVT_PROCESS_IDLE` after the last step. `ST_COMPLETE` becomes transient inside the completing tick — the alarm (`hal_audio_alarm_complete`, ~7.5 s async, self-terminating) plays while the next step sits armed. That is acceptable and intentional: do **not** add `alarm_cancel` to `start_running` and do not block on the melody.
|
||||||
|
|
||||||
|
**IN:**
|
||||||
|
1. `app_machine.c`: set `s_auto_advance = true` in `app_machine_init`. Nothing else in the state machine changes.
|
||||||
|
2. Keep the `ST_COMPLETE` + `CMD_STOP` alarm-cancel branch in `app_machine_handle_cmd` — it becomes unreachable while auto-advance is on, but stays as a defensive path.
|
||||||
|
3. Rework `tests/host/test_machine.c` for transient COMPLETE (Custom has 4 steps, ECN-2 RemJet is step 1, C41 Prewarm is step 0):
|
||||||
|
- `arm_start_complete_custom_10s`: after `tick(10000)` expect `ST_ARMED` and `step_index == 1` (auto-armed next step); drop the `tick(10001)` "stays complete" assertion.
|
||||||
|
- Replace `stop_during_complete_cancels_alarm` with `auto_advance_last_step_goes_idle`: select Custom, `cmd_arm(3)`, `cmd_start(3)`, `tick(0)`, `tick(10000)` → `ST_IDLE`; drain events and assert `EVT_PROCESS_IDLE` present.
|
||||||
|
- `ecn2_remjet_zero_time`: after `tick(0)` expect `ST_ARMED` at step index 2.
|
||||||
|
- `c41_clock`: after `tick(180000)` expect `ST_ARMED` at step index 1.
|
||||||
|
- All other cases unchanged (`select_c41_step_view`, `adjust_does_not_mutate_const`, `stop_disables_motor_and_resume`, `return_from_stopped`, `stop_ignored_meaningless_in_idle`).
|
||||||
|
- Note: `complete_step` still emits `EVT_STEP_COMPLETE` before `EVT_STEP_ARMED`/`EVT_PROCESS_IDLE`; tests may assert that ordering via `pop_ids` if convenient.
|
||||||
|
4. `docs/megaplans/REFACTOR-MEGAPLAN.md`: add a one-line `Notes` entry recording that the `auto_advance` default-false freeze is superseded by AUDIT A02 (owner-approved via audit item 3). Do not rewrite the frozen-constraint text itself.
|
||||||
|
|
||||||
|
**OUT:** `CMD_TOGGLE_AUTO_ADVANCE` (rejected by owner), any UI/input changes, NVS, recipe values.
|
||||||
|
|
||||||
|
**FORBIDDEN:** auto-starting the next step's motor; removing the `ST_COMPLETE` STOP branch; changing recipe tables; editing the frozen-constraint lines of REFACTOR-MEGAPLAN.
|
||||||
|
|
||||||
|
**VERIFY:**
|
||||||
|
```
|
||||||
|
cmake -S tests/host -B build/host && cmake --build build/host && ctest --test-dir build/host --output-on-failure
|
||||||
|
```
|
||||||
|
All green, including the new `auto_advance_last_step_goes_idle`. Firmware builds run in CI on push.
|
||||||
|
|
||||||
|
**COMMITS:**
|
||||||
|
1. `Restore auto-advance to arm next step after step complete`
|
||||||
|
2. `Update machine tests for auto-advance`
|
||||||
|
|
||||||
|
**DoD checkboxes:**
|
||||||
|
- [x] `s_auto_advance = true`; auto-ARM only, never auto-run.
|
||||||
|
- [x] Last step completes → `ST_IDLE` + `EVT_PROCESS_IDLE`.
|
||||||
|
- [x] `ST_COMPLETE` + `CMD_STOP` branch retained.
|
||||||
|
- [x] REFACTOR-MEGAPLAN supersede note added.
|
||||||
|
- [x] Host ctest green.
|
||||||
|
- [x] STATUS → DONE here and in the megaplan table.
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
# A03 — explicit task watchdog setup in app_main
|
||||||
|
|
||||||
|
STATUS: DONE
|
||||||
|
DEPENDS: A00 (independent of A01/A02 — `main/main.c` only)
|
||||||
|
|
||||||
|
**READ:** this file, `docs/megaplans/AUDIT-MEGAPLAN.md`, `docs/audit_remediation_plan.md` item 4, `main/main.c`, `sdkconfig.defaults`
|
||||||
|
|
||||||
|
**Context (audit correction):** the audit's "TWDT never initialized → panic at boot" is unlikely under the current config — `sdkconfig.defaults` sets `CONFIG_ESP_TASK_WDT_EN=y` + `CONFIG_ESP_TASK_WDT_TIMEOUT_S=10` + `CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=y`, and IDF v5.4 auto-initialises the TWDT before `app_main`, so `esp_task_wdt_add(NULL)` in the tasks already succeeds. The real gaps: setup is implicit (breaks silently if the Kconfig default ever changes), and every `esp_task_wdt_add` return value is ignored. Make setup explicit and failures observable. **Keep the effective config identical** — 10 s already bounds the ~750 ms `hal_temp_tick` block; do not adopt the audit's 3–5 s suggestion.
|
||||||
|
|
||||||
|
**IN:**
|
||||||
|
1. `main/main.c`, in `app_main` before the `xTaskCreate` block, under `#if CONFIG_ESP_TASK_WDT_EN`:
|
||||||
|
```c
|
||||||
|
esp_task_wdt_config_t wdt_cfg = {
|
||||||
|
.timeout_ms = CONFIG_ESP_TASK_WDT_TIMEOUT_S * 1000,
|
||||||
|
.idle_core_mask =
|
||||||
|
#if CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0
|
||||||
|
(1u << 0)
|
||||||
|
#else
|
||||||
|
0
|
||||||
|
#endif
|
||||||
|
#if CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1
|
||||||
|
| (1u << 1)
|
||||||
|
#endif
|
||||||
|
,
|
||||||
|
#if CONFIG_ESP_TASK_WDT_PANIC
|
||||||
|
.trigger_panic = true,
|
||||||
|
#else
|
||||||
|
.trigger_panic = false,
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
esp_err_t wdt_err = esp_task_wdt_init(&wdt_cfg);
|
||||||
|
if (wdt_err == ESP_ERR_INVALID_STATE) {
|
||||||
|
wdt_err = esp_task_wdt_reconfigure(&wdt_cfg); /* already auto-initialised */
|
||||||
|
}
|
||||||
|
if (wdt_err != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "task wdt setup: %s", esp_err_to_name(wdt_err));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Verify field names/signatures against `esp_task_wdt.h` in the pinned IDF (v5.4) and adjust the sketch only as needed to compile.
|
||||||
|
2. In each of `input_task`, `machine_task`, `ui_task`, `temp_task`: capture `esp_err_t werr = esp_task_wdt_add(NULL);` and `ESP_LOGW(TAG, "... wdt add failed: %s", esp_err_to_name(werr))` on non-OK; continue regardless — the unconditional `esp_task_wdt_reset()` no-ops harmlessly if the task is not subscribed.
|
||||||
|
|
||||||
|
**OUT:** `app_machine`, `hal_temp` internals, UI code, `sdkconfig*` edits.
|
||||||
|
|
||||||
|
**FORBIDDEN:** disabling the TWDT or idle-task watch; `vTaskDelete`; shrinking the timeout; committing a generated `sdkconfig`.
|
||||||
|
|
||||||
|
**VERIFY:**
|
||||||
|
```
|
||||||
|
cmake -S tests/host -B build/host && cmake --build build/host && ctest --test-dir build/host --output-on-failure
|
||||||
|
```
|
||||||
|
(unchanged — `main.c` is not host-compiled; run anyway to catch regressions). Firmware builds for both `esp32` and `esp32s3` run in CI on push — confirm both go green.
|
||||||
|
|
||||||
|
**COMMITS:**
|
||||||
|
1. `Make task watchdog setup explicit in app_main`
|
||||||
|
|
||||||
|
**DoD checkboxes:**
|
||||||
|
- [x] init-or-reconfigure runs before task creation.
|
||||||
|
- [x] `esp_task_wdt_add` failures are logged in all four tasks.
|
||||||
|
- [x] Effective config unchanged: 10 s timeout, CPU0 idle watched, no panic.
|
||||||
|
- [x] Both CI firmware builds green.
|
||||||
|
- [x] STATUS → DONE here and in the megaplan table.
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# A04 — (OPTIONAL) non-blocking DS18B20 conversion in hal_temp
|
||||||
|
|
||||||
|
STATUS: DONE
|
||||||
|
DEPENDS: A03 (shares `temp_task` in `main/main.c`)
|
||||||
|
|
||||||
|
**READ:** this file, `docs/megaplans/AUDIT-MEGAPLAN.md`, `docs/audit_remediation_plan.md` item 5, `components/hal_temp/hal_temp.c`, `main/main.c`
|
||||||
|
|
||||||
|
**Context:** `hal_temp_tick()` currently blocks `temp_task` for ~750 ms per conversion (plus 750 ms on each error path). It is safely isolated from the UI, so this is a robustness/latency improvement only — the audit itself allows keeping the block if the WDT constraint is documented (A03 does that). Implement only if the phase session chooses to spend it.
|
||||||
|
|
||||||
|
**IN:**
|
||||||
|
1. `components/hal_temp/hal_temp.c`: turn `hal_temp_tick()` into a two-state machine driven by a deadline (`esp_timer_get_time()` / 1000, or `xTaskGetTickCount()`):
|
||||||
|
- `T_START`: `onewire_bus_reset` + `CMD_SKIP_ROM`/`CMD_CONVERT_T`; on success record `s_deadline_ms = now + 750` and move to `T_WAIT`. On any bus error: `s_last_ok = false`, set a ~750 ms retry deadline, stay in `T_START` (preserves the current retry cadence).
|
||||||
|
- `T_WAIT`: if `now < s_deadline_ms` return immediately; else `onewire_bus_reset` + `CMD_SKIP_ROM`/`CMD_READ_SCRATCH`, read 9 bytes, CRC-check via existing `scratch_valid`, update `s_last_c`/`s_last_ok`, move back to `T_START` (next conversion starts on the next tick).
|
||||||
|
- The `s_bus == NULL` path must also be non-blocking (`s_last_ok = false`, return).
|
||||||
|
- Keep `TEMP_OFFSET`, `scratch_valid`, `hal_temp_init`, and `hal_temp_read_c` semantics unchanged.
|
||||||
|
2. `main/main.c` `temp_task`: `hal_temp_tick` no longer blocks, so add `vTaskDelay(pdMS_TO_TICKS(100))` at the end of the loop (~10 Hz poll) so the task always sleeps. Calling `app_machine_on_temp` every loop is acceptable (16-deep event queue drained at 40 Hz by `ui_task`); keep it unconditional to keep the diff small.
|
||||||
|
3. Host stub `components/hal_temp/stub/hal_temp.c` / `tests/host/stubs/hal_temp.c`: no signature change — leave as-is unless the implementation forces otherwise.
|
||||||
|
|
||||||
|
**OUT:** additional sensors, temperature alarms, heater control, `hal_temp_read_c` contract changes.
|
||||||
|
|
||||||
|
**FORBIDDEN:** changing `TEMP_OFFSET` or the CRC; any `vTaskDelay` ≥ 750 ms left inside `hal_temp_tick`; blocking the UI task; removing the watchdog.
|
||||||
|
|
||||||
|
**VERIFY:**
|
||||||
|
```
|
||||||
|
cmake -S tests/host -B build/host && cmake --build build/host && ctest --test-dir build/host --output-on-failure
|
||||||
|
```
|
||||||
|
plus CI firmware builds for both targets.
|
||||||
|
|
||||||
|
**COMMITS:**
|
||||||
|
1. `Make DS18B20 conversion non-blocking in hal_temp`
|
||||||
|
|
||||||
|
**DoD checkboxes:**
|
||||||
|
- [x] No `vTaskDelay(750)` inside `hal_temp_tick`; conversion waits via deadline.
|
||||||
|
- [x] `temp_task` always blocks (explicit 100 ms delay).
|
||||||
|
- [x] `TEMP_OFFSET`, CRC, and read semantics unchanged.
|
||||||
|
- [x] Host ctest green; both CI firmware builds green.
|
||||||
|
- [x] STATUS → DONE here and in the megaplan table (or noted `SKIPPED` with reason).
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
STATUS: TODO
|
STATUS: DONE
|
||||||
DEPENDS: I01
|
DEPENDS: I01
|
||||||
|
Notes: Poll API named `hal_temp_tick` (declared in hal_temp.h). `idf.py build` for both targets verified via CI run 38616 (no local IDF). S3 flash watchdog check not run — no device attached to this machine.
|
||||||
|
|
||||||
**READ:** `docs/megaplans/INTEGRATION-MEGAPLAN.md`, `main/main.c`, `components/hal_temp/*`
|
**READ:** `docs/megaplans/INTEGRATION-MEGAPLAN.md`, `main/main.c`, `components/hal_temp/*`
|
||||||
|
|
||||||
@@ -17,6 +18,6 @@ DEPENDS: I01
|
|||||||
2. `Fix temp_task starvation bug on non-WROOM`
|
2. `Fix temp_task starvation bug on non-WROOM`
|
||||||
|
|
||||||
**DoD checkboxes:**
|
**DoD checkboxes:**
|
||||||
- [ ] `hal_temp` generalized to use board accessors.
|
- [x] `hal_temp` generalized to use board accessors.
|
||||||
- [ ] `temp_task` starvation bug fixed.
|
- [x] `temp_task` starvation bug fixed.
|
||||||
- [ ] Build succeeds on both targets.
|
- [x] Build succeeds on both targets.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
STATUS: TODO
|
STATUS: DONE
|
||||||
DEPENDS: I01
|
DEPENDS: I01
|
||||||
|
|
||||||
**READ:** `docs/megaplans/INTEGRATION-MEGAPLAN.md`, `components/hal_motor/*`
|
**READ:** `docs/megaplans/INTEGRATION-MEGAPLAN.md`, `components/hal_motor/*`
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
STATUS: TODO
|
STATUS: DONE
|
||||||
DEPENDS: I01
|
DEPENDS: I01
|
||||||
|
Notes: `idf.py build` for both targets verified via CI run 38684 (no local IDF).
|
||||||
|
|
||||||
**READ:** `docs/megaplans/INTEGRATION-MEGAPLAN.md`, `components/hal_audio/*`
|
**READ:** `docs/megaplans/INTEGRATION-MEGAPLAN.md`, `components/hal_audio/*`
|
||||||
|
|
||||||
@@ -17,6 +18,6 @@ DEPENDS: I01
|
|||||||
1. `Add I2S speaker audio for S3`
|
1. `Add I2S speaker audio for S3`
|
||||||
|
|
||||||
**DoD checkboxes:**
|
**DoD checkboxes:**
|
||||||
- [ ] `hal_audio` I2S implementation added for S3.
|
- [x] `hal_audio` I2S implementation added for S3.
|
||||||
- [ ] Queue protocol and non-blocking semantics maintained.
|
- [x] Queue protocol and non-blocking semantics maintained.
|
||||||
- [ ] Build succeeds.
|
- [x] Build succeeds.
|
||||||
|
|||||||
+46
-9
@@ -28,8 +28,6 @@ static const char *TAG = "app";
|
|||||||
#if defined(AUTOFILM_BOARD_WROOM) || defined(AUTOFILM_BOARD_S3)
|
#if defined(AUTOFILM_BOARD_WROOM) || defined(AUTOFILM_BOARD_S3)
|
||||||
static QueueHandle_t s_cmdq;
|
static QueueHandle_t s_cmdq;
|
||||||
|
|
||||||
extern void autofilm_temp_tick(void);
|
|
||||||
|
|
||||||
static void map_and_post_key(char key)
|
static void map_and_post_key(char key)
|
||||||
{
|
{
|
||||||
ui_cmd_t cmd;
|
ui_cmd_t cmd;
|
||||||
@@ -99,7 +97,10 @@ static void map_and_post_key(char key)
|
|||||||
static void input_task(void *arg)
|
static void input_task(void *arg)
|
||||||
{
|
{
|
||||||
(void)arg;
|
(void)arg;
|
||||||
esp_task_wdt_add(NULL);
|
esp_err_t werr = esp_task_wdt_add(NULL);
|
||||||
|
if (werr != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "input wdt add failed: %s", esp_err_to_name(werr));
|
||||||
|
}
|
||||||
const TickType_t period = pdMS_TO_TICKS(40); /* 25 Hz */
|
const TickType_t period = pdMS_TO_TICKS(40); /* 25 Hz */
|
||||||
for (;;) {
|
for (;;) {
|
||||||
ui_raw_key_t raw;
|
ui_raw_key_t raw;
|
||||||
@@ -121,7 +122,10 @@ static void input_task(void *arg)
|
|||||||
static void machine_task(void *arg)
|
static void machine_task(void *arg)
|
||||||
{
|
{
|
||||||
(void)arg;
|
(void)arg;
|
||||||
esp_task_wdt_add(NULL);
|
esp_err_t werr = esp_task_wdt_add(NULL);
|
||||||
|
if (werr != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "machine wdt add failed: %s", esp_err_to_name(werr));
|
||||||
|
}
|
||||||
const TickType_t period = pdMS_TO_TICKS(20); /* 50 Hz */
|
const TickType_t period = pdMS_TO_TICKS(20); /* 50 Hz */
|
||||||
for (;;) {
|
for (;;) {
|
||||||
ui_cmd_t cmd;
|
ui_cmd_t cmd;
|
||||||
@@ -138,7 +142,10 @@ static void machine_task(void *arg)
|
|||||||
static void ui_task(void *arg)
|
static void ui_task(void *arg)
|
||||||
{
|
{
|
||||||
(void)arg;
|
(void)arg;
|
||||||
esp_task_wdt_add(NULL);
|
esp_err_t werr = esp_task_wdt_add(NULL);
|
||||||
|
if (werr != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "ui wdt add failed: %s", esp_err_to_name(werr));
|
||||||
|
}
|
||||||
|
|
||||||
hal_display_clear();
|
hal_display_clear();
|
||||||
hal_display_text(0, 0, "AUTOFILM");
|
hal_display_text(0, 0, "AUTOFILM");
|
||||||
@@ -166,15 +173,17 @@ static void ui_task(void *arg)
|
|||||||
static void temp_task(void *arg)
|
static void temp_task(void *arg)
|
||||||
{
|
{
|
||||||
(void)arg;
|
(void)arg;
|
||||||
esp_task_wdt_add(NULL);
|
esp_err_t werr = esp_task_wdt_add(NULL);
|
||||||
|
if (werr != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "temp wdt add failed: %s", esp_err_to_name(werr));
|
||||||
|
}
|
||||||
for (;;) {
|
for (;;) {
|
||||||
#ifdef AUTOFILM_BOARD_WROOM
|
hal_temp_tick();
|
||||||
autofilm_temp_tick();
|
|
||||||
#endif
|
|
||||||
float c = 0.0f;
|
float c = 0.0f;
|
||||||
bool ok = (hal_temp_read_c(&c) == ESP_OK);
|
bool ok = (hal_temp_read_c(&c) == ESP_OK);
|
||||||
app_machine_on_temp(ok ? c : 0.0f, ok);
|
app_machine_on_temp(ok ? c : 0.0f, ok);
|
||||||
esp_task_wdt_reset();
|
esp_task_wdt_reset();
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(100));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -209,6 +218,34 @@ void app_main(void)
|
|||||||
|
|
||||||
s_cmdq = xQueueCreate(UI_CMD_QUEUE_LEN, sizeof(ui_cmd_t));
|
s_cmdq = xQueueCreate(UI_CMD_QUEUE_LEN, sizeof(ui_cmd_t));
|
||||||
|
|
||||||
|
#if CONFIG_ESP_TASK_WDT_EN
|
||||||
|
esp_task_wdt_config_t wdt_cfg = {
|
||||||
|
.timeout_ms = CONFIG_ESP_TASK_WDT_TIMEOUT_S * 1000,
|
||||||
|
.idle_core_mask =
|
||||||
|
#if CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0
|
||||||
|
(1u << 0)
|
||||||
|
#else
|
||||||
|
0
|
||||||
|
#endif
|
||||||
|
#if CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1
|
||||||
|
| (1u << 1)
|
||||||
|
#endif
|
||||||
|
,
|
||||||
|
#if CONFIG_ESP_TASK_WDT_PANIC
|
||||||
|
.trigger_panic = true,
|
||||||
|
#else
|
||||||
|
.trigger_panic = false,
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
esp_err_t wdt_err = esp_task_wdt_init(&wdt_cfg);
|
||||||
|
if (wdt_err == ESP_ERR_INVALID_STATE) {
|
||||||
|
wdt_err = esp_task_wdt_reconfigure(&wdt_cfg); /* already auto-initialised */
|
||||||
|
}
|
||||||
|
if (wdt_err != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "task wdt setup: %s", esp_err_to_name(wdt_err));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
xTaskCreate(temp_task, "temp", 4096, NULL, 2, NULL);
|
xTaskCreate(temp_task, "temp", 4096, NULL, 2, NULL);
|
||||||
xTaskCreate(input_task, "input", 3072, NULL, 8, NULL);
|
xTaskCreate(input_task, "input", 3072, NULL, 8, NULL);
|
||||||
xTaskCreate(machine_task, "machine", 4096, NULL, 6, NULL);
|
xTaskCreate(machine_task, "machine", 4096, NULL, 6, NULL);
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ add_executable(test_machine
|
|||||||
stubs/hal_motor.c
|
stubs/hal_motor.c
|
||||||
stubs/hal_temp.c
|
stubs/hal_temp.c
|
||||||
stubs/hal_audio.c
|
stubs/hal_audio.c
|
||||||
|
freertos/queue.c
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(test_machine PRIVATE ${HOST_INCLUDES})
|
target_include_directories(test_machine PRIVATE ${HOST_INCLUDES})
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
typedef long BaseType_t;
|
||||||
|
typedef unsigned long UBaseType_t;
|
||||||
|
typedef uint32_t TickType_t;
|
||||||
|
|
||||||
|
#define pdFALSE ((BaseType_t)0)
|
||||||
|
#define pdTRUE ((BaseType_t)1)
|
||||||
|
#define pdPASS pdTRUE
|
||||||
|
#define pdFAIL pdFALSE
|
||||||
|
|
||||||
|
#define pdMS_TO_TICKS(xTimeInMs) ((TickType_t)(xTimeInMs))
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
#include "freertos/queue.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
struct QueueDefinition {
|
||||||
|
UBaseType_t len;
|
||||||
|
UBaseType_t item_size;
|
||||||
|
UBaseType_t head;
|
||||||
|
UBaseType_t tail;
|
||||||
|
UBaseType_t count;
|
||||||
|
uint8_t *storage;
|
||||||
|
};
|
||||||
|
|
||||||
|
QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength, UBaseType_t uxItemSize)
|
||||||
|
{
|
||||||
|
struct QueueDefinition *q = calloc(1, sizeof(*q));
|
||||||
|
if (q == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
q->storage = malloc((size_t)uxQueueLength * uxItemSize);
|
||||||
|
if (q->storage == NULL) {
|
||||||
|
free(q);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
q->len = uxQueueLength;
|
||||||
|
q->item_size = uxItemSize;
|
||||||
|
return q;
|
||||||
|
}
|
||||||
|
|
||||||
|
BaseType_t xQueueSend(QueueHandle_t xQueue, const void *pvItemToQueue, TickType_t xTicksToWait)
|
||||||
|
{
|
||||||
|
(void)xTicksToWait;
|
||||||
|
if (xQueue == NULL || pvItemToQueue == NULL) {
|
||||||
|
return pdFALSE;
|
||||||
|
}
|
||||||
|
if (xQueue->count == xQueue->len) {
|
||||||
|
return pdFALSE;
|
||||||
|
}
|
||||||
|
memcpy(xQueue->storage + (size_t)xQueue->tail * xQueue->item_size,
|
||||||
|
pvItemToQueue, xQueue->item_size);
|
||||||
|
xQueue->tail = (xQueue->tail + 1u) % xQueue->len;
|
||||||
|
xQueue->count++;
|
||||||
|
return pdTRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
BaseType_t xQueueReceive(QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait)
|
||||||
|
{
|
||||||
|
(void)xTicksToWait;
|
||||||
|
if (xQueue == NULL || pvBuffer == NULL) {
|
||||||
|
return pdFALSE;
|
||||||
|
}
|
||||||
|
if (xQueue->count == 0u) {
|
||||||
|
return pdFALSE;
|
||||||
|
}
|
||||||
|
memcpy(pvBuffer, xQueue->storage + (size_t)xQueue->head * xQueue->item_size,
|
||||||
|
xQueue->item_size);
|
||||||
|
xQueue->head = (xQueue->head + 1u) % xQueue->len;
|
||||||
|
xQueue->count--;
|
||||||
|
return pdTRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
BaseType_t xQueueReset(QueueHandle_t xQueue)
|
||||||
|
{
|
||||||
|
if (xQueue == NULL) {
|
||||||
|
return pdFALSE;
|
||||||
|
}
|
||||||
|
xQueue->head = 0;
|
||||||
|
xQueue->tail = 0;
|
||||||
|
xQueue->count = 0;
|
||||||
|
return pdTRUE;
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
|
||||||
|
struct QueueDefinition;
|
||||||
|
typedef struct QueueDefinition *QueueHandle_t;
|
||||||
|
|
||||||
|
QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength, UBaseType_t uxItemSize);
|
||||||
|
BaseType_t xQueueSend(QueueHandle_t xQueue, const void *pvItemToQueue, TickType_t xTicksToWait);
|
||||||
|
BaseType_t xQueueReceive(QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait);
|
||||||
|
BaseType_t xQueueReset(QueueHandle_t xQueue);
|
||||||
@@ -10,6 +10,10 @@ void hal_temp_init(void)
|
|||||||
stub_temp_c = 20.0f;
|
stub_temp_c = 20.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void hal_temp_tick(void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
esp_err_t hal_temp_read_c(float *out)
|
esp_err_t hal_temp_read_c(float *out)
|
||||||
{
|
{
|
||||||
if (stub_temp_fail) {
|
if (stub_temp_fail) {
|
||||||
|
|||||||
+17
-17
@@ -145,12 +145,10 @@ static void arm_start_complete_custom_10s(void)
|
|||||||
app_machine_tick(9999);
|
app_machine_tick(9999);
|
||||||
expect_int((int)app_machine_state(), ST_RUNNING, "custom tick9999");
|
expect_int((int)app_machine_state(), ST_RUNNING, "custom tick9999");
|
||||||
app_machine_tick(10000);
|
app_machine_tick(10000);
|
||||||
expect_int((int)app_machine_state(), ST_COMPLETE, "custom complete");
|
expect_int((int)app_machine_state(), ST_ARMED, "custom auto-armed next");
|
||||||
|
expect_int((int)app_machine_step_index(), 1, "custom auto-advance step");
|
||||||
expect_int(stub_motor_enabled ? 1 : 0, 0, "custom motor off complete");
|
expect_int(stub_motor_enabled ? 1 : 0, 0, "custom motor off complete");
|
||||||
expect_int(stub_alarm_count >= 1 ? 1 : 0, 1, "custom alarm");
|
expect_int(stub_alarm_count >= 1 ? 1 : 0, 1, "custom alarm");
|
||||||
/* default auto_advance false */
|
|
||||||
app_machine_tick(10001);
|
|
||||||
expect_int((int)app_machine_state(), ST_COMPLETE, "auto_advance stays complete");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void stop_disables_motor_and_resume(void)
|
static void stop_disables_motor_and_resume(void)
|
||||||
@@ -182,7 +180,7 @@ static void stop_disables_motor_and_resume(void)
|
|||||||
expect_int(stub_beep_count >= beeps + 1 ? 1 : 0, 1, "resume beep");
|
expect_int(stub_beep_count >= beeps + 1 ? 1 : 0, 1, "resume beep");
|
||||||
app_machine_tick(1000);
|
app_machine_tick(1000);
|
||||||
app_machine_tick(1000 + rem);
|
app_machine_tick(1000 + rem);
|
||||||
expect_int((int)app_machine_state(), ST_COMPLETE, "resume then complete");
|
expect_int((int)app_machine_state(), ST_ARMED, "resume then auto-armed");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void return_from_stopped(void)
|
static void return_from_stopped(void)
|
||||||
@@ -208,23 +206,23 @@ static void return_from_stopped(void)
|
|||||||
expect_int((int)app_process_get(PROC_CUSTOM)->steps[0].time_s, 10, "const still 10");
|
expect_int((int)app_process_get(PROC_CUSTOM)->steps[0].time_s, 10, "const still 10");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void stop_during_complete_cancels_alarm(void)
|
static void auto_advance_last_step_goes_idle(void)
|
||||||
{
|
{
|
||||||
app_machine_init();
|
app_machine_init();
|
||||||
ui_cmd_t c = cmd_select(PROC_CUSTOM);
|
ui_cmd_t c = cmd_select(PROC_CUSTOM);
|
||||||
app_machine_handle_cmd(&c);
|
app_machine_handle_cmd(&c);
|
||||||
ui_cmd_t arm = cmd_arm(0);
|
ui_cmd_t arm = cmd_arm(3);
|
||||||
app_machine_handle_cmd(&arm);
|
app_machine_handle_cmd(&arm);
|
||||||
ui_cmd_t start = cmd_start(0);
|
ui_cmd_t start = cmd_start(3);
|
||||||
app_machine_handle_cmd(&start);
|
app_machine_handle_cmd(&start);
|
||||||
app_machine_tick(0);
|
app_machine_tick(0);
|
||||||
app_machine_tick(10000);
|
app_machine_tick(10000);
|
||||||
expect_int((int)app_machine_state(), ST_COMPLETE, "complete before stop");
|
expect_int((int)app_machine_state(), ST_IDLE, "last step goes idle");
|
||||||
int cancels = stub_alarm_cancel_count;
|
ui_evt_id_t ids[8];
|
||||||
ui_cmd_t stop = cmd_id(CMD_STOP);
|
int n = pop_ids(ids, 8);
|
||||||
app_machine_handle_cmd(&stop);
|
if (!has_id(ids, n, EVT_PROCESS_IDLE)) {
|
||||||
expect_int((int)app_machine_state(), ST_STEP_SELECT, "stop from complete");
|
fail("last step idle event");
|
||||||
expect_int(stub_alarm_cancel_count >= cancels + 1 ? 1 : 0, 1, "alarm cancel");
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ecn2_remjet_zero_time(void)
|
static void ecn2_remjet_zero_time(void)
|
||||||
@@ -246,7 +244,8 @@ static void ecn2_remjet_zero_time(void)
|
|||||||
expect_int(stub_motor_enabled ? 1 : 0, 0, "remjet no enable");
|
expect_int(stub_motor_enabled ? 1 : 0, 0, "remjet no enable");
|
||||||
expect_int(stub_agitate_start_count, starts, "remjet no agitate");
|
expect_int(stub_agitate_start_count, starts, "remjet no agitate");
|
||||||
app_machine_tick(0);
|
app_machine_tick(0);
|
||||||
expect_int((int)app_machine_state(), ST_COMPLETE, "remjet complete next tick");
|
expect_int((int)app_machine_state(), ST_ARMED, "remjet auto-armed next");
|
||||||
|
expect_int((int)app_machine_step_index(), 2, "remjet auto-advance step");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void stop_ignored_meaningless_in_idle(void)
|
static void stop_ignored_meaningless_in_idle(void)
|
||||||
@@ -275,7 +274,8 @@ static void c41_clock(void)
|
|||||||
app_machine_tick(179999);
|
app_machine_tick(179999);
|
||||||
expect_int((int)app_machine_state(), ST_RUNNING, "c41 tick179999");
|
expect_int((int)app_machine_state(), ST_RUNNING, "c41 tick179999");
|
||||||
app_machine_tick(180000);
|
app_machine_tick(180000);
|
||||||
expect_int((int)app_machine_state(), ST_COMPLETE, "c41 tick180000");
|
expect_int((int)app_machine_state(), ST_ARMED, "c41 auto-armed next");
|
||||||
|
expect_int((int)app_machine_step_index(), 1, "c41 auto-advance step");
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(void)
|
int main(void)
|
||||||
@@ -285,7 +285,7 @@ int main(void)
|
|||||||
arm_start_complete_custom_10s();
|
arm_start_complete_custom_10s();
|
||||||
stop_disables_motor_and_resume();
|
stop_disables_motor_and_resume();
|
||||||
return_from_stopped();
|
return_from_stopped();
|
||||||
stop_during_complete_cancels_alarm();
|
auto_advance_last_step_goes_idle();
|
||||||
ecn2_remjet_zero_time();
|
ecn2_remjet_zero_time();
|
||||||
stop_ignored_meaningless_in_idle();
|
stop_ignored_meaningless_in_idle();
|
||||||
c41_clock();
|
c41_clock();
|
||||||
|
|||||||
Reference in New Issue
Block a user