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.
- 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.
- The communication link to Home Assistant is via an MQTT broker, and I want to move to a direct connection.
- 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:
- It needs to report average solar, battery and load voltages, current and power readings at regular intervals.
- It needs to report its own status.
- It needs to action commands to switch on/off relay driven devices.
- It needs to automatically lower power usage when the battery is low.
- It needs to switch on a nightlight when a person enters the room where it is located.
- It needs to provide audible acknowledgment on restart and whenever a person enters the room where it is located.
- 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.

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.
