If it is any consolation, I just ordered a board and I am having the exact same issue as you are ![]()
You need to hold the IO0 button which is normally labeled as BOOT on other devices and then press the EN button (normally labeled as RESET). This will then cause it to reboot into a mode that lets you flash it.
There is a bridge between IO0 and Ground.
I am holding the RESET button and releasing it after I click install.
That is (unfortunately) a consolation!
However⌠I wished I read this before I ordered a new board. It just arrived and I still have the same problem. If I read your message before, I would probably have gone with the ESP32 + Relayboard routeâŚ
This project has given me many grey hairs
Care to share your modified version?
I had the same challenges.
What resolved it for me was to check if the drivers of the FTDI were installed. I looked into the device manager.
Then I downloaded from the esphome web builder. I choose install and then manual download.
Lastly I flashed through web.esphome.io
I followed all the steps above in this thread to put it to flash.
Maybe it helps you too.
My FTDI adapter works with other boards. I canât successfully upload a sketch to the board via Arduino IDE either. I contacted the seller and they sent through a manual in Chinese that translates to:
Use a jumper cap to connect IO0 and GND, and prepare a TTL serial module (such as FT232). Connect to the computerâs USB port.
2. Connect the serial port module and the development board as follows:
⢠GND â GND
⢠TX â RX
⢠RX â TX
⢠5V â 5V
3. In Arduino IDE, select the board as ESPino (ESP-12 module).
4. Open the desired program, select the correct COM port, then click âUploadâ â the program will compile and download to the board automatically.
5. After upload, disconnect IO0 from GND and either re-power the board or press the reset button to start the uploaded program.
I have tried this but it doesnât work. Maybe a bad batch of boards were produced or some change?
I have had success migrating my Rainbird 24vac sprinkler system over to a ESP32 based board that I bought from Amazon.
For scheduling, Iâm using Home Assistant to crawl my local agriculture extensionâs weather data and then deciding when to run the sprinklers using Evapotranspiration (basically the soil is a water battery that discharges due to the sun and gets recharged by the sprinklers or rain)
I bought this AC power adapter for my Rainbird sprinklers. I just ordered a LM2596HV AC-DC buck converter to remove the extra 5v power charger.
sounds like my experience with the lights cycling after the reset button was released. In my case one the gnd pin wasnât connected to the board so the IO0 pin wasnât actually grounded. I quickly used a jumper wire to connect to gnd on the power supply and it worked on my first attempt after that.
I actually only needed to modify 2 sections of the code - (1) sprinker and (2) switch, to only provide 4 and to match the GPIO with how Iâd connected the relay. See below. Everything else is the same.
sprinkler:
- id: ${id_prefix}_sprinklers
main_switch: "Sprinkler Cycle Active"
auto_advance_switch: "Auto Advance"
valve_overlap: 5s
valves:
- valve_switch: "Zone 1 Active"
enable_switch: "Zone 1 Auto"
run_duration_number:
id: zone_1_run_duration
name: "Zone 1 Run Duration"
icon: "mdi:timer-outline"
initial_value: 1
unit_of_measurement: min
valve_switch_id: ${id_prefix}_valve_sw1
- valve_switch: "Zone 2 Active"
enable_switch: "Zone 2 Auto"
run_duration_number:
id: zone_2_run_duration
name: "Zone 2 Run Duration"
icon: "mdi:timer-outline"
initial_value: 1
unit_of_measurement: min
valve_switch_id: ${id_prefix}_valve_sw2
- valve_switch: "Zone 3 Active"
enable_switch: "Zone 3 Auto"
run_duration_number:
id: zone_3_run_duration
name: "Zone 3 Run Duration"
icon: "mdi:timer-outline"
initial_value: 1
unit_of_measurement: min
valve_switch_id: ${id_prefix}_valve_sw3
- valve_switch: "Zone 4 Active"
enable_switch: "Zone 4 Auto"
run_duration_number:
id: zone_4_run_duration
name: "Zone 4 Run Duration"
icon: "mdi:timer-outline"
initial_value: 1
unit_of_measurement: min
valve_switch_id: ${id_prefix}_valve_sw4
switch:
# relays
- platform: gpio
id: ${id_prefix}_valve_sw1
pin: GPIO33
restore_mode: ALWAYS_OFF
- platform: gpio
id: ${id_prefix}_valve_sw2
pin: GPIO25
restore_mode: ALWAYS_OFF
- platform: gpio
id: ${id_prefix}_valve_sw3
pin: GPIO26
restore_mode: ALWAYS_OFF
- platform: gpio
id: ${id_prefix}_valve_sw4
pin: GPIO27
restore_mode: ALWAYS_OFF
...
Iâve been playing about with the sprinkler for a while now, Iâve just added a 2.42â OLED screen
I added the same screen and an rotary encoder, itâs functional but not as pretty as yours.
Hope you share when ready so I can pilfer your work!
Pilfer away. Ill probably be making some more changes but this is how it is now
esphome:
name: 1-relay-basket-irrigation
esp32:
board: esp32dev
framework:
type: arduino
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
logger:
logs:
switch: NONE
binary_sensor: NONE
api:
reboot_timeout: 0s
ota:
- platform: esphome
# -------------------------
# I2C BUS for OLED
# -------------------------
i2c:
sda: GPIO13
scl: GPIO14
scan: true
id: bus_a
# -------------------------
# FONTS
# -------------------------
font:
- file: "fonts/arial.ttf"
id: font_small
size: 10
- file: "fonts/arial.ttf"
id: font_large
size: 20
- file: "fonts/materialdesignicons-webfont.ttf"
id: mdi_font
size: 28
glyphs: [
"\U000F1068", # mdi:valve-open
"\U000F1067" # mdi:valve (closed)
# "\U000F05A9" # Wi-Fi icon
]
- file: "fonts/materialdesignicons-webfont.ttf"
id: mdi_font_small
size: 14
glyphs: [
"\U000F05A9" # mdi:wifi
]
# -------------------------
# DISPLAY CONFIGURATION
# -------------------------
display:
- platform: ssd1306_i2c
model: "SSD1306 128x64"
address: 0x3C
id: oled_display
rotation: 0
lambda: |-
// Draw Wi-Fi signal strength (bars)
int wifi = (int)id(wifi_signal_db).state;
int bars = 0;
if (wifi > -50) bars = 4;
else if (wifi > -60) bars = 3;
else if (wifi > -70) bars = 2;
else if (wifi > -80) bars = 1;
else bars = 0;
// Draw WiFi bars (top right)
for (int i = 0; i < 4; i++) {
if (i < bars)
it.filled_rectangle(116 + (i * 3), 2 + (3 - i) * 2, 2, (i + 1) * 2);
else
it.rectangle(116 + (i * 3), 2 + (3 - i) * 2, 2, (i + 1) * 2);
}
// Label WiFi strength
it.printf(0, 0, id(mdi_font_small), "\U000F05A9"); // Wi-Fi icon
it.printf(20, 0, id(font_small), "%d%%", (int)min(max(2 * (wifi + 100.0), 0.0), 100.0));
// Show which valve is ON
int y = 24;
bool any_on = false;
if (id(valve_sw1).state) {
it.printf(0, y, id(mdi_font), "\U000F1068"); // mdi:valve-open
it.printf(34, y + 2, id(font_large), "Valve 1");
any_on = true;
} else if (id(valve_sw2).state) {
it.printf(0, y, id(mdi_font), "\U000F1068");
it.printf(34, y + 2, id(font_large), "Valve 2");
any_on = true;
} else if (id(valve_sw3).state) {
it.printf(0, y, id(mdi_font), "\U000F1068");
it.printf(34, y + 2, id(font_large), "Valve 3");
any_on = true;
} else if (id(valve_sw4).state) {
it.printf(0, y, id(mdi_font), "\U000F1068");
it.printf(34, y + 2, id(font_large), "Valve 4");
any_on = true;
} else if (id(valve_sw5).state) {
it.printf(0, y, id(mdi_font), "\U000F1068");
it.printf(34, y + 2, id(font_large), "Valve 5");
any_on = true;
}
if (!any_on) {
it.printf(0, y, id(mdi_font), "\U000F1067"); // mdi:valve (closed)
it.printf(34, y + 2, id(font_large), "All Closed");
}
# -------------------------
# SPRINKLER CONTROL
# -------------------------
sprinkler:
- id: basket_irrigation
main_switch: "Pump"
auto_advance_switch: "Lawn Sprinklers Auto Advance"
valve_overlap: 2s
valves:
- valve_switch: "Valve 1"
enable_switch: "Enable Valve 1"
pump_switch_id: pump_sw
run_duration_number:
id: valve_1_run_duration
name: "Valve 1 Run Duration"
icon: "mdi:timer-outline"
initial_value: 1
unit_of_measurement: min
min_value: 1
max_value: 60
valve_switch_id: valve_sw1
- valve_switch: "Valve 2"
enable_switch: "Enable Valve 2"
pump_switch_id: pump_sw
run_duration_number:
id: valve_2_run_duration
name: "Valve 2 Run Duration"
icon: "mdi:timer-outline"
initial_value: 1
unit_of_measurement: min
min_value: 1
max_value: 60
valve_switch_id: valve_sw2
- valve_switch: "Valve 3"
enable_switch: "Enable Valve 3"
pump_switch_id: pump_sw
run_duration_number:
id: valve_3_run_duration
name: "Valve 3 Run Duration"
icon: "mdi:timer-outline"
initial_value: 1
unit_of_measurement: min
min_value: 1
max_value: 60
valve_switch_id: valve_sw3
- valve_switch: "Valve 4"
enable_switch: "Enable Valve 4"
pump_switch_id: pump_sw
run_duration_number:
id: valve_4_run_duration
name: "Valve 4 Run Duration"
icon: "mdi:timer-outline"
initial_value: 1
unit_of_measurement: min
min_value: 1
max_value: 60
valve_switch_id: valve_sw4
- valve_switch: "Valve 5"
enable_switch: "Enable Valve 5"
pump_switch_id: pump_sw
run_duration_number:
id: valve_5_run_duration
name: "Valve 5 Run Duration"
icon: "mdi:timer-outline"
initial_value: 1
unit_of_measurement: min
min_value: 1
max_value: 60
valve_switch_id: valve_sw5
# -------------------------
# SWITCHES
# -------------------------
switch:
- platform: gpio
id: pump_sw
pin: GPIO16
- platform: gpio
id: pump_sw1
pin: GPIO17
- platform: gpio
id: valve_sw1
pin: GPIO25
- platform: gpio
id: valve_sw2
pin: GPIO26
- platform: gpio
id: valve_sw3
pin: GPIO32
- platform: gpio
id: valve_sw4
pin: GPIO33
- platform: gpio
id: valve_sw5
pin: GPIO27
# -------------------------
# SENSORS
# -------------------------
sensor:
- platform: wifi_signal
name: "WiFi Signal dB"
id: wifi_signal_db
update_interval: 60s
entity_category: "diagnostic"
- platform: copy
source_id: wifi_signal_db
name: "WiFi Signal Percent"
filters:
- lambda: return min(max(2 * (x + 100.0), 0.0), 100.0);
unit_of_measurement: "%"
entity_category: "diagnostic"
- platform: uptime
name: Uptime
entity_category: "diagnostic"
# -------------------------
# STATUS BINARY SENSOR
# -------------------------
binary_sensor:
- platform: status
name: "Connection Status"
# GPIO16 Pump
# GPIO25 1
# GPIO26 2
# GPIO32 3
# GPIO33 4
# GPIO27 5
# OLED
# GPIO13 SDA
# GPIO14 SCL
What I wanted to add was a countdown timer but i donât know how to do that. I did try but it didnât work. The set time did display but didnât count down, something like this would be great but anything would do at the moment
lambda: |-
// Draw Wi-Fi signal strength (bars)
int wifi = (int)id(wifi_signal_db).state;
int bars = 0;
if (wifi > -50) bars = 4;
else if (wifi > -60) bars = 3;
else if (wifi > -70) bars = 2;
else if (wifi > -80) bars = 1;
else bars = 0;
for (int i = 0; i < 4; i++) {
if (i < bars)
it.filled_rectangle(116 + (i * 3), 2 + (3 - i) * 2, 2, (i + 1) * 2);
else
it.rectangle(116 + (i * 3), 2 + (3 - i) * 2, 2, (i + 1) * 2);
}
it.printf(0, 0, id(mdi_font_small), "\U000F05A9"); // Wi-Fi icon
it.printf(20, 0, id(font_small), "%d%%", (int)min(max(2 * (wifi + 100.0), 0.0), 100.0));
// Show which valve is ON
int y = 24;
bool any_on = false;
float remaining = 0.0;
const char* valve_name = "";
if (id(valve_sw1).state) {
it.printf(0, y, id(mdi_font), "\U000F1068");
it.printf(34, y + 2, id(font_large), "Valve 1");
any_on = true;
remaining = id(valve_1_run_duration).state;
valve_name = "Valve 1";
} else if (id(valve_sw2).state) {
it.printf(0, y, id(mdi_font), "\U000F1068");
it.printf(34, y + 2, id(font_large), "Valve 2");
any_on = true;
remaining = id(valve_2_run_duration).state;
valve_name = "Valve 2";
} else if (id(valve_sw3).state) {
it.printf(0, y, id(mdi_font), "\U000F1068");
it.printf(34, y + 2, id(font_large), "Valve 3");
any_on = true;
remaining = id(valve_3_run_duration).state;
valve_name = "Valve 3";
} else if (id(valve_sw4).state) {
it.printf(0, y, id(mdi_font), "\U000F1068");
it.printf(34, y + 2, id(font_large), "Valve 4");
any_on = true;
remaining = id(valve_4_run_duration).state;
valve_name = "Valve 4";
} else if (id(valve_sw5).state) {
it.printf(0, y, id(mdi_font), "\U000F1068");
it.printf(34, y + 2, id(font_large), "Valve 5");
any_on = true;
remaining = id(valve_5_run_duration).state;
valve_name = "Valve 5";
}
if (!any_on) {
it.printf(0, y, id(mdi_font), "\U000F1067");
it.printf(34, y + 2, id(font_large), "All Closed");
} else {
// Show remaining run duration at the bottom
it.printf(0, 52, id(font_small), "%s remaining: %.0f min", valve_name, remaining);
}
With a lot of messing and stuff, The countdown timer is now working on the screen. There is probably unused code in there that Ive missed
esphome:
name: 1-relay-basket-irrigation
esp32:
board: esp32dev
framework:
type: arduino
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# -------------------------
# GLOBAL VARIABLES
# -------------------------
globals:
- id: remaining_time
type: float
restore_value: no
initial_value: '0'
logger:
logs:
switch: NONE
binary_sensor: NONE
api:
reboot_timeout: 0s
ota:
- platform: esphome
# -------------------------
# I2C BUS for OLED
# -------------------------
i2c:
sda: GPIO13
scl: GPIO14
scan: true
id: bus_a
# -------------------------
# FONTS
# -------------------------
font:
- file: "fonts/arial.ttf"
id: font_small
size: 10
- file: "fonts/arial.ttf"
id: font_large
size: 20
- file: "fonts/materialdesignicons-webfont.ttf"
id: mdi_font
size: 28
glyphs: [
"\U000F1068", # mdi:valve-open
"\U000F1067" # mdi:valve (closed)
# "\U000F05A9" # Wi-Fi icon
]
- file: "fonts/materialdesignicons-webfont.ttf"
id: mdi_font_small
size: 14
glyphs: [
"\U000F05A9" # mdi:wifi
]
# -------------------------
# DISPLAY CONFIGURATION
# -------------------------
display:
- platform: ssd1306_i2c
model: "SSD1306 128x64"
address: 0x3C
id: oled_display
rotation: 0
lambda: |-
// Draw Wi-Fi signal strength (bars)
int wifi = (int)id(wifi_signal_db).state;
int bars = 0;
if (wifi > -50) bars = 4;
else if (wifi > -60) bars = 3;
else if (wifi > -70) bars = 2;
else if (wifi > -80) bars = 1;
else bars = 0;
for (int i = 0; i < 4; i++) {
if (i < bars)
it.filled_rectangle(116 + (i * 3), 2 + (3 - i) * 2, 2, (i + 1) * 2);
else
it.rectangle(116 + (i * 3), 2 + (3 - i) * 2, 2, (i + 1) * 2);
}
it.printf(0, 0, id(mdi_font_small), "\U000F05A9"); // Wi-Fi icon
it.printf(20, 0, id(font_small), "%d%%", (int)min(max(2 * (wifi + 100.0), 0.0), 100.0));
// Show which valve is ON
int y = 24;
bool any_on = false;
const char* valve_name = "";
if (id(valve_sw1).state) {
it.printf(0, y, id(mdi_font), "\U000F1068");
it.printf(34, y + 2, id(font_large), "Valve 1");
any_on = true;
valve_name = "Valve 1";
} else if (id(valve_sw2).state) {
it.printf(0, y, id(mdi_font), "\U000F1068");
it.printf(34, y + 2, id(font_large), "Valve 2");
any_on = true;
valve_name = "Valve 2";
} else if (id(valve_sw3).state) {
it.printf(0, y, id(mdi_font), "\U000F1068");
it.printf(34, y + 2, id(font_large), "Valve 3");
any_on = true;
valve_name = "Valve 3";
} else if (id(valve_sw4).state) {
it.printf(0, y, id(mdi_font), "\U000F1068");
it.printf(34, y + 2, id(font_large), "Valve 4");
any_on = true;
valve_name = "Valve 4";
} else if (id(valve_sw5).state) {
it.printf(0, y, id(mdi_font), "\U000F1068");
it.printf(34, y + 2, id(font_large), "Valve 5");
any_on = true;
valve_name = "Valve 5";
}
if (!any_on) {
it.printf(0, y, id(mdi_font), "\U000F1067");
it.printf(34, y + 2, id(font_large), "All Closed");
} else {
// Show remaining formatted time from text sensor
it.printf(0, 52, id(font_small), "%s remaining: %s", valve_name, id(time_remaining_text).state.c_str());
}
# -------------------------
# SPRINKLER CONTROL
# -------------------------
sprinkler:
- id: basket_irrigation
main_switch: "Pump"
auto_advance_switch: "Lawn Sprinklers Auto Advance"
valve_overlap: 2s
valves:
- valve_switch: "Valve 1"
enable_switch: "Enable Valve 1"
pump_switch_id: pump_sw
run_duration_number:
id: valve_1_run_duration
name: "Valve 1 Run Duration"
icon: "mdi:timer-outline"
initial_value: 1
unit_of_measurement: min
min_value: 1
max_value: 60
valve_switch_id: valve_sw1
- valve_switch: "Valve 2"
enable_switch: "Enable Valve 2"
pump_switch_id: pump_sw
run_duration_number:
id: valve_2_run_duration
name: "Valve 2 Run Duration"
icon: "mdi:timer-outline"
initial_value: 1
unit_of_measurement: min
min_value: 1
max_value: 60
valve_switch_id: valve_sw2
- valve_switch: "Valve 3"
enable_switch: "Enable Valve 3"
pump_switch_id: pump_sw
run_duration_number:
id: valve_3_run_duration
name: "Valve 3 Run Duration"
icon: "mdi:timer-outline"
initial_value: 1
unit_of_measurement: min
min_value: 1
max_value: 60
valve_switch_id: valve_sw3
- valve_switch: "Valve 4"
enable_switch: "Enable Valve 4"
pump_switch_id: pump_sw
run_duration_number:
id: valve_4_run_duration
name: "Valve 4 Run Duration"
icon: "mdi:timer-outline"
initial_value: 1
unit_of_measurement: min
min_value: 1
max_value: 60
valve_switch_id: valve_sw4
- valve_switch: "Valve 5"
enable_switch: "Enable Valve 5"
pump_switch_id: pump_sw
run_duration_number:
id: valve_5_run_duration
name: "Valve 5 Run Duration"
icon: "mdi:timer-outline"
initial_value: 1
unit_of_measurement: min
min_value: 1
max_value: 60
valve_switch_id: valve_sw5
# -------------------------
# SWITCHES
# -------------------------
switch:
- platform: gpio
id: pump_sw
pin: GPIO16
- platform: gpio
id: pump_sw1
pin: GPIO17
- platform: gpio
id: valve_sw1
pin: GPIO25
- platform: gpio
id: valve_sw2
pin: GPIO26
- platform: gpio
id: valve_sw3
pin: GPIO32
- platform: gpio
id: valve_sw4
pin: GPIO33
- platform: gpio
id: valve_sw5
pin: GPIO27
# -------------------------
# SENSORS
# -------------------------
sensor:
- platform: wifi_signal
name: "WiFi Signal dB"
id: wifi_signal_db
update_interval: 60s
entity_category: "diagnostic"
- platform: copy
source_id: wifi_signal_db
name: "WiFi Signal Percent"
filters:
- lambda: return min(max(2 * (x + 100.0), 0.0), 100.0);
unit_of_measurement: "%"
entity_category: "diagnostic"
- platform: uptime
name: Uptime
entity_category: "diagnostic"
text_sensor:
# Readable formatted text version
- platform: template
id: time_remaining_text
name: "Time Remaining"
update_interval: 1s
icon: "mdi:timer-sand"
lambda: |-
int seconds = round(id(basket_irrigation).time_remaining_active_valve().value_or(0));
int days = seconds / (24 * 3600);
seconds = seconds % (24 * 3600);
int hours = seconds / 3600;
seconds = seconds % 3600;
int minutes = seconds / 60;
seconds = seconds % 60;
char buffer[32];
if (days > 0)
snprintf(buffer, sizeof(buffer), "%dd %dh %dm %ds", days, hours, minutes, seconds);
else if (hours > 0)
snprintf(buffer, sizeof(buffer), "%dh %dm %ds", hours, minutes, seconds);
else if (minutes > 0)
snprintf(buffer, sizeof(buffer), "%dm %ds", minutes, seconds);
else
snprintf(buffer, sizeof(buffer), "%ds", seconds);
return std::string(buffer);
# -------------------------
# STATUS BINARY SENSOR
# -------------------------
binary_sensor:
- platform: status
name: "Connection Status"
# GPIO16 Pump
# GPIO25 1
# GPIO26 2
# GPIO32 3
# GPIO33 4
# GPIO27 5
# OLED
# GPIO13 SDA
# GPIO14 SCL
I got my board to work - if it helps anyone else, I had to source the ground from the power input terminal and then hold a wire to the actual IO pin on the chip itself for it to enter programming mode as the headers didnât work ![]()
this is a very interesting project.
I bought a 4relay board in aliexpress yesterday. i will play with it when it arrives.
But i have a little question.
I am thinking on a 4 relay board.
relay 1 is MASTER VALVE.
relay 2 is the âzone 1â
relay 3 is the âzone 2â.
When the zone 1 active, the master must be active too.
when the zone 2 active, the master must be active too.
The master setup its interesting for safety. it the valve of the zone, broke, the master will cut the wather and everything its safety.
its posible to make this?
Yes, the ESPHome irrigation component supports the concept of a master valve.
ok, thanks a lot.
im waiting to arrive the device and i will play with it.
This is very interesting!!!
i received the esp today (4relays). But i cant flash the esphome firmware. The device give me timeouts.
I dont understand what is the problem:
- the jumper is ok, the pins is ok. the programer is in 3.3jumper option. i power the esp32 with a external 5v power and not work.
- i power the esp32 with a external 12v and not work
- i power the esp32 with the 230v power suply and not work
the problem is allways the same: - push the reset button and keep the reset buton.
- power on the external power supply
- plug the programer to the usb port
- the device apears in the web interface of esphome, select the com port and next
- time out and process fails
if i release the button reset, just after release the button, the process fails
any idea?







