DinnerRecv: working esp-idf version with ack

This commit is contained in:
2025-09-15 16:26:41 -07:00
parent 0b4b9f512d
commit a18a0a30b0
7 changed files with 287 additions and 162 deletions

View File

@ -1,42 +0,0 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
DinnerRecv is an Arduino project for a FireBeetle 2 ESP32-E microcontroller that controls a WS2812B LED strip. The project creates a sequential white light animation across 8 LEDs.
## Hardware Configuration
- **Microcontroller**: FireBeetle 2 ESP32-E
- **LED Strip**: WS2812B (8 pixels)
- **Data Pin**: GPIO 16
- **Power**: 5V from bottom of the board (under USB connector)
- **Resistor**: 470 Ohm between data line and LED strip
## Development Environment
This is an Arduino sketch (.ino file) that should be developed using:
- Arduino IDE
- PlatformIO
- Or other Arduino-compatible development environment
## Key Dependencies
- `Adafruit_NeoPixel` library for WS2812B LED control
## Code Architecture
The code follows standard Arduino structure:
- `setup()`: Initializes serial communication (115200 baud) and LED strip
- `loop()`: Continuously runs the LED animation sequence
## Hardware Reference
Implementation follows the tutorial at: https://esp32io.com/tutorials/esp32-ws2812b-led-strip
## Configuration Constants
- `PIN 16`: Data pin for LED strip
- `NUMPIXELS 8`: Number of LEDs in the strip
- `DELAYVAL 50`: Delay in milliseconds between LED updates

View File

@ -1,121 +1,143 @@
#include "esp_check.h"
#include "led_strip_encoder.h"
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
static const char *TAG = "led_strip_encoder";
#include "led_strip_encoder.h"
#include "esp_check.h"
static const char *TAG = "led_encoder";
typedef struct {
rmt_encoder_t base;
rmt_encoder_t *bytes_encoder;
rmt_encoder_t *copy_encoder;
int state;
rmt_symbol_word_t reset_code;
rmt_encoder_t base;
rmt_encoder_t *bytes_encoder;
rmt_encoder_t *copy_encoder;
int state;
rmt_symbol_word_t reset_code;
} rmt_led_strip_encoder_t;
static size_t rmt_encode_led_strip(rmt_encoder_t *encoder, rmt_channel_handle_t channel, const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state)
{
rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base);
rmt_encoder_handle_t bytes_encoder = led_encoder->bytes_encoder;
rmt_encoder_handle_t copy_encoder = led_encoder->copy_encoder;
rmt_encode_state_t session_state = 0;
rmt_encode_state_t state = 0;
size_t encoded_symbols = 0;
switch (led_encoder->state) {
case 0: // send RGB data
encoded_symbols += bytes_encoder->encode(bytes_encoder, channel, primary_data, data_size, &session_state);
if (session_state & RMT_ENCODING_COMPLETE) {
led_encoder->state = 1; // switch to next state when current encoding session finished
}
if (session_state & RMT_ENCODING_MEM_FULL) {
state |= RMT_ENCODING_MEM_FULL;
goto out; // yield if there's no free space for encoding artifacts
}
// fall-through
case 1: // send reset code
encoded_symbols += copy_encoder->encode(copy_encoder, channel, &led_encoder->reset_code,
sizeof(led_encoder->reset_code), &session_state);
if (session_state & RMT_ENCODING_COMPLETE) {
led_encoder->state = 0; // back to the initial encoding session
state |= RMT_ENCODING_COMPLETE;
}
if (session_state & RMT_ENCODING_MEM_FULL) {
state |= RMT_ENCODING_MEM_FULL;
goto out; // yield if there's no free space for encoding artifacts
}
RMT_ENCODER_FUNC_ATTR
static size_t rmt_encode_led_strip(rmt_encoder_t *encoder,
rmt_channel_handle_t channel,
const void *primary_data, size_t data_size,
rmt_encode_state_t *ret_state) {
rmt_led_strip_encoder_t *led_encoder =
__containerof(encoder, rmt_led_strip_encoder_t, base);
rmt_encoder_handle_t bytes_encoder = led_encoder->bytes_encoder;
rmt_encoder_handle_t copy_encoder = led_encoder->copy_encoder;
rmt_encode_state_t session_state = RMT_ENCODING_RESET;
rmt_encode_state_t state = RMT_ENCODING_RESET;
size_t encoded_symbols = 0;
switch (led_encoder->state) {
case 0: // send RGB data
encoded_symbols += bytes_encoder->encode(
bytes_encoder, channel, primary_data, data_size, &session_state);
if (session_state & RMT_ENCODING_COMPLETE) {
led_encoder->state =
1; // switch to next state when current encoding session finished
}
if (session_state & RMT_ENCODING_MEM_FULL) {
state |= RMT_ENCODING_MEM_FULL;
goto out; // yield if there's no free space for encoding artifacts
}
// fall-through
case 1: // send reset code
encoded_symbols +=
copy_encoder->encode(copy_encoder, channel, &led_encoder->reset_code,
sizeof(led_encoder->reset_code), &session_state);
if (session_state & RMT_ENCODING_COMPLETE) {
led_encoder->state =
RMT_ENCODING_RESET; // back to the initial encoding session
state |= RMT_ENCODING_COMPLETE;
}
if (session_state & RMT_ENCODING_MEM_FULL) {
state |= RMT_ENCODING_MEM_FULL;
goto out; // yield if there's no free space for encoding artifacts
}
}
out:
*ret_state = state;
return encoded_symbols;
*ret_state = state;
return encoded_symbols;
}
static esp_err_t rmt_del_led_strip_encoder(rmt_encoder_t *encoder)
{
rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base);
rmt_del_encoder(led_encoder->copy_encoder);
rmt_del_encoder(led_encoder->bytes_encoder);
free(led_encoder);
return ESP_OK;
static esp_err_t rmt_del_led_strip_encoder(rmt_encoder_t *encoder) {
rmt_led_strip_encoder_t *led_encoder =
__containerof(encoder, rmt_led_strip_encoder_t, base);
rmt_del_encoder(led_encoder->bytes_encoder);
rmt_del_encoder(led_encoder->copy_encoder);
free(led_encoder);
return ESP_OK;
}
static esp_err_t rmt_led_strip_encoder_reset(rmt_encoder_t *encoder)
{
rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base);
rmt_encoder_reset(led_encoder->bytes_encoder);
rmt_encoder_reset(led_encoder->copy_encoder);
led_encoder->state = 0;
return ESP_OK;
RMT_ENCODER_FUNC_ATTR
static esp_err_t rmt_led_strip_encoder_reset(rmt_encoder_t *encoder) {
rmt_led_strip_encoder_t *led_encoder =
__containerof(encoder, rmt_led_strip_encoder_t, base);
rmt_encoder_reset(led_encoder->bytes_encoder);
rmt_encoder_reset(led_encoder->copy_encoder);
led_encoder->state = RMT_ENCODING_RESET;
return ESP_OK;
}
esp_err_t rmt_new_led_strip_encoder(const led_strip_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder)
{
esp_err_t ret = ESP_OK;
rmt_led_strip_encoder_t *led_encoder = NULL;
ESP_GOTO_ON_FALSE(config && ret_encoder, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
led_encoder = calloc(1, sizeof(rmt_led_strip_encoder_t));
ESP_GOTO_ON_FALSE(led_encoder, ESP_ERR_NO_MEM, err, TAG, "no mem for led strip encoder");
led_encoder->base.encode = rmt_encode_led_strip;
led_encoder->base.del = rmt_del_led_strip_encoder;
led_encoder->base.reset = rmt_led_strip_encoder_reset;
// different led strip might have its own timing requirements, following parameter is for WS2812
rmt_bytes_encoder_config_t bytes_encoder_config = {
.bit0 = {
.level0 = 1,
.duration0 = 0.3 * config->resolution / 1000000, // T0H=0.3us
.level1 = 0,
.duration1 = 0.9 * config->resolution / 1000000, // T0L=0.9us
},
.bit1 = {
.level0 = 1,
.duration0 = 0.9 * config->resolution / 1000000, // T1H=0.9us
.level1 = 0,
.duration1 = 0.3 * config->resolution / 1000000, // T1L=0.3us
},
.flags.msb_first = 1 // WS2812 transfer bit order: G7...G0R7...R0B7...B0
};
ESP_GOTO_ON_ERROR(rmt_new_bytes_encoder(&bytes_encoder_config, &led_encoder->bytes_encoder), err, TAG, "create bytes encoder failed");
rmt_copy_encoder_config_t copy_encoder_config = {};
ESP_GOTO_ON_ERROR(rmt_new_copy_encoder(&copy_encoder_config, &led_encoder->copy_encoder), err, TAG, "create copy encoder failed");
uint32_t reset_ticks = config->resolution / 1000000 * 50 / 2; // reset code duration defaults to 50us
led_encoder->reset_code = (rmt_symbol_word_t) {
.level0 = 0,
.duration0 = reset_ticks,
.level1 = 0,
.duration1 = reset_ticks,
};
*ret_encoder = &led_encoder->base;
return ESP_OK;
esp_err_t rmt_new_led_strip_encoder(const led_strip_encoder_config_t *config,
rmt_encoder_handle_t *ret_encoder) {
esp_err_t ret = ESP_OK;
rmt_led_strip_encoder_t *led_encoder = NULL;
ESP_GOTO_ON_FALSE(config && ret_encoder, ESP_ERR_INVALID_ARG, err, TAG,
"invalid argument");
led_encoder = rmt_alloc_encoder_mem(sizeof(rmt_led_strip_encoder_t));
ESP_GOTO_ON_FALSE(led_encoder, ESP_ERR_NO_MEM, err, TAG,
"no mem for led strip encoder");
led_encoder->base.encode = rmt_encode_led_strip;
led_encoder->base.del = rmt_del_led_strip_encoder;
led_encoder->base.reset = rmt_led_strip_encoder_reset;
// different led strip might have its own timing requirements, following
// parameter is for WS2812
rmt_bytes_encoder_config_t bytes_encoder_config = {
.bit0 =
{
.level0 = 1,
.duration0 = 0.3 * config->resolution / 1000000, // T0H=0.3us
.level1 = 0,
.duration1 = 0.9 * config->resolution / 1000000, // T0L=0.9us
},
.bit1 =
{
.level0 = 1,
.duration0 = 0.9 * config->resolution / 1000000, // T1H=0.9us
.level1 = 0,
.duration1 = 0.3 * config->resolution / 1000000, // T1L=0.3us
},
.flags.msb_first = 1 // WS2812 transfer bit order: G7...G0R7...R0B7...B0
};
ESP_GOTO_ON_ERROR(
rmt_new_bytes_encoder(&bytes_encoder_config, &led_encoder->bytes_encoder),
err, TAG, "create bytes encoder failed");
rmt_copy_encoder_config_t copy_encoder_config = {};
ESP_GOTO_ON_ERROR(
rmt_new_copy_encoder(&copy_encoder_config, &led_encoder->copy_encoder),
err, TAG, "create copy encoder failed");
uint32_t reset_ticks = config->resolution / 1000000 * 50 /
2; // reset code duration defaults to 50us
led_encoder->reset_code = (rmt_symbol_word_t){
.level0 = 0,
.duration0 = reset_ticks,
.level1 = 0,
.duration1 = reset_ticks,
};
*ret_encoder = &led_encoder->base;
return ESP_OK;
err:
if (led_encoder) {
if (led_encoder->bytes_encoder) {
rmt_del_encoder(led_encoder->bytes_encoder);
}
if (led_encoder->copy_encoder) {
rmt_del_encoder(led_encoder->copy_encoder);
}
free(led_encoder);
if (led_encoder) {
if (led_encoder->bytes_encoder) {
rmt_del_encoder(led_encoder->bytes_encoder);
}
return ret;
}
if (led_encoder->copy_encoder) {
rmt_del_encoder(led_encoder->copy_encoder);
}
free(led_encoder);
}
return ret;
}

View File

@ -1,6 +1,12 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "driver/rmt_encoder.h"
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
@ -20,7 +26,7 @@ typedef struct {
* @param[out] ret_encoder Returned encoder handle
* @return
* - ESP_ERR_INVALID_ARG for any invalid arguments
* - ESP_ERR_NO_MEM out of memory when creating encoder
* - ESP_ERR_NO_MEM out of memory when creating led strip encoder
* - ESP_OK if creating encoder successfully
*/
esp_err_t rmt_new_led_strip_encoder(const led_strip_encoder_config_t *config,

View File

@ -1,3 +1,4 @@
#include "driver/gpio.h"
#include "driver/rmt_tx.h"
#include "esp_log.h"
#include "esp_now.h"
@ -13,14 +14,18 @@
#include <string.h>
#define LED_STRIP_GPIO_NUM 8
#define LED_STRIP_LED_NUMBERS 4
#define LED_STRIP_LED_NUMBERS 8
#define LED_STRIP_RMT_RES_HZ \
(10 * 1000 * 1000) // 10MHz resolution, 1 tick = 0.1us
#define BUTTON_GPIO_NUM 7
#define ESPNOW_WIFI_CHANNEL 6
static const char *TAG = "DinnerRecv";
static const uint8_t dinner_send_mac[6] = {0x08, 0x3A, 0xF2, 0x3E, 0x6D, 0x34};
// Global variables
static bool dinner_alert = false;
static uint64_t dinner_start_time = 0;
@ -28,6 +33,8 @@ static uint64_t last_blink_time = 0;
static bool led_state = false;
static uint8_t breath = 0;
static uint64_t last_breath_time = 0;
static bool button_pressed = false;
static uint64_t last_button_time = 0;
// RMT and LED strip handles
static rmt_channel_handle_t led_chan = NULL;
@ -86,13 +93,13 @@ static void on_data_received(const esp_now_recv_info_t *mac_addr,
}
static void blink_all_leds(void) {
if (get_time_ms() - last_blink_time >= 2000) {
if (get_time_ms() - last_blink_time >= 500) {
last_blink_time = get_time_ms();
led_state = !led_state;
if (led_state) {
for (int i = 0; i < LED_STRIP_LED_NUMBERS; i++) {
led_strip_set_pixel(i, 255, 0, 0);
led_strip_set_pixel(i, 0, 0, 255); // G R B
}
} else {
led_strip_clear();
@ -104,13 +111,13 @@ static void blink_all_leds(void) {
static void breathe(void) {
uint8_t val = breath > 128 ? 256 - breath : breath;
for (int i = 0; i < LED_STRIP_LED_NUMBERS; i++) {
led_strip_set_pixel(i, 0, 0, val);
led_strip_set_pixel(i, val, val, val);
}
led_strip_show();
}
static void dinner_animation(void) {
if (get_time_ms() - dinner_start_time > 15 * 1000) {
if (get_time_ms() - dinner_start_time > 20 * 1000) {
dinner_alert = false;
ESP_LOGI(TAG, "Dinner alert timeout - returning to normal mode");
led_strip_clear();
@ -151,6 +158,19 @@ static esp_err_t init_wifi(void) {
return ESP_OK;
}
static esp_err_t send_dinner_ack(void) {
const char *message = "dinner-ack";
esp_err_t ret =
esp_now_send(dinner_send_mac, (const uint8_t *)message, strlen(message));
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to send dinner-ack: %s", esp_err_to_name(ret));
return ret;
}
ESP_LOGI(TAG, "Sent dinner-ack message");
return ESP_OK;
}
static esp_err_t init_espnow(void) {
esp_err_t ret = esp_now_init();
if (ret != ESP_OK) {
@ -161,6 +181,19 @@ static esp_err_t init_espnow(void) {
ESP_ERROR_CHECK(esp_now_register_recv_cb(on_data_received));
ESP_LOGI(TAG, "ESP-NOW initialized and ready to receive messages");
ret = esp_now_add_peer(&(esp_now_peer_info_t){
.peer_addr = {dinner_send_mac[0], dinner_send_mac[1], dinner_send_mac[2],
dinner_send_mac[3], dinner_send_mac[4], dinner_send_mac[5]},
.channel = ESPNOW_WIFI_CHANNEL,
.encrypt = false,
});
if (ret != ESP_OK && ret != ESP_ERR_ESPNOW_EXIST) {
ESP_LOGE(TAG, "Failed to add peer: %s", esp_err_to_name(ret));
return ret;
}
ESP_LOGI(TAG, "ESP-NOW sender registered");
return ESP_OK;
}
@ -191,16 +224,47 @@ static esp_err_t init_led_strip(void) {
return ESP_OK;
}
static esp_err_t init_button(void) {
gpio_config_t io_conf = {
.pin_bit_mask = (1ULL << BUTTON_GPIO_NUM),
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
ESP_ERROR_CHECK(gpio_config(&io_conf));
ESP_LOGI(TAG, "Button initialized on GPIO %d", BUTTON_GPIO_NUM);
return ESP_OK;
}
static void check_button(void) {
int button_level = gpio_get_level(BUTTON_GPIO_NUM);
uint64_t current_time = get_time_ms();
if (button_level == 0 && !button_pressed &&
(current_time - last_button_time > 200)) {
button_pressed = true;
last_button_time = current_time;
ESP_LOGI(TAG, "Button pressed!");
if (dinner_alert) {
dinner_alert = false;
ESP_LOGI(TAG, "Dinner alert dismissed by button press");
led_strip_clear();
led_strip_show();
send_dinner_ack();
}
} else if (button_level == 1) {
button_pressed = false;
}
}
void app_main(void) {
ESP_LOGI(TAG, "Dinner RECV");
// Initialize LED strip
ESP_ERROR_CHECK(init_led_strip());
// Initialize WiFi
ESP_ERROR_CHECK(init_button());
ESP_ERROR_CHECK(init_wifi());
// Initialize ESP-NOW
ESP_ERROR_CHECK(init_espnow());
// Print WiFi parameters
@ -214,11 +278,14 @@ void app_main(void) {
// Main loop
while (1) {
check_button();
if (dinner_alert) {
dinner_animation();
} else {
breath_animation();
}
vTaskDelay(pdMS_TO_TICKS(10)); // Small delay to prevent watchdog timeout
vTaskDelay(pdMS_TO_TICKS(10)); // prevent watchdog timeout
}
}