@mx738: Toni, how did you configure the LAN PoE for that board? Are you able to kindly share?
I just got the non camera version but POE works fine using the above. You can’t put in WiFi or an AP/captive portal part when using Ethernet. Haven’t had time to add various sensors yet so left the relay in. Just flash it using USB C then plug into POE Ethernet and it shows up to add in HA.
substitutions:
name: ws-poe-s3
friendly_name: ws_poe_s3
device_sampling_time: 30s
esphome:
name: ${name}
friendly_name: ${friendly_name}
esp32:
board: esp32-s3-devkitc-1
framework:
type: esp-idf
flash_size: 16MB
psram:
mode: octal
speed: 80MHz
switch:
- platform: gpio
name: ws-poe-32-relay1
id: relay1
pin: GPIO8
restore_mode: ALWAYS_OFF
setup_priority: 800
# Enable logging
logger:
debug:
update_interval: ${device_sampling_time}
# Enable Home Assistant API
api:
encryption:
key: "Api123="
ota:
- platform: esphome
password: "otapw"
#wifi:
# ssid: !secret wifi_ssid
# password: !secret wifi_password
ethernet:
type: W5500
mosi_pin: GPIO11
miso_pin: GPIO12
clk_pin: GPIO13
cs_pin: GPIO14
reset_pin: GPIO9
interrupt_pin: GPIO10
clock_speed: 25MHz
time:
- platform: homeassistant
id: homeassistant_time
button:
- platform: safe_mode
name: Safe Mode Boot
- platform: factory_reset
name: Factory reset
- platform: restart
id: reboot_btn
name: restart
sensor:
#-------------------------------------------------------------------------------
# ESP32 internal sensors
#-------------------------------------------------------------------------------
# Temperature
- platform: internal_temperature
name: Internal Temperature
update_interval: ${device_sampling_time}
- platform: debug
free:
name: Heap free
id: ${friendly_name}_heap_free
block:
name: Heap max block
id: ${friendly_name}_heap_max_block
loop_time:
name: Loop time
id: ${friendly_name}_loop_time
- platform: template
name: Heap free (%)
id: ${friendly_name}_heap_free_percent
unit_of_measurement: '%'
accuracy_decimals: 2
icon: mdi:percent
update_interval: ${device_sampling_time}
entity_category: diagnostic
# 524288 B (SRAM total)
# 327680 B (DRAM usable)
lambda: return ( id(${friendly_name}_heap_free).state * 100 / 327680 );
- platform: template
name: Heap max block (%)
id: ${friendly_name}_heap_max_block_percent
unit_of_measurement: '%'
accuracy_decimals: 2
icon: mdi:percent
update_interval: ${device_sampling_time}
entity_category: diagnostic
# 524288 B (SRAM total)
# 327680 B (DRAM usable)
lambda: return ( id(${friendly_name}_heap_max_block).state * 100 / 327680 );
- platform: uptime
name: "Uptime Sensor"
id: ${friendly_name}_uptime_sensor
update_interval: ${device_sampling_time}
internal: true
on_raw_value:
then:
- text_sensor.template.publish:
id: ${friendly_name}_uptime_human
state: !lambda |-
int seconds = round(id(${friendly_name}_uptime_sensor).raw_state);
int days = seconds / (24 * 3600);
seconds = seconds % (24 * 3600);
int hours = seconds / 3600;
seconds = seconds % 3600;
int minutes = seconds / 60;
seconds = seconds % 60;
return (
(days ? to_string(days) + "d " : "") +
(hours ? to_string(hours) + "h " : "") +
(minutes ? to_string(minutes) + "m " : "") +
(to_string(seconds) + "s")
).c_str();
text_sensor:
- platform: ethernet_info
ip_address:
name: "IP Address"
#-------------------------------------------------------------------------------
# Custom Text sensors
#-------------------------------------------------------------------------------
- platform: template
name: Uptime Human Readable
id: ${friendly_name}_uptime_human
icon: mdi:clock-start
web_server:
port: 80
version: 2
include_internal: true
ota: false
# Enable fallback hotspot (captive portal) in case wifi connection fails
#ap:
# ssid: "Ws-Poe-S3 Fallback Hotspot"
# password: "AB6J9ECa7R0F"
#captive_portal:
Thanks to the helpful posts above, the camera finally works quite well and with ethernet the BLE proxy works much much better than via WiFi:
No problem with brightness in esphome 2025.05.
Config with Ethernet, Camera and BLE-Proxy:
#===============================================================================
substitutions:
device_name: you-name-for-your-device
room_friendly_name: Maybe Your Workshop Room
#===============================================================================
esphome:
name: ${device_name}
platformio_options:
board_build.arduino.memory_type: qio_opi
board_build.flash_mode: dio
upload_speed: 921600
monitor_speed: 115200
esp32:
board: esp32-s3-devkitc-1
flash_size: 16MB
framework:
type: esp-idf
sdkconfig_options:
CONFIG_ESP32_S3_BOX_BOARD: "y"
psram:
mode: octal
speed: 80MHz
logger:
api:
encryption:
key: !secret api_encryption_psk
ota:
- platform: esphome
password: !secret ota_password
ethernet:
type: W5500
clk_pin: GPIO13
mosi_pin: GPIO11
miso_pin: GPIO12
cs_pin: GPIO14
interrupt_pin: GPIO10
reset_pin: GPIO9
######################################
### Camera
######################################
esp32_camera:
name: ${room_friendly_name} Camera
external_clock:
pin: GPIO3
frequency: 20MHz
i2c_pins:
sda: GPIO48
scl: GPIO47
data_pins: [GPIO41, GPIO45, GPIO46, GPIO42, GPIO40, GPIO38, GPIO15, GPIO18]
vsync_pin: GPIO1
href_pin: GPIO2
pixel_clock_pin: GPIO39
power_down_pin: GPIO8
resolution: 640x480
jpeg_quality: 10
#resolution: 1280x1024
#jpeg_quality: 30
vertical_flip: false
horizontal_mirror: false
# aec_mode: MANUAL
# aec_value: 1200
# brightness: 2
# wb_mode: HOME
# agc_mode: AUTO
#######################################
### Bluetooth Proxy
#######################################
esp32_ble_tracker:
scan_parameters:
# only use these values on Ethernet not WIFI
interval: 1100ms
window: 1100ms
active: true
bluetooth_proxy:
active: true
I also made an enclosure for the board, if anybody needs one:
Thanks to this thread i made it finally work.
now… this thing gets hot? or is it just in my case? (ESP32-S3-ETH, PoE module and also camera)
absolutely … any ESP32 where the camera is in use, gets quite hot in my experience. On top of that, I am sure the POE-module also generates it’s share of heat.
so: yes, that’s normal
Just wanted to thank @bernhardt for this. It’s exactly what I needed to get mine working as a camera and proxy.
I also printed that case and wall mount. They fit perfectly.
Hi everyone. I managed to set up a Waveshare ESP32-S3-ETH W5500 (S3-N16R8) + POE Module (B) + OV5640 (DCXYX-LZTKQJ-5M-357-V1 FF) for web streaming in ESP-IDF 5.3. Ethernet or Wi-Fi is possible. And some nice extras like core temperature, NTP date, and clock. Please insert your own values into " *********** "
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <sys/time.h>
#include "esp_log.h"
#include "esp_eth.h"
#include "esp_eth_mac_w5500.h"
#include "esp_eth_phy_w5500.h"
#include "esp_event.h"
#include "esp_netif.h"
#include "esp_netif_sntp.h"
#include "driver/spi_master.h"
#include "driver/gpio.h"
#include "driver/temperature_sensor.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_camera.h"
#include "esp_http_server.h"
#include "esp_ota_ops.h"
#include "esp_wifi.h"
#include "nvs_flash.h"
static const char *TAG = "********";
#define ETH_CS 14
#define ETH_INT 10
#define ETH_RST 9
#define PWR_CTRL 8
#define WIFI_SSID "********"
#define WIFI_PASS "*********"
static bool eth_connected = false;
float get_chip_temp() {
static temperature_sensor_handle_t temp_sensor = NULL;
if (temp_sensor == NULL) {
temperature_sensor_config_t temp_sensor_config = TEMPERATURE_SENSOR_CONFIG_DEFAULT(20, 100);
temperature_sensor_install(&temp_sensor_config, &temp_sensor);
temperature_sensor_enable(temp_sensor);
}
float tsens_out = 0;
temperature_sensor_get_celsius(temp_sensor, &tsens_out);
return tsens_out;
}
esp_err_t date_handler(httpd_req_t *req) {
char buf[16];
time_t now; struct tm ti;
time(&now); localtime_r(&now, &ti);
strftime(buf, sizeof(buf), "%d.%m.%Y", &ti);
return httpd_resp_send(req, buf, strlen(buf));
}
esp_err_t time_handler(httpd_req_t *req) {
char buf[16];
time_t now; struct tm ti;
time(&now); localtime_r(&now, &ti);
strftime(buf, sizeof(buf), "%H:%M:%S", &ti);
return httpd_resp_send(req, buf, strlen(buf));
}
esp_err_t temp_handler(httpd_req_t *req) {
char buf[16];
snprintf(buf, sizeof(buf), "%.1f", get_chip_temp());
return httpd_resp_send(req, buf, strlen(buf));
}
esp_err_t update_post_handler(httpd_req_t *req) {
esp_ota_handle_t update_handle = 0;
const esp_partition_t *update_part = esp_ota_get_next_update_partition(NULL);
if (!update_part) return ESP_FAIL;
esp_ota_begin(update_part, OTA_SIZE_UNKNOWN, &update_handle);
char *buf = malloc(1024);
int remaining = req->content_len;
while (remaining > 0) {
int recv_len = httpd_req_recv(req, buf, (remaining > 1024) ? 1024 : remaining);
if (recv_len <= 0) break;
esp_ota_write(update_handle, buf, recv_len);
remaining -= recv_len;
}
free(buf);
if (esp_ota_end(update_handle) == ESP_OK && esp_ota_set_boot_partition(update_part) == ESP_OK) {
ESP_LOGI(TAG, "OTA Success. Rebooting...");
vTaskDelay(pdMS_TO_TICKS(1000));
esp_restart();
}
return ESP_OK;
}
esp_err_t stream_handler(httpd_req_t *req) {
camera_fb_t * fb = NULL;
esp_err_t res = ESP_OK;
char hdr[64];
httpd_resp_set_type(req, "multipart/x-mixed-replace;boundary=frame");
while(res == ESP_OK) {
fb = esp_camera_fb_get();
if (fb) {
// ESP_LOGW(TAG, "Frame: %dx%d, Size: %u bytes", fb->width, fb->height, fb->len);
res = httpd_resp_send_chunk(req, "\r\n--frame\r\n", 11);
if(res == ESP_OK) {
int hlen = snprintf(hdr, 64, "Content-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n", fb->len);
res = httpd_resp_send_chunk(req, hdr, hlen);
}
if(res == ESP_OK) res = httpd_resp_send_chunk(req, (const char *)fb->buf, fb->len);
esp_camera_fb_return(fb);
} else {
ESP_LOGE(TAG, "Camera capture failed");
res = ESP_FAIL;
}
vTaskDelay(pdMS_TO_TICKS(250)); // 4 FPS
}
return res;
}
esp_err_t index_handler(httpd_req_t *req) {
const char* html = "<html><head><meta charset='utf-8'><style>"
"body{margin:0;background:#000;color:#fff;font-family:sans-serif;overflow:hidden;padding:0;}"
".container{display:flex; justify-content:center; align-items:flex-start; width:100vw; height:100vh;}"
".video-wrapper{position:relative; display:inline-block; max-width:100%; max-height:100%;}"
"img{display:block; max-width:100%; max-height:100%; object-fit:contain;}"
".osd{position:absolute; top:10px; right:10px; font-size:14px; font-weight:bold; text-align:right;"
"color:#fff; text-shadow:-1px -1px 0 #000,1px -1px 0 #000,-1px 1px 0 #000,1px 1px 0 #000; z-index:10; pointer-events:none;}"
"</style></head><body>"
"<div class='container'>"
"<div class='video-wrapper'>"
"<img id='stream_img' src=''>"
"<div class='osd'>CAM 02<br><span id='date'>--.--.----</span><br>"
"<span id='time'>--:--:--</span><br><span id='temp'>--</span>°C</div>"
"</div>"
"</div>"
"<script>document.getElementById('stream_img').src='http://'+window.location.hostname+':81/stream';"
"async function update(){try{"
"const[t,c,d]=await Promise.all([fetch('/temp'),fetch('/time'),fetch('/date')]);"
"if(t.ok)document.getElementById('temp').innerText=await t.text();"
"if(c.ok)document.getElementById('time').innerText=await c.text();"
"if(d.ok)document.getElementById('date').innerText=await d.text();"
"}catch(e){}setTimeout(update,2000);}update();</script></body></html>";
return httpd_resp_send(req, html, strlen(html));
}
static camera_config_t camera_config = {
.pin_pwdn = -1, .pin_reset = -1, .pin_xclk = 3,
.pin_sccb_sda = 48, .pin_sccb_scl = 47,
.pin_d7 = 18, .pin_d6 = 15, .pin_d5 = 38, .pin_d4 = 40,
.pin_d3 = 42, .pin_d2 = 46, .pin_d1 = 45, .pin_d0 = 41,
.pin_vsync = 1, .pin_href = 2, .pin_pclk = 39,
.xclk_freq_hz = 20000000, .ledc_timer = LEDC_TIMER_0, .ledc_channel = LEDC_CHANNEL_0,
.pixel_format = PIXFORMAT_JPEG,
.frame_size = FRAMESIZE_XGA,
.jpeg_quality = 3,
.fb_count = 2, .fb_location = CAMERA_FB_IN_PSRAM,
.grab_mode = CAMERA_GRAB_WHEN_EMPTY,
};
// --- [CORE 1 TASK] ---
void camera_server_task(void *pvParameters) {
esp_err_t err = esp_camera_init(&camera_config);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Camera init failed with error 0x%x", err);
vTaskDelete(NULL);
return;
}
sensor_t * s = esp_camera_sensor_get();
if (s) {
s->set_vflip(s, 1);
s->set_hmirror(s, 1);
s->set_reg(s, 0x3035, 0xff, 0xA1);
s->set_whitebal(s, 1);
s->set_gain_ctrl(s, 1);
s->set_aec_value(s, 1);
s->set_brightness(s, 0);
s->set_contrast(s, -1);
s->set_saturation(s, -1);
s->set_sharpness(s, 2);
s->set_ae_level(s, 0);
}
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
config.max_uri_handlers = 12;
// PORT 80
config.server_port = 80;
httpd_handle_t server_data = NULL;
if (httpd_start(&server_data, &config) == ESP_OK) {
httpd_uri_t uri_idx = { .uri = "/", .method = HTTP_GET, .handler = index_handler };
httpd_uri_t uri_dt = { .uri = "/date", .method = HTTP_GET, .handler = date_handler };
httpd_uri_t uri_ti = { .uri = "/time", .method = HTTP_GET, .handler = time_handler };
httpd_uri_t uri_tp = { .uri = "/temp", .method = HTTP_GET, .handler = temp_handler };
httpd_uri_t uri_ota = { .uri = "/update", .method = HTTP_POST, .handler = update_post_handler };
httpd_register_uri_handler(server_data, &uri_idx);
httpd_register_uri_handler(server_data, &uri_dt);
httpd_register_uri_handler(server_data, &uri_ti);
httpd_register_uri_handler(server_data, &uri_tp);
httpd_register_uri_handler(server_data, &uri_ota);
}
// PORT 81
config.server_port = 81;
config.ctrl_port = 32769;
httpd_handle_t server_stream = NULL;
if (httpd_start(&server_stream, &config) == ESP_OK) {
httpd_uri_t uri_str = { .uri = "/stream", .method = HTTP_GET, .handler = stream_handler };
httpd_register_uri_handler(server_stream, &uri_str);
}
ESP_LOGI(TAG, "Camera and Servers started on Core 1");
while (1) vTaskDelay(pdMS_TO_TICKS(1000));
}
static void event_handler(void* arg, esp_event_base_t base, int32_t id, void* data) {
if (base == ETH_EVENT) {
if (id == ETHERNET_EVENT_CONNECTED) {
ESP_LOGW(TAG, "Ethernet Connected. Disabling Wi-Fi...");
eth_connected = true;
esp_wifi_stop();
} else if (id == ETHERNET_EVENT_DISCONNECTED) {
ESP_LOGW(TAG, "Ethernet Disconnected. Enabling Wi-Fi...");
eth_connected = false;
esp_wifi_start();
}
} else if (base == WIFI_EVENT) {
if (id == WIFI_EVENT_STA_START || id == WIFI_EVENT_STA_DISCONNECTED) {
if (!eth_connected) {
ESP_LOGI(TAG, "Wi-Fi connecting...");
esp_wifi_connect();
}
}
} else if (base == IP_EVENT) {
if (id == IP_EVENT_STA_GOT_IP || id == IP_EVENT_ETH_GOT_IP) {
ESP_LOGI(TAG, "IP Received!");
esp_sntp_config_t sntp_cfg = ESP_NETIF_SNTP_DEFAULT_CONFIG("**Your Gate**");
esp_netif_sntp_init(&sntp_cfg);
}
}
}
void app_main(void) {
nvs_flash_init();
gpio_install_isr_service(0);
gpio_set_direction(PWR_CTRL, GPIO_MODE_OUTPUT);
gpio_set_level(PWR_CTRL, 0);
vTaskDelay(pdMS_TO_TICKS(500));
gpio_set_direction(ETH_RST, GPIO_MODE_OUTPUT);
gpio_set_level(ETH_RST, 0);
vTaskDelay(pdMS_TO_TICKS(100));
gpio_set_level(ETH_RST, 1);
vTaskDelay(pdMS_TO_TICKS(500));
esp_netif_init();
esp_event_loop_create_default();
esp_netif_config_t eth_cfg = ESP_NETIF_DEFAULT_ETH();
esp_netif_t *eth_netif = esp_netif_new(ð_cfg);
spi_bus_config_t buscfg = { .miso_io_num = 12, .mosi_io_num = 11, .sclk_io_num = 13, .quadwp_io_num = -1, .quadhd_io_num = -1 };
spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO);
spi_device_interface_config_t devcfg = { .command_bits = 16, .address_bits = 8, .mode = 0, .clock_speed_hz = 25000000, .spics_io_num = ETH_CS, .queue_size = 30 };
eth_w5500_config_t w5500_cfg = ETH_W5500_DEFAULT_CONFIG(SPI2_HOST, &devcfg);
w5500_cfg.int_gpio_num = ETH_INT;
esp_eth_mac_t *mac = esp_eth_mac_new_w5500(&w5500_cfg, &(eth_mac_config_t)ETH_MAC_DEFAULT_CONFIG());
esp_eth_phy_t *phy = esp_eth_phy_new_w5500(&(eth_phy_config_t)ETH_PHY_DEFAULT_CONFIG());
esp_eth_handle_t eth_handle = NULL;
esp_eth_driver_install(&(esp_eth_config_t)ETH_DEFAULT_CONFIG(mac, phy), ð_handle);
uint8_t eth_mac_addr[6] = {0xE8, 0xF6, 0x0A, 0x84, 0x9D, 0xD5};
mac->set_addr(mac, eth_mac_addr);
esp_netif_attach(eth_netif, esp_eth_new_netif_glue(eth_handle));
esp_netif_create_default_wifi_sta();
wifi_init_config_t w_cfg = WIFI_INIT_CONFIG_DEFAULT();
esp_wifi_init(&w_cfg);
esp_wifi_set_mode(WIFI_MODE_STA);
uint8_t wifi_mac_addr[6] = {0xE8, 0xF6, 0x0A, 0x84, 0x9D, 0xD4};
esp_wifi_set_mac(WIFI_IF_STA, wifi_mac_addr);
wifi_config_t wifi_config = { .sta = { .ssid = WIFI_SSID, .password = WIFI_PASS } };
esp_wifi_set_config(WIFI_IF_STA, &wifi_config);
esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL);
esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL);
esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL);
esp_eth_start(eth_handle);
esp_wifi_start();
setenv("TZ", "*************", 1);
tzset();
vTaskDelay(pdMS_TO_TICKS(2000));
xTaskCreatePinnedToCore(camera_server_task, "cam_task", 8192, NULL, 5, NULL, 1);
while (1) vTaskDelay(pdMS_TO_TICKS(1000));
}
After moving I needed to rebuild my HA setup for the new place. Unfortently after reinstalling ESP Home and the script from @bernhardt the camera no longer works. All I get is this: [W][esp32_camera:211]: Got invalid frame from camera!
Does anyone know what I am doing wrong?
first of all, THANK YOU! just got my board, and ethernet is already working (camera is on the way, prob it is gonna work too).
do you guys know any available GPIO i could hook a normally open push button? i’d like to use as a doorbel
EDIT1: ok, GPIO16 looks good