18 +2 in/16 out ESP32 development board

I’ve recently completed an ESP32 based project for control and monitoring equipment on my Koi Carp pond.

This necessitated designing a board in Kicad, getting it printed, building, testing and installing.

I’m pleased to say that the design and construction have been successful.

The board has:
16 x 5V outputs
18 x Inputs
2 x 1 Wire inputs
On board reset switch
On board Wifi LED
On board Status LED
On board 5V LED
On board 12V LED
12V PSU
5V PSU

So, if there is any interest in this, I’d gladly support anyone with either supplying the board, KiCad files and documentation.

It can, of course, be used for any project, not just Koi ponds!

7 Likes

Very nice work - i wish my soldering skills were up to that level !!

Craig

Very impressive !

1 Like

Hi Ash, really impressive layout. I’m interested in the board/files/documentation if you would share it kindly?

We have a (sort of) similar setup and have some issues. Maybe you can help us out?

Here’s the ESPHome file I’m using:

esphome:
  name: pond_control_2
  platform: ESP32
  board: esp-wrover-kit

  on_boot:
    then:
      - binary_sensor.template.publish:
          id: enable_water_fill
          state: ON
      - binary_sensor.template.publish:
          id: drum_filter_recovery_mode
          state: OFF
      - binary_sensor.template.publish:
          id: drum_empty
          state: OFF
      - binary_sensor.template.publish:
          id: pond_control_restarted
          state: On

globals:
  - id: water_change_amount_variable
    type: int
    restore_value: no
    initial_value: '25'
  - id: drum_wash_timer_variable
    type: int
    restore_value: no
    initial_value: '10'
  - id: spindrifter_off_timer_variable
    type: int
    restore_value: no
    initial_value: '20'
  - id: heron_scarer_on_timer_variable
    type: int
    restore_value: no
    initial_value: '5'

wifi:
  ssid: !secret dryderdale_wifi_SSID
  password: !secret dryderdale_wifi_password
  manual_ip:
    static_ip: 10.0.1.116
    gateway: 10.0.1.1
    subnet: 255.255.255.0

  ap:
    ssid: "Pond Control"
    password: !secret OTA_password

web_server:
  port: 80
  auth:
    username: admin
    password: !secret OTA_password

status_led:
  pin:
    number: GPIO0
    inverted: True

api:

ota:
  safe_mode: True
  password: !secret OTA_password

captive_portal:

logger:
  level: debug

dallas:
  - pin: GPIO26
    update_interval: 60s

i2c:
  sda: 21
  scl: 22

pcf8574:
  - id: 'expander_1'
    address: 0x20
    pcf8575: False
  - id: 'expander_2'
    address: 0x24
    pcf8575: False

time:
  - platform: homeassistant
    id: homeassistant_time
    on_time:
###########################
##### Waterfall timer #####
###########################
      # Every morning Spring to Autumn
      - minutes: 00
        hours: 10
        months: 4,5,6,7,8,9,10
        then:
          - switch.turn_on: waterfall_pump
      - minutes: 00
        hours: 22
        months: 4,5,6,7,8,9,10
        then:
          - switch.turn_off: waterfall_pump
      - minutes: 00
        hours: 10
        months: 11,12,1,2,3
        then:
          - switch.turn_on: waterfall_pump
      - minutes: 10
        hours: 10
        months: 11,12,1,2,3
        then:
          - switch.turn_off: waterfall_pump

########################################
###### Weekly Water Change Control #####
########################################
      - minutes: 00
        hours: 08
        days_of_week: THU
        then:
          - script.execute: do_water_change


###################
##### Outputs #####
###################

switch:

  - platform: restart
    name: "Restart koi pond control"
    id: restart_pond_control

  - platform: gpio
    pin: GPIO2
    name: "Drum motor"
    id: drum_motor
    inverted: true
    restore_mode: RESTORE_DEFAULT_OFF

  - platform: gpio
    pin: GPIO4
    name: "Drum wash pump"
    id: drum_wash_pump
    inverted: true
    restore_mode: RESTORE_DEFAULT_OFF

  - platform: gpio
    pin: GPIO5
    name: "Main pump"
    id: main_pump
    inverted: true
    icon: mdi:water-pump
    restore_mode: RESTORE_DEFAULT_ON

  - platform: gpio
    pin: GPIO12
    name: "Waterfall pump"
    id: waterfall_pump
    inverted: true
    restore_mode: RESTORE_DEFAULT_ON

  - platform: gpio
    pin: GPIO13
    name: "Skimmer pump"
    id: skimmer_pump
    inverted: true
    icon: mdi:water-pump
    restore_mode: RESTORE_DEFAULT_ON

  - platform: gpio
    pin: GPIO14
    name: "Spindrifter air"
    id: spindrifter_air
    inverted: true
    icon: mdi:air-filter
    restore_mode: RESTORE_DEFAULT_ON

  - platform: gpio
    pin: GPIO15
    name: "Anoxic air"
    id: anoxic_air
    inverted: true
    icon: mdi:air-filter
    restore_mode: RESTORE_DEFAULT_ON

  - platform: gpio
    pin: GPIO16
    name: "Pond lights"
    id: pond_lights
    inverted: true
    restore_mode: RESTORE_DEFAULT_OFF

  - platform: gpio
    pin: GPIO17
    name: "Water fill valve"
    id: water_fill_valve
    inverted: true
    restore_mode: RESTORE_DEFAULT_OFF

  - platform: gpio
    pin: 
      pcf8574: expander_1
      number: 5
      mode: OUTPUT
    name: "Ozone air"
    inverted: True
    icon: mdi:air-filter
    restore_mode: RESTORE_DEFAULT_ON
    id: ozone_air

  - platform: gpio
    pin: 
      pcf8574: expander_1
      number: 6
      mode: OUTPUT
      inverted: False
    id: wifi_connected_led

  - platform: gpio
    pin: 
      pcf8574: expander_1
      number: 0
      mode: OUTPUT
      inverted: True
    id: fish_feeder
    restore_mode: ALWAYS_OFF

  - platform: gpio
    pin: 
      pcf8574: expander_1
      number: 7
      mode: OUTPUT
      inverted: True
    name: "Heron scarer"
    id: heron_scarer
    restore_mode: ALWAYS_OFF

##################
##### Inputs #####
##################

binary_sensor:
  - platform: gpio
    pin: 
      number: GPIO18
      mode: INPUT_PULLUP
    name: "Drum motor toggle"
    id: drum_motor_toggle
    filters:
      - invert:
    on_press:
      then:
        - switch.turn_on: drum_motor
    on_release:
      then:
        - switch.turn_off: drum_motor

  - platform: gpio
    pin: 
      number: GPIO19
      mode: INPUT_PULLUP
    name: "Drum wash pump toggle"
    filters:
      - invert:
    on_press:
      then:
        - switch.turn_on: drum_wash_pump
    on_release:
      then:
        - switch.turn_off: drum_wash_pump

  - platform: gpio
    name: "Main pump toggle"
    pin:
      number: GPIO36
      mode: INPUT_PULLUP
    id: main_pump_toggle
    on_press:
      then:
        - switch.toggle: main_pump

  - platform: gpio
    name: "Waterfall pump toggle"
    pin: 
      number: GPIO33
      mode: INPUT_PULLUP
    id: waterfall_pump_toggle
    on_press:
      then:
        - switch.toggle: waterfall_pump

  - platform: gpio
    name: "Skimmer pump toggle"
    pin: 
      number: GPIO23
      mode: INPUT_PULLUP
      inverted: False
    id: skimmer_pump_toggle
    on_press:
      then:
        - switch.toggle: skimmer_pump

  - platform: gpio
    pin: 
      number: GPIO25
      mode: INPUT_PULLUP
    name: "Spindrifter air toggle"
    id: spindrifter_air_toggle
    on_press:
      then:
        - switch.toggle: spindrifter_air

  - platform: gpio
    pin: 
      number: GPIO27
      mode: INPUT_PULLUP
    name: "Anoxic air toggle"
    id: anoxic_air_toggle
    on_press:
      then:
        - switch.toggle: anoxic_air

  - platform: gpio
    pin: 
      number: GPIO32
      mode: INPUT_PULLUP
    name: "IR sensor"
    id: ir_sensor
    filters:
      - invert:
      - delayed_on_off: 100ms
    on_press:
      then:
        - if:
            condition:
              and:
                - binary_sensor.is_off: disable_spindrifter_ir
            then:
              - script.execute: do_turn_off_spindrifter_and_skimmer
              - script.execute: do_turn_on_heron_scarer

  - platform: gpio
    pin: 
      number: GPIO34
      mode: INPUT_PULLUP
    name: "Water meter sensor"
    id: water_meter_sensor

  - platform: gpio
    name: "Water level high sensor"
    pin:
      pcf8574: expander_1
      number: 2
      mode: INPUT
      inverted: False
    id: water_level_high_sensor
    on_release:
      then:
        - switch.turn_off: water_fill_valve
        - if:
            condition:
              - binary_sensor.is_on: drum_filter_recovery_mode
            then:
              - binary_sensor.template.publish:
                  id: drum_filter_recovery_mode
                  state: OFF
              - binary_sensor.template.publish:
                  id: enable_water_fill
                  state: ON
      

  - platform: gpio
    name: "Water level low sensor"
    pin:
      pcf8574: expander_1
      number: 3
      mode: INPUT
      inverted: False
    id: water_level_low_sensor
    on_press:
      then:
        - if:
            condition:
              and:
                - binary_sensor.is_on: water_level_high_sensor # inhibit wash if drum wash low level
                - binary_sensor.is_on: enable_water_fill
            then:
              - switch.turn_on: water_fill_valve
    on_release:
      then:
        - if:
            condition:
              - binary_sensor.is_on: drum_filter_recovery_mode
            then:
              - script.execute: do_recovery_drum_wash

  - platform: gpio
    pin: 
      number: GPIO35
      mode: INPUT_PULLUP
    name: "Drum low level switch"
    id: drum_low_level_switch
    on_press:
      then:
        - if:
            condition:
              and:
                - binary_sensor.is_off: drum_wash_low_level_switch # inhibit wash if drum wash low level
                - binary_sensor.is_off: disable_drum_wash
            then:
              - if:
                  condition:
                    - binary_sensor.is_off: drum_filter_recovery_mode
                  then:
                    - script.execute: do_drum_wash
    on_release:
      then:
        - if:
            condition:
                - binary_sensor.is_off: drum_wash_low_level_switch
            then:
              - binary_sensor.template.publish:
                  id: drum_empty
                  state: OFF

  - platform: gpio
    name: "Drum wash low level switch"
    pin:
      pcf8574: expander_1
      number: 4
      mode: INPUT
      inverted: False
    id: drum_wash_low_level_switch
    on_press:
      then:
        - switch.turn_off: drum_wash_pump
        - if:
            condition:
              and:
                - binary_sensor.is_on: drum_wash_low_level_switch
                - binary_sensor.is_on: drum_low_level_switch
            then:
              - binary_sensor.template.publish:
                  id: drum_empty
                  state: ON
              - binary_sensor.template.publish:
                  id: drum_filter_recovery_mode
                  state: ON
              - binary_sensor.template.publish:
                  id: enable_water_fill
                  state: OFF
              - switch.turn_on: water_fill_valve

  - platform: gpio
    pin: 
      number: GPIO39
      mode: INPUT_PULLUP
    name: "Manual Water Fill"
    id: manual_water_fill
    filters:
      - invert:
    on_click:
      - min_length: 1s
        max_length: 2s
        then:
          - script.execute: do_water_change
      - min_length: 3s
        max_length: 5s
        then:
          - switch.turn_on: water_fill_valve
    on_press:
      then:
        - switch.toggle: water_fill_valve
    

  - platform: gpio
    pin:
      pcf8574: expander_1
      number: 1
      mode: INPUT
      inverted: False
    name: "Ozone air toggle"
    id: ozone_air_toggle
    on_press:
      then:
        - switch.toggle: ozone_air

  - platform: gpio
    pin:
      pcf8574: expander_2
      number: 0
      mode: INPUT
      inverted: False
    name: "pond control restart"
    on_press:
      then:
        - switch.toggle: restart_pond_control

  - platform: gpio
    pin:
      pcf8574: expander_2
      number: 1
      mode: INPUT
      inverted: False
    name: "Fish feeder PB"
    on_press:
      then:
        - switch.turn_on: fish_feeder
    on_release:
      then:
        - switch.turn_off: fish_feeder

  - platform: status
    name: "Pond control"

### Binary sensors to HA ###

  - platform: template
    name: "Water change in progress"
    id: water_change_in_progress_binary

  - platform: template
    name: "Drum wash in progress"
    id: drum_wash_in_progress
  
  - platform: template
    name: "Drum filter in recovery mode"
    id: drum_filter_recovery_mode
    on_press:
      then:
        - script.execute: do_recovery_first_drum_wash

  - platform: template
    name: "Enable water fill"
    id: enable_water_fill
  
  - platform: template
    name: "Drum empty"
    id: drum_empty
  
  - platform: template
    name: "Pond control has restarted"
    id: pond_control_restarted

### Binary sensors from HA ###

  - platform: homeassistant
    name: "Manual drum wash"
    entity_id: input_boolean.manual_drum_wash
    id: manual_drum_wash
    on_press:
      then:
        - if:
            condition:
              - binary_sensor.is_off: drum_filter_recovery_mode
            then:
              - script.execute: do_drum_wash


  - platform: homeassistant
    name: "Manual spindrifter off"
    entity_id: input_boolean.manual_spindrifter_off
    id: manual_spindrifter_off
    on_press:
      then:
        - script.execute: do_turn_off_spindrifter_and_skimmer

  - platform: homeassistant
    name: "Manual water change"
    entity_id: input_boolean.manual_water_change
    id: manual_water_change
    on_press:
      then:
        - script.execute: do_water_change  

  - platform: homeassistant
    name: "Manual cancel water change"
    entity_id: input_boolean.manual_cancel_water_change
    id: manual_cancel_water_change

  - platform: homeassistant
    name: "Disable heron scarer"
    entity_id: input_boolean.disable_heron_scarer
    id: disable_heron_scarer

  - platform: homeassistant
    name: "Disable spindrifter IR"
    entity_id: input_boolean.disable_spindrifter_ir
    id: disable_spindrifter_ir

  - platform: homeassistant
    name: "Disable drum wash"
    entity_id: input_boolean.disable_drum_wash
    id: disable_drum_wash

### Sensors ###

sensor:    
  - platform: dallas
    address: 0x570119279E6EDF28
    name: "Pond Water Temperature"
    id: pond_water_temperature
    icon: mdi:temperature-celsius
    
  - platform: dallas
    address: 0x7301192788034C28
    name: "Pond Air Temperature"
    id: pond_air_temperature
    icon: mdi:temperature-celsius
  
  - platform: integration
    name: "Water change rate"
    id: pond_water_change
    accuracy_decimals: 0
    sensor: water_meter_pulse_counter
    time_unit: min

  - platform: pulse_counter
    pin: GPIO34
    name: "Water meter"
    id: water_meter_pulse_counter
    accuracy_decimals: 0
    update_interval: 60s
    unit_of_measurement: 'litres'
  
  ###  Values from Home Assistant ###
  - platform: homeassistant
    name: "Water change amount"
    entity_id: sensor.water_change_amount
    id: water_change_amount
    on_value:
      then:
        - globals.set:
            id: water_change_amount_variable
            value: !lambda 'return x;'
  
  - platform: homeassistant
    name: "Drum wash timer"
    entity_id: sensor.drum_wash_timer
    id: drum_wash_timer
    on_value:
      then:
        - globals.set:
            id: drum_wash_timer_variable
            value: !lambda 'return x;'
  
  - platform: homeassistant
    name: "Spindrifter off timer"
    entity_id: sensor.spindrifter_off_timer
    id: spindrifter_off_timer
    on_value:
      then:
        - globals.set:
            id: spindrifter_off_timer_variable
            value: !lambda 'return x;'
  
  - platform: homeassistant
    name: "Heron scarer on timer"
    entity_id: sensor.heron_scarer_on_timer_2
    id: heron_scarer_on_timer
    on_value:
      then:
        - globals.set:
            id: heron_scarer_on_timer_variable
            value: !lambda 'return x;'

### Scripts ###

script:
  - id: do_water_change
    then:
      - sensor.integration.reset: pond_water_change
      - if: 
          condition:
            switch.is_on: water_fill_valve
          then:
            - binary_sensor.template.publish:
                id: water_change_in_progress_binary
                state: ON
          else:
            - binary_sensor.template.publish:
                id: water_change_in_progress_binary
                state: ON
            - switch.turn_on: water_fill_valve
      - wait_until:
          condition:
            or:
              - lambda: 'return id(pond_water_change).state >= id(water_change_amount_variable);'
              - binary_sensor.is_on: manual_cancel_water_change
      - switch.turn_off: water_fill_valve
      - binary_sensor.template.publish:
          id: water_change_in_progress_binary
          state: OFF

  - id: do_drum_wash
    then:
      - switch.turn_on: drum_motor
      - switch.turn_on: drum_wash_pump
      - binary_sensor.template.publish:
          id: drum_wash_in_progress
          state: ON 
      - delay: !lambda 'return id(drum_wash_timer_variable) * 1000;'
      - switch.turn_off: drum_motor
      - switch.turn_off: drum_wash_pump
      - binary_sensor.template.publish:
          id: drum_wash_in_progress
          state: OFF

  - id: do_recovery_first_drum_wash
    then:
      - switch.turn_on: drum_motor
      - switch.turn_on: drum_wash_pump 
      - delay: !lambda 'return id(drum_wash_timer_variable) * 2000;'
      - switch.turn_off: drum_motor
      - switch.turn_off: drum_wash_pump

  - id: do_recovery_drum_wash
    then:
      - switch.turn_on: drum_motor
      - switch.turn_on: drum_wash_pump 
      - delay: !lambda 'return id(drum_wash_timer_variable) * 1000;'
      - switch.turn_off: drum_motor
      - switch.turn_off: drum_wash_pump

  - id: do_turn_off_spindrifter_and_skimmer
    then:
      - switch.turn_off: spindrifter_air
      - switch.turn_off: skimmer_pump
      - delay: !lambda 'return id(spindrifter_off_timer_variable) * 1000;'
      - switch.turn_on: spindrifter_air
      - switch.turn_on: skimmer_pump

  - id: do_turn_on_heron_scarer
    then:
        - if:
            condition:
                - binary_sensor.is_off: disable_heron_scarer
            then:
              - switch.turn_on: heron_scarer
              - delay: !lambda 'return id(heron_scarer_on_timer_variable) * 1000;'
              - switch.turn_off: heron_scarer

interval:
  - interval: 10s
    then:
      - if:
          condition:
            wifi.connected:
          then:
            - switch.turn_on: wifi_connected_led
          else:
          - switch.turn_off: wifi_connected_led

Have you got this hardwired on set up on a breadboard? I experienced some random on/off changes whilst breadboard. All went away once hardwired.

Do the logs show the output being switched?

From your diagram it looks like you are powering the relays from the ESP32. This is not good!

Do you have a link for that board?

Here’s a jpg of the circuit:

Hi Ashscott! First of all thanks for your comments on our setup and sorry for responsing just now!
Currently we’re using a breadboard like wire style. the ESP we’re using is the esp32 devkit1 36 pins (layout). The logs shows the output being switched continously, yes.

Another issue we experience is that we run out of I/O ports (didnt overtought the design) for other devices rather than relais. We’re running into more issues as we’re trying to improve(??) our design.
Therefor your setup you mention in your first post would be really helpful and solve all our problems.
Is it possible for you to send us your KiCad files so we try to make a print for our own?

Sure. Let me know where to send the download link.

Thanks very much! You may send the files to [email protected].
Do you use a specific program to convert/print the kicad files?

Link to file sent.

Hi Ashscott

I know ow this is an old thread, but do you still happen to have the files that you could share? This is exactly what I am currently doing with 3 separate esp32s so would like to combine them.
Many thanks

I’ll need to have a look. I’ll get back to you.

Send me a PM and I’ll add it to my list.