Skip to content

Elecrow All-in-One Starter Kit for ESP32-P4

Device Type:misc
Electrical Standard:global
Board:esp32
Difficulty:Plug-n-flash (2/5)

Elecrow ESP32-P4 All-in-One Starter Kit

Product Description

The Elecrow ESP32-P4 All-in-One Starter Kit is a comprehensive AI and IoT development board based on Espressif’s ESP32-P4 microprocessor. It integrates 16 common sensor modules on a single circuit board, eliminating the need for complex soldering and wiring, making it ideal for students, educators, makers, and hardware engineers to learn AI applications and embedded development.

Elecrow ESP32-P4 All-In-One Kit

Specifications

ParameterValue
Main ProcessorESP32-P4NRW32
CPU (HP)32-bit RISC-V dual-core processor (up to 360 MHz)
CPU (LP)32-bit RISC-V single-core processor (up to 40 MHz)
System Memory32 MB in-Package PSRAM
Storage128 KB HP ROM, 16 KB LP ROM, 128 Mbit (16 MB) QSPI NOR Flash
Display Size7 inch
Display Resolution1024×600
Display TypeIPS Capacitive Touch
Camera2 MegaPixel, 100° wide-angle lens
AudioDual stereo speakers
Wireless ExpansionReserved module slot for optional Wi-Fi / HaLow / Bluetooth / LoRa / Zigbee / Thread / nRF / Temperature / Humidity / TinyML modules
16-in-1 Integrated ModulesAll sensors integrated on single PCB
InterfacesTYPE-C, I2C, UART, I/O, CSI, USB, Ethernet, TF Card Slot
Input Voltage5V DC - 4W-7W
Dimensions195×170×46 mm
Weight600g

Back to top

Integrated Sensors

The board includes 16 pre-integrated sensor modules:

  1. Ultrasonic Sensor (HC-SR04)
  2. Light/Luminosity Sensor (BH1750)
  3. RGBW Red Yellow Green Blue LED WS2814A
  4. Temperature & Humidity Sensor (DHT20)
  5. DSI Display (EK79007)
  6. PIR Motion Sensor (Silvan BIS0001)
  7. Servo Motor (V1.0 continuous / V1.1 180°)
  8. Accelerometer & Gyro (STMicroelectronics LSM6DS3TR-C IMU 6-DoF)
  9. Hall Effect Sensor (Hallwee HAL248)
  10. Custom_key (4 x Buttons) (ADC)
  11. Touch Sensor (Tontek TTP223)
  12. Microphone (LinkMems LMD4737T261-OAC02)
  13. Audio (Nsiway Tech NS4168 L/R)
  14. Gas Sensor (Hanwei MQ2)
  15. Relay Module (SRD-05VDC-SL-C)
  16. CSI Camera (SmartSens SC2336 2MP CMOS sensor)

Additional I/O:

  1. SD-Card Slot
  2. UART to USB-C (CH340K)
  3. USB C Host
  4. USB A Host
  5. Ethernet (IP101)

Lessons

The following lessons demonstrate how to use each sensor and component on the board.

Each lesson below is from the manual and corresponds to a conversion of the ESP-IDF code to an ESPHome example YAML. There are also a few extras at the end (Ethernet, SD-Card, USB, wireless modules, etc.).

esphome:
name: elecrow-esp32-p4-aio
esp32:
variant: esp32p4
framework:
type: esp-idf
psram:
mode: hex
speed: 200MHz
i2c:
- id: camera_i2c
sda: GPIO45
scl: GPIO46
- id: sensor_i2c
sda: GPIO18
scl: GPIO19
spi:
clk_pin: GPIO09
mosi_pin: GPIO11
miso_pin: GPIO10

📖 For detailed lesson instructions, see the Official User Manual V1.0 (PDF) or User Manual V1.1 (PDF).

  1. Lesson 1 GPIO LED Control
  2. Lesson 2 Relay
  3. Lesson 3 Touch Sensor
  4. Lesson 4 PIR Motion Sensor
  5. Lesson 5 Hall Effect Sensor
  6. Lesson 6 UART Communication
  7. Lesson 7 Timer
  8. Lesson 8 Servo Motor (V1.0/V1.1)
  9. Lesson 9 Display Touch
  10. Lesson 10 Ultrasonic Distance Sensor
  11. Lesson 11 Temperature & Humidity Sensor
  12. Lesson 12 Light Sensor
  13. Lesson 13 Accelerometer & Gyro (IMU 6DoF)
  14. Lesson 14 RGBW LED
  15. Lesson 15 ADC Buttons
  16. Lesson 16 Gas Sensor
  17. Lesson 17 Microphone
  18. Lesson 18 Speaker
  19. Lesson 19 Display LVGL Touch

Quick Dev Environment Setup For New Users

How to setup a quick environment to play with.

Required Versions

  • Python 3.13+
  • ESPHome 2026.02+
  • ESP-IDF 5.4.2+ (the ESP32-C6 ESP-Hosted build is tested with 5.5.2)

Installation

Development Environment Setup

  1. Install Visual Studio Code

  2. Install Helpful VSCode Extensions

  3. Install ESPHome

Requirements: Git, Python 3.13+, and pip. Clone the ESPHome repository:

Terminal window
git clone https://github.com/esphome/esphome.git
cd esphome
git checkout dev

Linux

Terminal window
python3 -m venv venv
source venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -e .

The P4 usually appears as /dev/ttyACM0 or /dev/ttyUSB0. If access is denied, add your user to the dialout group, then sign out and back in:

Terminal window
sudo usermod -a -G dialout "$USER"

macOS

Terminal window
python3 -m venv venv
source venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -e .

The P4 usually appears as /dev/cu.usbmodem* or /dev/cu.SLAB_USBtoUART.

Windows PowerShell

Terminal window
py -3.13 -m venv venv
.\venv\Scripts\Activate.ps1
python -m pip install --upgrade pip
python -m pip install -e .

If PowerShell blocks activation, run:

Terminal window
Set-ExecutionPolicy -Scope CurrentUser RemoteSigned

The P4 usually appears as a COM port. Find the exact port in Device Manager under Ports (COM & LPT).

  1. Connect Device
    • Connect AIO ESP32-P4 kit via USB Type-C cable.
    • Ensure device is detected with the proper drivers and lights up.

Backup ESP-IDF Factory Firmware

Backup the factory firmware just in case you want to test to see if everything is working again.

Identify P4

Terminal window
esptool --chip esp32p4 --port PORT flash-id

Dump Flash P4

Terminal window
esptool --chip esp32p4 --port PORT read-flash 0 0x1000000 elecrow_aio_kit_firmware_backup.bin

Restore Flash P4

Terminal window
esptool --chip esp32p4 --port PORT write-flash 0 elecrow_aio_kit_firmware_backup.bin

Replace PORT with /dev/ttyUSB0 or /dev/ttyACM0 on Linux, /dev/cu.usbmodemXXXX on macOS, or COM5 (for example) on Windows.

ESPHome ESP32-P4 Basic Run Command (Compile/Upload/Logs)

This command will compile your YAML, then upload it, and then attach to the log to see what’s up!

Linux:

Terminal window
esphome run Your-YAML-Name-Here-001.yaml --device /dev/ttyACM0

macOS:

Terminal window
esphome run Your-YAML-Name-Here-001.yaml --device /dev/cu.usbmodemXXXX

Windows PowerShell:

Terminal window
esphome run Your-YAML-Name-Here-001.yaml --device COM5

Back to top

Pinout / Schematics / Examples

Note: These are in order by schematic number not lesson number.

16 Integrated Sensors GPIO & Pinout

1. Ultrasonic Distance Sensor (HC-SR04) J14

Schematic 1

ESPHome Docs - Ultrasonic

Ultrasonic Sensor(HC-SR04)

Ultrasonic (HC-SR04)

GPIOLabel
GPIO12Echo
GPIO13Trigger
sensor:
# 1. Ultrasonic Distance Sensor
- platform: ultrasonic
trigger_pin: GPIO13
echo_pin: GPIO12
name: "Distance"
update_interval: 5s
timeout: 3m

Lesson: Ultrasonic Distance Sensor

Manual Lesson 10

Description: Measure distance using the HC-SR04 ultrasonic sensor.

sensor:
- platform: ultrasonic
trigger_pin: GPIO13
echo_pin: GPIO12
name: "Distance"
update_interval: 1s
timeout: 3m
filters:
- filter_out: nan
- sliding_window_moving_average:
window_size: 5
send_every: 1
on_value:
then:
- logger.log:
format: "Distance: %.1f m"
args: ['x']

2. Light Luminosity Sensor (BH1750)

ESPHome Docs - BH1750

Light/Luminosity Sensor (BH1750)

Light/Luminosity Sensor (BH1750)

GPIOLabel
GPIO18SDA (0x5c)
GPIO19SCL (0x5c)
i2c:
sda: GPIO18
scl: GPIO19
sensor:
# 2. Light Sensor
- platform: bh1750
address: 0x5C
name: "Illuminance"
update_interval: 10s

Lesson: Light Sensor (BH1750)

Manual Lesson 12

Description: Measure ambient light intensity.

i2c:
sda: GPIO18
scl: GPIO19
sensor:
- platform: bh1750
address: 0x5C
name: "Illuminance"
update_interval: 5s
on_value_range:
- below: 10
then:
- logger.log: "It's dark!"
- above: 500
then:
- logger.log: "It's bright!"

3. RGBW Red Yellow Green Blue LED WS2814A U14

ESPHome Docs - LED Strip

WS2814A RGBW

WS2814A RGBW

GPIOLabel
GPIO08DIN
light:
- platform: esp32_rmt_led_strip
id: rgb_leds
name: "RGBW LED"
chipset: WS2814
pin:
number: GPIO8
num_leds: 1
rgb_order: WRGB
is_wrgb: true
color_interlock: true
rmt_symbols: 192
default_transition_length: 0s

Lesson: RGBW LED (WS2814A)

Manual Lesson 14

Description: Control the single RGBW LED, cycling through red, white, green, and blue every second.

light:
- platform: esp32_rmt_led_strip
id: rgb_leds
name: "RGBW LED"
chipset: WS2814
pin:
number: GPIO8
num_leds: 1
rgb_order: WRGB
is_wrgb: true
color_interlock: true
rmt_symbols: 192
default_transition_length: 0s
interval:
- interval: 1s
then:
- light.turn_on:
id: rgb_leds
white: 0%
red: 100%
green: 0%
blue: 0%
- delay: 1s
- light.turn_on:
id: rgb_leds
white: 100%
red: 0%
green: 0%
blue: 0%
- delay: 1s
- light.turn_on:
id: rgb_leds
white: 0%
red: 0%
green: 100%
blue: 0%
- delay: 1s
- light.turn_on:
id: rgb_leds
white: 0%
red: 0%
green: 0%
blue: 100%

4. Temperature & Humidity Sensor (DHT20) U5

ESPHome Docs - DHT

Temperature & Humidity Sensor (DHT20)

Temperature & Humidity Sensor (DHT20)

GPIOLabel
GPIO18SDA (0x38)
GPIO19SCL (0x38)
i2c:
sda: GPIO18
scl: GPIO19
sensor:
# 4. Temperature & Humidity
- platform: aht10
variant: AHT20
address: 0x38
temperature:
name: "Temperature"
humidity:
name: "Humidity"
update_interval: 30s

Lesson: Temperature & Humidity (DHT20)

Manual Lesson 11

Description: Read temperature and humidity from the DHT20 sensor.

i2c:
sda: GPIO18
scl: GPIO19
scan: true
sensor:
- platform: aht10
variant: AHT20
address: 0x38
temperature:
name: "Temperature"
filters:
- offset: 0.0
humidity:
name: "Humidity"
filters:
- offset: 0.0
update_interval: 10s

5. MIPI DSI (EK79007) J17

ESPHome Docs - MIPI DSI

Display (EK79007)

Display (EK79007)

Key Display Parameters (Verified from Official Code):

ParameterValueSource
ControllerEK79007esp_lcd_new_panel_ek79007()
Resolution1024×600H_size × V_size
Lane Bit Rate1000 MbpsTested custom AIO YAML configuration
DPI Clock51 MHzdpi_config.dpi_clock_freq_mhz
Data Lanes2bus_config.num_data_lanes
Pixel FormatRGB565/RGB666/RGB888Configurable (16/18/24-bit)
Hsync Back Porch160video_timing.hsync_back_porch
Hsync Pulse Width70video_timing.hsync_pulse_width
Hsync Front Porch160video_timing.hsync_front_porch
Vsync Back Porch23video_timing.vsync_back_porch
Vsync Pulse Width10video_timing.vsync_pulse_width
Vsync Front Porch12video_timing.vsync_front_porch
UPDN (GPIO32)0Vertical scan normal
SHLR (GPIO33)1Horizontal scan inverted
Reset GPIOGPIO5LCD_GPIO_RESET
Backlight GPIOGPIO20LCD_GPIO_BLIGHT (PWM)

Backlight PWM Settings:

  • Frequency: 1 kHz (BLIGHT_PWM_Hz in code)
  • Resolution: 11-bit (LEDC_TIMER_11_BIT)
  • Formula: duty = (brightness * 18) + 200 where brightness is 0-100
  • Channel: LEDC_CHANNEL_0, LEDC_TIMER_0, Low-speed mode
  • Min duty: 0 (off), Max duty: ~2048 (100% = 1800 + 200 = 2000)
GPIOLabel
GPIO05LCD Reset / LED
GPIO20LCD Backlight (PWM)
GPIO32UPDN
GPIO33SHLR
GPIO18SDA (Not used for LCD)
GPIO19SCL (Not used for LCD)
  1. MIPI-DSI Display: PR #11886
# MIPI-DSI Display Configuration (EK79007 Controller)
# Parameters verified from official Elecrow ESP-IDF code
display:
- platform: mipi_dsi
id: main_display
model: CUSTOM # EK79007 controller
rotation: 0 # Set to 180 if needed
update_interval: never
auto_clear_enabled: false
lanes: 2
lane_bit_rate: 1000Mbps # Tested custom AIO configuration
color_order: rgb
pclk_frequency: 51MHz # Official: 51 MHz DPI clock
dimensions:
width: 1024
height: 600
# Timing parameters from official code
hsync_back_porch: 160
hsync_pulse_width: 70
hsync_front_porch: 160
vsync_back_porch: 23
vsync_pulse_width: 10 # Official code uses 10, not 1
vsync_front_porch: 12
# Display initialization sequence
# This uses model: CUSTOM, so the board-specific EK79007 sequence must be
# supplied here; it is not provided automatically by the generic driver.
init_sequence:
- [0xB2, 0x10]
- [0x80, 0x8B]
- [0x81, 0x78]
- [0x82, 0x84]
- [0x83, 0x88]
- [0x84, 0xA8]
- [0x85, 0xE3]
- [0x86, 0x88]
- delay: 120ms
- [0x29] # Display on
# LDO Regulator Configuration
esp_ldo:
- channel: 3
voltage: 2.5V
# GPIO Control for Display
# UPDN: GPIO32 (vertical scan direction: 0=normal, 1=inverted)
# SHLR: GPIO33 (horizontal scan direction: 0=normal, 1=inverted)
switch:
- platform: gpio
pin: GPIO32
name: "Display UPDN"
id: display_updn
internal: true
restore_mode: RESTORE_DEFAULT_OFF
- platform: gpio
pin: GPIO33
name: "Display SHLR"
id: display_shlr
internal: true
restore_mode: RESTORE_DEFAULT_ON # Official code sets this to 1
Touch Screen (Goodix GT911) J17

ESPHome Docs - Touch Screen

Touch Screen (Goodix GT911)

Touchscreen (Goodix GT911))

GPIOLabel
GPIO40Reset TP
GPIO41INT TP
GPIO18SDA (0x14 or 0x5d)
GPIO19SCL (0x14 or 0x5d)
# Touch Controller GT911
# Configuration verified from official Elecrow code
i2c:
sda: GPIO18
scl: GPIO19
frequency: 400kHz
touchscreen:
- platform: gt911
id: touch_panel
address: 0x5D # GT911 backup address (IDF driver tries this first)
reset_pin: GPIO40
interrupt_pin:
number: GPIO41
mode: INPUT # SPI ESP-Hosted Wi-Fi may conflict with this interrupt.
transform:
swap_xy: false
mirror_x: false
mirror_y: false
calibration:
x_min: 0
x_max: 1024
y_min: 0
y_max: 600
update_interval: 50ms # Required when SPI ESP-Hosted Wi-Fi is enabled.
on_touch:
- lambda: |-
ESP_LOGI("cal", "x=%d, y=%d, x_raw=%d, y_raw=%0d",
touch.x,
touch.y,
touch.x_raw,
touch.y_raw
);
# Note: GT911 has two possible I2C addresses:
# Primary: 0x5D (ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS_BACKUP)
# Secondary: 0x14 (ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS)
# The driver tries backup address first, then falls back to primary
# I2C clock: 400 kHz (Fast Mode)

Lesson: Display & Touch

Manual Lesson 9

Description: Initialize the 7-inch display with LVGL and touch support.

i2c:
- id: i2c_bus
sda: GPIO18
scl: GPIO19
frequency: 400kHz
touchscreen:
- platform: gt911
id: touch_panel
i2c_id: i2c_bus
address: 0x5D # GT911 backup address (IDF driver tries this first)
reset_pin: GPIO40
interrupt_pin:
number: GPIO41
mode: INPUT # SPI ESP-Hosted Wi-Fi may conflict with this interrupt.
transform:
swap_xy: false
mirror_x: false
mirror_y: false
calibration:
x_min: 0
x_max: 1024
y_min: 0
y_max: 600
update_interval: 50ms # Required when SPI ESP-Hosted Wi-Fi is enabled.
on_touch:
- lambda: |-
ESP_LOGI("cal", "x=%d, y=%d, x_raw=%d, y_raw=%0d",
touch.x,
touch.y,
touch.x_raw,
touch.y_raw
);
# LDO Regulators - both required (LDO3 for DSI PHY, LDO4 for 3.3V peripherals)
esp_ldo:
- channel: 3
voltage: 2.5V
display:
- platform: mipi_dsi
id: main_display
model: CUSTOM
reset_pin:
number: GPIO05
inverted: false
rotation: 0
#show_test_card: true
auto_clear_enabled: false
lanes: 2
lane_bit_rate: 1000Mbps
color_order: rgb
#invert_colors: true
pclk_frequency: 51MHz
dimensions:
width: 1024
height: 600
hsync_back_porch: 160
hsync_pulse_width: 70
hsync_front_porch: 160
vsync_back_porch: 23
vsync_pulse_width: 10
vsync_front_porch: 12
init_sequence:
- [0xB2, 0x10] # PAD_CONTROL register: configures the DSI pad for 2-lane operation (0x10 = 2 lanes)
- [0x80, 0x8B] # DSI timing/PLL register, sets lane speed or clock divider parameter
- [0x81, 0x78] # DSI timing register, typically horizontal timing or PLL multiplier
- [0x82, 0x84] # DSI timing register, typically vertical timing or PLL divider
- [0x83, 0x88] # DSI timing register, front/back porch or sync width parameter
- [0x84, 0xA8] # DSI timing register, another porch/sync parameter
- [0x85, 0xE3] # DSI timing register, likely horizontal active or blanking period
- [0x86, 0x88] # DSI timing register, last of the timing chain parameters
- [0x11] # MIPI DCS Sleep Out; wakes the panel for normal operation.
output:
- platform: ledc
pin: GPIO20
id: backlight_pwm
frequency: 30000Hz # IDF default is 30kHz (CONFIG_BLIGHT_PWM_Hz)
light:
- platform: monochromatic
output: backlight_pwm
name: "Display Backlight"
id: display_backlight
restore_mode: ALWAYS_ON
# UPDN=0 (normal vertical), SHLR=1 (mirrored horizontal) per IDF gpio_set_level calls
switch:
- platform: gpio
pin: GPIO32
name: "Display UPDN"
id: display_updn
internal: true
restore_mode: RESTORE_DEFAULT_OFF # UPDN = 0
- platform: gpio
pin: GPIO33
name: "Display SHLR"
id: display_shlr
internal: true
restore_mode: RESTORE_DEFAULT_ON # SHLR = 1

Lesson: Display with LVGL & Touch

Manual Lesson 19

Description: Create interactive UI with LVGL graphics library and touch input.

i2c:
- id: i2c_bus
sda: GPIO18
scl: GPIO19
frequency: 400kHz
touchscreen:
- platform: gt911
id: touch_panel
i2c_id: i2c_bus
address: 0x5D # GT911 backup address (IDF driver tries this first)
reset_pin: GPIO40
interrupt_pin:
number: GPIO41
mode: INPUT # SPI ESP-Hosted Wi-Fi may conflict with this interrupt.
transform:
swap_xy: false
mirror_x: false
mirror_y: false
calibration:
x_min: 0
x_max: 1024
y_min: 0
y_max: 600
update_interval: 50ms # Required when SPI ESP-Hosted Wi-Fi is enabled.
on_touch:
- lambda: |-
ESP_LOGI("cal", "x=%d, y=%d, x_raw=%d, y_raw=%0d",
touch.x,
touch.y,
touch.x_raw,
touch.y_raw
);
# LDO Regulators - both required (LDO3 for DSI PHY, LDO4 for 3.3V peripherals)
esp_ldo:
- channel: 3
voltage: 2.5V
display:
- platform: mipi_dsi
id: main_display
model: CUSTOM
reset_pin:
number: GPIO05
inverted: false
rotation: 0
#show_test_card: true
auto_clear_enabled: false
lanes: 2
lane_bit_rate: 1000Mbps
color_order: rgb
#invert_colors: true
pclk_frequency: 51MHz
dimensions:
width: 1024
height: 600
hsync_back_porch: 160
hsync_pulse_width: 70
hsync_front_porch: 160
vsync_back_porch: 23
vsync_pulse_width: 10
vsync_front_porch: 12
init_sequence:
- [0xB2, 0x10] # PAD_CONTROL register: configures the DSI pad for 2-lane operation (0x10 = 2 lanes)
- [0x80, 0x8B] # DSI timing/PLL register, sets lane speed or clock divider parameter
- [0x81, 0x78] # DSI timing register, typically horizontal timing or PLL multiplier
- [0x82, 0x84] # DSI timing register, typically vertical timing or PLL divider
- [0x83, 0x88] # DSI timing register, front/back porch or sync width parameter
- [0x84, 0xA8] # DSI timing register, another porch/sync parameter
- [0x85, 0xE3] # DSI timing register, likely horizontal active or blanking period
- [0x86, 0x88] # DSI timing register, last of the timing chain parameters
- [0x11] # MIPI DCS Sleep Out; wakes the panel for normal operation.
output:
- platform: ledc
pin: GPIO20
id: backlight_pwm
frequency: 30000Hz # IDF default is 30kHz (CONFIG_BLIGHT_PWM_Hz)
light:
- platform: monochromatic
output: backlight_pwm
name: "Display Backlight"
id: display_backlight
restore_mode: ALWAYS_ON
# UPDN=0 (normal vertical), SHLR=1 (mirrored horizontal) per IDF gpio_set_level calls
switch:
- platform: gpio
pin: GPIO32
name: "Display UPDN"
id: display_updn
internal: true
restore_mode: RESTORE_DEFAULT_OFF # UPDN = 0
- platform: gpio
pin: GPIO33
name: "Display SHLR"
id: display_shlr
internal: true
restore_mode: RESTORE_DEFAULT_ON # SHLR = 1
on_turn_on:
lambda: |-
it.print(0, 0, id(font), "Hello ESP32-P4!");
font:
- file: "fonts/arial.ttf"
id: font
size: 20

Back to top

6. PIR Motion Sensor (Silvan BIS0001) J101

ESPHome Docs - PIR

PIR Motion Sensor (Silvan BIS0001)

PIR Motion Sensor (Silvan BIS0001)

GPIOLabel
GPIO24VO

GPIO24 is the PIR sensor output on the AIO board. This example assumes the board is using UART0 rather than the USB-Serial-JTAG interface.

binary_sensor:
# 6. PIR Motion Sensor
- platform: gpio
pin: GPIO24
name: "Motion Detected"
device_class: motion

Lesson: PIR Motion Sensor

Manual Lesson 4

Description: Detect motion using the passive infrared sensor and control an LED.

output:
- platform: gpio
pin: GPIO06
id: led_06_output
binary_sensor:
- platform: gpio
pin: GPIO24
name: "Motion Sensor"
device_class: motion
on_press:
- output.turn_off: led_06_output
- logger.log: "Motion detected - LED OFF"
on_release:
- output.turn_on: led_06_output
- logger.log: "Motion stopped - LED ON"

7. Servo (V1.0 Continuous / V1.1 180°) J3

ESPHome Docs - Servo

Servo (V1.0 continuous / V1.1 180°)

Servo (V1.0 continuous / V1.1 180°)

Pin / GPIOLabel
1GND
2V5
3N/C
GPIO25Signal (PWM)

GPIO25 is the AIO servo PWM output (IO25_SERVO). This example assumes the board is using UART0 rather than the USB-Serial-JTAG interface.

Board revision note: The V1.0 kit uses a continuous-rotation servo, so servo levels represent direction and speed around the stop position. The V1.1 kit uses a standard 180° servo, so servo levels represent position instead. Both revisions use the GPIO25 PWM signal shown below.

servo:
# 7. Servo Motor
- id: my_servo
output: servo_pwm
output:
- platform: ledc
id: servo_pwm
pin: GPIO25
frequency: 50Hz

Lesson: Servo Motor Control (V1.0 Continuous Rotation)

Manual Lesson 8

Description: Control the V1.0 continuous-rotation servo with different speeds and directions in a continuous loop.

servo:
- id: my_servo
output: servo_pwm
output:
- platform: ledc
id: servo_pwm
pin: GPIO25
frequency: 50Hz
interval:
- interval: 2s
then:
# Forward direction at different speeds
- servo.write:
id: my_servo
level: -100% # Full speed reverse
- delay: 2s
- servo.write:
id: my_servo
level: -50% # Half speed reverse
- delay: 2s
- servo.write:
id: my_servo
level: 0% # Stop
- delay: 2s
- servo.write:
id: my_servo
level: 50% # Half speed forward
- delay: 2s
- servo.write:
id: my_servo
level: 100% # Full speed forward
- delay: 2s

Lesson: Servo Motor Control (V1.1 180° Servo)

Use this example with the V1.1 board and its standard 180° servo. The same GPIO25 PWM signal is used, but servo.write now selects a position rather than continuous-rotation speed.

servo:
- id: my_servo_v11
output: servo_pwm_v11
min_level: 3%
idle_level: 7.5%
max_level: 12%
output:
- platform: ledc
id: servo_pwm_v11
pin: GPIO25
frequency: 50Hz
interval:
- interval: 3s
then:
- servo.write:
id: my_servo_v11
level: -100% # Approximately 0°
- delay: 3s
- servo.write:
id: my_servo_v11
level: 0% # Approximately 90°
- delay: 3s
- servo.write:
id: my_servo_v11
level: 100% # Approximately 180°

8. Accelerometer & Gyro (STMicroelectronics LSM6DS3TR-C IMU 6-DoF) U6

Accelerometer & Gyro (LSM6DS3TR-C)

Accelerometer & Gyro (LSM6DS3TR-C)

GPIOLabel
GPIO18SDA (0x6b)
GPIO19SCL (0x6b)
i2c:
sda: GPIO18
scl: GPIO19
sensor:
# 8. IMU 6-DoF
- platform: lsm6ds
address: 0x6B
accel_x:
name: "Accel X"
accel_y:
name: "Accel Y"
accel_z:
name: "Accel Z"
gyro_x:
name: "Gyro X"
gyro_y:
name: "Gyro Y"
gyro_z:
name: "Gyro Z"

Lesson: IMU 6-DoF (LSM6DS3TR-C)

Manual Lesson 13

Description: Read accelerometer and gyroscope data from the 6-axis IMU.

i2c:
sda: GPIO18
scl: GPIO19
sensor:
- platform: lsm6ds
address: 0x6B
update_interval: 100ms
accel_x:
name: "Accel X"
accel_y:
name: "Accel Y"
accel_z:
name: "Accel Z"
gyro_x:
name: "Gyro X"
gyro_y:
name: "Gyro Y"
gyro_z:
name: "Gyro Z"
temperature:
name: "IMU Temperature"

9. Hall Effect Sensor (Hallwee HAL248) U7

Hall Effect Sensor (HAL248)

Hall Effect Sensor (HAL248)

GPIOLabel
GPIO07VOUT

Note: This pin is used in the wireless module header so you can’t use them at the same time.

binary_sensor:
# 9. Hall Effect Sensor
- platform: gpio
pin: GPIO7
name: "Magnetic Sensor"

Lesson: Hall Effect Sensor

Manual Lesson 5

Description: Detect magnetic fields using the Hall effect sensor.

binary_sensor:
- platform: gpio
pin: GPIO7
name: "Magnetic Sensor"
on_press:
- logger.log: "Magnet detected!"

10. ADC Buttons Custom Key Ladder k5 k6 k7 k8

ADC buttons

ADC Buttons

GPIOResistorKey / Direction
GPIO161kΩK8 [Left]
GPIO162kΩK7 [Right]
GPIO163.6kΩK6 [Down]
GPIO168.2kΩK5 [Up]
sensor:
# 10. ADC Buttons
- platform: adc
pin: GPIO16
name: "Button ADC"
update_interval: 100ms
attenuation: 12db

Lesson: ADC Buttons

Manual Lesson 15

Description: Read multiple buttons using a single ADC pin and control RGB LED colors based on button press.

sensor:
- platform: adc
pin: GPIO16
name: "Button ADC"
id: button_adc
update_interval: 50ms
attenuation: 12db
filters:
- median:
window_size: 5
send_every: 1
on_value_range:
- below: 0.5
above: 0.3
then:
- logger.log: "Button Left pressed"
- light.turn_on:
id: rgb_leds
red: 100%
green: 0%
blue: 0%
- below: 1.0
above: 0.8
then:
- logger.log: "Button Right pressed"
- light.turn_on:
id: rgb_leds
red: 0%
green: 0%
blue: 100%
- below: 1.7
above: 1.5
then:
- logger.log: "Button Down pressed"
- light.turn_on:
id: rgb_leds
red: 100%
green: 100%
blue: 0%
- below: 2.5
above: 2.3
then:
- logger.log: "Button Up pressed"
- light.turn_on:
id: rgb_leds
red: 0%
green: 100%
blue: 0%
- above: 3.0
then:
- logger.log: "Button released"
- light.turn_off: rgb_leds
light:
- platform: esp32_rmt_led_strip
chipset: WS2814
pin:
number: GPIO8
num_leds: 1
name: "RGBW LED"
id: rgb_leds
rgb_order: WRGB
is_wrgb: true
color_interlock: true
rmt_symbols: 192
default_transition_length: 0s

11. Touch Pad Button (Tontek TTP223) U123

ESPHome Docs - Touchpad

Touch Pad Button (TTP223)

Touch Pad Button (TTP223)

GPIOLabel
GPIO02OUT
binary_sensor:
# 11. Touch Button
- platform: gpio
pin: GPIO2
name: "Touch Button"

Lesson: Touch Sensor

Manual Lesson 3

Description: Detect touch input using the capacitive touch sensor and control an LED.

output:
- platform: gpio
pin: GPIO8
id: led_output
binary_sensor:
- platform: gpio
pin: GPIO2
name: "Touch Button"
on_press:
- output.turn_on: led_output
- logger.log: "Touch detected - LED ON"
on_release:
- output.turn_off: led_output
- logger.log: "Touch released - LED OFF"

12. Mic (LinkMems LMD4737T261-OAC02) U8

ESPHome Docs - Microphone

Tested on ESP32-P4: The onboard microphone works as a PDM microphone. ESPHome required an additional P4 PDM-microphone code change because P4 support was initially treated as unavailable. The configuration below was tested after that change was added.

Mic (LMD4737T261-OAC02)

Mic (LMD4737T261-OAC02)

GPIOLabel
GPIO03CLK
GPIO04Data
i2s_audio:
- id: i2s_input
i2s_lrclk_pin: GPIO3
microphone:
# 12. I2S Microphone
- platform: i2s_audio
id: mic_i2s
i2s_audio_id: i2s_input
i2s_din_pin: GPIO4
adc_type: external
pdm: true

Lesson: Microphone (I2S)

Manual Lesson 17

Description: Capture audio using the I2S microphone.

i2s_audio:
- id: i2s_input
i2s_lrclk_pin: GPIO3
microphone:
- platform: i2s_audio
id: mic_i2s
i2s_audio_id: i2s_input
i2s_din_pin: GPIO4
adc_type: external
pdm: true

13. I2S Audio (Nsiway Tech) NS4168-L U9 NS4168-R U10

ESPHome Docs - I2S Audio

I2S Audio (NS4168)

I2S Audio (NS4168)

Pinout

GPIOLabel
GPIO06Enable
GPIO21LRCLK
GPIO22BCLK
GPIO23DSDIN

ESPHome Basic Setup

i2s_audio:
- id: i2s_output
i2s_lrclk_pin: GPIO21
i2s_bclk_pin: GPIO22
speaker:
# 13. I2S Speaker
- platform: i2s_audio
id: speaker_output
i2s_audio_id: i2s_output
dac_type: external
i2s_dout_pin: GPIO23
channel: stereo

Lesson 18: Speaker (I2S Audio)

Manual Lesson 18

Description: Play audio through the I2S speakers.

i2s_audio:
- id: i2s_output
i2s_lrclk_pin: GPIO21
i2s_bclk_pin: GPIO22
speaker:
- platform: i2s_audio
id: speaker_output
i2s_audio_id: i2s_output
dac_type: external
i2s_dout_pin: GPIO23
channel: stereo
switch:
- platform: gpio
pin: GPIO6
name: "Speaker Enable"
id: speaker_enable
restore_mode: ALWAYS_ON

14. Gas Sensor (Hanwei MQ-2) J6

ESPHome Docs - ADC

Gas Sensor (MQ-2)

Gas Sensor (MQ-2)

GPIOLabel
GPIO17AOUT (ADC)
sensor:
# 14. Gas Sensor
- platform: adc
pin: GPIO17
name: "Gas Level"
update_interval: 5s
attenuation: 11db

Lesson: Gas Sensor (MQ2)

Manual Lesson 16

Description: Detect gas levels using the analog MQ2 sensor.

sensor:
- platform: adc
pin: GPIO17
name: "Gas Level"
update_interval: 2s
attenuation: 11db
filters:
- sliding_window_moving_average:
window_size: 10
on_value_range:
- above: 2.0
then:
- logger.log: "Gas detected!"

15. Relay (SRD-05VDC-SL-C) K4

ESPHome Docs - Switch

Relay

Relay

GPIOLabel
GPIO42Transistor Relay GND Enable
switch:
# 15. Relay
- platform: gpio
pin: GPIO42
name: "Relay"

Lesson: Relay Control

Manual Lesson 2

Description: Control a relay to switch external devices on and off every 5 seconds.

switch:
- platform: gpio
pin: GPIO42
name: "Relay"
id: relay_switch
interval:
- interval: 5s
then:
- switch.toggle: relay_switch

16. Camera CSI (SmartSens SC2336 2MP) J12

ESPHome Docs - CSI Camera

Camera CSI (SC2336)

Camera CSI (SC2336)

GPIOLabel
GPIO45SDA (0x30)
GPIO46SCL (0x30)

This example uses the SC2336 camera component from the external project used by the tested AIO configuration. It is not part of a stable ESPHome release.

# CSI camera example for the Elecrow ESP32-P4 AIO.
# Experimental: these components are external to the stable ESPHome release.
# The camera connector uses the separate camera I2C bus on GPIO45/GPIO46.
external_components:
- source:
type: git
url: https://github.com/youkorr/test2_esp_video_esphome
ref: main
components:
- esp_video
- esp_cam_sensor
refresh: always
i2c:
- id: camera_i2c
sda: GPIO45
scl: GPIO46
frequency: 400kHz
esp_video:
i2c_id: camera_i2c
xclk_pin: GPIO30
xclk_freq: 24000000
enable_jpeg: true
enable_isp: true
use_heap_allocator: false
esp_cam_sensor:
id: sc2336_cam
i2c_id: camera_i2c
sensor_type: SC2336
resolution: "1280x720"
pixel_format: RGB565
framerate: 30
jpeg_quality: 15
mirror_x: false
mirror_y: false
rotation: 0
crop_offset_x: 0

Back to top

Additional Information

Lesson: Timer

Manual Lesson 7

Description: Use timers for periodic tasks and delays.

interval:
- interval: 1s
then:
- logger.log: "Timer tick every second"

SD-Card Slot J7

Temporary dependency: Official ESPHome SD-card support for this board is not available yet. Until it is accepted upstream, this example uses the dev branch of the p1ngb4ck/esphome fork. The refresh: 0s setting is intentional during development so the latest compatible changes are used. Pin this dependency to a tested commit or release once upstream support is available.

ESPHome Docs - TF Card Slot

SD-Card Slot

SD-Card Slot

Function / GPIOHardware Pin
GPIO39DO
GPIO43SCK
GPIO44CMD
external_components:
# - source:
# type: git
# url: https://github.com/youkorr/webdavbox3
# ref: main
# components: [sd_mmc_card, storage, webdavbox3]
# refresh: 0s
- source: github://p1ngb4ck/esphome@dev
components:
- audio
- binary_storage
- http_file_browser
- nfs_client
- picture_viewer
- uart
- usb_host
- usb_uart
- usb_storage
- sd_storage
- smb_client
- storage
- transcoder
- video_player
- webdav_server
refresh: 0s
sd_storage:
id: sd_card
clk_pin: GPIO43
cmd_pin: GPIO44
data0_pin: GPIO39
#data1_pin: GPIO1
#data2_pin: GPIO6
#data3_pin: GPIO5
mode_1bit: true
# power_ctrl_pin: GPIO43
storage:
id: storage_id
webdav_server:
id: webdav_sdcard
storage_id: storage_id
root_path: "/"
url_prefix: "/"
port: 81
username: REPLACE_ME
# SECURITY: credential omitted from documentation example
http_file_browser:
storage_id: storage_id
url_prefix: "/files"
root_path: "/"
auth_enabled: true
username: REPLACE_ME
# SECURITY: credential omitted from documentation example
# Safer defaults: enable uploads and deletion only on a trusted network.
enable_upload: false
enable_download: true
enable_deletion: false

Add the credentials to ESPHome’s secrets.yaml file:

sdcard_username: "choose-a-username"
# SECURITY: credential omitted from documentation example
web_username: "choose-a-web-username"
# SECURITY: credential omitted from documentation example

ESP32-P4 Primary Header Interface

ESPHome Docs - P4

ESP32-P4 Drawing

ESP32-P4 Schematic

IO LED

IO LED

J8

Function / GPIOHardware Pin
VDD_3V3J8 Pin 01
VDD_3V3J8 Pin 02
VDD_3V3J8 Pin 03
GNDJ8 Pin 04
GNDJ8 Pin 05
GNDJ8 Pin 06
N/CJ8 Pin 07
N/CJ8 Pin 08
N/CJ8 Pin 09
N/CJ8 Pin 10
N/CJ8 Pin 11
N/CJ8 Pin 12
GNDJ8 Pin 13
GPIO41J8 Pin 14
N/CJ8 Pin 15
N/CJ8 Pin 16
GNDJ8 Pin 17
GPIO45J8 Pin 18
GPIO46J8 Pin 19
GNDJ8 Pin 20
J9
Function / GPIOHardware Pin
VDD 5VJ9 Pin 01
VDD 5VJ9 Pin 02
GPIO14J9 Pin 03
GPIO11J9 Pin 04
GPIO10J9 Pin 05
GPIO09J9 Pin 06
GPIO53J9 Pin 07
GPIO54J9 Pin 08
GPIO15J9 Pin 09
GPIO07J9 Pin 10
GPIO08J9 Pin 11
GPIO06J9 Pin 12
GPIO05J9 Pin 13
USB_A_PJ9 Pin 14
USB_A_NJ9 Pin 15
GNDJ9 Pin 16
GNDJ9 Pin 17
GPIO47J9 Pin 18
GPIO48J9 Pin 19
GNDJ9 Pin 20
# Individual Output Definitions
output:
- platform: gpio
pin: GPIO41
id: out_41
- platform: gpio
pin: GPIO45
id: out_45
- platform: gpio
pin: GPIO46
id: out_46
- platform: gpio
pin: GPIO14
id: out_14
- platform: gpio
pin: GPIO11
id: out_11
- platform: gpio
pin: GPIO10
id: out_10
- platform: gpio
pin: GPIO09
id: out_09
- platform: gpio
pin: GPIO53
id: out_53
- platform: gpio
pin: GPIO54
id: out_54
- platform: gpio
pin: GPIO15
id: out_15
- platform: gpio
pin: GPIO07
id: out_07
- platform: gpio
pin: GPIO08
id: out_08
- platform: gpio
pin: GPIO06
id: out_06
- platform: gpio
pin: GPIO05
id: out_05
- platform: gpio
pin: GPIO47
id: out_47
- platform: gpio
pin: GPIO48
id: out_48
# Expose them as simple Light entities
light:
- platform: binary
name: "Pin 41"
output: out_41
- platform: binary
name: "Pin 45"
output: out_45
- platform: binary
name: "Pin 46"
output: out_46
- platform: binary
name: "Pin 14"
output: out_14
- platform: binary
name: "Pin 11"
output: out_11
- platform: binary
name: "Pin 10"
output: out_10
- platform: binary
name: "Pin 09"
output: out_09
- platform: binary
name: "Pin 53"
output: out_53
- platform: binary
name: "Pin 54"
output: out_54
- platform: binary
name: "Pin 15"
output: out_15
- platform: binary
name: "Pin 07"
output: out_07
- platform: binary
name: "Pin 08"
output: out_08
- platform: binary
name: "Pin 06"
output: out_06
- platform: binary
name: "Pin 05"
output: out_05
- platform: binary
name: "Pin 47"
output: out_47
- platform: binary
name: "Pin 48"
output: out_48
# Automation to make them all blink
script:
- id: blink_all
mode: restart
then:
- while:
condition:
lambda: 'return true;'
then:
- lambda: |-
for (auto *o : {out_41, out_45, out_46, out_14, out_11, out_10,
out_09, out_53, out_54, out_15, out_07, out_08,
out_06, out_05, out_47, out_48}) {
o->turn_on();
}
- delay: 500ms
- lambda: |-
for (auto *o : {out_41, out_45, out_46, out_14, out_11, out_10,
out_09, out_53, out_54, out_15, out_07, out_08,
out_06, out_05, out_47, out_48}) {
o->turn_off();
}
- delay: 500ms

Lesson: GPIO - LED Control

Manual Lesson 1

Description: Learn basic GPIO output control by toggling an LED on and off every 500ms.

output:
- platform: gpio
pin: GPIO8
id: led_output
switch:
- platform: output
name: "LED"
output: led_output
interval:
- interval: 500ms
then:
- output.turn_on: led_output
- delay: 500ms
- output.turn_off: led_output

Accessory Module Header

Wireless Module

Wireless Module

Wireless-module pin conflict warning: The accessory header shares ESP32-P4 GPIOs with other board functions. When a wireless module is installed, do not use GPIO2, GPIO7, GPIO9, GPIO10, GPIO11, GPIO14, GPIO15, GPIO53, or GPIO54 for unrelated sensors, LEDs, or switches. GPIO2 is the C6 enable/reset line; GPIO7, GPIO9-GPIO11, and GPIO14-GPIO15 carry the one-wire full-duplex SPI transport; GPIO53 and GPIO54 are module control lines. Configure only the signals required by the installed module and verify the module-specific pin table before enabling any other example.

J13
Function / GPIOHardware Pin
GPIO53 W TX CEJ13 Pin 01
GPIO09 W CLKJ13 Pin 02
GPIO10 W MISOJ13 Pin 03
GPIO11 W MOSIJ13 Pin 04
VDD 3V3J13 Pin 05
GNDJ13 Pin 06
VDD 5VJ13 Pin 07
J16
Function / GPIOHardware Pin
GPIO54 W RX IRQJ16 Pin 01
GPIO07 CMDJ16 Pin 02
GPIO02 CLKJ16 Pin 03
N/CJ16 Pin 04
GPIO15 WJ16 Pin 05
GPIO14 W BusyJ16 Pin 06
N/CJ16 Pin 07

Add-On Modules

The Elecrow AIO ESP32-P4 kit supports several expansion modules:

1. Wireless Modules (via expansion slot)

ESP32-C6 (Wi-Fi 6, Bluetooth 5.3) Wireless Module

ESPHome Docs - C6

ESP32-C6 Wireless Module

Wireless Module

Wireless Module

Specifications
FunctionWiFi/WiFi 6
ChipESP32-C6FH4
Flash4MB Quad SPI
Frequency Range2402-2480MHz
Size18mm x 23.7mm
AntennaIPEX
Left Pins
Module signal / GPIOModule pinESP32-P4 GPIOSignal role
U1RXDLeft Pin 01GPIO53Not used by SPI transport
GPIO21Left Pin 02GPIO09MISO
GPIO22Left Pin 03GPIO10Data-ready
GPIO23Left Pin 04GPIO11Handshake
VDD 3V3Left Pin 05VDD 3V3Power 3V3
GNDLeft Pin 06GNDGROUND
VDD 5VLeft Pin 07VDD 5VPower 5V
Right Pins
Module signal / GPIOModule pinESP32-P4 GPIOSignal role
U1TXDRight Pin 01GPIO54Not used by SPI transport
GPIO20Right Pin 02GPIO07MOSI
ENRight Pin 03GPIO02Reset
GPIO05Right Pin 04N/CNot Connected
GPIO18Right Pin 05GPIO15SCLK
GPIO19Right Pin 06GPIO14CS
GPIO00Right Pin 07N/CNot Connected
Back Pads
Function / GPIOHardware PinESP32-P4
GPIO14Back Pad 14N/C
GPIO07Back Pad 07N/C
GPIO06Back Pad 06N/C
GPIO03Back Pad 03N/C
GPIO02Back Pad 02N/C
GPIO01Back Pad 01N/C

Flashing SPI (1-Wire) Firmware (Not SDIO 4-Wire)

Note:The Elecrow ESP32-P4 AIO Kit doesn’t work with SDIO, which the module is programmed from the factory with. You MUST reprogram it or it will not work in SPI (1-Wire) mode.

Remove wireless module from AIO board, plug USB-C cable into the ESP32-C6 wireless module.

The commands below use the placeholder C6_PORT: use /dev/ttyACM0 on Linux, /dev/cu.usbmodemXXXX on macOS, or the appropriate COM port on Windows.

Identify C6

Linux/macOS:

Terminal window
esptool --chip esp32c6 --port C6_PORT flash-id
esptool v5.2.0
Connected to ESP32-C6 on /dev/ttyACM0:
Chip type: ESP32-C6FH4 (QFN32) (revision v0.2)
Features: Wi-Fi 6, BT 5 (LE), IEEE802.15.4, Single Core + LP Core, 160MHz, Embedded Flash 4MB
Crystal frequency: 40MHz
USB mode: USB-Serial/JTAG
MAC: ##:##:##:##:##:##
BASE MAC: ##:##:##:##:##:##
MAC_EXT: ##:##
Stub flasher running.
Flash Memory Information:
=========================
Manufacturer: 46
Device: 4016
Detected flash size: 4MB
Hard resetting via RTS pin...

Windows PowerShell (replace COM5 with the port shown in Device Manager):

Terminal window
python -m esptool --chip esp32c6 --port COM5 flash-id

Read Flash C6

Linux/macOS:

Terminal window
esptool --chip esp32c6 --port C6_PORT read-flash 0 0x400000 elecrow-esp32-c6-wireless-module-firmware-backup.bin

Windows PowerShell:

Terminal window
python -m esptool --chip esp32c6 --port COM5 read-flash 0 0x400000 .\elecrow-esp32-c6-wireless-module-firmware-backup.bin

Compile SPI ESP_Hosted Firmware

Terminal window
# Clone and setup ESP-IDF
git clone -b v5.5.2 --recursive https://github.com/espressif/esp-idf.git
cd esp-idf
git submodule update --init --recursive
./install.sh esp32c6 # or your target
source export.sh # for Linux/macOS
# Windows Command Prompt: export.bat
# Windows PowerShell: .\export.ps1
cd ..
# Create project from ESP-Hosted example
idf.py create-project-from-example "espressif/esp_hosted==2.12.3:slave"
cd slave/
# Build for your target
idf.py set-target esp32c6 # or your target
idf.py menuconfig
(Top) Example Configuration Bus Config in between Host and Co-processor Transport layer (X) SPI Full-duplex
Configure the SPI GPIOs for the one-wire full-duplex transport as follows:
Espressif IoT Development Framework Configuration
(19) Slave GPIO pin for Host CS
(18) Slave GPIO pin for Host CLK
(20) Slave GPIO pin for Host MOSI
(21) Slave GPIO pin for Host MISO
(22) Slave GPIO pin for Data Ready
DataReady GPIO Config (Active High) --->
(23) Slave GPIO pin for Host Handshake
idf.py build
Creating esp32c6 image...
Successfully created esp32c6 image.
Generated slave/build/network_adapter.bin
network_adapter.bin binary size 0x127370 bytes. Smallest app partition is 0x1e0000 bytes. 0xb8c90 bytes (38%) free.

On Windows, run the equivalent commands from ESP-IDF Command Prompt or PowerShell. Use install.bat and export.bat from Command Prompt, or export.ps1 from PowerShell, instead of the Unix shell scripts:

Terminal window
git clone -b v5.5.2 --recursive https://github.com/espressif/esp-idf.git
cd esp-idf
git submodule update --init --recursive
.\install.bat esp32c6
.\export.ps1
cd ..
idf.py create-project-from-example "espressif/esp_hosted==2.12.3:slave"
cd slave
idf.py set-target esp32c6
idf.py menuconfig
idf.py build

Write Flash C6 ESP_Hosted

Terminal window
Project build complete. To flash, run:
idf.py flash
or
idf.py -p PORT flash
or
python -m esptool --chip esp32c6 -b 460800 --before default-reset --after hard-reset write-flash --flash-mode dio --flash-size 4MB --flash-freq 80m 0x0 build/bootloader/bootloader.bin 0x8000 build/partition_table/partition-table.bin 0xd000 build/ota_data_initial.bin 0x10000 build/network_adapter.bin
or from the "slave/build" dir
python -m esptool --chip esp32c6 -b 460800 --before default-reset --after hard-reset write-flash "@flash_args"
Terminal window
esptool --chip esp32c6 --port C6_PORT write-flash 0 build/network_adapter.bin

Windows PowerShell:

Terminal window
python -m esptool --chip esp32c6 --port COM5 write-flash 0 .\build\network_adapter.bin

For the generated multi-image build, flash from the slave directory with the Windows paths shown below:

Terminal window
python -m esptool --chip esp32c6 -b 460800 --before default-reset --after hard-reset write-flash --flash-mode dio --flash-size 4MB --flash-freq 80m 0x0 .\build\bootloader\bootloader.bin 0x8000 .\build\partition_table\partition-table.bin 0xd000 .\build\ota_data_initial.bin 0x10000 .\build\network_adapter.bin

If successful, unplug your ESP32-C6 wireless module from the USB-C cable, identify the correct pinout for the module to go in the right way 3V3 to the 3V3 header, plug the USB-C cable back into the AIO Kit “Serial/JTAG Power In” USB-C port.

Do not restore SDIO firmware for use with this AIO board. The AIO supports only the 1-bit, full-duplex SPI transport described above. If you remove the module and want to use it with another board that supports ESP-Hosted SDIO, you can restore compatible SDIO firmware from the ESP-Hosted firmware downloads.

Pin Alignment

ESP32-P4 C6-Wireless-Module
GPIO02 EN
GPIO14 GPIO19
GPIO15 GPIO18
GPIO07 GPIO20
GPIO09 GPIO21
GPIO10 GPIO22
GPIO11 GPIO23
YAML Example
# Wi-Fi via ESP32-C6 using ESP-Hosted SPI full duplex (1-wire)
esp32_hosted:
variant: ESP32C6
active_high: true
# SPI Full Duplex (Don't use half-duplex)
type: spi
reset_pin: GPIO2 # EN Wireless Module Reset
cs_pin: GPIO14 # GPIO19 Wireless Module CS
clk_pin: GPIO15 # GPIO18 Wireless Module CLK
mosi_pin: GPIO7 # GPIO20 Wireless Module MOSI
miso_pin: GPIO9 # GPIO21 Wireless Module MISO
data_ready_pin: GPIO10 # GPIO22 Wireless Module Ready
handshake_pin: GPIO11 # GPIO23 Wireless Module Handshake
# This requires the custom C6 build. Point it to your own compiled binary.
# update:
# - platform: esp32_hosted
# path: network_adapter.bin
# sha256: 622980747c593d340e29fc08221da6bc6dd64b8ee7d7773b75796b17de7ecb3f
# id: esp32update
# on_update_available:
# - lvgl.label.update:
# id: version_label
# text: "Update Available!"
# text_color: 0xFFFF00
# button:
# - platform: template
# name: "Update ESP32-C6 Firmware"
# on_press:
# - lambda: |-
# printf("Pressed!");
wifi:
ssid: REPLACE_ME
# SECURITY: credential omitted from documentation example
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "${device_name} Fallback"
# SECURITY: credential omitted from documentation example
captive_portal:
Resources

ESP32-H2 Wireless Module

ESP32-H2 Wireless Module

Specifications
FunctionThread/Zigbee/Matter
ChipESP32-H2FH4
Flash4MB Quad SPI
Frequency Range2402-2480MHz
Size18mm x 23.7mm
AntennaIPEX

The module-side H2 pinout below is taken from the supplied H2 module image. The module GPIO names are not ESP32-P4 GPIO numbers. The host-side AIO slot signals are shown separately using the confirmed J13/J16 header mapping.

Left Pins
Module signal / GPIOModule pinESP32-P4 GPIOSignal role
U1TXDLeft Pin 01GPIO53UART transmit (module)
GPIO01Left Pin 02GPIO09Module GPIO
GPIO00Left Pin 03GPIO10Module GPIO
GPIO02Left Pin 04GPIO11Module GPIO
GPIO22Left Pin 05VDD 3V3Module GPIO; header pin is 3V3
GPIO10Left Pin 06GNDModule GPIO; header pin is ground
GPIO11Left Pin 07VDD 5VModule GPIO; header pin is 5V
Right Pins
Module signal / GPIOModule pinESP32-P4 GPIOSignal role
U1RXDRight Pin 01GPIO54UART receive (module)
GPIO12Right Pin 02GPIO07Module GPIO; host mapping unverified
GPIO13Right Pin 03GPIO02Module GPIO; host mapping unverified
GPIO14Right Pin 04N/CModule GPIO; host mapping unverified
3V3Right Pin 05GPIO15Module power label; J16 pin 5 is GPIO15
GNDRight Pin 06GPIO14Module ground label; J16 pin 6 is GPIO14
NCRight Pin 07N/CNot connected on module or header

ESP32-H2 (Bluetooth, Thread, Zigbee)

esphome:
name: thread-test
friendly_name: thread-test
esp32:
board: esp32-h2-devkitm-1
variant: ESP32H2
framework:
type: esp-idf
# Enable logging
logger:
# Enable Home Assistant API
api:
encryption:
key: "YourEncryptionKeyGenerated by ESPHome"
ota:
- platform: esphome
# SECURITY: credential omitted from documentation example
network:
enable_ipv6: true
openthread:
tlv: yourTLVfromStep0
# Output and light configuration for a single red LED
output:
- platform: gpio
pin: GPIO1
id: red_led
light:
- platform: binary
name: "Red LED"
output: red_led
Resources

LoRa Wireless Module

Wireless Module

Specifications
FunctionLoRa
ChipSX1262
Frequency Range860-930MHz
Size18mm x 23.7mm
AntennaIPEX

The pinout below is the module-side pinout shown on the supplied LoRa module image. These are module connector signals, not ESP32-P4 GPIO numbers. The ESP32-P4 column uses the confirmed J13/J16 adapter-header mapping.

Left Pins
Module signal / GPIOModule pinESP32-P4 GPIOSignal role
NRESETLeft Pin 01GPIO54LoRa reset input
NCLeft Pin 02N/CNot connected on module
NCLeft Pin 03N/CNot connected on module
DIO2Left Pin 04N/CLoRa digital I/O; not assigned in the example
BUSYLeft Pin 05GPIO15LoRa busy output
NSSLeft Pin 06GPIO14LoRa chip select
DIO3Left Pin 07N/CLoRa digital I/O; not assigned in the example
Right Pins
Module signal / GPIOModule pinESP32-P4 GPIOSignal role
DIO1Right Pin 01GPIO53LoRa digital I/O
SCKRight Pin 02GPIO09SPI clock
MISORight Pin 03GPIO10SPI controller input
MOSIRight Pin 04GPIO11SPI controller output
3V3Right Pin 05VDD 3V3Module power input
GNDRight Pin 06GNDModule ground
NCRight Pin 07N/CNot connected on module or header
Basic Setup (AIO Expansion Header)

This is an example of the LoRa component configuration using the confirmed AIO host GPIO assignments shown above. The module-side labels remain the module’s own pin names.

spi:
clk_pin: GPIO09
mosi_pin: GPIO11
miso_pin: GPIO10
sx126x:
dio1_pin: GPIO53
cs_pin: GPIO14
busy_pin: GPIO15
rst_pin: GPIO54
pa_power: 3
bandwidth: 125_0kHz
crc_enable: true
frequency: 433.92MHz
modulation: LORA
hw_version: sx1262
rf_switch: true
sync_value: [0x14, 0x24]
preamble_size: 8
spreading_factor: 7
coding_rate: CR_4_6
tcxo_voltage: 1_8V
tcxo_delay: 5ms

YAML Example (AIO Expansion Header)

spi:
clk_pin: GPIO09
mosi_pin: GPIO11
miso_pin: GPIO10
sx126x:
dio1_pin: GPIO53
cs_pin: GPIO14
busy_pin: GPIO15
rst_pin: GPIO54
pa_power: 3
bandwidth: 125_0kHz
crc_enable: true
frequency: 433.92MHz
modulation: LORA
hw_version: sx1262
rf_switch: true
sync_value: [0x14, 0x24]
preamble_size: 8
spreading_factor: 7
coding_rate: CR_4_6
tcxo_voltage: 1_8V
tcxo_delay: 5ms
# Device 1
esphome:
name: device-1
packet_transport:
binary_sensors:
- relay1_sensor
switch:
- platform: gpio
pin: GPIO6
id: relay1
name: "Device 1 switch"
binary_sensor:
- platform: template
id: relay1_sensor
lambda: "return id(relay1).state;"
- platform: packet_transport
provider: device-2
id: relay2_sensor
on_press:
switch.turn_on: relay1
on_release:
switch.turn_off: relay1
# The remote device (device-2) must use its own separate ESPHome YAML file.
# This file intentionally contains only the device-1 configuration so it is
# valid when rendered or linted as a standalone example.
Resources

nRF2401 (ISM 2.4GHz) Wireless Module

nRF24L01+ Wireless Module

Specifications
FunctionISM
ChipnRF24L01+
Frequency RangeWorldwide 2.4GHz ISM band operation
Size18mm x 23.7mm
AntennaIPEX

The pinout below is the module-side pinout shown on the supplied nRF2401 module image. The GPIO labels are GPIOs of the module’s onboard controller, not ESP32-P4 GPIOs. The ESP32-P4 column uses the confirmed J13/J16 adapter-header mapping; it does not rename the module GPIOs.

Left Pins
Module signal / GPIOModule pinESP32-P4 GPIOSignal role
U1TXDLeft Pin 01GPIO54Module UART TX; radio-side label CSN
GPIO23Left Pin 02N/CModule GPIO; radio-side label NC
GPIO22Left Pin 03N/CModule GPIO; radio-side label NC
GPIO21Left Pin 04N/CModule GPIO; radio-side label NC
GPIO20Left Pin 05GPIO15Module GPIO; radio-side label IRQ
GPIO19Left Pin 06GNDModule GPIO; radio-side label NC
GPIO18Left Pin 07N/CModule GPIO; radio-side label NC
Right Pins
Module signal / GPIOModule pinESP32-P4 GPIOSignal role
U1RXDRight Pin 01GPIO53Module UART RX; radio-side label CE
GPIO0Right Pin 02GPIO09Module GPIO; radio-side label SCK
GPIO1Right Pin 03GPIO10Module GPIO; radio-side label MISO
GPIO2Right Pin 04GPIO11Module GPIO; radio-side label MOSI
3V3Right Pin 05VDD 3V3Module power input
GNDRight Pin 06GNDModule ground
NCRight Pin 07N/CNot connected on module or header

YAML Example (AIO Expansion Header)

This is an example of the external component configuration using the confirmed AIO host GPIO assignments shown above. The module GPIO labels are module-side names and are not ESP32-P4 GPIO numbers.

Experimental external component: the nRF24 component is hosted outside ESPHome and is not pinned to a release or commit. Verify its current schema before compiling this example.

external_components:
- source: github://flax2000/esp_nrf24l01
components: [nrf24]
spi:
clk_pin: GPIO09
mosi_pin: GPIO11
miso_pin: GPIO10
nrf24:
id: my_nrf24
ce_pin: GPIO53
csn_pin: GPIO54
# Optional IRQ connection, if supported by the external component:
# irq_pin: GPIO15
packet_transport:
- platform: custom # Or the platform name provided by the specific external component
nrf24_id: my_nrf24
sensors:
- sensor_to_transmit
Resources

HaLow (Sub 1GHz) Wireless Module

HaLow Wireless Module HaLow Wireless Module

Wi-Fi HaLow serves as a specialized wireless protocol designed specifically for Internet of Things (IoT) use cases. Functioning below the 1 GHz frequency band, this technology surpasses conventional Wi-Fi in its ability to transmit over greater distances and through barriers more effectively. Its low-power design with efficient sleep and wake mechanisms extends battery life, while supporting high device density and stable connections in crowded environments, solving congestion issues of traditional Wi-Fi.

Specifications
FunctionHaLow WiFi
ChipQuectel FGH100MABMD (MM61080)
Frequency Range850-950 MHz
StandardIEEE 802.11ah
DistanceUp to 1km
Data RateUp to 32.5Mbps
Size18mm x 23.7mm
AntennaIPEX

The pinout below is the module-side pinout shown on the supplied HaLow module image. These are module connector signals, not ESP32-P4 GPIO numbers. The ESP32-P4 column uses the confirmed J13/J16 adapter-header mapping.

Left Pins
Module signal / GPIOModule pinESP32-P4 GPIOSignal role
INTLeft Pin 01GPIO53Module interrupt output
NCLeft Pin 02GPIO09Not connected on module
NCLeft Pin 03GPIO10Not connected on module
BUSYLeft Pin 04GPIO11Module busy output
RESET_NLeft Pin 05VDD 3V3Module reset input; header pin is 3V3
WAKEUP_INLeft Pin 06GNDModule wake-up input; header pin is ground
NCLeft Pin 07VDD 5VNot connected on module; header pin is 5V
Right Pins
Module signal / GPIOModule pinESP32-P4 GPIOSignal role
CSRight Pin 01GPIO54SPI chip select
CLKRight Pin 02GPIO07SPI clock
MISORight Pin 03GPIO02SPI controller input
MOSIRight Pin 04N/CSPI controller output; header pin is N/C
3V3Right Pin 05GPIO15Module power input; header pin is GPIO15
GNDRight Pin 06GPIO14Module ground; header pin is GPIO14
5VRight Pin 07N/CModule supply input; header pin is N/C

YAML Example

Not Supported Yet
Resources

Elecrow Wiki

Elecrow Github

Official Product Website

3RD Party Modules

1. GRC EnSens BME688 Air Quality Sensor Add-on

EnSens

Environmental sensor add-on module with Bosch BME688 for VOC, eCO₂, IAQ, temperature, pressure, and humidity monitoring. Plug-and-play with Elecrow Panel via I²C. Ideal for smart homes, air quality dashboards, and automation.

  • Temperature
  • Relative Humidity
  • Barometric Pressure
  • VOC (Volatile Organic Compounds)
  • CO₂ Equivalent (eCO₂)
  • IAQ (Indoor Air Quality Index)

Pinout

Left Pins
Module signal / GPIOModule pinESP32-P4 GPIOSignal role
?Left Pin 01GPIO53?
?Left Pin 02GPIO09?
?Left Pin 03GPIO10?
?Left Pin 04GPIO11?
?Left Pin 05VDD 3V3Power 3V3
?Left Pin 06GNDGROUND
?Left Pin 07VDD 5VPower 5V
Right Pins
Module signal / GPIOModule pinESP32-P4 GPIOSignal role
?Right Pin 01GPIO54?
?Right Pin 02GPIO07?
?Right Pin 03GPIO02?
?Right Pin 04N/C?
?Right Pin 05GPIO15?
?Right Pin 06GPIO14?
?Right Pin 07N/C?

YAML Example

bme68x_bsec2_i2c:
address: 0x76
model: bme680
operating_age: 28d
sample_rate: LP
supply_voltage: 3.3V
sensor:
- platform: bme68x_bsec2
temperature:
name: "BME68x Temperature"
pressure:
name: "BME68x Pressure"
humidity:
name: "BME68x Humidity"
iaq:
name: "BME68x IAQ"
id: iaq
co2_equivalent:
name: "BME68x CO2 Equivalent"
breath_voc_equivalent:
name: "BME68x Breath VOC Equivalent"
text_sensor:
- platform: bme68x_bsec2
iaq_accuracy:
name: "BME68x IAQ Accuracy"
- platform: template
name: "BME68x IAQ Classification"
lambda: |-
if (int(id(iaq).state) <= 50) {
return {"Excellent"};
} else if (int(id(iaq).state) >= 51 && int(id(iaq).state) <= 100) {
return {"Good"};
} else if (int(id(iaq).state) >= 101 && int(id(iaq).state) <= 150) {
return {"Lightly polluted"};
} else if (int(id(iaq).state) >= 151 && int(id(iaq).state) <= 200) {
return {"Moderately polluted"};
} else if (int(id(iaq).state) >= 201 && int(id(iaq).state) <= 250) {
return {"Heavily polluted"};
} else if (int(id(iaq).state) >= 251 && int(id(iaq).state) <= 350) {
return {"Severely polluted"};
} else if (int(id(iaq).state) >= 351 && int(id(iaq).state) <= 500) {
return {"Extremely polluted"};
} else {
return {"Error"};
}

2. GRC AI Add-on (Himax HX6538)

HX6538 HX6538

Ready-to-use AI coprocessor with open-source firmware, hardware, and examples. Turn your CrowPanel into a smart, AI-powered device — run TinyML and TinyLM locally, with no cloud and no setup.

AI Core
MicrocontrollerHimax HX6538-A06TDFG
CPUArm Cortex-M55
NPUArm Ethos-U55
SRAM2 MB
ROM4 MB
External Flash128 Mbit (16 MB) QSPI NOR Flash
Board size~20×25 mm
Voltage3.3V

Usage

  • TinyML/TinyLM inference
  • Frameworks supported: CMSIS-NN, GRC SDK, TensorFlow Lite for Micro (adapted)
  • Text generation, classification
  • Voice interfaces

Demo

TinyStories

An interactive storytelling prototype based on the TinyStories Language Model. It showcases how compact hardware can be used for interactive natural language generation — all at the edge, with low power consumption and no internet required for model inference.

The pinout below uses the HX6538 module’s PB labels for the module side and the IOxx nets shown in the supplied connector schematic for the ESP32-P4 side. These are different numbering systems. The connector is shown counter-clockwise in that schematic; verify the physical orientation before installing the module.

Left Pins
Module signal / GPIOModule pinESP32-P4 GPIOSignal role
PB0Left Pin 01GPIO20IO20_TX_CE / M55 GPIO0
PB4Left Pin 02GPIO05IO5_W_CLK / M55 GPIO4
PB3Left Pin 03GPIO04IO4_W_MISO / M55 GPIO3
PB2Left Pin 04GPIO06IO6_W_MOSI / M55 GPIO2
VDD 3V3Left Pin 05VDD 3V3Power 3V3
GNDLeft Pin 06GNDGROUND
N/CLeft Pin 07N/CConnector signal not identified in the supplied schematic
Right Pins
Module signal / GPIOModule pinESP32-P4 GPIOSignal role
PB1Right Pin 01GPIO19IO19_RX_IRQ / M55 GPIO1
PB5Right Pin 02GPIO16IO16_SCL / M55 GPIO5
PB6Right Pin 03GPIO15IO15_SDA / M55 GPIO6
PB7Right Pin 04N/CHost net is unconnected in the supplied schematic
PB9Right Pin 05GPIO02IO2_W_CS / M55 GPIO9
RESETRight Pin 06GPIO00IO0_BOOT_BUSY / M55 reset
PB8Right Pin 07N/CHost net is unconnected in the supplied schematic

YAML Example

Not Supported Yet

Back to top

Reset Switch K1

K1 is connected to the ESP32-P4 RST_EN reset net. It is a hardware reset switch and is not connected to GPIO35 or available as a user GPIO.

Boot Switch K2

K2 is the ESP32-P4 boot button. The schematic labels its signal IO35_BOOT, so it is connected to GPIO35 and should be held while entering the bootloader during flashing.

I2C Header JST-HY2.0 J10

ESPHome Docs - I2C

I2C Header

I2C Header

Pin/GPIOLabel
Pin 1GND
Pin 25V
GPIO18SDA
GPIO19SCL
i2c:
- id: bus_a
sda: GPIO18
scl: GPIO19
scan: true
frequency: 400kHz

I2C Header JST-HY2.0 J18

I2C Header

I2C Header

Pin/GPIOLabel
Pin 1GND
Pin 25V
GPIO18SDA
GPIO19SCL
i2c:
- id: bus_a
sda: GPIO18
scl: GPIO19
scan: true
frequency: 400kHz

UART Header JST-HY2.0 J19

ESPHome Docs - UART

UART Header

UART Header

PinLabel
Pin 1GND
Pin 25V
GPIO47TX
GPIO48RX
uart:
tx_pin: GPIO47
rx_pin: GPIO48
baud_rate: 9600

Lesson 6: UART Communication

Description: Send and receive data via UART serial communication.

uart:
tx_pin: GPIO47
rx_pin: GPIO48
baud_rate: 115200
logger:
level: DEBUG

SPI (Add-on Module Header)

ESPHome Docs - SPI

GPIOLabel
GPIO09CLK
GPIO10MISO
GPIO11MOSI
spi:
- id: spi_bus0
clk_pin: GPIO09
mosi_pin: GPIO11
miso_pin: GPIO10

USB1 & Power In J5

USB C

USB C

PinLabel
1USB1_5V
2USB1_D_N
3USB1_D_P
4GND

USB2 High Speed & Power In J1

USB C

USB C

PinLabel
1USB2_5V
2USB_A_N
3USB_A_P
4GND

USB Type A J4

USB Type A

USB C

PinLabel
1VBUS_OUT
2USBD_N
3USBD_P
4GND

Ethernet (IP101) J2

ESPHome Docs - Ethernet

Ethernet

Ethernet

GPIOLabel
GPIO31MDC
GPIO50CLK
GPIO51Power
GPIO52MDIO
# Ethernet Configuration (RMII definition)
ethernet:
type: IP101
mdc_pin: GPIO31
mdio_pin: GPIO52
power_pin: GPIO51
clk:
mode: CLK_EXT_IN
pin: GPIO50
phy_addr: 1

Power Rails

Power

RailVoltageICNotes
VDD_3V33.3VRY3430Main 3.3V rail
ESP_VDD_HP_1V21.2VRY3430ESP32-P4 core supply (U3)
VDD_5V5VInputUSB/External
VDD_5V25.2VMT3540Boosted 5V rail
LCD_VDD_1V81.8VME6211Display logic
LCD_AVDD_9V69.6VSX1308Display analog bias output
LCD_VGH_18V18VSX1308Display high-voltage bias output
LCD_VGL_-6V-6VSX1308Display negative bias output
PHY_3V33.3V-Ethernet PHY

Extra Examples Not In Manual

Minimal Configuration

substitutions:
device_name: esp32-p4-elecrow-aio-kit
friendly_name: "ESP32-P4 Elecrow All-In-One Kit"
esphome:
name: ${device_name}
friendly_name: ${friendly_name}
esp32:
variant: esp32p4
flash_size: 16MB
framework:
type: esp-idf
# PSRAM
psram:
mode: hex
speed: 200MHz
# Logger
logger:
level: INFO
hardware_uart: UART0
# Home Assistant API
api:
encryption:
key: REPLACE_ME
# OTA Updates
ota:
- platform: esphome
# SECURITY: credential omitted from documentation example
# Ethernet Configuration (RMII definition)
ethernet:
type: IP101
mdc_pin: GPIO31
mdio_pin: GPIO52
power_pin: GPIO51
clk:
mode: CLK_EXT_IN
pin: GPIO50
phy_addr: 1
# Wi-Fi transport: one-wire SPI, full duplex (not SDIO)
# P4 GPIO02: C6 EN/reset
# P4 GPIO14: C6 CS
# P4 GPIO15: C6 CLK
# P4 GPIO07: C6 MOSI
# P4 GPIO09: C6 MISO
# P4 GPIO10: C6 data-ready
# P4 GPIO11: C6 handshake
#
# Use the ESP32-C6 ESP-Hosted SPI configuration shown above. Do not use
# cmd_pin or d0_pin-d3_pin; those are for SDIO and do not work on this board.

Complete AIO Project Configuration (Tested)

This is the complete project configuration used to test the Elecrow AIO features. It is board-specific, but it is not a portable baseline: it depends on custom components and development branches listed in the YAML. Replace all REPLACE_ME values and review the external component sources before building.

substitutions:
device_name: esp32-p4-elecrow-aio
friendly_name: "ESP32-P4 Elecrow All-In-One"
light_recessed: "\U000F179B"
wall_sconce_round: "\U000F0748"
gas_burner: "\U000F1A1B"
home_icon: "\U000F02DC"
menu_left: "\U000F0A02"
menu_right: "\U000F035F"
close: "\U000F0156"
delete: "\U000F01B4"
backspace: "\U000F006E"
check: "\U000F012C"
arrow_down: "\U000F004B"
esphome:
name: ${device_name}
friendly_name: ${friendly_name}
includes:
- custom.h
- lvgl_detect_image_format.h
platformio_options:
build_flags:
- -DLIBSSH2_MBEDTLS
- -DHAVE_CONFIG_H
- -DESPHOME_STB_IMAGE_IMPL
includes_c:
- stb_image_impl.h
on_boot:
priority: -100
then:
- lambda: |-
auto files = id(sd_card).list_directory("/", 0);
ESP_LOGI("sdcard", "SD boot: %d files", files.size());
- lambda: |-
lvgl_keyboard_init(
id(onscreen_kb)->obj,
id(terminal_input),
[]() { id(osk_enter_pressed) = true; },
[]() { id(ssh_client)->handle_key(0x03); }
);
id(ssh_client)->set_lvgl_callback([](const std::string &text) {
lvgl_textarea_set_text(id(terminal_output), text.c_str());
lvgl_scroll_to_bottom(id(terminal_output));
});
- lvgl.label.update:
id: sd_label
text: !lambda |-
auto files = id(sd_card).list_directory("/", 0);
return str_sprintf("SD: %d files", (int)files.size());
esp32:
variant: esp32p4
flash_size: 16MB
framework:
type: esp-idf
#log_level: VERBOSE
#version: 6.13.0
#platform_version: 55.3.37
advanced:
enable_idf_experimental_features: true
disable_vfs_support_dir: false
include_builtin_idf_components:
- fatfs
- wear_levelling
sdkconfig_options:
#CONFIG_ESP_TASK_WDT_TIMEOUT_S: "30"
CONFIG_ESP_H264_DUAL_TASK: "y"
CONFIG_SPIRAM_FETCH_INSTRUCTIONS: "y"
CONFIG_SPIRAM_RODATA: "y"
CONFIG_ESP32_SPIRAM_SUPPORT: "y"
CONFIG_SPIRAM_USE_MALLOC: "y"
CONFIG_FATFS_LFN_STACK: "y"
CONFIG_VFS_SUPPORT_IO: "y"
CONFIG_VFS_MAX_COUNT: "16"
CONFIG_ESP_MAIN_TASK_STACK_SIZE: "16384"
#CONFIG_ESP_MAIN_TASK_STACK_SIZE: "65536"
CONFIG_ESP_TIMER_TASK_STACK_SIZE: "4096"
#CONFIG_ESP_TIMER_TASK_STACK_SIZE: "8192"
CONFIG_ESP_TASK_WDT_TIMEOUT_S: "60"
CONFIG_ESP_TASK_LOOP_STACK_SIZE: "16384"
CONFIG_FATFS_LFN_HEAP: "y"
DCONFIG_USE_AUDIO_AAC_SUPPORT: "y"
#DCONFIG_ESP_JPG_DECODE_ENABLE: "y"
#DCONFIG_ESPHOME_ENABLE_PNGLE: "1"
CONFIG_SPIRAM_SPEED_200M: "y" # ← NOUVEAU: 200 MHz au lieu de défaut
CONFIG_SPIRAM_SPEED: "200"
CONFIG_LWIP_SOCKET: "y"
#CONFIG_LWIP_SO_RCVBUF: "y"
CONFIG_LWIP_MAX_SOCKETS: "16"
#CONFIG_HTTPD_MAX_REQ_HDR_LEN: "800"
#CONFIG_HTTPD_WS_SUPPORT: "y"
engineering_sample: true
safe_mode:
disabled: true
external_components:
#### uncomment to use old external component to use ***
- source:
# https://github.com/esphome/esphome/pull/14933
type: git
url: https://github.com/kahrendt/esphome
ref: 89dd9fb3173a6ae7d5c730fa671f6089d7c9a2b8
components: [sendspin, generic_image]
#### leave alone ***
- source:
type: git
url: https://github.com/esphome/esphome
ref: 57397a318a2e08ea4c44bb7836358a7da20985f2
components: [audio, audio_http] #, audio_file
refresh: 0s
- source:
type: git
url: https://github.com/NonaSuomy/esphome
ref: lsm6ds_test_001
components: [lsm6ds]
refresh: always
- source: github://pr#15517
components: [i2s_audio]
- source: github://swoboda1337/esphome@esp32-hosted-spi
components: [esp32_hosted]
- source:
type: local
path: components
components: [lvgl, image, font, esp32_rmt_led_strip, usb_hidx, sd_mmc_card, microssh]
- source:
type: git
url: https://github.com/youkorr/test2_esp_video_esphome
ref: main
components:
- esp_video
- esp_cam_sensor
- webdavbox3
- storage
- lvgl_camera_display
- simple_video_player
- network_camera
- face_detection
refresh: always
# - source: github://guillempages/esphome@tm1637-brightness
# components: [online_image]
sd_mmc_card:
id: sd_card
clk_pin: GPIO43
cmd_pin: GPIO44
data0_pin: GPIO39
#data1_pin: GPIO40
#data2_pin: GPIO41
#data3_pin: GPIO42
mode_1bit: true
slot: 1
storage:
platform: sd_direct
id: my_storage
sd_component: sd_card
root_path: "/sdcard"
#frigate: "http://home-assistant.local:8123/api/frigate/frigate/notifications/1775568754.477969-9focpa/snapshot.jpg"
http_request:
verify_ssl: false
online_image:
- url: "http://home-assistant.local:8123/api/frigate/frigate/notifications/1775568754.477969-9focpa/snapshot.jpg"
format: JPG
id: cctv_img
byte_order: little_endian
type: RGB565
resize: 1000x600
#on_download_finished:
#- lvgl.image.update:
#id: cctv_img_lvgl
#src: cctv_img
# - url: "http://home-assistant.local:8123/local/placeholder.png"
# format: PNG
# id: album_art_img
# byte_order: little_endian
# type: RGB565
# resize: 80x80
# on_download_finished:
# then:
# - lvgl.image.update:
# id: album_art
# src: album_art_img
# on_error:
# then:
# - lvgl.image.update:
# id: album_art
# src: cat_img
- url: "http://home-assistant.local:8123/local/placeholder.png"
format: JPG # Changed to JPG as it is more common for album art
id: album_art_img
byte_order: little_endian
type: RGB565
resize: 80x80
on_download_finished:
then:
- lvgl.image.update:
id: album_art
src: album_art_img
on_error:
then:
# Fallback to a local image if download fails
- lvgl.image.update:
id: album_art
src: cat_img
# PSRAM
psram:
mode: hex
speed: 200MHz
microssh:
id: ssh_client
hostname: "REPLACE_ME"
port: "REPLACE_ME"
username: "REPLACE_ME"
private_key: "REPLACE_ME"
passphrase: "REPLACE_ME"
default_command: "REPLACE_ME"
speaker_id: speaker_output
usb_host:
enable_hubs: true
usb_hidx:
hub: true
keyboard:
layout: us
gamepad:
device_id: gamepad_main
type: xbox360 #xbox360 #generic
# Logger
logger:
level: info
hardware_uart: UART0
# Home Assistant API
api:
encryption:
key: "REPLACE_ME"
# OTA Updates
ota:
- platform: esphome
# Ethernet Configuration (RMII definition)
ethernet:
type: IP101
mdc_pin: GPIO31
mdio_pin: GPIO52
power_pin: GPIO51
clk:
mode: CLK_EXT_IN
pin: GPIO50
phy_addr: 1
use_address: "REPLACE_ME"
on_connect:
then:
- lvgl.label.update:
id: eth_status_icon
text: "\U000F0200"
text_color: 0x00FF00
on_disconnect:
then:
- lvgl.label.update:
id: eth_status_icon
text: "\U000F1CD1"
text_color: 0xFF0000
# WiFi via Wireless Module (SPI interface)
# IMPORTANT: This kit uses SPI wireless modules (nRF24L01+, SX1262, ESP32-C6/H2 via SPI)
# NOT SDIO like the CrowPanel Advance
# See wireless module documentation for specific configuration
# Example for ESP32-C6 module (if using SPI-based wireless)
# esp32_hosted:
# variant: ESP32C6
# active_high: true
# # SPI Full Duplex (Don't use half-duplex)
# type: spi
# reset_pin: GPIO2 # EN Wireless Module Reset
# cs_pin: GPIO14 #19 Wireless Module CS
# clk_pin: GPIO15 #18 Wireless Module CLK
# mosi_pin: GPIO7 # 20 Wireless Module MOSI
# miso_pin: GPIO9 # 21 Wireless Module MISO
# data_ready_pin: GPIO10 # 22 Wireless Module Ready
# handshake_pin: GPIO11 # 23 Wireless Module Handshake
# SDIO
# sdio_frequency: 10MHz
# reset_pin: GPIO2
# cmd_pin: GPIO15
# clk_pin: GPIO14
# d0_pin: GPIO7
# d1_pin: GPIO9
# d2_pin: GPIO10
# d3_pin: GPIO11
# wifi:
# ssid: "REPLACE_ME"
# password: "REPLACE_ME"
# # Enable fallback hotspot (captive portal) in case wifi connection fails
# ap:
# ssid: "${device_name} Fallback"
# password: "REPLACE_ME"
# on_connect:
# then:
# - lvgl.label.update:
# id: wifi_status_icon
# text: "\U000F0928"
# text_color: 0x00FF00
# on_disconnect:
# then:
# - lvgl.label.update:
# id: wifi_status_icon
# text: "\U000F0929"
# text_color: 0xFF0000
# on_sta_connected:
# then:
# - lambda: |-
# int rssi = WiFi.RSSI();
# const char* icon;
# if (rssi >= -55) icon = "\U000F0928"; // full
# else if (rssi >= -67) icon = "\U000F0925"; // medium-high
# else if (rssi >= -78) icon = "\U000F0922"; // medium-low
# else icon = "\U000F091F"; // low
# lv_label_set_text(id(wifi_status_icon)->obj, icon);
# lv_obj_set_style_text_color(id(wifi_status_icon)->obj, lv_color_hex(0x00FF00), LV_PART_MAIN);
# captive_portal:
# update:
# - platform: esp32_hosted
# path: network_adapter.bin
# sha256: 622980747c593d340e29fc08221da6bc6dd64b8ee7d7773b75796b17de7ecb3f
# id: esp32update
# on_update_available:
# - lvgl.label.update:
# id: version_label
# text: "Update Available!"
# text_color: 0xFFFF00
# button:
# - platform: template
# name: "Update ESP32-C6 Firmware"
# on_press:
# - lambda: |-
# printf("Pressed!");
time:
- platform: sntp
id: sntp_time
timezone: "America/Toronto" # Adjusted for your likely location
on_time:
- seconds: 0
then:
- lvgl.label.update:
id: clock_label
text: !lambda 'return id(sntp_time).now().strftime("%H:%M");'
- lvgl.label.update:
id: date_label
text: !lambda 'return id(sntp_time).now().strftime("%Y-%m-%d");'
# LDO Regulators - both required (LDO3 for DSI PHY, LDO4 for 3.3V peripherals)
esp_ldo:
- channel: 3
voltage: 2.5V
- channel: 4
voltage: 2.5V
lsm6ds:
id: imu
i2c_id: bsp2_bus
address: 0x6B
accel_range: 2G
gyro_range: 250DPS
accel_odr: 26HZ
gyro_odr: 26HZ
update_interval: 100ms
i2c:
# Camera
- id: bsp_bus
sda: GPIO45
scl: GPIO46
frequency: 400kHz
# Light Sensor, Humidity/Temperature Sensor, Touch Screen
- id: bsp2_bus
sda: GPIO18
scl: GPIO19
frequency: 400kHz
touchscreen:
- platform: gt911
id: touch_panel
i2c_id: bsp2_bus
address: 0x5D # GT911 backup address (IDF driver tries this first)
reset_pin: GPIO40
interrupt_pin:
number: GPIO41
# SPI ESP32_Hosted WiFi may conflict with this interrupt.
mode: INPUT
transform:
swap_xy: false
mirror_x: false
mirror_y: false
calibration:
x_min: 0
x_max: 1024
y_min: 0
y_max: 600
update_interval: 50ms # If you use #mode: INPUT for SPI ESP32_Hosted WiFi you need to set this.
on_touch:
- lambda: |-
ESP_LOGI("cal", "x=%d, y=%d, x_raw=%d, y_raw=%0d",
touch.x,
touch.y,
touch.x_raw,
touch.y_raw
);
display:
- platform: mipi_dsi
id: main_display
model: CUSTOM
reset_pin:
number: GPIO05
inverted: false
rotation: 0
#show_test_card: true
auto_clear_enabled: false
lanes: 2
lane_bit_rate: 1000Mbps
color_order: rgb
#invert_colors: true
pclk_frequency: 51MHz
dimensions:
width: 1024
height: 600
hsync_back_porch: 160
hsync_pulse_width: 70
hsync_front_porch: 160
vsync_back_porch: 23
vsync_pulse_width: 10
vsync_front_porch: 12
init_sequence:
- [0xB2, 0x10] # PAD_CONTROL register: configures the DSI pad for 2-lane operation (0x10 = 2 lanes)
- [0x80, 0x8B] # DSI timing/PLL register, sets lane speed or clock divider parameter
- [0x81, 0x78] # DSI timing register, typically horizontal timing or PLL multiplier
- [0x82, 0x84] # DSI timing register, typically vertical timing or PLL divider
- [0x83, 0x88] # DSI timing register, front/back porch or sync width parameter
- [0x84, 0xA8] # DSI timing register, another porch/sync parameter
- [0x85, 0xE3] # DSI timing register, likely horizontal active or blanking period
- [0x86, 0x88] # DSI timing register, last of the timing chain parameters
# MIPI DCS Sleep Out command; wakes the panel for normal operation.
- [0x11]
esp_video:
i2c_id: bsp_bus
xclk_pin: GPIO30
xclk_freq: 24000000
enable_jpeg: true
enable_isp: true
use_heap_allocator: false
esp_cam_sensor:
id: sc2336_cam
i2c_id: bsp_bus
sensor_type: SC2336 #"sensor_type" au lieu de "sensor"
resolution: "1280x720" # , 1024x600 ,vga
pixel_format: "RGB565" # Zero-copy
framerate: 30
jpeg_quality: 15
mirror_x: false # Flip horizontal (hardware)
mirror_y: false # Flip vertical
rotation: 0 # 0/90/180/270°
crop_offset_x: 0 # Crop pixels from left
lvgl_camera_display:
id: camera_display
camera_id: sc2336_cam
canvas_id: camera_canvas
update_interval: 33ms
#face_detection_id: face_detect
#yolo11_detection_id: yolo_detect
output:
- platform: ledc
pin: GPIO20
id: backlight_pwm
frequency: 30000Hz # IDF default is 30kHz (CONFIG_BLIGHT_PWM_Hz)
- platform: gpio
pin: GPIO06
id: led_06_output
- platform: gpio
pin: GPIO27
id: led_27_output
- platform: ledc
id: servo_pwm
pin: GPIO25
frequency: 50Hz
servo:
- id: servo_id
output: servo_pwm
light:
- platform: monochromatic
output: backlight_pwm
name: "Display Backlight"
id: display_backlight
restore_mode: ALWAYS_ON
# Lesson 4 WS2814A LED
- platform: esp32_rmt_led_strip
id: rgb_leds
name: "RGB LEDs"
chipset: WS2814
pin:
number: GPIO8
num_leds: 1
rgb_order: WRGB
is_wrgb: true
color_interlock: true
# bit0_high: 400ns
# bit0_low: 900ns
# bit1_high: 600ns
# bit1_low: 700ns
# reset_low: 300us
rmt_symbols: 192
default_transition_length: 0s
# UPDN=0 (normal vertical), SHLR=1 (mirrored horizontal) per IDF gpio_set_level calls
switch:
- platform: gpio
pin: GPIO42
name: "Relay"
id: relay_switch_hw
- platform: gpio
pin: GPIO32
name: "Display UPDN"
id: display_updn
internal: true
restore_mode: RESTORE_DEFAULT_OFF # UPDN = 0
- platform: gpio
pin: GPIO33
name: "Display SHLR"
id: display_shlr
internal: true
restore_mode: RESTORE_DEFAULT_ON # SHLR = 1
- platform: template
name: "LVGL Camera Display"
id: lvgl_display_enable_switch
restore_mode: RESTORE_DEFAULT_OFF
optimistic: true
turn_on_action:
- esp_cam_sensor.start_streaming:
id: sc2336_cam
- lambda: |-
id(camera_display).set_enabled(true);
turn_off_action:
- lambda: |-
id(camera_display).set_enabled(false);
- esp_cam_sensor.stop_streaming:
id: sc2336_cam
lvgl:
id: lvgl_component
use_ppa: true
buffer_size: 0.25
color_depth: 16
byte_order: little_endian
default_font: roboto20
log_level: WARN
msgboxes:
- id: simple_box
title: Simple
body: ""
close_button: true
- id: message_box
close_button: true
title: Messagebox
bg_color: 0xffff
body:
text: This is a sample messagebox
bg_color: 0x808080
#button_style:
# bg_color: 0x00FF00
buttons:
- id: msgbox_button
text: Button
on_click:
lvgl.widget.show: simple_box
- id: msgbox_apply
text: "Close"
on_click:
then:
- lvgl.widget.hide: message_box
on_idle:
- timeout: 10s
then:
- logger.log: idle timeout
- if:
condition:
lvgl.is_idle:
timeout: !lambda return 5000;
then:
- logger.log: LVGL is idle
- timeout: 15s
then:
- logger.log: idle 15s timeout
#- lvgl.pause:
#- light.turn_off:
# id: display_backlight
# transition_length: 5s
bottom_layer:
bg_image_src: background_main
bg_color: color_back
#bg_opa: TRANSP
gradients:
- id: color_bar
direction: hor
#dither: none
stops:
-
color: 0xFF0000
position: 0
-
color: 0xFFFF00
position: 42
-
color: 0x00FF00
position: 84
-
color: 0x00FFFF
position: 127
-
color: 0x0000FF
position: 169
-
color: 0xFF00FF
position: 212
-
color: 0xFF0000
position: 255
# --- GLOBAL STYLES ---
style_definitions:
- id: main_bg_style
bg_opa: 0%
pad_all: 0
border_width: 0
- id: hud_text
text_color: 0x00D4FF # Neon Cyan
text_font: roboto20
- id: label_title
text_font: roboto20
text_color: 0x00D4FF
- id: transparent_card
bg_opa: 0%
border_width: 0
pad_all: 10
- id: sensor_label
text_color: 0x00D4FF
text_font: roboto20
align: LV_ALIGN_BOTTOM_RIGHT
- id: transparent_matrix
bg_opa: 0%
border_width: 0
pad_all: 0
text_color: 0x00D4FF
text_font: roboto20
- id: matrix_btn_style
bg_opa: 0%
border_width: 1
border_color: 0x00D4FF
text_color: 0x00D4FF
radius: 4
- id: card_style # Used for nested boxes like Voice Assistant
bg_color: 0x222222
bg_opa: 30%
radius: 10
border_width: 1
border_color: 0x444444
- id: mag_style_off
bg_color: 0x444444
border_color: 0x444444
border_width: 2
radius: 20
- id: header_footer
bg_opa: 0% # Transparent background
border_width: 0
text_color: 0x00D4FF # Neon Cyan
text_font: helv20
theme:
arc:
scroll_on_focus: true
slider:
scroll_on_focus: true
button:
text_font: roboto10
scroll_on_focus: true
border_width: 2
border_side: [left, top]
outline_pad: 6
text_color: 0x00D4FF
checked:
text_color: 0x00D4FF
pressed:
border_color: 0xFF0000
text_color: 0x00D4FF
focused:
border_color: 0x00FF00
checkbox:
border_color: 0xC0C0C0
indicator:
text_font: montserrat_14
checked:
bg_color: 0xC0C0C0
top_layer:
text_font: helv20
widgets:
- buttonmatrix:
width: 100%
height: 10%
y: 17
align: bottom_mid
id: top_layer
styles: header_footer
items:
styles: header_footer
rows:
- buttons:
- id: top_prev
text: "${menu_left}"
width: 1
on_click:
lvgl.page.previous:
- id: top_home
text: "${home_icon}"
width: 1
on_click:
then:
lvgl.page.show: main_page
- id: top_next
text: "${menu_right}"
width: 1
on_click:
lvgl.page.next:
# --- PAGE CONFIGURATION ---
pages:
- id: main_page
styles: main_bg_style
width: 1024
height: 600
pad_all: 0
text_color: 0x00D4FF
widgets:
# --- LAYER 0: BACKGROUND ---
- image:
id: bg_canvas
src: background_main
width: 1024
height: 600
# --- HEADER ---
- obj:
height: 60
width: 100%
bg_opa: 0%
widgets:
# MAGNETIC SENSOR ICON
- label:
id: mag_icon
x: 32
y: 0
align: TOP_LEFT
text_font: roboto20
text: "\U000F0347"
text_color: 0x444444
# MOTION SENSOR ICON
- label:
id: motion_icon
x: 56
y: 0
align: TOP_LEFT
text_font: roboto20
text: "\U000F0583"
text_color: 0x444444
- label: {id: clock_label, text: "21:22", align: RIGHT_MID, x: -125, styles: hud_text}
- label: {text: "CONTROL CENTER", align: CENTER, styles: hud_text}
- label: {id: date_label, text: "2026-04-03", align: RIGHT_MID, x: -8, styles: hud_text}
- label:
id: nrf_status_icon
x: -350
y: 0
align: RIGHT_MID
text_font: roboto20
text: "\U000F1119"
text_color: 0x666666
- label:
id: bluetooth_status_icon
x: -325
y: 0
align: RIGHT_MID
text_font: roboto20
text: "\U000F00AF"
text_color: 0x666666
- label:
id: meshtastic_status_icon
x: -300
y: 0
align: RIGHT_MID
text_font: roboto20
text: "\U000F043A"
text_color: 0x666666
- label:
id: matter_status_icon
x: -275
y: 0
align: RIGHT_MID
text_font: roboto20
text: "\U000F0AE5"
text_color: 0x666666
- label:
id: zigbee_status_icon
x: -250
y: 0
align: RIGHT_MID
text_font: roboto20
text: "\U000F0D41"
text_color: 0x666666
- label:
id: wifi_status_icon
x: -225
y: 0
align: RIGHT_MID
text_font: roboto20
text: "\U000F0929"
text_color: 0x666666
- label:
id: eth_status_icon
x: -200
y: 0
styles: hud_text
align: RIGHT_MID
text_font: roboto20
text: "\U000F1CD1"
text_color: 0xFF0000
# --- LEFT COLUMN ---
- obj:
x: 25
y: 77
width: 265
height: 498
border_width: 2
styles: transparent_card
widgets:
- label: {text: "VOICE ASSISTANT", x: -15, y: -5, styles: label_title, align: TOP_MID}
- obj:
y: 35
width: 100%
height: 80
styles: card_style
widgets:
- label:
id: voice_text
text: "Listening..."
align: CENTER
styles: hud_text
long_mode: WRAP
width: 100%
# --- POWER / RELAY CONTROL ---
- obj:
x: -8
y: 115
width: 265
height: 100
styles: transparent_card
widgets:
- label: {text: "RELAY CONTROL", x: -10, y: 0, align: TOP_MID, styles: hud_text}
- switch:
id: relay_switch
x: -10
y: 10
align: CENTER
width: 60
height: 30
bg_color: 0x333333
on_value:
then:
- lambda: |-
if (x) {
id(relay_switch_hw).turn_on();
} else {
id(relay_switch_hw).turn_off();
}
- label: {text: "SERVO CONTROL", x: 0, y: 210, styles: label_title, align: TOP_MID}
- arc:
id: servo_control
x: 45
y: 240
width: 150
height: 150
min_value: -100
max_value: 100
value: 0
start_angle: 120
end_angle: 60
bg_color: 1973790
arc_color: 255
adjustable: true
on_value:
then:
- lambda: |-
float level = (float)x / 100.0f;
id(servo_id).write(level);
- lvgl.label.update:
id: servo_angle_label
text: !lambda 'return str_sprintf("%d°", x);'
widgets:
- label:
id: servo_angle_label
text: ""
text_font: roboto20
align: CENTER
text_color: 0x00D4FF
- label: {text: "SYSTEM STATUS", y: 390, align: TOP_MID, styles: hud_text}
- label: {id: wifi_sig, text: "WiFi: -65dBm", x: 0, y: 420, styles: hud_text}
- label: {id: eth_status, text: "ETH: OK", x: 0, y: 445, styles: hud_text}
# --- CENTER COLUMN ---
- obj:
x: 300
y: 77
width: 455
height: 235
border_width: 2
styles: transparent_card
widgets:
- label: {text: "LIVE FEED", x: -125, y: -5, styles: label_title, align: TOP_MID}
- canvas:
id: camera_canvas
width: 320
height: 180
x: 5
y: 35
bg_color: 0x4682B4
border_width: 0
pad_all: 0
- button:
id: start_streaming
width: 70
height: 45
x: 345
y: 55
bg_color: 0x00CC44
bg_opa: COVER
radius: 8
scroll_on_focus: false
on_click:
then:
- lambda: |-
if (id(sc2336_cam).start_streaming()) {
lvgl_set_bg_color(id(camera_canvas), 0x000000, 0);
id(camera_display).configure_canvas(id(camera_canvas));
}
widgets:
- label: {text: "START", text_color: 0xFFFFFF, text_font: roboto10, align: CENTER}
- button:
id: stop_streaming
width: 70
height: 45
x: 345
y: 110
bg_color: 0xFF4444
bg_opa: COVER
radius: 8
scroll_on_focus: false
on_click:
then:
- lambda: |-
id(sc2336_cam).stop_streaming();
lvgl_set_bg_color(id(camera_canvas), 0x4682B4, 0);
widgets:
- label: {text: "STOP", text_color: 0xFFFFFF, text_font: roboto10, align: CENTER}
- button:
width: 70
height: 30
x: 345
y: 165
bg_color: 0x224488
bg_opa: COVER
radius: 8
on_click:
then:
- lvgl.page.show: camera_page
widgets:
- label: {text: "FULL\nSCREEN", text_color: 0xFFFFFF, text_font: roboto10, align: CENTER}
- obj:
x: 300
y: 323
width: 455
height: 155
border_width: 2
styles: transparent_card
widgets:
- label: {text: "MEDIA & STORAGE", x: -125, y: -5, styles: label_title, align: TOP_MID}
- image:
id: album_art
src: album_art_img
x: 15
y: 35
width: 80
height: 80
radius: 5
- label: {id: track_name, text: "Idle", x: 110, y: 25, styles: hud_text}
- label: {id: artist_name, text: "NonaSuomy", x: 110, y: 48, text_font: roboto10, styles: hud_text}
- buttonmatrix:
id: media_controls
x: 110
y: 68
width: 180
height: 38
styles: transparent_matrix
text_color: 0x00D4FF
items:
styles: matrix_btn_style
rows:
- buttons: [{text: "\U000F04AE"}, {text: "\U000F040A"}, {text: "\U000F04AD"}]
on_click:
then:
- lambda: |-
if (x == 1) {
ESP_LOGI("MEDIA", "Play/Pause Toggled");
}
- bar: {id: music_progress, width: 85%, height: 6, align: BOTTOM_MID, y: -8, value: 45}
- slider:
id: volume_slider
x: -10
y: 35
width: 12
height: 90
align: TOP_RIGHT
min_value: 0
max_value: 100
value: 60
on_value:
then:
- media_player.volume_set:
id: speaker_player
volume: !lambda return x / 100.0;
- obj:
x: 300
y: 488
width: 455
height: 87
border_width: 2
styles: transparent_card
widgets:
- label:
id: sd_status_icon
text: "\U000F07DC"
x: 8
y: 5
text_font: roboto20
text_color: 0x00FF00
- label: {id: sd_label, text: "SD: Checking...", x: 35, y: 8, styles: hud_text}
- bar: {id: sd_usage_bar, width: 200, height: 8, x: 35, y: 30, value: 0}
- label: {text: "WEBDAV:", x: 250, y: -10, styles: hud_text}
- label: {id: webdav_status, text: "--", x: 320, y: -10, text_color: 0x888888, styles: hud_text}
- label: {text: "HTTP-FS:", x: 250, y: 10, styles: hud_text}
- label: {id: http_status, text: "--", x: 330, y: 10, text_color: 0x888888, styles: hud_text}
- button:
width: 80
height: 25
align: BOTTOM_RIGHT
x: -5
y: 0
widgets:
- label: {text: "REMOUNT", align: CENTER, text_font: roboto10}
on_click:
then:
- lambda: |-
auto files = id(sd_card).list_directory("/", 0);
ESP_LOGI("STORAGE", "SD remount: %d files", files.size());
- lvgl.label.update:
id: sd_label
text: !lambda |-
auto files = id(sd_card).list_directory("/", 0);
return str_sprintf("SD: %d files", (int)files.size());
- button:
width: 80
height: 25
align: BOTTOM_RIGHT
x: -90
y: 0
widgets:
- label: {text: "EJECT", align: CENTER, text_font: roboto10}
on_click:
then:
- lambda: |-
id(sd_card).unmount();
- lvgl.label.update:
id: sd_label
text: "SD: Ejected"
# --- RIGHT COLUMN ---
- obj:
x: 770
y: 77
width: 230
height: 305
border_width: 2
styles: transparent_card
widgets:
- label: {text: "ENVIRONMENT", y: -5, align: TOP_MID, styles: hud_text}
- label:
id: temperature_label
text: "--.-°C"
y: 40
align: TOP_MID
text_font: space40
styles: hud_text
- label: {id: humidity_label, text: "--.-%", y: 90, align: TOP_MID, styles: hud_text}
- label: {id: light_label, text: "LIGHT: -- lx", y: 120, align: TOP_MID, styles: hud_text}
- label: {id: distance_label, text: "DIST: --- cm", y: 145, align: TOP_MID, styles: hud_text}
- label: {id: gas_status_label, text: "Air Quality: Clean", y: 170, align: TOP_MID, styles: hud_text}
- bar:
id: gas_bar
width: 90%
height: 15
align: TOP_MID
y: 195
value: 0
- label:
id: accel_data
text: "X:0.0 Y:0.1 Z:9.8"
align: TOP_LEFT
x: 0
y: 220
text_font: space16
styles: hud_text
- label:
id: gyro_data
text: "P:0.0 R:0.0 Y:0.0"
align: TOP_LEFT
x: 0
y: 240
text_font: space16
styles: hud_text
- label:
id: imu_temp_label
text: "IMU Temp: --.-°C"
align: TOP_LEFT
x: 0
y: 260
text_font: space16
styles: hud_text
- obj:
x: 770
y: 394
width: 230
height: 180
border_width: 2
styles: transparent_card
widgets:
- obj: {id: led_red, width: 15, height: 15, radius: 10, bg_color: 0x440000, x: 0, y: 0}
- obj: {id: led_yellow, width: 15, height: 15, radius: 10, bg_color: 0x444400, x: 20, y: 0}
- label:
text: INPUT MATRIX
y: -5
align: TOP_MID
styles: hud_text
- obj: {id: led_green, width: 15, height: 15, radius: 10, bg_color: 0x004400, x: 170, y: 0}
- obj: {id: led_blue, width: 15, height: 15, radius: 10, bg_color: 0x000044, x: 190, y: 0}
- buttonmatrix:
id: adc_btns_view
width: 210
height: 130
align: BOTTOM_MID
y: 0
styles: transparent_matrix
text_color: 0x00D4FF
items:
styles: matrix_btn_style
rows:
- buttons:
- text: "\U000F005B"
- text: "\U000F005D"
- text: "\U000F005C"
- buttons:
- text: "\U000F004D"
- text: "\U000F0741"
- text: "\U000F0054"
- buttons:
- text: "\U000F0042"
- text: "\U000F0045"
- text: "\U000F0043"
- id: camera_page
bg_color: 0x000000
widgets:
- canvas:
id: camera_canvas_fs
width: 1024
height: 600
x: 0
y: 0
bg_color: 0x000000
border_width: 0
pad_all: 0
- button:
id: fs_start_streaming
width: 90
height: 50
align: BOTTOM_LEFT
x: 10
y: -10
bg_color: 0x00CC44
bg_opa: COVER
radius: 8
on_click:
then:
- lambda: |-
if (id(sc2336_cam).start_streaming()) {
lvgl_set_bg_color(id(camera_canvas_fs), 0x000000, 0);
id(camera_display).configure_canvas(id(camera_canvas_fs));
}
widgets:
- label: {text: "START", text_color: 0xFFFFFF, text_font: roboto10, align: CENTER}
- button:
id: fs_stop_streaming
width: 90
height: 50
align: BOTTOM_LEFT
x: 110
y: -10
bg_color: 0xFF4444
bg_opa: COVER
radius: 8
on_click:
then:
- lambda: |-
id(sc2336_cam).stop_streaming();
lvgl_set_bg_color(id(camera_canvas_fs), 0x000000, 0);
widgets:
- label: {text: "STOP", text_color: 0xFFFFFF, text_font: roboto10, align: CENTER}
- button:
width: 90
height: 50
align: BOTTOM_RIGHT
x: -10
y: -10
bg_color: 0x224488
bg_opa: COVER
radius: 8
on_click:
then:
- lambda: |-
id(sc2336_cam).stop_streaming();
- lvgl.page.show: main_page
widgets:
- label: {text: "BACK", text_color: 0xFFFFFF, text_font: roboto10, align: CENTER}
- id: terminal_page
bg_color: 0x000000
layout:
type: FLEX
flex_flow: COLUMN
pad_all: 0
widgets:
- obj:
width: 100%
height: 40
bg_color: 0x1E1E1E
bg_opa: COVER
border_width: 0
scrollbar_mode: "OFF"
layout:
type: FLEX
flex_flow: ROW
flex_align_main: SPACE_BETWEEN
flex_align_cross: CENTER
pad_left: 10
pad_right: 10
widgets:
- label:
text: "MICRO SSH TERMINAL"
text_color: 0x00FF00
text_font: title_font
- button:
id: quick_ssh_button
width: 70
height: 30
bg_color: 0x00FF00
on_click:
- lambda: |-
lvgl_textarea_set_text(id(terminal_input), "ssh nonasuomy@example.local");
widgets:
- label:
text: "Quick"
align: CENTER
text_color: 0x000000
text_font: mono_font_16
- button:
id: rotate_button
width: 60
height: 30
bg_color: 0x00FF00
on_click:
- lambda: |-
id(display_rotation) = (id(display_rotation) + 90) % 360;
auto rot = static_cast<esphome::display::DisplayRotation>(id(display_rotation));
id(lvgl_component).set_rotation(rot);
widgets:
- label:
text: "ROT"
align: CENTER
text_color: 0x000000
text_font: mono_font_16
- button:
id: lock_rotation_button
width: 60
height: 30
bg_color: 0xFF6600
on_click:
- lambda: |-
id(rotation_locked) = !id(rotation_locked);
- lvgl.label.update:
id: lock_rotation_label
text: !lambda 'return std::string(id(rotation_locked) ? "UNLK" : "LOCK");'
widgets:
- label:
id: lock_rotation_label
text: "LOCK"
align: CENTER
text_color: 0x000000
text_font: mono_font_16
- button:
id: kbd_toggle_button
width: 60
height: 30
bg_color: 0x00FF00
on_click:
- lambda: |-
lvgl_toggle_hidden(id(onscreen_kb)->obj);
widgets:
- label:
text: "KBD"
align: CENTER
text_color: 0x000000
text_font: mono_font_16
- label:
id: connection_status
text: "Disconnected"
text_color: 0xFF0000
text_font: mono_font_16
- textarea:
id: terminal_output
width: 100%
flex_grow: 1
text_color: 0x00FF00
bg_color: 0x000000
bg_opa: COVER
border_color: 0x00FF00
border_width: 2
text_font: mono_font_16
one_line: false
text: "$ Waiting for connection...\n"
scrollbar_mode: "AUTO"
- keyboard:
id: onscreen_kb
width: 100%
height: 250
hidden: true
bg_color: 0x000000
bg_opa: COVER
border_color: 0x00FF00
border_width: 1
text_font: mono_font_16
items:
text_color: 0x00FF00
bg_color: 0x1E1E1E
bg_opa: COVER
border_color: 0x00FF00
border_width: 1
radius: 4
- obj:
width: 100%
height: 60
bg_color: 0x1E1E1E
bg_opa: COVER
border_width: 0
layout:
type: FLEX
flex_flow: ROW
flex_align_cross: CENTER
pad_left: 10
pad_right: 10
pad_top: 0
pad_bottom: 0
widgets:
- label:
text: "$"
text_color: 0x00FF00
text_font: mono_font_16
- textarea:
id: terminal_input
flex_grow: 1
height: 40
text_color: 0x00FF00
bg_color: 0x000000
bg_opa: COVER
border_color: 0x00FF00
border_width: 1
text_font: mono_font_16
one_line: true
placeholder_text: "Enter command..."
- button:
id: send_button
width: 80
height: 40
bg_color: 0x00FF00
scrollbar_mode: "OFF"
on_click:
- lambda: |-
auto textarea = id(terminal_input);
const char* text = lvgl_textarea_get_text(textarea);
if (text && strlen(text) > 0) {
std::string cmd = text;
std::string output = "$ " + cmd + "\n";
if (id(ssh_client)->get_state() == esphome::microssh::SSH_CONNECTED) {
id(ssh_client)->send_command(cmd);
output += "[Command sent to SSH server]\n";
} else if (cmd.find("ssh") == 0) {
output += "Connecting to SSH...\n";
id(ssh_client)->connect_ssh();
} else {
output += "Not connected. Type 'ssh' to connect.\n";
}
lvgl_textarea_add_text(id(terminal_output), output.c_str());
lvgl_textarea_set_text(textarea, "");
}
widgets:
- label:
text: "SEND"
align: CENTER
text_color: 0x000000
text_font: mono_font_16
- id: page_1
styles: main_bg_style
pad_all: 0
widgets:
- button:
x: 162
y: 300
width: 100
height: 100
bg_color: 0xFF0000
on_press:
then:
- output.turn_on: led_06_output
- logger.log: "Red Button Pressed"
on_release:
then:
- output.turn_off: led_06_output
- button:
x: 272
y: 300
width: 100
height: 100
bg_color: 0x00FF00
on_press:
then:
- logger.log: "Green Button Pressed"
on_release:
then:
- logger.log: "Green Button Released"
- button:
x: 382
y: 300
width: 100
height: 100
bg_color: 0x0000FF
on_press:
then:
- logger.log: "Blue Button Pressed"
on_release:
then:
- logger.log: "Blue Button Released"
- button:
x: 492
y: 300
width: 100
height: 100
bg_color: 0xFFFFFF
on_press:
then:
- logger.log: "White Button Pressed"
on_release:
then:
- logger.log: "White Button Released"
- id: gamepad_page
bg_color: 0x111111
pad_all: 0
widgets:
- label:
text: "GAMEPAD"
align: TOP_MID
y: 8
styles: hud_text
# D-PAD
- obj:
x: 60
y: 120
width: 130
height: 130
bg_opa: 0%
border_width: 0
widgets:
- label: {id: gp_dup, text: "\U000F005D", align: TOP_MID, text_color: 0x444444, text_font: roboto20}
- label: {id: gp_ddown, text: "\U000F0045", align: BOTTOM_MID, text_color: 0x444444, text_font: roboto20}
- label: {id: gp_dleft, text: "\U000F004D", align: LEFT_MID, text_color: 0x444444, text_font: roboto20}
- label: {id: gp_dright, text: "\U000F0054", align: RIGHT_MID, text_color: 0x444444, text_font: roboto20}
# FACE BUTTONS
- obj:
x: 820
y: 120
width: 130
height: 130
bg_opa: 0%
border_width: 0
widgets:
- label: {id: gp_y, text: "Y", align: TOP_MID, text_color: 0x444444, text_font: roboto20}
- label: {id: gp_a, text: "A", align: BOTTOM_MID, text_color: 0x444444, text_font: roboto20}
- label: {id: gp_x, text: "X", align: LEFT_MID, text_color: 0x444444, text_font: roboto20}
- label: {id: gp_b, text: "B", align: RIGHT_MID, text_color: 0x444444, text_font: roboto20}
# SHOULDER BUTTONS
- label: {id: gp_l, text: "LB", x: 60, y: 80, text_color: 0x444444, styles: hud_text}
- label: {id: gp_r, text: "RB", x: 900, y: 80, text_color: 0x444444, styles: hud_text}
- label: {id: gp_zl, text: "LT", x: 60, y: 55, text_color: 0x444444, styles: hud_text}
- label: {id: gp_zr, text: "RT", x: 900, y: 55, text_color: 0x444444, styles: hud_text}
# CENTER BUTTONS
- label: {id: gp_minus, text: "\U000F0374", x: 420, y: 200, text_color: 0x444444, text_font: roboto20}
- label: {id: gp_plus, text: "\U000F0415", x: 560, y: 200, text_color: 0x444444, text_font: roboto20}
- label: {id: gp_home, text: "\U000F02DC", x: 490, y: 170, text_color: 0x444444, text_font: roboto20}
# STICK BUTTONS
- label: {id: gp_l3, text: "L3", x: 200, y: 310, text_color: 0x444444, styles: hud_text}
- label: {id: gp_r3, text: "R3", x: 760, y: 310, text_color: 0x444444, styles: hud_text}
# STICK INDICATORS (arcs showing axis position)
- arc:
id: gp_lx_arc
x: 160
y: 340
width: 80
height: 80
min_value: -100
max_value: 100
value: 0
start_angle: 135
end_angle: 45
arc_color: 0x00D4FF
bg_color: 0x333333
clickable: false
widgets:
- label: {text: "L", align: CENTER, styles: hud_text}
- arc:
id: gp_rx_arc
x: 720
y: 340
width: 80
height: 80
min_value: -100
max_value: 100
value: 0
start_angle: 135
end_angle: 45
arc_color: 0x00D4FF
bg_color: 0x333333
clickable: false
widgets:
- label: {text: "R", align: CENTER, styles: hud_text}
- label:
id: gp_device_label
text: "No device"
x: 512
y: 10
align: TOP_MID
styles: hud_text
globals:
- id: selected_option
type: int
initial_value: '0'
- id: last_btn
type: int
restore_value: no
initial_value: '-2'
- id: mock_is_playing
type: bool
initial_value: 'false'
- id: mock_volume
type: float
initial_value: '0.6'
- id: display_rotation
type: int
restore_value: false
initial_value: '0'
- id: osk_enter_pressed
type: bool
restore_value: false
initial_value: 'false'
- id: last_imu_rotation
type: int
restore_value: false
initial_value: '-1'
- id: rotation_locked
type: bool
restore_value: false
initial_value: 'false'
- id: imu_rot_candidate
type: int
restore_value: false
initial_value: '-1'
- id: imu_rot_count
type: int
restore_value: false
initial_value: '0'
- id: sendspin_album_art_valid
type: bool
restore_value: no
initial_value: 'false'
#external_components:
# - source:
# type: git
# url: "file:///Users/clyde/dev/opensourceprojects/esp/esphome"
# ref: "lvgl-rounding"
# components: [lvgl, image]
# refresh: 30s
sensor:
- platform: usb_hidx
type: gamepad
axis: lx
id: gp_axis_lx
name: "Gamepad Left X"
internal: true
on_value:
- lambda: lv_arc_set_value(id(gp_lx_arc), (int)x);
- platform: usb_hidx
type: gamepad
axis: rx
id: gp_axis_rx
name: "Gamepad Right X"
internal: true
on_value:
- lambda: lv_arc_set_value(id(gp_rx_arc), (int)x);
# Lesson 1 Ultrasonic Sensor
- platform: ultrasonic
id: ultrasonic_sensor_001
trigger_pin: GPIO13
echo_pin: GPIO12
name: "Distance"
update_interval: 1s
timeout: 3m
filters:
- filter_out: nan
- sliding_window_moving_average:
window_size: 5
send_every: 1
on_value:
then:
- lvgl.label.update:
id: distance_label
text:
format: "Distance: %.1f m"
args: [x]
# Lesson 12 Light Sensor
- platform: bh1750
id: light_sensor_001
i2c_id: bsp2_bus
address: 0x5C
name: "Illuminance"
update_interval: 5s
on_value_range:
- below: 10
then:
- lvgl.label.update:
id: light_label
text: !lambda 'return str_sprintf("DARK: %.0f lx", id(light_sensor_001).state);'
- above: 500
then:
- lvgl.label.update:
id: light_label
text: !lambda 'return str_sprintf("BRIGHT: %.0f lx", id(light_sensor_001).state);'
on_value:
then:
- lvgl.label.update:
id: light_label
text: !lambda 'return str_sprintf("LIGHT: %.0f lx", x);'
# Lesson 11 Temperature and Humidity
- platform: aht10
variant: AHT20 # Explicitly setting the variant for better internal timing
i2c_id: bsp2_bus
address: 0x38
update_interval: 10s
temperature:
name: "Temperature"
id: sensor_temp
on_value:
then:
- lvgl.label.update:
id: temperature_label
text:
format: "%.1f°C"
args: [x]
humidity:
name: "Humidity"
id: sensor_hum
on_value:
then:
- lvgl.label.update:
id: humidity_label
text:
format: "Hum: %.1f%%"
args: [x]
- platform: lsm6ds
accel_x:
name: "Accel X"
id: imu_ax
on_value:
then:
- lvgl.label.update:
id: accel_data
text: !lambda |-
return str_sprintf("X:%.1f Y:%.1f Z:%.1f",
id(imu_ax).state, id(imu_ay).state, id(imu_az).state);
accel_y:
name: "Accel Y"
id: imu_ay
accel_z:
name: "Accel Z"
id: imu_az
gyro_x:
name: "Gyro X"
id: imu_gx
on_value:
then:
- lvgl.label.update:
id: gyro_data
text: !lambda |-
return str_sprintf("P:%.1f R:%.1f Y:%.1f",
id(imu_gx).state, id(imu_gy).state, id(imu_gz).state);
gyro_y:
name: "Gyro Y"
id: imu_gy
gyro_z:
name: "Gyro Z"
id: imu_gz
temperature:
name: "IMU Temperature"
id: imu_temp
on_value:
then:
- lvgl.label.update:
id: imu_temp_label
text:
format: "IMU Temp: %.1f°C"
args: [x]
# - switch:
# id: lv_switch
# bg_color: green
# indicator:
# checked:
# bg_color: 0xFF0000
# on_value:
# - if:
# condition:
# lambda: return x;
# then:
# - lvgl.label.update:
# id: label_1_2
# text: !lambda |-
# ESP_LOGD("label", "update label");
# return "Switch is on";
# else:
# - lvgl.label.update:
# id: label_1_1
# text: !lambda return "Switch state:";
# - lvgl.label.update:
# id: label_1_2
# text: "it's off"
- platform: adc
pin: GPIO16
id: button_adc_sensor
update_interval: 50ms
attenuation: 12db
filters:
- median:
window_size: 5
send_every: 1
on_value:
then:
- lambda: |-
int btn = -1;
// 1. IDLE CHECK (HIGH VOLTAGE)
// Since your log shows 3.286V when let go, anything above 3.15V is "OFF"
if (x > 3.15) {
btn = -1;
}
// 2. BUTTON LADDER (Based on your calibrated logs)
else if (x < 1.48) btn = 6; // DOWN/LEFT (~1.44V)
else if (x < 1.60) btn = 0; // UP/LEFT (~1.55V)
else if (x < 1.75) btn = 3; // LEFT (~1.64V)
else if (x < 1.95) btn = 8; // DOWN/RIGHT (~1.85V)
else if (x < 2.10) btn = 2; // UP/RIGHT (~2.02V)
else if (x < 2.40) btn = 5; // RIGHT (~2.19V)
else if (x < 2.75) btn = 7; // DOWN (~2.57V)
else if (x < 3.10) btn = 1; // UP (~2.93V)
else btn = -1; // Catch-all for noise
if (btn != id(last_btn)) {
id(last_btn) = btn;
auto *obj = id(adc_btns_view).obj;
// 3. UNSET ALL: Explicitly loop through all 9 buttons to clear highlight
for (int i = 0; i <= 8; i++) {
lv_btnmatrix_clear_btn_ctrl(obj, i, LV_BTNMATRIX_CTRL_CHECKED);
}
if (btn == -1) {
id(rgb_leds).turn_off().perform();
ESP_LOGI("button_adc", "CLEAN RELEASE DETECTED (%.3fV)", x);
} else {
// Highlight the specific button
lv_btnmatrix_set_btn_ctrl(obj, btn, LV_BTNMATRIX_CTRL_CHECKED);
// LED Mixing Logic
float r=0, g=0, b=0, w=0;
if (btn == 0 || btn == 1 || btn == 2) g = 1.0; // UP components
if (btn == 6 || btn == 7 || btn == 8) w = 1.0; // DOWN components
if (btn == 0 || btn == 3 || btn == 6) r = 1.0; // LEFT components
if (btn == 2 || btn == 5 || btn == 8) b = 1.0; // RIGHT components
id(rgb_leds).turn_on()
.set_red(r).set_green(g).set_blue(b).set_white(w)
.perform();
ESP_LOGI("button_adc", "MATCH: btn=%d (%.3fV)", btn, x);
}
// Force redraw to ensure "Checked" state is visually removed
lv_obj_invalidate(obj);
}
- platform: adc
pin: GPIO17
id: gas_sensor_adc
name: "Gas Sensor Voltage"
update_interval: 500ms
attenuation: 12db
on_value:
then:
# Update Gas Bar (Idle 0.18V, Max ~2.7V)
- lvgl.bar.update:
id: gas_bar
value: !lambda |-
float val = (x - 0.18) * 35.0;
if (val < 0) return 0;
if (val > 100) return 100;
return (int)val;
# UI Warning Label Logic
- if:
condition: {lambda: "return x > 1.5;"}
then:
- lvgl.label.update: {id: gas_status_label, text: "GAS DETECTED!"}
else:
- lvgl.label.update: {id: gas_status_label, text: "Air Quality: Clean"}
binary_sensor:
- platform: usb_hidx
type: gamepad
name: "GP A"
id: gp_btn_a
internal: true
button_a: true
on_press:
- lvgl.label.update:
id: gp_a
text_color: 0x00FF00
on_release:
- lvgl.label.update:
id: gp_a
text_color: 0x444444
- platform: usb_hidx
type: gamepad
name: "GP B"
id: gp_btn_b
internal: true
button_b: true
on_press:
- lvgl.label.update:
id: gp_b
text_color: 0xFF0000
on_release:
- lvgl.label.update:
id: gp_b
text_color: 0x444444
- platform: usb_hidx
type: gamepad
name: "GP X"
id: gp_btn_x
internal: true
button_x: true
on_press:
- lvgl.label.update:
id: gp_x
text_color: 0x0088FF
on_release:
- lvgl.label.update:
id: gp_x
text_color: 0x444444
- platform: usb_hidx
type: gamepad
name: "GP Y"
id: gp_btn_y
internal: true
button_y: true
on_press:
- lvgl.label.update:
id: gp_y
text_color: 0xFFFF00
on_release:
- lvgl.label.update:
id: gp_y
text_color: 0x444444
- platform: usb_hidx
type: gamepad
name: "GP LB"
id: gp_btn_l
internal: true
button_l: true
on_press:
- lvgl.label.update:
id: gp_l
text_color: 0x00D4FF
on_release:
- lvgl.label.update:
id: gp_l
text_color: 0x444444
- platform: usb_hidx
type: gamepad
name: "GP RB"
id: gp_btn_r
internal: true
button_r: true
on_press:
- lvgl.label.update:
id: gp_r
text_color: 0x00D4FF
on_release:
- lvgl.label.update:
id: gp_r
text_color: 0x444444
- platform: usb_hidx
type: gamepad
name: "GP LT"
id: gp_btn_zl
internal: true
button_zl: true
on_press:
- lvgl.label.update:
id: gp_zl
text_color: 0x00D4FF
on_release:
- lvgl.label.update:
id: gp_zl
text_color: 0x444444
- platform: usb_hidx
type: gamepad
name: "GP RT"
id: gp_btn_zr
internal: true
button_zr: true
on_press:
- lvgl.label.update:
id: gp_zr
text_color: 0x00D4FF
on_release:
- lvgl.label.update:
id: gp_zr
text_color: 0x444444
- platform: usb_hidx
type: gamepad
name: "GP Minus"
id: gp_btn_minus
internal: true
button_minus: true
on_press:
- lvgl.label.update:
id: gp_minus
text_color: 0x00D4FF
on_release:
- lvgl.label.update:
id: gp_minus
text_color: 0x444444
- platform: usb_hidx
type: gamepad
name: "GP Plus"
id: gp_btn_plus
internal: true
button_plus: true
on_press:
- lvgl.label.update:
id: gp_plus
text_color: 0x00D4FF
on_release:
- lvgl.label.update:
id: gp_plus
text_color: 0x444444
- platform: usb_hidx
type: gamepad
name: "GP Home"
id: gp_btn_home
internal: true
button_home: true
on_press:
- lvgl.label.update:
id: gp_home
text_color: 0xFFFFFF
on_release:
- lvgl.label.update:
id: gp_home
text_color: 0x444444
- platform: usb_hidx
type: gamepad
name: "GP L3"
id: gp_btn_l3
internal: true
button_l3: true
on_press:
- lvgl.label.update:
id: gp_l3
text_color: 0x00D4FF
on_release:
- lvgl.label.update:
id: gp_l3
text_color: 0x444444
- platform: usb_hidx
type: gamepad
name: "GP R3"
id: gp_btn_r3
internal: true
button_r3: true
on_press:
- lvgl.label.update:
id: gp_r3
text_color: 0x00D4FF
on_release:
- lvgl.label.update:
id: gp_r3
text_color: 0x444444
- platform: usb_hidx
type: gamepad
name: "GP D-Up"
id: gp_dpad_up
internal: true
dpad_up: true
on_press:
- lvgl.label.update:
id: gp_dup
text_color: 0x00D4FF
on_release:
- lvgl.label.update:
id: gp_dup
text_color: 0x444444
- platform: usb_hidx
type: gamepad
name: "GP D-Left"
id: gp_dpad_left
internal: true
dpad_left: true
on_press:
- lvgl.label.update:
id: gp_dleft
text_color: 0x00D4FF
on_release:
- lvgl.label.update:
id: gp_dleft
text_color: 0x444444
- platform: usb_hidx
type: gamepad
name: "GP D-Right"
id: gp_dpad_right
internal: true
dpad_right: true
on_press:
- lvgl.label.update:
id: gp_dright
text_color: 0x00D4FF
on_release:
- lvgl.label.update:
id: gp_dright
text_color: 0x444444
- platform: usb_hidx
type: gamepad
name: "GP D-Down"
id: gp_dpad_down
internal: true
dpad_down: true
on_press:
- lvgl.label.update:
id: gp_ddown
text_color: 0x00D4FF
on_release:
- lvgl.label.update:
id: gp_ddown
text_color: 0x444444
- platform: gpio
pin: GPIO24
name: "Motion Sensor"
device_class: motion
on_press:
- output.turn_off: led_27_output
- logger.log: "Motion detected - LED OFF"
- lvgl.label.update:
id: motion_icon
text: "\U000F0583"
text_color: 0x00D4FF
on_release:
- output.turn_on: led_27_output
- logger.log: "Motion stopped - LED ON"
- lvgl.label.update:
id: motion_icon
text: "\U000F0583"
text_color: 0x444444
- platform: gpio
pin:
number: GPIO07
inverted: true
id: mag_sensor
on_press:
- lvgl.label.update:
id: mag_icon
text: "\U000F0348"
text_color: 0x00D4FF
on_release:
- lvgl.label.update:
id: mag_icon
text: "\U000F0347"
text_color: 0x444444
- platform: gpio
pin:
number: GPIO2
mode: INPUT_PULLUP
inverted: false # <--- Change this from true to false
name: "Touch Button"
filters:
- delayed_on: 10ms
- delayed_off: 10ms
on_press:
then:
- output.turn_on: led_27_output
- lambda: |-
auto *obj = id(adc_btns_view).obj;
lv_btnmatrix_clear_btn_ctrl_all(obj, LV_BTNMATRIX_CTRL_CHECKED);
lv_btnmatrix_set_btn_ctrl(obj, 4, LV_BTNMATRIX_CTRL_CHECKED);
on_release:
then:
- output.turn_off: led_27_output
- lambda: |-
lv_btnmatrix_clear_btn_ctrl_all(id(adc_btns_view).obj, LV_BTNMATRIX_CTRL_CHECKED);
id(last_btn) = -1;
font:
- file: "gfonts://Roboto Mono"
id: mono_font
size: 8
- file: "gfonts://Roboto Mono"
id: mono_font_16
size: 16
bpp: 4
- file: "gfonts://Roboto Mono"
id: title_font
size: 20
bpp: 4
- file: "fonts/roboto.ttf"
id: roboto20
size: 20
bpp: 4
glyphsets: GF_Latin_Kernel
glyphs:
- 'aAbBDcCxXyYzZáÁéÉíÍóÓőŐúÚűŰ0'
extras:
- file: 'fonts/materialdesignicons-webfont.ttf'
glyphs: [
"\U000F004B", # mdi-arrow-up
"\U000F006E", # mdi-arrow-down
"\U000F012C", # mdi-check
"\U000F179B", # mdi-light-recessed
"\U000F0748", # mdi-wall-sconce-round
"\U000F1A1B", # mdi-gas-burner
"\U000F02DC", # mdi-home
"\U000F0A02", # mdi-menu-left
"\U000F035F", # mdi-menu-right
"\U000F0156", # mdi-close
"\U000F0C5F", # mdi-close-network-outline
"\U000f0084", # mdi-battery-charging
"\U000f0091", # mdi-battery-unknown
"\U000F0200", # mdi-ethernet
"\U000F1CD1", # mdi-ethernet-off
"\U000F0929", # mdi-wifi-strength-off
"\U000F091F", # mdi-wifi-strength-1 (Low)
"\U000F0922", # mdi-wifi-strength-2 (Medium-Low)
"\U000F0925", # mdi-wifi-strength-3 (Medium-High)
"\U000F0928", # mdi-wifi-strength-4 (Full)
"\U000F0D41", # mdi-zigbee
"\U000F0AE5", # mdi-matter
"\U000F1119", # mdi-antenna
"\U000F00AF", # mdi-bluetooth
"\U000F043A", # mdi-meshtastic
"\U000F0347", # mdi-magnet-off
"\U000F0348", # mdi-magnet-on
"\U000F0A71", # mdi-motion-sensor
"\U000F0552", # mdi-arrow-top-left
"\U000F0553", # mdi-arrow-top-right
"\U000F0554", # mdi-arrow-bottom-left
"\U000F0555", # mdi-arrow-bottom-right
"\U000F0460", # mdi-refresh
"\U000F04AD", # mdi-skip-previous
"\U000F040A", # mdi-play-pause
"\U000F04AE", # mdi-skip-next
"\U000F07DC", # mdi-micro-sd
"\U000F004D", # mdi-arrow-left
"\U000F004E", # mdi-arrow-right
"\U000F0583", # mdi-walk
"\U000F0374", # mdi-minus-circle
"\U000F0415", # mdi-plus-circle
"\U000F005B", # mdi-arrow-top-left
"\U000F005C", # mdi-arrow-top-right
"\U000F005D", # mdi-arrow-up
"\U000F0042", # mdi-arrow-bottom-left
"\U000F0043", # mdi-arrow-bottom-right
"\U000F0045", # mdi-arrow-down
"\U000F0054", # mdi-arrow-right
"\U000F0741", # mdi-gesture-tap
]
- file: "fonts/roboto.ttf"
id: roboto10
size: 10
bpp: 4
extras:
- file: 'fonts/materialdesignicons-webfont.ttf'
glyphs: [
"\U000F004B", # mdi-arrow-up
"\U000F006E", # mdi-arrow-down
"\U000F012C", # mdi-check
"\U000F179B", # mdi-light-recessed
"\U000F0748", # mdi-wall-sconce-round
"\U000F1A1B", # mdi-gas-burner
"\U000F02DC", # mdi-home
"\U000F0A02", # mdi-menu-left
"\U000F035F", # mdi-menu-right
"\U000F0156", # mdi-close
"\U000F0C5F", # mdi-close-network-outline
"\U000f0084", # mdi-battery-charging
"\U000f0091", # mdi-battery-unknown
"\U000F0200", # mdi-ethernet
"\U000F1CD1", # mdi-ethernet-off
"\U000F0929", # mdi-wifi-strength-off
"\U000F091F", # mdi-wifi-strength-1 (Low)
"\U000F0922", # mdi-wifi-strength-2 (Medium-Low)
"\U000F0925", # mdi-wifi-strength-3 (Medium-High)
"\U000F0928", # mdi-wifi-strength-4 (Full)
"\U000F0D41", # mdi-zigbee
"\U000F0AE5", # mdi-matter
"\U000F1119", # mdi-antenna
"\U000F00AF", # mdi-bluetooth
"\U000F043A", # mdi-meshtastic
"\U000F0347", # mdi-magnet-off
"\U000F0348", # mdi-magnet-on
"\U000F0A71", # mdi-motion-sensor
"\U000F0552", # mdi-arrow-top-left
"\U000F0553", # mdi-arrow-top-right
"\U000F0554", # mdi-arrow-bottom-left
"\U000F0555", # mdi-arrow-bottom-right
"\U000F0460", # mdi-refresh
"\U000F04AD", # mdi-skip-previous
"\U000F040A", # mdi-play-pause
"\U000F04AE", # mdi-skip-next
"\U000F07DC", # mdi-micro-sd
"\U000F004D", # mdi-arrow-left
"\U000F004E", # mdi-arrow-right
"\U000F0583", # mdi-walk
"\U000F0374", # mdi-minus-circle
"\U000F0415", # mdi-plus-circle
"\U000F005B", # mdi-arrow-top-left
"\U000F005C", # mdi-arrow-top-right
"\U000F005D", # mdi-arrow-up
"\U000F0042", # mdi-arrow-bottom-left
"\U000F0043", # mdi-arrow-bottom-right
"\U000F0045", # mdi-arrow-down
"\U000F0054", # mdi-arrow-right
"\U000F0741", # mdi-gesture-tap
]
- file: "fonts/Helvetica.ttf"
id: helv20
bpp: 2
size: 20
extras:
- file: 'fonts/materialdesignicons-webfont.ttf'
glyphs: [
"\U000F004B", # mdi-arrow-up
"\U000F006E", # mdi-arrow-down
"\U000F012C", # mdi-check
"\U000F179B", # mdi-light-recessed
"\U000F0748", # mdi-wall-sconce-round
"\U000F1A1B", # mdi-gas-burner
"\U000F02DC", # mdi-home
"\U000F0A02", # mdi-menu-left
"\U000F035F", # mdi-menu-right
"\U000F0156", # mdi-close
"\U000F0C5F", # mdi-close-network-outline
"\U000f0084", # mdi-battery-charging
"\U000f0091", # mdi-battery-unknown
"\U000F0200", # mdi-ethernet
"\U000F1CD1", # mdi-ethernet-off
"\U000F0929", # mdi-wifi-strength-off
"\U000F091F", # mdi-wifi-strength-1 (Low)
"\U000F0922", # mdi-wifi-strength-2 (Medium-Low)
"\U000F0925", # mdi-wifi-strength-3 (Medium-High)
"\U000F0928", # mdi-wifi-strength-4 (Full)
"\U000F0D41", # mdi-zigbee
"\U000F0AE5", # mdi-matter
"\U000F1119", # mdi-antenna
"\U000F00AF", # mdi-bluetooth
"\U000F043A", # mdi-meshtastic
"\U000F0347", # mdi-magnet-off
"\U000F0348", # mdi-magnet-on
"\U000F0A71", # mdi-motion-sensor
"\U000F0552", # mdi-arrow-top-left
"\U000F0553", # mdi-arrow-top-right
"\U000F0554", # mdi-arrow-bottom-left
"\U000F0555", # mdi-arrow-bottom-right
"\U000F0460", # mdi-refresh
"\U000F04AD", # mdi-skip-previous
"\U000F040A", # mdi-play-pause
"\U000F04AE", # mdi-skip-next
"\U000F07DC", # mdi-micro-sd
"\U000F004D", # mdi-arrow-left
"\U000F004E", # mdi-arrow-right
"\U000F0583", # mdi-walk
"\U000F0374", # mdi-minus-circle
"\U000F0415", # mdi-plus-circle
"\U000F005B", # mdi-arrow-top-left
"\U000F005C", # mdi-arrow-top-right
"\U000F005D", # mdi-arrow-up
"\U000F0042", # mdi-arrow-bottom-left
"\U000F0043", # mdi-arrow-bottom-right
"\U000F0045", # mdi-arrow-down
"\U000F0054", # mdi-arrow-right
"\U000F0741", # mdi-gesture-tap
]
- file: "fonts/Helvetica.ttf"
id: helv30
bpp: 2
size: 30
extras:
- file: 'fonts/materialdesignicons-webfont.ttf'
glyphs: [
"\U000F004B", # mdi-arrow-up
"\U000F006E", # mdi-arrow-down
"\U000F012C", # mdi-check
"\U000F179B", # mdi-light-recessed
"\U000F0748", # mdi-wall-sconce-round
"\U000F1A1B", # mdi-gas-burner
"\U000F02DC", # mdi-home
"\U000F0A02", # mdi-menu-left
"\U000F035F", # mdi-menu-right
"\U000F0156", # mdi-close
"\U000F0C5F", # mdi-close-network-outline
"\U000f0084", # mdi-battery-charging
"\U000f0091", # mdi-battery-unknown
"\U000F0200", # mdi-ethernet
"\U000F1CD1", # mdi-ethernet-off
"\U000F0929", # mdi-wifi-strength-off
"\U000F091F", # mdi-wifi-strength-1 (Low)
"\U000F0922", # mdi-wifi-strength-2 (Medium-Low)
"\U000F0925", # mdi-wifi-strength-3 (Medium-High)
"\U000F0928", # mdi-wifi-strength-4 (Full)
"\U000F0D41", # mdi-zigbee
"\U000F0AE5", # mdi-matter
"\U000F1119", # mdi-antenna
"\U000F00AF", # mdi-bluetooth
"\U000F043A", # mdi-meshtastic
"\U000F0347", # mdi-magnet-off
"\U000F0348", # mdi-magnet-on
"\U000F0A71", # mdi-motion-sensor
"\U000F0552", # mdi-arrow-top-left
"\U000F0553", # mdi-arrow-top-right
"\U000F0554", # mdi-arrow-bottom-left
"\U000F0555", # mdi-arrow-bottom-right
"\U000F0460", # mdi-refresh
"\U000F04AD", # mdi-skip-previous
"\U000F040A", # mdi-play-pause
"\U000F04AE", # mdi-skip-next
"\U000F07DC", # mdi-micro-sd
"\U000F004D", # mdi-arrow-left
"\U000F004E", # mdi-arrow-right
"\U000F0583", # mdi-walk
"\U000F0374", # mdi-minus-circle
"\U000F0415", # mdi-plus-circle
"\U000F005B", # mdi-arrow-top-left
"\U000F005C", # mdi-arrow-top-right
"\U000F005D", # mdi-arrow-up
"\U000F0042", # mdi-arrow-bottom-left
"\U000F0043", # mdi-arrow-bottom-right
"\U000F0045", # mdi-arrow-down
"\U000F0054", # mdi-arrow-right
"\U000F0741", # mdi-gesture-tap
]
- file: "fonts/roboto-condensed.ttf"
id: roboto64
size: 64
bpp: 4
glyphs: '-+X'
- file: "fonts/roboto-condensed.ttf"
id: roboto42
size: 28
bpp: 4
glyphs: 'aAbBcCxXyYzZáÁéÉíÍóÓőŐúÚűŰ'
- file: fonts/montserrat.ttf
id: mont20
glyphs: 'aAbBDcCxXyYzZáÁéÉíÍóÓőŐúÚűŰ0'
size: 20
bpp: 4
- file: "fonts/space-mono.ttf"
id: space16
size: 16
bpp: 4
glyphsets:
- GF_Latin_Kernel
glyphs:
- '0123456789+- .:'
- file: "fonts/space-mono.ttf"
id: space40
size: 40
bpp: 4
color:
- id: color_back
hex: "404040"
- id: color_blue
hex: 0000FF
image:
defaults:
resize: 100x100
rgb565:
- file: images/cat.jpg
id: cat_img
- file: images/background-landscape.png
id: background_main
resize: 1024x600
- file: https://raw.githubusercontent.com/RealDeco/xiaozhi-esphome/main/images/Gwen/240x240/playing.png
id: fallback_playing_image
resize: 80x80
type: RGB565
dither: NONE
generic_image:
- platform: sendspin
id: sendspin_album_art
format: jpg
type: RGB565
resize: 80x80
image_source: ALBUM
on_image_decoded:
- lambda: id(sendspin_album_art_valid) = true;
- lvgl.image.update:
id: album_art
src: sendspin_album_art
on_image_error:
- lambda: id(sendspin_album_art_valid) = false;
- lvgl.image.update:
id: album_art
src: fallback_playing_image
sendspin:
id: sendspin_hub
task_stack_in_psram: false
# binary:
# opaque:
# - file: images/logo-text-trans.svg
# resize: 100x100
# id: alpha_binary
# grayscale:
# alpha_channel:
# - file: images/bluemax.png
# resize: 100x100
# id: bluemax_png
# transparency: alpha_channel
# SD card images moved to inline rgb565 above
interval:
- interval: 100ms
then:
- lambda: |-
if (id(osk_enter_pressed)) {
id(osk_enter_pressed) = false;
auto textarea = id(terminal_input);
const char* text = lvgl_textarea_get_text(textarea);
if (text && strlen(text) > 0) {
std::string cmd = text;
std::string output = "$ " + cmd + "\n";
if (id(ssh_client)->get_state() == esphome::microssh::SSH_CONNECTED) {
id(ssh_client)->send_command(cmd);
output += "[Command sent to SSH server]\n";
} else if (cmd.find("ssh") == 0) {
output += "Connecting to SSH...\n";
id(ssh_client)->connect_ssh();
} else {
output += "Not connected. Type 'ssh' to connect.\n";
}
lvgl_textarea_add_text(id(terminal_output), output.c_str());
lvgl_textarea_set_text(textarea, "");
}
}
if (!id(rotation_locked)) {
float ax = id(imu_ax).state;
float ay = id(imu_ay).state;
int new_rot = -1;
if (ax > 4.0f) new_rot = 90;
else if (ax < -4.0f) new_rot = 270;
else if (ay < -4.0f) new_rot = 180;
else if (ay > 4.0f) new_rot = 0;
if (new_rot != -1) {
if (new_rot == id(imu_rot_candidate)) {
id(imu_rot_count) = id(imu_rot_count) + 1;
} else {
id(imu_rot_candidate) = new_rot;
id(imu_rot_count) = 1;
}
if (id(imu_rot_count) >= 5 && new_rot != id(last_imu_rotation)) {
id(last_imu_rotation) = new_rot;
id(display_rotation) = new_rot;
id(lvgl_component).set_rotation(
static_cast<esphome::display::DisplayRotation>(new_rot));
lvgl_invalidate_screen();
}
} else {
id(imu_rot_count) = 0;
}
}
static std::string last_output = "";
std::string current_output = id(ssh_client)->get_output();
if (current_output != last_output) {
lvgl_textarea_set_text(id(terminal_output), current_output.c_str());
last_output = current_output;
lvgl_scroll_to_bottom(id(terminal_output));
}
auto state = id(ssh_client)->get_state();
if (state == esphome::microssh::SSH_CONNECTED) {
lvgl_label_set_text(id(connection_status), "Connected");
lvgl_set_text_color(id(connection_status), 0x00FF00, LV_PART_MAIN);
} else {
lvgl_label_set_text(id(connection_status), "Disconnected");
lvgl_set_text_color(id(connection_status), 0xFF0000, LV_PART_MAIN);
}
// ===== VIRTUAL LED SYNC =====
{
auto &color = id(rgb_leds).current_values;
uint8_t r = 0, g = 0, b = 0, w = 0;
auto mode = static_cast<uint8_t>(color.get_color_mode());
if (mode & 2) { // RGB mode
r = (uint8_t)(color.get_color_brightness() * color.get_red() * 255);
g = (uint8_t)(color.get_color_brightness() * color.get_green() * 255);
b = (uint8_t)(color.get_color_brightness() * color.get_blue() * 255);
}
if (mode & 1) { // WHITE mode
w = (uint8_t)(color.get_white() * 255);
}
lvgl_set_bg_color_rgb(id(led_red), r, 0, 0, LV_PART_MAIN);
lvgl_set_bg_color_rgb(id(led_yellow), w, w, 0, LV_PART_MAIN);
lvgl_set_bg_color_rgb(id(led_green), 0, g, 0, LV_PART_MAIN);
lvgl_set_bg_color_rgb(id(led_blue), 0, 0, b, LV_PART_MAIN);
}
# text_sensor:
# - platform: usb_hidx
# type: device
# id: usb_device_name
# name: "USB Device"
# internal: true
# on_value:
# - lambda: lvgl_label_set_text(id(gp_device_label), x.c_str());
# - platform: homeassistant
# id: ha_player_state
# entity_id: media_player.esp32_p4_elecrow_all_in_one
# on_value:
# then:
# - lambda: |-
# if (x == "idle" || x == "off") {
# lvgl_label_set_text(id(track_name), "Idle");
# lvgl_label_set_text(id(artist_name), "");
# }
# - platform: homeassistant
# id: ha_media_title
# entity_id: media_player.your_player_id # Replace with your actual entity ID
# attribute: media_title
# on_value:
# then:
# - lvgl.label.update:
# id: track_name
# text: !lambda 'return x.c_str();'
# - platform: homeassistant
# id: ha_media_artist
# entity_id: media_player.your_player_id # Replace with your actual entity ID
# attribute: media_artist
# on_value:
# then:
# - lvgl.label.update:
# id: artist_name
# text: !lambda 'return x.c_str();'
# - platform: homeassistant
# id: ha_track_name
# entity_id: media_player.esp32_p4_elecrow_all_in_one
# attribute: media_title
# on_value:
# then:
# - lvgl.label.update:
# id: track_name
# text: !lambda 'return x.empty() ? "Idle" : x.c_str();'
# - platform: homeassistant
# id: ha_artist_name
# entity_id: media_player.esp32_p4_elecrow_all_in_one
# attribute: media_artist
# on_value:
# then:
# - lvgl.label.update:
# id: artist_name
# text: !lambda 'return x.c_str();'
# - platform: homeassistant
# id: ha_album_art_url
# entity_id: media_player.esp32_p4_elecrow_all_in_one
# attribute: entity_picture
# on_value:
# then:
# - lambda: |-
# if (!x.empty()) {
# std::string url = x;
# // Prepend HA URL if it's a relative proxy path
# if (url.find("http") == std::string::npos) {
# url = "http://home-assistant.local:8123" + url;
# }
# // Call your C++ function to download the image into PSRAM
# // This function must be defined in your 'includes:' file
# download_album_art(url.c_str());
# }
text_sensor:
- platform: usb_hidx
type: device
id: usb_device_name
name: "USB Device"
internal: true
on_value:
- lambda: lvgl_label_set_text(id(gp_device_label), x.c_str());
- platform: homeassistant
id: ha_player_state
entity_id: media_player.esp32_p4_elecrow_all_in_one
on_value:
then:
- lambda: |-
if (x == "idle" || x == "off") {
lvgl_label_set_text(id(track_name), "Idle");
lvgl_label_set_text(id(artist_name), "");
}
- platform: sendspin
id: sendspin_title
type: title
on_value:
- lambda: id(sendspin_album_art_valid) = false;
- lvgl.label.update:
id: track_name
text: !lambda 'return x.empty() ? "Idle" : x.c_str();'
- platform: sendspin
id: sendspin_artist
type: artist
on_value:
- lvgl.label.update:
id: artist_name
text: !lambda 'return x.c_str();'
- platform: homeassistant
id: ha_track_name
entity_id: media_player.esp32_p4_elecrow_all_in_one
attribute: media_title
on_value:
then:
- lvgl.label.update:
id: track_name
text: !lambda 'return x.empty() ? "Idle" : x.c_str();'
- platform: homeassistant
id: ha_artist_name
entity_id: media_player.esp32_p4_elecrow_all_in_one
attribute: media_artist
on_value:
then:
- lvgl.label.update:
id: artist_name
text: !lambda 'return x.c_str();'
- platform: homeassistant
id: ha_album_art_url
entity_id: media_player.esp32_p4_elecrow_all_in_one
attribute: entity_picture
on_value:
then:
- lambda: |-
if (!x.empty()) {
std::string url = x;
if (url.find("http") == std::string::npos) {
url = "http://home-assistant.local:8123" + url;
}
auto img = id(album_art_img);
img->set_url(url);
img->update();
}
- platform: usb_hidx
type: keyboard
name: "Keyboard Input"
id: keyboard_input_sensor
internal: true
on_value:
- lambda: |-
if (x.empty()) return;
char c = x[0];
auto textarea = id(terminal_input);
if (c == '\n') {
id(ssh_client)->handle_key('\n');
lvgl_textarea_set_text(textarea, "");
} else if (c == '\b') {
const char* current_text = lvgl_textarea_get_text(textarea);
if (current_text && strlen(current_text) > 0) {
std::string s = current_text;
s.pop_back();
lvgl_textarea_set_text(textarea, s.c_str());
}
id(ssh_client)->handle_key('\b');
} else if (c >= 32 && c <= 126) {
char buf[2] = {c, 0};
lvgl_textarea_add_text(textarea, buf);
id(ssh_client)->handle_key(c);
}
i2s_audio:
- id: i2s_output
i2s_lrclk_pin: GPIO21
i2s_bclk_pin: GPIO22
- id: i2s_input
i2s_lrclk_pin: GPIO3
speaker:
- platform: i2s_audio
id: speaker_output
i2s_audio_id: i2s_output
dac_type: external
i2s_dout_pin: GPIO23
channel: stereo
buffer_duration: 100ms
bits_per_sample: 16bit
sample_rate: 48000
- platform: mixer
id: mixing_speaker
output_speaker: speaker_output
num_channels: 2
task_stack_in_psram: true
source_speakers:
- id: announcement_mixing_input
- id: media_mixing_input
- platform: resampler
id: announcement_resampling_speaker
output_speaker: announcement_mixing_input
sample_rate: 48000
bits_per_sample: 16
- platform: resampler
id: media_resampling_speaker
output_speaker: media_mixing_input
sample_rate: 48000
bits_per_sample: 16
microphone:
- platform: i2s_audio
id: microphone_input
i2s_audio_id: i2s_input
i2s_din_pin: GPIO04
adc_type: external
pdm: true
channel: left
sample_rate: 16000
media_source:
- platform: audio_http
id: http_announcement_source
- platform: audio_http
id: http_media_source
- platform: sendspin
id: sendspin_media_source
rtttl:
id: buzzer
speaker: speaker_output
media_player:
- platform: sendspin
id: sendspin_group_media_player
name: Sendspin Group Media Player
- platform: speaker_source
name: ${friendly_name} Speaker
id: speaker_player
announcement_pipeline:
speaker: announcement_resampling_speaker
format: FLAC
sample_rate: 48000
num_channels: 1
sources:
- http_announcement_source
media_pipeline:
format: FLAC
num_channels: 2
sample_rate: 48000
speaker: media_resampling_speaker
sources:
- http_media_source
- sendspin_media_source
# on_announcement:
# # Stop the wake word (mWW or VA) if the mic is capturing
# - if:
# condition:
# - microphone.is_capturing:
# then:
# - micro_wake_word.stop:
# on_idle:
# then:
# - if:
# condition:
# not:
# voice_assistant.is_running:
# then:
# - micro_wake_word.start:
# micro_wake_word:
# id: mww
# models:
# - okay_nabu
# - hey_mycroft
# - hey_jarvis
# on_wake_word_detected:
# - voice_assistant.start:
# wake_word: !lambda return wake_word;
#voice_assistant:
# id: va
# microphone: microphone_input
# media_player: speaker_player
# micro_wake_word: mww
# on_end:
# # Wait a short amount of time to see if an announcement starts
# - wait_until:
# condition:
# - media_player.is_announcing:
# timeout: 0.5s
# # Announcement is finished and the I2S bus is free
# - wait_until:
# - and:
# - not:
# media_player.is_announcing:
# - not:
# speaker.is_playing:
# - micro_wake_word.start:
# #- lvgl.widget.update: {id: led_white, bg_color: 0x444444}
# on_client_connected:
# - micro_wake_word.start:
# on_client_disconnected:
# - micro_wake_word.stop:
# on_listening:
# - lvgl.label.update: {id: voice_text, text: "Listening..."}
# on_stt_end:
# - lvgl.label.update:
# id: voice_text
# text: !lambda 'return x.c_str();'

Voice Assistant Integration

# Voice Assistant example for the Elecrow ESP32-P4 AIO.
#
# This is an experimental working-project configuration. It requires the
# external audio components below and a Home Assistant API connection in the
# main device configuration. Do not add credentials to this example file.
external_components:
- source:
type: git
url: https://github.com/esphome/esphome
ref: 57397a318a2e08ea4c44bb7836358a7da20985f2
components:
- audio
- audio_http
refresh: 0s
- source: github://pr#15517
components:
- i2s_audio
# The AIO microphone is PDM: GPIO3 is the clock and GPIO4 is the data line.
i2s_audio:
- id: i2s_input
i2s_lrclk_pin: GPIO3
- id: i2s_output
i2s_lrclk_pin: GPIO21
i2s_bclk_pin: GPIO22
microphone:
- platform: i2s_audio
id: microphone_input
i2s_audio_id: i2s_input
i2s_din_pin: GPIO4
adc_type: external
pdm: true
channel: left
sample_rate: 16000
speaker:
- platform: i2s_audio
id: speaker_output
i2s_audio_id: i2s_output
dac_type: external
i2s_dout_pin: GPIO23
channel: stereo
buffer_duration: 100ms
bits_per_sample: 16bit
sample_rate: 48000
- platform: mixer
id: mixing_speaker
output_speaker: speaker_output
num_channels: 2
task_stack_in_psram: true
source_speakers:
- id: announcement_mixing_input
- id: media_mixing_input
- platform: resampler
id: announcement_resampling_speaker
output_speaker: announcement_mixing_input
sample_rate: 48000
bits_per_sample: 16
- platform: resampler
id: media_resampling_speaker
output_speaker: media_mixing_input
sample_rate: 48000
bits_per_sample: 16
media_source:
- platform: audio_http
id: http_announcement_source
- platform: audio_http
id: http_media_source
media_player:
- platform: speaker_source
name: "ESP32-P4 AIO Speaker"
id: speaker_player
announcement_pipeline:
speaker: announcement_resampling_speaker
format: FLAC
sample_rate: 48000
num_channels: 1
sources:
- http_announcement_source
media_pipeline:
format: FLAC
num_channels: 2
sample_rate: 48000
speaker: media_resampling_speaker
sources:
- http_media_source
# Optional local wake-word support.
micro_wake_word:
id: mww
models:
- okay_nabu
- hey_mycroft
- hey_jarvis
on_wake_word_detected:
- voice_assistant.start:
wake_word: !lambda return wake_word;
voice_assistant:
id: voice_assistant_id
microphone: microphone_input
media_player: speaker_player
micro_wake_word: mww
on_end:
- wait_until:
condition:
- media_player.is_announcing:
timeout: 0.5s
- wait_until:
- and:
- not:
media_player.is_announcing:
- not:
speaker.is_playing
- micro_wake_word.start:
on_client_connected:
- micro_wake_word.start
on_client_disconnected:
- micro_wake_word.stop
on_listening:
- logger.log: "Voice assistant is listening"
on_stt_end:
- logger.log:
format: "Voice assistant heard: %s"
args:
- x.c_str()

Home Assistant Integration

Dashboard Example

configuration.yaml
sensor:
- platform: template
sensors:
esp32_p4_temperature:
friendly_name: "Room Temperature"
unit_of_measurement: "°C"
value_template: "{{ states('sensor.temperature') }}"
binary_sensor:
- platform: template
sensors:
esp32_p4_motion:
friendly_name: "Room Motion"
device_class: motion
value_template: "{{ is_state('binary_sensor.motion_detected', 'on') }}"
automation:
- alias: "Motion Detected Alert"
trigger:
- platform: state
entity_id: binary_sensor.motion_detected
to: 'on'
action:
- service: notify.mobile_app
data:
message: "Motion detected in room"
- alias: "High Temperature Warning"
trigger:
- platform: numeric_state
entity_id: sensor.temperature
above: 30
action:
- service: switch.turn_on
entity_id: switch.relay # Turn on fan/ventilation
  • Product Information: Official Website
  • EnSens Add-on firmware on GitHub: Github
  • Android App on GitHub: Github
  • Example project for CrowPanel Advance 3.5”, and 2.8”: Github
  • nRF52833 Product Specification v1.5: PDF
  • BME 688 Datasheet: PDF

Resources

Official Resources

Elecrow Community