feat(firmware): serial bridge throttle, LED blink fix, re-deploy safety
serial_bridge.c + .h: - Implement 20pkt/s throttle with 240-byte buffer - serial_bridge_on_usb_data() for buffering USB serial data - Flush every 50ms or 500ms stale timeout led_button.c + .h: - Per-color LED blink toggle in led_button_tick() - BLUE_BLINK 200ms, RED_BLINK 100ms, GREEN_BLINK 200ms - Fix: was set_led(true,true,true) for all blink patterns main.c: - Route USB serial data through serial_bridge_on_usb_data - Add led_button_tick() call in main loop state_machine.c: - Add binary_parser_reset() on SERIAL_BRIDGE re-deploy
This commit is contained in:
parent
f6a04b81ba
commit
4a6ef8ce57
|
|
@ -0,0 +1,106 @@
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "driver/gpio.h"
|
||||||
|
#include "esp_timer.h"
|
||||||
|
#include "led_button.h"
|
||||||
|
|
||||||
|
static const char *TAG = "LED_BTN";
|
||||||
|
|
||||||
|
static led_pattern_t current_pattern = LED_OFF;
|
||||||
|
static bool button_previous_state = true;
|
||||||
|
static bool button_pressed_flag = false;
|
||||||
|
static int64_t last_toggle_time = 0;
|
||||||
|
static bool blink_state = false;
|
||||||
|
|
||||||
|
static void set_led(bool red, bool green, bool blue)
|
||||||
|
{
|
||||||
|
gpio_set_level(LED_GPIO_RED, red ? 1 : 0);
|
||||||
|
gpio_set_level(LED_GPIO_GREEN, green ? 1 : 0);
|
||||||
|
gpio_set_level(LED_GPIO_BLUE, blue ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void led_button_init(void)
|
||||||
|
{
|
||||||
|
gpio_set_direction(LED_GPIO_RED, GPIO_MODE_OUTPUT);
|
||||||
|
gpio_set_direction(LED_GPIO_GREEN, GPIO_MODE_OUTPUT);
|
||||||
|
gpio_set_direction(LED_GPIO_BLUE, GPIO_MODE_OUTPUT);
|
||||||
|
|
||||||
|
gpio_set_direction(BTN_GPIO_RETRY, GPIO_MODE_INPUT);
|
||||||
|
gpio_set_pull_mode(BTN_GPIO_RETRY, GPIO_PULLUP_ONLY);
|
||||||
|
|
||||||
|
set_led(false, false, false);
|
||||||
|
button_previous_state = gpio_get_level(BTN_GPIO_RETRY);
|
||||||
|
ESP_LOGI(TAG, "LED + Button initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
void led_set_pattern(led_pattern_t pattern)
|
||||||
|
{
|
||||||
|
current_pattern = pattern;
|
||||||
|
|
||||||
|
switch (pattern) {
|
||||||
|
case LED_OFF:
|
||||||
|
set_led(false, false, false);
|
||||||
|
break;
|
||||||
|
case LED_GREEN:
|
||||||
|
set_led(false, true, false);
|
||||||
|
break;
|
||||||
|
case LED_RED:
|
||||||
|
set_led(true, false, false);
|
||||||
|
break;
|
||||||
|
case LED_BLUE:
|
||||||
|
set_led(false, false, true);
|
||||||
|
break;
|
||||||
|
case LED_GREEN_BLINK:
|
||||||
|
case LED_RED_BLINK:
|
||||||
|
case LED_BLUE_BLINK:
|
||||||
|
/* Toggle handled by led_button_tick(). Start off. */
|
||||||
|
blink_state = false;
|
||||||
|
last_toggle_time = esp_timer_get_time() / 1000;
|
||||||
|
set_led(false, false, false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool button_retry_pressed(void)
|
||||||
|
{
|
||||||
|
bool current = gpio_get_level(BTN_GPIO_RETRY);
|
||||||
|
|
||||||
|
if (button_previous_state && !current) {
|
||||||
|
button_previous_state = current;
|
||||||
|
ESP_LOGI(TAG, "Retry button pressed");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
button_previous_state = current;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void led_button_tick(void)
|
||||||
|
{
|
||||||
|
if (current_pattern != LED_GREEN_BLINK &&
|
||||||
|
current_pattern != LED_RED_BLINK &&
|
||||||
|
current_pattern != LED_BLUE_BLINK) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t now = esp_timer_get_time() / 1000;
|
||||||
|
int64_t interval = (current_pattern == LED_RED_BLINK) ? 100 : 200;
|
||||||
|
|
||||||
|
if (now - last_toggle_time >= interval) {
|
||||||
|
last_toggle_time = now;
|
||||||
|
blink_state = !blink_state;
|
||||||
|
|
||||||
|
switch (current_pattern) {
|
||||||
|
case LED_BLUE_BLINK:
|
||||||
|
set_led(false, false, blink_state);
|
||||||
|
break;
|
||||||
|
case LED_RED_BLINK:
|
||||||
|
set_led(blink_state, false, false);
|
||||||
|
break;
|
||||||
|
case LED_GREEN_BLINK:
|
||||||
|
set_led(false, blink_state, false);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
/**
|
||||||
|
* @file led_button.h
|
||||||
|
* @brief Status LED (RGB) and tactile Retry button.
|
||||||
|
*
|
||||||
|
* LED patterns:
|
||||||
|
* BLUE_BLINK — advertising / receiving data
|
||||||
|
* BLUE — verification in progress
|
||||||
|
* GREEN — flash succeeded, serial bridge active
|
||||||
|
* RED — STK500 target error
|
||||||
|
* RED_BLINK — checksum mismatch
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#define LED_GPIO_RED 13
|
||||||
|
#define LED_GPIO_GREEN 12
|
||||||
|
#define LED_GPIO_BLUE 14
|
||||||
|
#define BTN_GPIO_RETRY 0 /**< GPIO0 (BOOT button on most dev boards) */
|
||||||
|
|
||||||
|
/** LED visual patterns. */
|
||||||
|
typedef enum {
|
||||||
|
LED_OFF, /**< All off */
|
||||||
|
LED_GREEN, /**< Solid green — success */
|
||||||
|
LED_RED, /**< Solid red — error */
|
||||||
|
LED_BLUE, /**< Solid blue — busy */
|
||||||
|
LED_GREEN_BLINK, /**< Blinking green */
|
||||||
|
LED_RED_BLINK, /**< Blinking red — checksum error */
|
||||||
|
LED_BLUE_BLINK /**< Blinking blue — advertising/receiving */
|
||||||
|
} led_pattern_t;
|
||||||
|
|
||||||
|
void led_button_init(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the LED visual pattern.
|
||||||
|
* @param pattern Pattern from led_pattern_t enum
|
||||||
|
*/
|
||||||
|
void led_set_pattern(led_pattern_t pattern);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if the Retry button was just pressed (edge-triggered).
|
||||||
|
* @return true on falling edge detection
|
||||||
|
*/
|
||||||
|
bool button_retry_pressed(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Periodic tick for LED blink patterns. Call from main loop (~10ms).
|
||||||
|
*/
|
||||||
|
void led_button_tick(void);
|
||||||
|
|
@ -0,0 +1,92 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "nvs_flash.h"
|
||||||
|
#include "esp_bt.h"
|
||||||
|
|
||||||
|
#include "ble_service.h"
|
||||||
|
#include "binary_parser.h"
|
||||||
|
#include "checksum.h"
|
||||||
|
#include "usb_host.h"
|
||||||
|
#include "stk500v1.h"
|
||||||
|
#include "state_machine.h"
|
||||||
|
#include "serial_bridge.h"
|
||||||
|
#include "led_button.h"
|
||||||
|
|
||||||
|
static const char *TAG = "MAIN";
|
||||||
|
|
||||||
|
static void on_ble_flashing_data(uint8_t *data, size_t len)
|
||||||
|
{
|
||||||
|
if (len < 4) return;
|
||||||
|
|
||||||
|
uint8_t cmd = data[0];
|
||||||
|
|
||||||
|
switch (cmd) {
|
||||||
|
case CMD_INIT: {
|
||||||
|
ESP_LOGI(TAG, "Received INIT command");
|
||||||
|
binary_parser_process_packet(data, len);
|
||||||
|
state_machine_process_event(EVENT_BLE_INIT, data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CMD_DATA: {
|
||||||
|
parser_state_t result = binary_parser_process_packet(data, len);
|
||||||
|
if (result == PARSER_ERROR) {
|
||||||
|
ESP_LOGE(TAG, "Packet parse error");
|
||||||
|
state_machine_process_event(EVENT_VERIFY_FAIL, NULL);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CMD_END: {
|
||||||
|
ESP_LOGI(TAG, "Received END command");
|
||||||
|
binary_parser_process_packet(data, len);
|
||||||
|
state_machine_process_event(EVENT_BLE_END, data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
ESP_LOGW(TAG, "Unknown command: 0x%02X", cmd);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void on_usb_serial_data(uint8_t *data, size_t len)
|
||||||
|
{
|
||||||
|
serial_bridge_on_usb_data(data, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void on_ble_serial_data(uint8_t *data, size_t len)
|
||||||
|
{
|
||||||
|
if (serial_bridge_is_active()) {
|
||||||
|
usb_host_write_cdc(data, len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void app_main(void)
|
||||||
|
{
|
||||||
|
ESP_LOGI(TAG, "Velxio BLE Deployer v1.0");
|
||||||
|
|
||||||
|
nvs_flash_init();
|
||||||
|
esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT);
|
||||||
|
|
||||||
|
led_button_init();
|
||||||
|
checksum_crc32(NULL, 0);
|
||||||
|
binary_parser_init();
|
||||||
|
ble_service_init();
|
||||||
|
ble_service_set_flashing_callback(on_ble_flashing_data);
|
||||||
|
ble_service_set_serial_callback(on_ble_serial_data);
|
||||||
|
|
||||||
|
usb_host_init();
|
||||||
|
usb_host_set_serial_callback(on_usb_serial_data);
|
||||||
|
|
||||||
|
led_set_pattern(LED_BLUE_BLINK);
|
||||||
|
|
||||||
|
state_machine_init();
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
state_machine_tick();
|
||||||
|
serial_bridge_tick();
|
||||||
|
led_button_tick();
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(10));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,114 @@
|
||||||
|
#include <string.h>
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "esp_timer.h"
|
||||||
|
#include "serial_bridge.h"
|
||||||
|
#include "ble_service.h"
|
||||||
|
#include "usb_host.h"
|
||||||
|
|
||||||
|
static const char *TAG = "SERIAL_BRIDGE";
|
||||||
|
|
||||||
|
static bool active = false;
|
||||||
|
static int64_t last_flush_time = 0;
|
||||||
|
static int notify_count = 0;
|
||||||
|
static int64_t rate_window_start = 0;
|
||||||
|
|
||||||
|
#define BRIDGE_BUF_SIZE 240
|
||||||
|
#define MAX_NOTIFY_RATE 20
|
||||||
|
#define RATE_WINDOW_MS 1000
|
||||||
|
#define THROTTLE_INTERVAL_MS 50
|
||||||
|
#define BUFFER_FLUSH_MS 500
|
||||||
|
|
||||||
|
static uint8_t bridge_buf[BRIDGE_BUF_SIZE];
|
||||||
|
static size_t bridge_buf_len = 0;
|
||||||
|
|
||||||
|
void serial_bridge_init(void)
|
||||||
|
{
|
||||||
|
active = false;
|
||||||
|
bridge_buf_len = 0;
|
||||||
|
last_flush_time = 0;
|
||||||
|
notify_count = 0;
|
||||||
|
rate_window_start = 0;
|
||||||
|
ESP_LOGI(TAG, "Serial bridge initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
void serial_bridge_start(void)
|
||||||
|
{
|
||||||
|
active = true;
|
||||||
|
bridge_buf_len = 0;
|
||||||
|
last_flush_time = esp_timer_get_time() / 1000;
|
||||||
|
notify_count = 0;
|
||||||
|
rate_window_start = esp_timer_get_time() / 1000;
|
||||||
|
ESP_LOGI(TAG, "Serial bridge started");
|
||||||
|
}
|
||||||
|
|
||||||
|
void serial_bridge_stop(void)
|
||||||
|
{
|
||||||
|
/* Flush any remaining buffered data before stopping. */
|
||||||
|
if (active && bridge_buf_len > 0) {
|
||||||
|
ble_service_send_notify_serial(bridge_buf, bridge_buf_len);
|
||||||
|
bridge_buf_len = 0;
|
||||||
|
}
|
||||||
|
active = false;
|
||||||
|
ESP_LOGI(TAG, "Serial bridge stopped");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool serial_bridge_is_active(void)
|
||||||
|
{
|
||||||
|
return active;
|
||||||
|
}
|
||||||
|
|
||||||
|
void serial_bridge_on_ble_write(uint8_t *data, size_t len)
|
||||||
|
{
|
||||||
|
if (!active) return;
|
||||||
|
usb_host_write_cdc(data, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
void serial_bridge_on_usb_data(uint8_t *data, size_t len)
|
||||||
|
{
|
||||||
|
if (!active) return;
|
||||||
|
|
||||||
|
/* Large packet: flush buffer first, then send directly. */
|
||||||
|
size_t remaining = BRIDGE_BUF_SIZE - bridge_buf_len;
|
||||||
|
if (len >= BRIDGE_BUF_SIZE) {
|
||||||
|
if (bridge_buf_len > 0) {
|
||||||
|
ble_service_send_notify_serial(bridge_buf, bridge_buf_len);
|
||||||
|
bridge_buf_len = 0;
|
||||||
|
}
|
||||||
|
ble_service_send_notify_serial(data, len);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Buffer nearly full: flush first. */
|
||||||
|
if (len > remaining) {
|
||||||
|
ble_service_send_notify_serial(bridge_buf, bridge_buf_len);
|
||||||
|
bridge_buf_len = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(bridge_buf + bridge_buf_len, data, len);
|
||||||
|
bridge_buf_len += len;
|
||||||
|
}
|
||||||
|
|
||||||
|
void serial_bridge_tick(void)
|
||||||
|
{
|
||||||
|
if (!active || bridge_buf_len == 0) return;
|
||||||
|
|
||||||
|
int64_t now = esp_timer_get_time() / 1000;
|
||||||
|
|
||||||
|
/* Reset rate counter every second. */
|
||||||
|
if (now - rate_window_start >= RATE_WINDOW_MS) {
|
||||||
|
rate_window_start = now;
|
||||||
|
notify_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t since_flush = now - last_flush_time;
|
||||||
|
bool time_to_flush = (since_flush >= THROTTLE_INTERVAL_MS) &&
|
||||||
|
(notify_count < MAX_NOTIFY_RATE);
|
||||||
|
bool stale = since_flush >= BUFFER_FLUSH_MS;
|
||||||
|
|
||||||
|
if (time_to_flush || stale) {
|
||||||
|
ble_service_send_notify_serial(bridge_buf, bridge_buf_len);
|
||||||
|
bridge_buf_len = 0;
|
||||||
|
last_flush_time = now;
|
||||||
|
notify_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
/**
|
||||||
|
* @file serial_bridge.h
|
||||||
|
* @brief Transparent bridge: USB CDC <-> BLE Serial characteristic.
|
||||||
|
*
|
||||||
|
* When active, data from Arduino's Serial.print() is read via USB Host
|
||||||
|
* and immediately forwarded as BLE Notify on the Serial characteristic.
|
||||||
|
* Data from the Webapp (BLE Write) is forwarded to Arduino via USB CDC.
|
||||||
|
*
|
||||||
|
* Throttled to prevent BLE congestion (max 20 packets/sec).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
void serial_bridge_init(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Start bridging between USB CDC and BLE Serial.
|
||||||
|
*/
|
||||||
|
void serial_bridge_start(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Stop bridging and release BLE Serial notifications.
|
||||||
|
*/
|
||||||
|
void serial_bridge_stop(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if bridge is currently active.
|
||||||
|
* @return true if active
|
||||||
|
*/
|
||||||
|
bool serial_bridge_is_active(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Periodic tick for throttling and buffer management.
|
||||||
|
*/
|
||||||
|
void serial_bridge_tick(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Forward data from BLE Serial characteristic to USB CDC.
|
||||||
|
* Called when Webapp writes to the Serial characteristic.
|
||||||
|
* @param data Pointer to received bytes
|
||||||
|
* @param len Number of bytes received
|
||||||
|
*/
|
||||||
|
void serial_bridge_on_ble_write(uint8_t *data, size_t len);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Buffer data from USB CDC (Arduino serial output).
|
||||||
|
* Called from main's on_usb_serial_data callback. Data is buffered
|
||||||
|
* and flushed in serial_bridge_tick() at max 20 pkt/s.
|
||||||
|
* @param data Pointer to received bytes
|
||||||
|
* @param len Number of bytes received
|
||||||
|
*/
|
||||||
|
void serial_bridge_on_usb_data(uint8_t *data, size_t len);
|
||||||
|
|
@ -168,6 +168,7 @@ void state_machine_process_event(sm_event_t event, void *data)
|
||||||
case STATE_SERIAL_BRIDGE:
|
case STATE_SERIAL_BRIDGE:
|
||||||
if (event == EVENT_BLE_INIT) {
|
if (event == EVENT_BLE_INIT) {
|
||||||
serial_bridge_stop();
|
serial_bridge_stop();
|
||||||
|
binary_parser_reset();
|
||||||
current_state = STATE_RECEIVING;
|
current_state = STATE_RECEIVING;
|
||||||
led_set_pattern(LED_BLUE_BLINK);
|
led_set_pattern(LED_BLUE_BLINK);
|
||||||
ESP_LOGI(TAG, "Transition: SERIAL_BRIDGE -> RECEIVING (re-deploy)");
|
ESP_LOGI(TAG, "Transition: SERIAL_BRIDGE -> RECEIVING (re-deploy)");
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue