Solar Power Monitor Project With ESP32 Micro Controller

Several years back, a little while after Raspberry foundation introduced the Raspberry pi zero, I decided to build a solar powered water fountain for the garden. Of course, as a fan of Home Assistant, I wanted to monitor and control the solar panel and fountain from Home Assistant.

This year, 2021, in a covid lockdown world, I thought I would update this solar panel project . I have wanted to improve a few things since the first development.

  1. The original project was based on raspberry pi zero and this is not the best hardware for any battery powered project (even if you are using a big battery). During winter months when the solar panel produces almost no power, the pi zero was draining the battery below it’s safe lower limit.
  2. The communication link to Home Assistant is via an MQTT broker, and I want to move to a direct connection.
  3. I want to add a few extras, like a pir detector and an audible buzzer to indicate the status of the system.

In the intervening years, I have also moved most of my projects to small low power micro controllers based on esp8266 and esp32. These are great for small projects. Another bonus is that you can even programme them with almost no coding, by using an excellent software stack called esphome. So I viewed this to be ideal hardware to base the new project on.

Programming with an esphome device is very simple. It’s just a matter of uploading the esphome firmware to the microcontroller and then constructing a YAML formatted configuration file to configure the microcontroller to do what you want. Even better, you can do this directly within Home Assistant using an esphome ‘add on’.

Using an esp32 microcontroller with Home Assistant and the esphome add on, solves all the above issues with the original project.

So what features does the microcontroller need:

  1. It needs to report average solar, battery and load voltages, current and power readings at regular intervals.
  2. It needs to report its own status.
  3. It needs to action commands to switch on/off relay driven devices.
  4. It needs to automatically lower power usage when the battery is low.
  5. It needs to switch on a nightlight when a person enters the room where it is located.
  6. It needs to provide audible acknowledgment on restart and whenever a person enters the room where it is located.
  7. It needs to report temperature and humidity at the location it is stored.

To do the above, the microcontroller needs some external sensors. To measure voltages,currents and power I have used 3 X ina219 current sensors which measure solar panel, battery and load terminals. These sensors use the I2C protocol, which the esp32 microcontroller supports. You can buy these sensors in a ready made package (example). 3 of these sensors are needed to measure at three points in the electric circuit, (solar panel, battery and load). In terms of other sensors, I also used a simple three legged PIR sensor to detect movement, an AM2320 temperature sensor and a piezo electric buzzer for audible sound. Whats really great about the esphome software stack is that it supports so many sensors and devices with just a few lines of code. You could maybe do the same in micropython or arduino on an esp32, but why make it hard for yourself.

My hardware configuration looks like this:

You can see the whole kit is supported an an MDF board, including battery. The MDF board is mounted to the wall, which is below the roof where the solar panel is situated. You will also notice there is a solar panel charge controller handling the battery charging and this also supplies power via a USB port to the microcontroller located in the white box. There’s not much else to say about the hardware other than I had to amend the surface mounted resistors in the ina219 current sensors so they could handle larger power loads than 3.2 amps. But I doubt that I will ever use up to the 3.2 amp limit anyway.

The microcontroller I used is a TTGO T7 v1.3 and allows an external WiFi antenna connection. This was important as the location is some distance from my house WiFi router and needed the boost provided by the external antenna.

TTGO t7 v1.3 microcontroller

Ok, hardware all explained, here is the YAML configuration file that carries out all the features. I have inserted some notes on what the configuration does.

# This line just sets up a name for the device which I can
# use later throughout the script.
substitutions:
  friendly_name: solar_controller

# I set up 3 global variables here to cumulatively count
# the power transferred through each sensor over the day. 
globals:
  - id: sp_panel_power_tally
    type: float
    restore_value: no
    initial_value: '0.0'
  - id: sp_battery_power_tally
    type: float
    restore_value: no
    initial_value: '0.0'
  - id: sp_load_power_tally
    type: float
    restore_value: no
    initial_value: '0.0'

# This tells the esphome software which type of micro 
# controller is being used.
esphome:
  name: solar_controller
  platform: ESP32
  board: esp-wrover-kit

# on boot up, after most core functions have started, I make
# the buzzer send a little chirp as an audible signal.
  on_boot:
    priority: -100
    then: 
      - output.turn_on: buzzer
      - output.set_level:
          id: buzzer
          level: "80%"
      - output.ledc.set_frequency:
          id: buzzer
          frequency: "3000Hz"
      - delay: 0.50s
      - output.turn_off: buzzer

# on shutdown, I send a message to home assistant and call
# the notification script. 
  on_shutdown:
    then:
      - homeassistant.service:
          service: script.notification_normal
          data:
            title: Warning
            message: Solar panel controller shutdown.

# This sets up the wifi connection to my network. I use a
# !secrets file in the config esphome directory in home
# assistant so that security information is not exposed in
# this script
wifi:
  networks:
  - ssid: !secret wifi_ssid_house
  - password: !secret wifi_pw

  
# Enable fallback hotspot (captive portal) in case wifi
# connection fails
  ap:
    ssid: "Solar Controller"
    password: !secret password

  manual_ip:
    static_ip: 192.168.1.xx
    gateway: 192.168.1.254
    subnet: 255.255.255.0


captive_portal:

# Enable logging
logger:

# Enable Home Assistant API
api:

ota:
  password: !secret password

# set up a binary sensor on pin 26 of the esp32
# microcontroller. the data pin of the PIR is connected to
# this.
binary_sensor:
  - platform: status
    name: $friendly_name Status
  - platform: gpio
    pin: 26
    name: "Solar Controller PIR Sensor"
    device_class: motion
    on_press:
      then:
        - output.turn_on: buzzer
        - output.ledc.set_frequency:
            id: buzzer
            frequency: "3000Hz"
        - output.set_level:
            id: buzzer
            level: "80%"
        - delay: 0.1s
        - output.turn_off: buzzer

# the sets up the esphome software driver to allow me send
# audio frequencies to the piezo electric buzzer
output:
  - platform: ledc
    ######################################################
    # One buzzer leg connected to GPIO12, the other to GND
    ######################################################
    pin: GPIO1
    id: buzzer

# obtain a time sync from the home assistant server
time:
  - platform: homeassistant
    id: homeassistant_time
    on_time:

    # power cycle solar controller once a week         
      - seconds: 0
        minutes: 0
        hours: 0
        days_of_week: SUN
        then:
           - switch.toggle: restart_switch    

# reset power tallys to zero at end of day. these are the
# golbal variables at the beginning of this script.
      - seconds: 45
        minutes: 59
        hours: 23
        days_of_week: MON-SUN
        then:
           - globals.set:
               id: sp_panel_power_tally
               value: '0.0'
           - globals.set:
               id: sp_battery_power_tally
               value: '0.0'
           - globals.set:
               id: sp_load_power_tally
               value: '0.0'

# send a notification to home assistant whenever a time sync
# happen and make an audible rising tone on the device as
# well. I find that the esp32 wifi does drop out
# intermittently through the day, but never more than a few
# seconds.
    on_time_sync:
      then:
        - delay: 1s
        - logger.log: "Home Assistant time is synchronised!"
        - homeassistant.service:
            service: script.notification_normal
            data:
              title: Notification
              message: Solar panel controller time synchronised to Home Assistant.
        - output.turn_on: buzzer
        - output.set_level:
            id: buzzer
            level: "80%"
        - output.ledc.set_frequency:
            id: buzzer
            frequency: "3000Hz"
        - delay: 0.25s
        - output.ledc.set_frequency:
            id: buzzer
            frequency: "4000Hz"
        - delay: 0.25s
        - output.ledc.set_frequency:
            id: buzzer
            frequency: "5000Hz"
        - delay: 1s
        - output.turn_off: buzzer

# set up i2c protocal for communication with the cureent
# sensors on pins 21 and 22 of the micro controller.
i2c:
  - id: bus_a
    sda: 21
    scl: 22
    scan: True

# set up a sensor in home assitant to report wifi signal
# strength every 30 seconds
sensor:

  - platform: wifi_signal
    name: $friendly_name WiFi Signal
    update_interval: 30s

# setup Template sensors which will report cumulative daily
# power from each of the 3 current sensors to Home Assistant
  - platform: template
    name: "sp_panel_generated_today"
    id: sp_panel_generated_today
    update_interval: 30s
    unit_of_measurement: "W"
    accuracy_decimals: 2
  - platform: template
    name: "sp_battery_generated_today"
    id: sp_battery_generated_today
    update_interval: 30s
    unit_of_measurement: "W"
    accuracy_decimals: 2
  - platform: template
    name: "sp_load_generated_today"
    id: sp_load_generated_today
    update_interval: 30s
    unit_of_measurement: "W"
    accuracy_decimals: 2

# the following 3 sections are to setup the reporting from
# the 3 current sensors. this first one is the sensor
# monitoring the solar panel. In short, I define the sensor
# as an INA219, and its i2c address on the i2c bus. i then
# define the resistor value between the input and output on
# the sensor. Remeber, I lowered my resistor values to
# increase the power range the sensor can measure. Then I set
# up sensor in home assitant for the current, bus voltage,
# shunt voltage and power. The shunt voltage is negligible
# and I dont use this as a measure in Home assistant. to get
# a more accurate reading of the power used over a 30 second
# period, I sample the reading 6 times, and take the average,
# but only report to home assistant on the 6th reading. The
# update interval at the bottom is set to 5 seconds, so 6
# samples * 5 seconds = 30 seconds. on the power reading
# there is a lambda code snippet which samples the power used
# in that 30 seconds and then divides by 120 (30/2/60) to
# calculate the watts per hour usage. This is then added to
# the cumulative power total for the day. Lastly, max voltage
# and max current are set so that the internals of the ina219
# sensor can report with the most optimum digital resolution.
# I know the solar panel will never exceed 25 volts and 5
# amps, so 32V/5amp are the closest values for the ina219
# sensor.
  - platform: ina219
    i2c_id: bus_a
    address: 0x40
    shunt_resistance: 0.01 ohm
    current:
      name: "sp_panel_current"
      filters:
        - sliding_window_moving_average:
            window_size: 6
            send_every: 6
      unit_of_measurement: "A"
      accuracy_decimals: 5
    bus_voltage:
      name: "sp_panel_voltage"
      filters:
        - sliding_window_moving_average:
            window_size: 6
            send_every: 6
      unit_of_measurement: "V"
      accuracy_decimals: 5
    shunt_voltage:
      name: "sp_panel_shunt_voltage"
      filters:
        - sliding_window_moving_average:
            window_size: 6
            send_every: 6
      unit_of_measurement: "V"
      accuracy_decimals: 5
    power:
      name: "sp_panel_power"
      id: sp_panel_power
      filters:
        - sliding_window_moving_average:
            window_size: 6
            send_every: 6
      unit_of_measurement: "W"
      accuracy_decimals: 5
      on_value:
        then:
          - globals.set:
              id: sp_panel_power_tally
              value: !lambda |-
                return (id(sp_panel_power_tally) + id(sp_panel_power).state / 120.0);
          - lambda: |-
              id(sp_panel_generated_today).publish_state(id(sp_panel_power_tally)); ESP_LOGI("main", "Cumulative Panel Power today: %f", id(sp_panel_power_tally));
    max_voltage: 32.0V  
    max_current: 5.0A
    update_interval: 5s

# Same as above, but this time for the battery sensor.
# However, I also add code to automatically switch off all
# relays if the battery voltage ever drops below 12v. So this
# is a failsafe mechanism to protect a lead acid type battery,
# which should never be fully drained.
  - platform: ina219
    i2c_id: bus_a
    address: 0x41
    shunt_resistance: 0.01 ohm
    current:
      name: "sp_battery_current"
      filters:
        - sliding_window_moving_average:
            window_size: 6
            send_every: 6
      unit_of_measurement: "A"
      accuracy_decimals: 5
    bus_voltage:
      name: "sp_battery_voltage"
      id: sp_battery_bus_voltage
      filters:
        - sliding_window_moving_average:
            window_size: 6
            send_every: 6
      unit_of_measurement: "V"
      accuracy_decimals: 5
      on_value:
        if:
          condition:
            sensor.in_range:
              id: sp_battery_bus_voltage
              below: 12.0
          then:
            - switch.turn_off: sp_relay_1
            - switch.turn_off: sp_relay_2
            - switch.turn_off: sp_relay_3
            - switch.turn_off: sp_relay_4
            - homeassistant.service:
                service: script.notification_priority
                data:
                  title: Warning
                  message: Battery is below safe limits. All relays turned off
    shunt_voltage:
      name: "sp_battery_shunt_voltage"
      filters:
        - sliding_window_moving_average:
            window_size: 6
            send_every: 6
      unit_of_measurement: "V"
      accuracy_decimals: 5
    power:
      name: "sp_battery_power"
      id: sp_battery_power
      filters:
        - sliding_window_moving_average:
            window_size: 6
            send_every: 6
      unit_of_measurement: "W"
      accuracy_decimals: 5
      on_value:
        then:
          - globals.set:
              id: sp_battery_power_tally
              value: !lambda |-
                return (id(sp_battery_power_tally) + id(sp_battery_power).state / 120);
          - lambda: |-
              id(sp_battery_generated_today).publish_state(id(sp_battery_power_tally)); ESP_LOGI("main", "Cumulative Battery Power today: %f", id(sp_battery_power_tally));
    max_voltage: 16.0V
    max_current: 5.0A
    update_interval: 5s

# same as above, but this time for the laod (relays)
  - platform: ina219
    i2c_id: bus_a
    address: 0x44
    shunt_resistance: 0.01 ohm
    current:
      name: "sp_load_current"
      filters:
        - sliding_window_moving_average:
            window_size: 6
            send_every: 6
      unit_of_measurement: "A"
      accuracy_decimals: 5
    bus_voltage:
      name: "sp_load_voltage"
      filters:
        - sliding_window_moving_average:
            window_size: 6
            send_every: 6
      unit_of_measurement: "V"
      accuracy_decimals: 5
    shunt_voltage:
      name: "sp_load_shunt_voltage"
      filters:
        - sliding_window_moving_average:
            window_size: 6
            send_every: 6
      unit_of_measurement: "V"
      accuracy_decimals: 5
    power:
      name: "sp_load_power"
      id: sp_load_power
      filters:
        - sliding_window_moving_average:
            window_size: 6
            send_every: 6
      unit_of_measurement: "W"
      accuracy_decimals: 5
      on_value:
        then:
          - globals.set:
              id: sp_load_power_tally
              value: !lambda |-
                return (id(sp_load_power_tally) + id(sp_load_power).state / 120);
          - lambda: |-
              id(sp_load_generated_today).publish_state(id(sp_load_power_tally)); ESP_LOGI("main", "Cumulative Load Power today: %f", id(sp_load_power_tally));
    max_voltage: 16.0V
    max_current: 5.0A
    update_interval: 5s

# sets up a Temperature and humidity sensor in home assistant.
# I use a simple DHT22 type temp sensor
  - platform: dht
    pin: 17
    temperature:
      name: "sp_room_temperature"
      unit_of_measurement: "C"
      accuracy_decimals: 1
    humidity:
      name: "sp_room_humidity"
      unit_of_measurement: "%"
      accuracy_decimals: 1
    update_interval: 60s

# sets up a switch to reboot the microcontoller from home
# assistant if I ever need to
switch:
  - platform: restart
    name: "Solar Controller Restart"
    id: restart_switch

# Sets up 4 relay switches in home assistant which are
# connected to the battery. My fountain is controlled by
# relay 1. The signal is inverted as the relays I use are
# energised on a low signal.
  - platform: gpio
    pin:
      number: 5
      inverted: yes
    name: "SP Relay 1"
    id: sp_relay_1
    restore_mode: ALWAYS_OFF
  - platform: gpio
    pin: 
      number: 23
      inverted: yes
    name: "SP Relay 2"
    id: sp_relay_2
    restore_mode: ALWAYS_OFF
  - platform: gpio
    pin: 
      number: 19
      inverted: yes
    name: "SP Relay 3"
    id: sp_relay_3
    restore_mode: ALWAYS_OFF
  - platform: gpio
    pin:
      number: 18
      inverted: yes
    name: "SP Relay 4"
    id: sp_relay_4
    restore_mode: ALWAYS_OFF
    

The last thing I should mention is that the esphome addon for Home assistant allows you to reprogram your micro controller over wifi after initial setup via a serial/ usb port. So its’ really simple to tinker with the above code and apply it ‘over the air’ without the burden of wires.

How did it work out ?

I now have a solar panel controller reporting directly into and controllable by Home Assistant. It uses 0.5 watts (which is about a third of the original setup). The picture below shows what it looks like in my Home Assistant.