246 lines
8.7 KiB
C
246 lines
8.7 KiB
C
#include <string.h>
|
|
#include "esp_log.h"
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/task.h"
|
|
#include "freertos/semphr.h"
|
|
#include "state_machine.h"
|
|
#include "binary_parser.h"
|
|
#include "checksum.h"
|
|
#include "usb_host.h"
|
|
#include "stk500v1.h"
|
|
#include "serial_bridge.h"
|
|
#include "led_button.h"
|
|
#include "ble_service.h"
|
|
|
|
static const char *TAG = "SM";
|
|
|
|
static deployer_state_t current_state = STATE_IDLE;
|
|
static uint16_t stk500_page_size = 128;
|
|
|
|
/* Flasher task: blocks on flasher_sem, runs stk500v1 off the BLE thread. */
|
|
static SemaphoreHandle_t flasher_sem = NULL;
|
|
static TaskHandle_t flasher_task_handle = NULL;
|
|
|
|
/* Snapshot of the buffer to flash (valid only while in STATE_FLASHING). */
|
|
static const uint8_t *flasher_buffer = NULL;
|
|
static size_t flasher_size = 0;
|
|
|
|
static void flasher_task(void *arg)
|
|
{
|
|
while (1) {
|
|
xSemaphoreTake(flasher_sem, portMAX_DELAY);
|
|
|
|
ESP_LOGI(TAG, "Flasher task started: %d bytes", (int)flasher_size);
|
|
|
|
/* Claim USB RX so STK500 can read CDC responses exclusively. */
|
|
usb_host_rx_claim();
|
|
|
|
bool ok = stk500v1_flash_buffer(flasher_buffer, flasher_size,
|
|
stk500_page_size);
|
|
|
|
usb_host_rx_release();
|
|
|
|
ESP_LOGI(TAG, "Flasher task done: %s", ok ? "OK" : "FAIL");
|
|
state_machine_process_event(ok ? EVENT_FLASH_OK : EVENT_FLASH_FAIL,
|
|
NULL);
|
|
}
|
|
}
|
|
|
|
void state_machine_init(void)
|
|
{
|
|
current_state = STATE_IDLE;
|
|
esp_log_level_set(TAG, ESP_LOG_DEBUG);
|
|
|
|
flasher_sem = xSemaphoreCreateBinary();
|
|
if (!flasher_sem) {
|
|
ESP_LOGE(TAG, "Failed to create flasher_sem");
|
|
return;
|
|
}
|
|
|
|
BaseType_t r = xTaskCreate(flasher_task, "flasher", 8192, NULL, 4,
|
|
&flasher_task_handle);
|
|
if (r != pdPASS) {
|
|
ESP_LOGE(TAG, "Failed to create flasher task");
|
|
}
|
|
|
|
ESP_LOGI(TAG, "State machine initialized: IDLE (flasher task ready)");
|
|
}
|
|
|
|
void state_machine_process_event(sm_event_t event, void *data)
|
|
{
|
|
ESP_LOGD(TAG, "Event: %d in state: %s", event, state_machine_get_state_name());
|
|
|
|
/* ACK unique index for END: 0xFFFF so the webapp can disambiguate from INIT (index=0). */
|
|
static const uint16_t END_ACK_INDEX = 0xFFFF;
|
|
|
|
switch (current_state) {
|
|
case STATE_IDLE:
|
|
if (event == EVENT_BLE_INIT) {
|
|
current_state = STATE_RECEIVING;
|
|
led_set_pattern(LED_BLUE_BLINK);
|
|
ESP_LOGI(TAG, "Transition: IDLE -> RECEIVING");
|
|
}
|
|
break;
|
|
|
|
case STATE_RECEIVING:
|
|
if (event == EVENT_BLE_END) {
|
|
current_state = STATE_VERIFYING;
|
|
ESP_LOGI(TAG, "Transition: RECEIVING -> VERIFYING");
|
|
state_machine_process_event(EVENT_VERIFY_OK, NULL);
|
|
} else if (event == EVENT_BLE_INIT) {
|
|
ESP_LOGE(TAG, "Unexpected INIT in RECEIVING state");
|
|
} else if (event == EVENT_BLE_DISCONNECT) {
|
|
ESP_LOGI(TAG, "BLE disconnect in RECEIVING, resetting to IDLE");
|
|
binary_parser_reset();
|
|
current_state = STATE_IDLE;
|
|
led_set_pattern(LED_BLUE_BLINK_SLOW);
|
|
}
|
|
break;
|
|
|
|
case STATE_VERIFYING:
|
|
if (event == EVENT_VERIFY_OK) {
|
|
uint32_t expected_crc = binary_parser_get_total_crc();
|
|
uint8_t *buffer = binary_parser_get_buffer();
|
|
size_t size = binary_parser_get_buffer_size();
|
|
uint32_t actual_crc = checksum_crc32(buffer, size);
|
|
|
|
if (actual_crc == expected_crc) {
|
|
ESP_LOGI(TAG, "CRC verification OK: 0x%08X", actual_crc);
|
|
current_state = STATE_FLASHING;
|
|
led_set_pattern(LED_BLUE_BLINK_FAST);
|
|
|
|
/* Hand off to flasher task instead of blocking the BLE
|
|
* thread. Snapshot buffer ptr/size — parser won't mutate
|
|
* them until next INIT (which can't happen in FLASHING). */
|
|
flasher_buffer = buffer;
|
|
flasher_size = size;
|
|
xSemaphoreGive(flasher_sem);
|
|
ESP_LOGI(TAG, "Transition: VERIFYING -> FLASHING (flasher armed)");
|
|
} else {
|
|
ESP_LOGE(TAG, "CRC verification FAILED: expected 0x%08X, got 0x%08X",
|
|
expected_crc, actual_crc);
|
|
state_machine_process_event(EVENT_VERIFY_FAIL, NULL);
|
|
}
|
|
} else if (event == EVENT_VERIFY_FAIL) {
|
|
current_state = STATE_ERROR_CHECKSUM;
|
|
led_set_pattern(LED_RED_BLINK);
|
|
|
|
uint8_t err[] = {CMD_ERR, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00};
|
|
memcpy(err + 4, "CRC MISMATCH", 12);
|
|
ble_service_send_notify_flashing(err, sizeof(err));
|
|
ESP_LOGI(TAG, "Transition: VERIFYING -> ERROR_CHECKSUM");
|
|
} else if (event == EVENT_BLE_DISCONNECT) {
|
|
ESP_LOGI(TAG, "BLE disconnect in VERIFYING, resetting to IDLE");
|
|
binary_parser_reset();
|
|
current_state = STATE_IDLE;
|
|
led_set_pattern(LED_BLUE_BLINK_SLOW);
|
|
}
|
|
break;
|
|
|
|
case STATE_FLASHING:
|
|
if (event == EVENT_FLASH_OK) {
|
|
/* Flasher task completed successfully — send ACK to webapp. */
|
|
current_state = STATE_SERIAL_BRIDGE;
|
|
led_set_pattern(LED_GREEN);
|
|
serial_bridge_start();
|
|
|
|
uint8_t ack[] = {CMD_ACK,
|
|
END_ACK_INDEX & 0xFF,
|
|
(END_ACK_INDEX >> 8) & 0xFF,
|
|
0x00, 0x00, 0x00, 0x00, 0x00};
|
|
ble_service_send_notify_flashing(ack, sizeof(ack));
|
|
ESP_LOGI(TAG, "Transition: FLASHING -> SERIAL_BRIDGE");
|
|
} else if (event == EVENT_FLASH_FAIL) {
|
|
current_state = STATE_ERROR_TARGET;
|
|
led_set_pattern(LED_RED);
|
|
|
|
uint8_t err[] = {CMD_ERR, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00};
|
|
memcpy(err + 4, "FLASH FAILED", 12);
|
|
ble_service_send_notify_flashing(err, sizeof(err));
|
|
ESP_LOGI(TAG, "Transition: FLASHING -> ERROR_TARGET");
|
|
} else if (event == EVENT_BLE_DISCONNECT) {
|
|
ESP_LOGW(TAG, "BLE disconnect during FLASHING — flasher will finish, SM reset to IDLE");
|
|
current_state = STATE_IDLE;
|
|
led_set_pattern(LED_BLUE_BLINK_SLOW);
|
|
}
|
|
break;
|
|
|
|
case STATE_SERIAL_BRIDGE:
|
|
if (event == EVENT_BLE_INIT) {
|
|
serial_bridge_stop();
|
|
/* Don't call binary_parser_reset() here — the parser's INIT
|
|
* handler (binary_parser.c:51-68) already resets buffer_offset,
|
|
* buffer_size, memset, and state. Calling reset here wipes
|
|
* expected_total_crc to 0 AFTER the parser already set it
|
|
* from the INIT payload, causing END CRC verification to fail. */
|
|
current_state = STATE_RECEIVING;
|
|
led_set_pattern(LED_BLUE_BLINK);
|
|
ESP_LOGI(TAG, "Transition: SERIAL_BRIDGE -> RECEIVING (re-deploy)");
|
|
} else if (event == EVENT_BLE_DISCONNECT || event == EVENT_USB_DISCONNECT) {
|
|
serial_bridge_stop();
|
|
current_state = STATE_ERROR_TARGET;
|
|
led_set_pattern(LED_RED);
|
|
ESP_LOGI(TAG, "Transition: SERIAL_BRIDGE -> ERROR_TARGET (disconnect)");
|
|
}
|
|
break;
|
|
|
|
case STATE_ERROR_TARGET:
|
|
case STATE_ERROR_CHECKSUM:
|
|
if (event == EVENT_BLE_INIT) {
|
|
ESP_LOGI(TAG, "Re-deploy from ERROR state — transition to RECEIVING");
|
|
serial_bridge_stop();
|
|
/* Parser resets itself on INIT — see note in SERIAL_BRIDGE handler. */
|
|
current_state = STATE_RECEIVING;
|
|
led_set_pattern(LED_BLUE_BLINK);
|
|
} else if (event == EVENT_BUTTON_RETRY) {
|
|
ESP_LOGI(TAG, "Button retry pressed, resetting to IDLE");
|
|
binary_parser_reset();
|
|
serial_bridge_stop();
|
|
current_state = STATE_IDLE;
|
|
led_set_pattern(LED_BLUE_BLINK_SLOW);
|
|
} else if (event == EVENT_BLE_DISCONNECT) {
|
|
current_state = STATE_IDLE;
|
|
led_set_pattern(LED_BLUE_BLINK_SLOW);
|
|
ESP_LOGI(TAG, "Transition: ERROR -> IDLE (BLE disconnect)");
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
deployer_state_t state_machine_get_current(void)
|
|
{
|
|
return current_state;
|
|
}
|
|
|
|
const char *state_machine_get_state_name(void)
|
|
{
|
|
switch (current_state) {
|
|
case STATE_IDLE: return "IDLE";
|
|
case STATE_RECEIVING: return "RECEIVING";
|
|
case STATE_VERIFYING: return "VERIFYING";
|
|
case STATE_FLASHING: return "FLASHING";
|
|
case STATE_SERIAL_BRIDGE: return "SERIAL_BRIDGE";
|
|
case STATE_ERROR_TARGET: return "ERROR_TARGET";
|
|
case STATE_ERROR_CHECKSUM: return "ERROR_CHECKSUM";
|
|
default: return "UNKNOWN";
|
|
}
|
|
}
|
|
|
|
void state_machine_tick(void)
|
|
{
|
|
if (current_state == STATE_SERIAL_BRIDGE) {
|
|
if (!usb_host_arduino_connected()) {
|
|
state_machine_process_event(EVENT_USB_DISCONNECT, NULL);
|
|
}
|
|
if (!ble_service_is_connected()) {
|
|
state_machine_process_event(EVENT_BLE_DISCONNECT, NULL);
|
|
}
|
|
}
|
|
|
|
if ((current_state == STATE_ERROR_TARGET || current_state == STATE_ERROR_CHECKSUM)) {
|
|
if (button_retry_pressed()) {
|
|
state_machine_process_event(EVENT_BUTTON_RETRY, NULL);
|
|
}
|
|
}
|
|
}
|