This project provides firmware for an ESP32-based relay boards which you can find in great number at Aliexpress, Amazon or Botland. Alternatively, you can use this firmware if you want to contol any kind of relay's (typically those from SRD-**VDC-SL-C family) or just any digital electronics component that is controlled by HIGH/LOW logical levels.
The device supports two types of units:
- Relay: The board controls an actuator which is controlled by HIGH/LOW logical states
- Dry Contact Sensor: The board monitors contact state between selected GPIO(s) and GND, providing information either contact is closed or open
- Purpose
- Features
- Prerequisites: What you need to start
- Tested Relay Boards / Configurations
- Wiring Sample
- Getting started: Building and Flashing
- Updating the firmware
- Initiation: Making device to work
- WEB Setup
- Units (Relays) Configuration
- Testing the Setup
- Home Assistant Integration
- OTA Firmware Update
- WEB API
- Known issues, problems and TODOs
- License and Credits
- Open Source: You see what flash into your device and thus you know what it does from a security standpoint. And what it doesn't.
- WiFi Connectivity: Supports connecting to WiFi for remote monitoring and control.
- Soft Configuration: GPIO pins that relays are connected to and other settings are defined via user interface and are stored in NVS (EEPROM).
- Relay State Memory: Relay (actuators) states are written to NVS (EEPROM) memory and will be restored after power loss.
- MQTT Support: Publishes relay and contact sensors states via MQTT for further integration with various platforms.
- Home Assistant Integration: Automatically detects and configures devices in Home Assistant using auto-discovery via MQTT.
- Web Interface: Provides a web-based user interface for configuration, control and monitoring.
- Web API: Simple JSON API is in place should you want to integrate the device into your custom infractucture projects.
- OTA (over the air) Firmware Update: Trigger firmware update via WEB interface from a provided URL.
- Remote/Network Logging: The device supports remote logging using the Syslog protocol (RFC 3164 / RFC 5424) over UDP or TCP.
- MemGuard: Automatically reboot device if it runs out of free memory to prevent device stall.
To get started, you will need:
- Hardware:
- ESP32, ESP32-C6 or ESP32-S3 microcontroller (all have been tested) with at least 4Mb flash.
- Relay. E.g., SRD-05VDC-SL-C-based mechanical relay module.
- OR ESP32-based relay board with minimum 4Mb flash in a single-board composition. Most of such boards use
ESP32variant of ESP32.
- Software:
- ESP-IDF framework (version 5.5 or higher recommended installed and configured).
- Operating system:
Linux(tested, recommended),macOS(tested, recommended),Windows(tested)
- LilyGO 4 Ch T-Relay
- Assign pins to the relay channels according to LilyGO specs:
{21, 19, 18, 05} - Use
ESP32as target device.
- Assign pins to the relay channels according to LilyGO specs:
- ESP32_Relay x2 from AliExpress
- Use pins: 16 (channel 0) and 17 (channel 1). No inversion.
- Use
ESP32as target device.
- ESP32-S3-DevKitM-1 + 2Ch Relay Board
- Wire as shown in Wiring section below
- Use
ESP32-S3as target device.
- ElectroDragon Wifi IoT SPDT Relay Board V2
- Assign pins to the relay channels according to ElectroDragon specs:
{6, 7} - You may use GPIO05 as sensor (located on the board)
- Use
ESP32-C3as target device.
- Assign pins to the relay channels according to ElectroDragon specs:
No need to do any wiring if you're using fully built ESP32-based relay board. It's just already done so you can skip this block.
However, if you're using a custom built combination of ESP32 board and the relay module, then you can wire them as following (example for 2 relay module):
+------------+-----------+
| Module Pin | ESP32 Pin |
+------------+-----------+
| GND | GND |
+------------+-----------+
| VCC | +5V |
+------------+-----------+
| IN1 | IO04 |
+------------+-----------+
| IN2 | IO05 |
+------------+-----------+
This is just a default (sample) wiring and do not worry if you'd like to use other pins: you can configure them via WEB interface
Note
But remember one thing: not every pin from ESP32 board/module can be used to control relays in this project, because some of those are reserved for system purposes and can make the device very unstable. Make sure that pins you'd like to use are in this list:
{4, 5, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 39}
- Setup the ESP-IDF Environment:
- Follow the official ESP-IDF setup guide to configure your development environment. The installation approach will be different depending on the OS you use.
- Further on we will assume, that you're using Unix-based OS (Linux, macOS) and you've installed ESP-IDF into
$HOME/esp/esp-idffolder. Note, that the project has been tested to be built underWindows, but the setup process and the commands might differ slightly.
- Clone this Repository:
git clone https://github.com/rpavlyuk/ESPRelayBoard cd ESPRelayBoard - Initiate ESP-IDF:
This will map the source folder to ESP-IDF framework and will enable all needed global variables (macOS, Linux).
OR For Windows, start
. $HOME/esp/esp-idf/export.sh
ESP IDF vX.X Powershellfrom Programs menu and change directory to project's root. E.g.:cd D:\Work\esp\projects\ESPRelayBoard - Prepare the configuration:
The project already contains some pre-built SDK configuration files (
sdkconfig) depending on the board type. You will find them in the project's root assdkconfig.*. Just copy the one which corresponds to you board, for example:See more details on how to play withcp -a sdkconfig.esp32-s3 sdkconfig
sdkconfigin the section Flash the Firmware below. - Build the firmware:
You will get
idf.py build
idf.py: command not foundif you didn't initiate ESP-IDF in the previous step. You may also get build errors if you've selected other board type thenESP32-C6,ESP32orESP32-S3. - Determine the port:
A connected ESP32 device is opening USB-2-Serial interface, which your system might see as
/dev/tty.usbmodem1101(macos example) or/dev/ttyUSB0(Linux example). Usels -al /devcommand to see the exact one. Also, please, refer this article to get much more comprehensive help and instructions on how to determine serial port depending on your OS. - (recommended for the initial setup of fresh ESP32 module) Erase the Flash
Remove all data from the ESP32 board (incl. WiFi connection settings and NVS storage).
Replace
idf.py -p /dev/ttyUSB0 erase-flash/dev/ttyUSB0with the appropriate port for your system. - Flash the Firmware:
You will get the device console log as the result of the command.
idf.py -p /dev/ttyUSB0 build flash monitor
Note
Every time you change the board type (e.g., from ESP32-C6 to ESP32-S3, the framework is re-creating sdkconfig file which makes some very important settings gone. Thus, you (might) need to change/set some settings if you've changed the device board:
- Open SDK settings menu by running:
idf.py menuconfig
- Set the following parameters:
- Partition table -> Custom partition table CSV as the option
- Partition table -> Custom partition CSV file set to
partition_table/partition.csv - Serial flasher config -> Flash size to 4Mb or 8Mb (depends on the board you use)
- Serial flasher config -> Detect flash size when flashing bootloader to enabled
- Component config -> HTTP Server -> Max HTTP Request Header Length to
8192 - Component config โ ESP System Settings -> Event loop task stack size to
4096 - Component config โ ESP System Settings -> Main task stack size to
4096 - Component config โ ESP System Settings -> Minimal allowed size for shared stack to
2048
- Alternatively, you can use some of the existing configurations that I've prepared for some of most popular ESP32 target devices. Those are
sdkconfig.<device>files in project root. You simply copy them assdkconfigand that will load all needed settings, no need to go tomenuconfigas described in the section above. For example, to enableESP32-S3as device, you can:
cp -a sdkconfig.esp32-s3 sdkconfigNote
This will overwrite any settings you've made via idf.py menuconfig
- You will need to do a full re-build after you either make changes via
menuconfigor by copyingsdkconfig.*file:
idf.py fullclean buildIf you already have ESP32 device flashed with previous version of ESPRelayBoard and you want to update it to the latest one -- just follow those simple steps (assuming that ESP-IDF is already configured and initiated as stated in section above).
- Pull the latest version from Github:
git pull
- Cleanup the code (recommended):
idf.py fullclean
- Flash the new version (assuming your device is accessible as
/dev/ttyUSB0):
idf.py -p /dev/ttyUSB0 build flash monitor
Your settings will be preserved after the update. However, there's a possibility that those settings will become incompatible due to the significant changes in the code. In that case (if you see that device is struggling to boot) you should do idf.py erase-flash as described in the section above.
It is recommeded however to use OTA Firmware Update after version 1.4 and higher.
Right upon initial flashing (or after flash was erased) the device will boot in Wi-Fi setup mode.
- On the first boot (or after flash erase), the WiFi will start in access point mode with the SSID
PROV_AP_XXXXXX. The exact named will be different depending on the device hardware ID (which is built in). The password is SSID name plus1234. For example,PROV_AP_XXXXXX1234 - Use the ESP SoftAP Prov (iOS, Android) mobile app to connect to the device and configure WiFi settings. Read more here if you want to know more about the SoftAP provisioning.
- Device will initiate itself with default settings once the WiFi was provisioned. All further configuration, including relays / units management, will/can be made via WEB interface.
Note
Currently only DHCP mode is supported.
- Device starts the WEB interfance available as
http://<WIFI-IP>/. The device implements no authorization (yet) so be sure to use it in safe environment. - You can start controlling the relays by visiting Status page (assuming you connected them to default pins).
- Basic configuration parameters on Configuration page:
MQTT Mode: defines how MQTT worksDisabled: MQTT is disabledConnect Once: MQTT is enabled. Connection will be established, but no reconnect attempts will be made once it is dropped.Auto-Connect: MQTT is enabled. Connection will operate in keep-alive mode, reconnecting on failures.
MQTT Server,MQTT Port,MQTT Protocol,MQTT User,MQTT Password: MQTT connection string parameters.MQTT Prefix: top level path in the MQTT tree. The path will look like:<MQTT_prefix>/<device_id>/...HomeAssistant Device integration MQTT Prefix: HomeAssistant MQTT device auto-discovery prefix. Usually, it is set tohomeassistantHomeAssistant Device update interval (ms): how often to update device definitions at HomeAssistant.
- System Update:
OTA Update URL: A URL pointing to.binfile with the firmware which you want to update the system to. See section OTA Firmware Update below for details.
- Relay Parameters:
Channels count (actuators): number of relays (actuators) you'd like to control (or your board has)Contact sensors count: number of contact sensors you'd like to activate and monitorRefresh interval (ms): relay/sensor reading update interval. Used in WEB interface.
- Network logging parameters:
Logging Type: what logging mechaism to use.Disabled: Default setting. No network logging is enabled.UDP,TCP: The device supports remote logging using the Syslog protocol (RFC 3164 / RFC 5424) over UDP or TCP (recommended). You may capture the logs using RSyslog or other compatible tools like Graylog. NOTE: SSL/TLS encryption is not supported, so ensure safe environment when sending logs outside the secure perimeter.MQTT: Logging to a specified MQTT topic. Currently not implemented
Logging Host: IP or hostname to send logs to. Must be within the same LAN when using UDP broadcast address.Logging Port: UDP/TCP port on the target host.Keep Stdout Logging: keep sending messages to STDOUT (console) even when network logging is enabled.
- Memguard parameters:
Memory Guard Mode: Either disable memguard at all OR set corresponding system reaction: warning only or full reboot.Memory Guard Threshold (bytes): Free heap memory in bytes which is considered as too low, so the action must be taken. Default is 65535 (64k). Setting the threashhold too high (90k+) may put the device in endless reboot loop. However, there's a prevention machanism:memguardactions are only taken 3 minutes since boot, so you have time to change the threashhold or disablememguardat all.
CA / Root Certificates: PEM formatted SSL root/intermediate certificate. Used by MQTTS connection and HTTPS firmware OTA update. You can separately configure the certificate for MQTT and for OTA update via HTTPS.
Changing number of sensors or relays requires a restart of the device. Use button Reboot Device at the bottom of configuration page.
All units (relays and sensors) can be configured on Relays page.
All units have the following properties:
Channel(read-only): An id of the control channel the relay or sensor is associated to. Relays and sensors have separate channel group.GPIO Pin: GPIO pin that is controlling the relay (for actuator) or for which the contact state is monitored (sensor).Inverted: Inverting the state of control or sensing signals correspondngly.Enabled(reserved, currently not in use and is ignored): Unit is enabled.
Setting values are being saved by Update button for each corresponding unit. Pin number be within the so called safe list and should not overlap with other pins already in use.
- Connect the module to corresponding GPIO pin (if using an external module) or configure the
GPIO Pinfor corresping relay channel. - Use page Status to change the relay state, observe the relay changing its state
- See console log output if any issues occur.
- Add at least one contact sensor on Configuration page and reboot the device
- Note a
GPIO Pinfor the contact sensor you'd like to test OR change it to the one you plan to use - Connect
GNDto the mentioned above pin - Observe the change of the state on Status page for corresponding contact sensor
- See console log output if any issues occur
Consider using state inversion (Inverted parameter) for both sensors and relay if needed.
The device will automatically enable itself in Home Assistant if:
- both HA and the device are connected to the same MQTT server
HomeAssistant Device integration MQTT Prefixis set correctly- auto-discovery is enable in Home Assistant (should be enabled by default)
You will see device shown as <device_id> (e.g., DAF3124C798E by ESP Relay Board) in the device list as soon as HA picks the auto-discovery records up.
The device allows updating the firmware from a provided URL pointing to the firmware image. The file is generate once you run a successful build using idf.py build command and is placed in ./build/ folder as ESPRelayBoard.bin.
You can either point OTA Update URL to one of the .bin files placed in Releases section at Github or you may have your own update location. Typically, the update flow in this case will look like this:
- Run a build:
idf.py build
- Place the file
./build/ESPRelayBoard.binto a WEB server that is accessible by the device. If you use HTTPS then make sure you've set correctCA / Root CertificateatHTTPStab. - (optional) Place file
./build/storage.binin the same WEB server directory as the firmware one. This will update SPIFFS where templates and other files are stored. - Adjust / provide
OTA Update URLif needed. - Click
Update Devicebutton on WEB UI. Device will update itself (takes 2-5 mins depending on the connection speed) and reboot with new hardware. - The device will revert to the previous (running) firmware if the OTA process crashes or is not complete successfully.
Note
OTA firmware update is generally quite robust. If you flash your device with incompatible firmware or use wrong URL and thus incidentally "brick" it, no problem -- just re-flash it using USB dongle as was described in section Building and Flashing above. You might need to erase-flash and reconfigure the device again.
The device is exposing a simple JSON API to control relays and get units information..
- Get all units (relays and sensors):
- Endpoint:
/api/relays - Method: GET
- Request payload (example): N/A
- Response payload (example):
{
"data": [{
"relay_key": "relay_ch_0",
"channel": 0,
"state": false,
"inverted": true,
"gpio_pin": 4,
"enabled": true,
"type": 0
}, {
"relay_key": "relay_ch_1",
"channel": 1,
"state": false,
"inverted": true,
"gpio_pin": 5,
"enabled": true,
"type": 0
}, {
"relay_key": "relay_sn_0",
"channel": 0,
"state": false,
"inverted": true,
"gpio_pin": 12,
"enabled": true,
"type": 1
}, {
"relay_key": "relay_sn_1",
"channel": 1,
"state": true,
"inverted": true,
"gpio_pin": 13,
"enabled": true,
"type": 1
}],
"status": {
"count": 4,
"code": 0,
"text": "ok"
}
}
- Update unit information:
- Endpoint:
/api/relay/update - Method: POST
- Request payload (example):
{"data":{"device_serial":"0O0RSJ3Q2XF03F8U2Z4CVLWUAFOOQ0TO","relay_key":"relay_ch_0","relay_channel":0,"relay_gpio_pin":4,"relay_enabled":true,"relay_inverted":true}}
Required parameters: device_serial, relay_key
- Response payload (example):
{
"data": {
"relay_key": "relay_ch_0",
"channel": 0,
"state": false,
"inverted": true,
"gpio_pin": 4,
"enabled": true,
"type": 0
},
"status": {
"error": "OK",
"code": 0
}
}
- Get device status:
- Endpoint:
/api/status - Method: GET
- Request payload (example): N/A
- Response payload (example):
{
"status": {
"free_heap": 206328,
"min_free_heap": 144852,
"time_since_boot": 6127315474
}
}
- Get device settings (all):
- Endpoint:
/api/setting/get/all - Method: GET
- Request params (GET):
device_iddevice_serial
- Request payload (example): N/A
- Response payload (example):
{
"data": {
"ota_update_url": {
"type": 2,
"max_size": 256,
"value": "http://localhost:8080/ota/relayboard.bin",
"size": 41
},
"ha_upd_intervl": {
"type": 0,
"max_size": 4,
"value": 60000,
"size": 4
},
"mqtt_connect": {
"type": 1,
"max_size": 2,
"value": 2,
"size": 2
},
"mqtt_server": {
"type": 2,
"max_size": 128,
"value": "192.168.1.2",
"size": 12
},
"mqtt_port": {
"type": 1,
"max_size": 2,
"value": 1883,
"size": 2
},
"mqtt_protocol": {
"type": 2,
"max_size": 10,
"value": "mqtt",
"size": 5
},
"mqtt_user": {
"type": 2,
"max_size": 64,
"value": "",
"size": 1
},
"mqtt_password": {
"type": 2,
"max_size": 64,
"value": "",
"size": 1
},
"mqtt_prefix": {
"type": 2,
"max_size": 128,
"value": "relay_board",
"size": 12
},
"ha_prefix": {
"type": 2,
"max_size": 128,
"value": "homeassistant",
"size": 14
},
"relay_refr_int": {
"type": 1,
"max_size": 2,
"value": 1000,
"size": 2
},
"relay_ch_count": {
"type": 1,
"max_size": 2,
"value": 2,
"size": 2
},
"relay_sn_count": {
"type": 1,
"max_size": 2,
"value": 1,
"size": 2
},
"net_log_type": {
"type": 1,
"max_size": 2,
"value": 2,
"size": 2
},
"net_log_host": {
"type": 2,
"max_size": 256,
"value": "127.0.0.1",
"size": 12
},
"net_log_port": {
"type": 1,
"max_size": 2,
"value": 5114,
"size": 2
},
"net_log_stdout": {
"type": 1,
"max_size": 2,
"value": 1,
"size": 2
}
},
"total": 17
}
- Get device settings (selected one):
- Endpoint:
/api/setting/get - Method: GET
- Request params (GET):
device_iddevice_serialkey: Setting key (seemain/settings.hfor keys reference)
- Request payload (example): N/A
- Response payload (example):
{
"mqtt_server": {
"type": 2,
"max_size": 128,
"value": "192.168.1.5",
"size": 12
}
}
- Set device setting(s):
- Endpoint:
/api/setting/update - Method: POST
- Request payload (example):
{
"device_id": "9XXE6E0MMC5C",
"device_serial": "VU7303USWVEP6ENQ3POTTFHVV7JH97QX",
"data": {
"mqtt_server": "192.168.1.5",
"mqtt_port": 1883
},
"action": 0
}
- Response payload (example):
{
"status": {
"success": 2,
"failed": 0,
"total": 2
},
"details": {
"mqtt_server": {
"old_value": "192.168.1.2",
"new_value": "192.168.1.5",
"status": 0,
"error_msg": "Updated setting 'mqtt_server' (was 192.168.1.2)"
},
"mqtt_port": {
"old_value": "8883",
"new_value": "1883",
"status": 0,
"error_msg": "Updated setting 'mqtt_port' (was 8883)"
}
}
}
- Forcing device reboot via API:
- Endpoint:
/api/setting/update - Method: POST
- Request payload (example):
{
"device_id": "9XXE6E0MMC5C",
"device_serial": "VU7303USWVEP6ENQ3POTTFHVV7JH97QX",
"data": {
},
"action": 2
}
- Response payload (example):
{
"status": {
"success": 0,
"failed": 0,
"total": 0
},
"details": {}
}
- Static IP support needed
- Device may have memory leaks when used very intensively (to be improved)
- GPLv3 -- you're free to use and modify the code or its parts, and thus make your over better ESP32 relay board :)
- Uses:
- ESP32_NVS library by VPavlusha๐บ๐ฆ
- esp-idf-net-logging library by nopnop2002๐ฏ๐ต
- Consider putting a star if you like the project
- Find me at roman.pavlyuk@gmail.com