CircuitSetup 6ch Energy Meter running ESPHome

This is a work in progress so I will be updating this post as I make progress with the build and deployment of the CircuitSetup 6ch Energy Monitor running ESPHome.

I live in the USA therefore my home has the typical residential 200A 240V split-phase service. The 120V loads in the home are balanced between the two phases, and 240V appliances draw from both phases.

image

I am installing the energy monitor at my exterior panel where the 200A breaker is, and where the breakers for my 240V appliances (oven, dryer, 2 x HVAC) are.

The electrical panel is not fully weatherproof (it may be fine for breakers, but I wouldn’t trust it for electronics) and I have my doubts on what one is allowed to put inside the panel, especially when it is not fully insulated (the energy monitor is a bare board), so I opted to install it externally inside a wall mounted weatherproof enclosure.

Old picture of the open panel…

Below is a picture of where it will be mounted. It is below the main panel so as not to obstruct the door which has to be able to open full 90 degrees without obstruction. Also, the conduit with the CT clamp wires will dome out of the bottom and enter the enclosure on the lower side. As long as I seal things up properly this should help keep things dry inside the enclosure.

The board on the left holds to PCB mounted transformers that I am using to provide a voltage reference for L2, and power the energy monitor from L1… plus a voltage reference for L1. I added fuses for safety given the smallest breakers I can find are 15A. The small transformer outputs 9VAC nominal so the actual voltage is around 15VAC (bit high but fine). The other transformer has 2 6V AC secondaries connected in series that end up outputting about 16VAC.

The next step was to calibrate the voltage measurements for L1 and L2. I have a benchtop 5 1/2 digit DMM that I was using to measure line voltage and since both transformers are powered by the same outlet, I calibrated both to show the same values. Below is a graph from Grafana that makes it so much easier to see if the two measurements are “close enough”. Contrary to what some may think, the voltage changes a lot and quickly depending on the load. The transformer powering our house also powers the house next door so if their HVAC turns on, it will likely result in a small fluctuation. I turned mine off for a bit to try to stabilize it but the DMM (set to slow!) was still changing rapidly. Also, I believe there is some time delay between measurements on both chips therefore I don’t think you can get the same exact reading on both at the same time. By using the Grafana chart I settled on ensuring that the two readings more or less followed each other. The chart is set to last 5 minutes, so if you look at the right side you can see where I figured I could stop as it was “good enough”.

I did not research all the technical specifications related to accuracy, repeatability of measurements, calibration, and temperature related drift, but I am pretty sure that the overall system accuracy is relatively low (in other words not measurement grade, but perfectly fine for energy monitoring). That is one reason I limited the decimals shown as showing more is likely just showing you noise. Plus… seeing 120.237V is more confusing that of any use… 120.2V is more likely to be correct and adds useful information (ie a load causing a small voltage sag). During the calibration I set the ESPHome code to update every 1s, but there was still some noticeable delay between the 2 voltage measurements (the alternative is that there is something causing larger than I would expect fluctuations).

The calibration I am attempting is sometimes called “system calibration”. The goal is not to calibrate just one part of the system, for example the board, but everything from the sensor to the board. I have labeled the CTs so that once installed, they will be connected back to the same inputs I used during calibration. For amperage calibration I am using a 60W incandescent lightbulb connected to the same power outlet as the energy meter using a modified extension cord. The modification is just wrapping the hot wire in a loop with 4 windings so that the current measured is 4 times higher which will make calibration easier. I have 2 CTs and a good quality Clamp Amperemeter that I am calibrating to. I should check my Clamp Amperemeter comparing it to current measurements of my DMM (which is definitely more accurate) but I have not done that yet. I can’t use my DMM in place of the clamp amperemeter because my DMM would only measure the actual current, not 4 x. My clamp Amperemeter reads 1.756A so I calibrated both CTs to show 1.75A (I limited the decimals to 2). I will repeat this for the 2 other pairs of inputs and then I will be ready to install the energy meter. This is why I got rid of the single shared calibration constants.

I also ordered the addon 6ch board so I can measure my 2 HVAC systems (4 CTs needed) but it is on backorder. Hopefully I can calibrate it without having to tear everything out if already installed…

Below is the ESPHome code I am currently using. Please keep in mind I am still experimenting and far from finished. One thing I did was subtract oven, and dryer power from the whole house power to get the HVAC power but… I forgot the 80A feed that powers the rest of the house. So what I am getting for HVAC is not that, but everything except Oven and Dryer. I am leaving it in the code for experimentation purposes for now. The code below is mostly by CircuitSetup… I am just adjusting it to my needs.

# Example config for when jp8-jp11 are all bridged - this connects all the voltage channels and allows for power to be calculated by the meter directly.
# Boards >= v1.4 jp8-jp11 are removed and have all voltage channels connected

substitutions:
  devicename: home-energy-meter
  devicename_no_dashes: home_energy_meter
  friendly_devicename: "Home Energy Meter"
  device_description: "Home Energy Meter"
  appliance1: "Dryer"
  appliance2: "Oven"
  appliance3: "HVAC"
# Current Transformers:
# 20A/25mA SCT-006: 11143
# 30A/1V SCT-013-030: 8650
# 50A/1V SCT-013-050: 15420
# 80A/26.6mA SCT-010: 41660
# 100A/50ma SCT-013-000: 27518
# 120A/40mA: SCT-016: 41787
# 200A/100mA SCT-024: 27518
# current_cal: '41660'
  current_cal_ct1: '41660'
  current_cal_ct2: '41660'
  current_cal_ct3: '42350'
  current_cal_ct4: '42200'
  current_cal_ct5: '41660'
  current_cal_ct6: '41660'
# Jameco 9VAC Transformer: 
#  For meter versions: 
#  >= v1.3: 7305
# voltage_cal: '5355'
  v1_cal: '5025'
  v2_cal: '5385'
  # Interval of how often the wifi info is updated
  update_interval_wifi: "10s"
  # Interval of how often the power is updated
  update_interval_s: "10s"
  
esphome:
  name: ${devicename}
  comment: ${device_description}
  platform: ESP32
  board: nodemcu-32s

wifi:
  ssid: !secret iot_wifi_ssid
  password: !secret iot_wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "${devicename} Hotspot"
    password: !secret iot_wifi_password

#Faster than DHCP. Also use if can't reach because of name change
#  manual_ip:
#    static_ip: 192.168.3.196
#    gateway: 192.168.3.1
#    subnet: 255.255.255.0
#    dns1: 192.168.1.25
#    dns2: 192.168.1.26

#Manually override what address to use to connect to the ESP.
#Defaults to auto-generated value. Example, if you have changed your
#static IP and want to flash OTA to the previously configured IP address.
#use_address: 192.168.3.196

# Enable logging
logger:

# Enable Home Assistant API
api: 
  password: !secret api_pwd

ota:
  password: !secret ota_pwd

web_server:
  port: 80

spi:
  clk_pin: 18
  miso_pin: 19
  mosi_pin: 23

text_sensor:
  - platform: wifi_info
    ip_address:
      name: "${friendly_devicename}: IP"
      icon: "mdi:ip-outline"
      update_interval: ${update_interval_wifi}
    ssid:
      name: "${friendly_devicename}: SSID"
      icon: "mdi:wifi-settings"
      update_interval: ${update_interval_wifi}
    bssid:
      name: "${friendly_devicename}: BSSID"
      icon: "mdi:wifi-settings"
      update_interval: ${update_interval_wifi}
    mac_address:
      name: "${friendly_devicename}: MAC"
      icon: "mdi:network-outline"
    scan_results:
      name: "${friendly_devicename}: Wifi Scan"
      icon: "mdi:wifi-refresh"
      disabled_by_default: true

sensor:
  - platform: wifi_signal
    name: "${friendly_devicename}: WiFi Signal"
    update_interval: ${update_interval_wifi}
    device_class: signal_strength

#IC1
  - platform: atm90e32
    cs_pin: 5
    phase_a:
      voltage:
        name: "${friendly_devicename}: L1 V"
        id: ic1Volts
        accuracy_decimals: 1
      current:
        name: "${friendly_devicename}: L1 A"
        id: ct1Amps
# The max value for current that the meter can output is 65.535. If you expect to measure current over 65A, 
# divide the gain_ct by 2 (120A CT) or 4 (200A CT) and multiply the current and power values by 2 or 4 by uncommenting the filter below
        filters:
          - multiply: 4
      power:
        name: "${friendly_devicename}: L1 W"
        id: ct1Watts
        filters:
          - multiply: 4
      gain_voltage: ${v1_cal}
      gain_ct: ${current_cal_ct1}
    phase_b:
      current:
        name: "${friendly_devicename}: ${appliance1} L1 A" #Dryer
        id: ct2Amps
      power:
        name: "${friendly_devicename}: ${appliance1} L1 W" #Dryer
        id: ct2Watts
      gain_voltage: ${v1_cal}
      gain_ct: ${current_cal_ct2}
    phase_c:
      current:
        name: "${friendly_devicename}: ${appliance2} L1 A" #Oven
        id: ct3Amps
      power:
        name: "${friendly_devicename}: ${appliance2} L1 W" #Oven
        id: ct3Watts
      gain_voltage: ${v1_cal}
      gain_ct: ${current_cal_ct3}
    frequency:
      name: "${friendly_devicename}: L1 Hz"
      device_class: frequency
    chip_temperature:
      name: "${friendly_devicename}: L1 Chip Temperature"
      id: l1_chip_temperature
      device_class: temperature
    line_frequency: 60Hz
    gain_pga: 1X
    update_interval: ${update_interval_s}
#IC2
  - platform: atm90e32
    cs_pin: 4
    phase_a:
      current:
        name: "${friendly_devicename}: ${appliance2} L2 A" #Oven
        id: ct4Amps
      power:
        name: "${friendly_devicename}: ${appliance2} L2 W" #Oven
        id: ct4Watts
      gain_voltage: ${v2_cal}
      gain_ct: ${current_cal_ct4}
    phase_b:
      current:
        name: "${friendly_devicename}: ${appliance1} L2 A" #Dryer
        id: ct5Amps
      power:
        name: "${friendly_devicename}: ${appliance1} L2 W" #Dryer
        id: ct5Watts
      gain_voltage: ${v2_cal}
      gain_ct: ${current_cal_ct5}
    phase_c:
#this voltage is only needed if monitoring 2 voltages
      voltage:
        name: "${friendly_devicename}: L2 V"
        id: ic2Volts
        accuracy_decimals: 1
      current:
        name: "${friendly_devicename}:  L2 A"
        id: ct6Amps
# The max value for current that the meter can output is 65.535. If you expect to measure current over 65A, 
# divide the gain_ct by 2 (120A CT) or 4 (200A CT) and multiply the current and power values by 2 or 4 by uncommenting the filter below
        filters:
          - multiply: 4
      power:
        name: "${friendly_devicename}: L2 W"
        id: ct6Watts
      gain_voltage: ${v2_cal}
      gain_ct: ${current_cal_ct6}
    frequency:
      name: "${friendly_devicename}: L2 Hz"
      device_class: frequency
    chip_temperature:
      name: "${friendly_devicename}: L2 Chip Temperature"
      id: l2_chip_temperature
      device_class: temperature
    line_frequency: 60Hz
    gain_pga: 1X
    update_interval: ${update_interval_s}
        
#Total Amps   
#  - platform: template
#    name: "${friendly_devicename}: Total Amps"
#    id: totalAmps
#    lambda: return id(ct1Amps).state + id(ct2Amps).state + id(ct3Amps).state + id(ct4Amps).state + id(ct5Amps).state + id(ct6Amps).state ;
#    accuracy_decimals: 2
#    unit_of_measurement: A
#    device_class: current
#    update_interval: ${update_interval_s}         

#Total Watts
#  - platform: template
#    name: "${friendly_devicename}: Total Watts"
#    id: totalWatts
#    lambda: return id(ct1Watts).state + id(ct2Watts).state + id(ct3Watts).state + id(ct4Watts).state + id(ct5Watts).state + id(ct6Watts).state ;
#    accuracy_decimals: 1
#    unit_of_measurement: W
#    device_class: energy
#    update_interval: ${update_interval_s}
#kWh
#  - platform: total_daily_energy
#    name: "${friendly_devicename}: Total kWh"
#    power_id: totalWatts
#    filters:
#      - multiply: 0.001
#    unit_of_measurement: kWh
#    device_class: energy
#    state_class: total_increasing

#Appliance3 Watts (HVAC)
  - platform: template
    name: "${friendly_devicename}: ${appliance3} W"
    id: appliance3TotalWatts
    lambda: return id(ct1Watts).state + id(ct6Watts).state - id(ct2Watts).state - id(ct3Watts).state - id(ct4Watts).state - id(ct5Watts).state ;
    accuracy_decimals: 1
    unit_of_measurement: "W"
    device_class: energy
    update_interval: ${update_interval_s}

#Appliance3 Amps (HVAC)   
  - platform: template
    name: "${friendly_devicename}: ${appliance3} A"
    id: appliance3TotalAmps
    lambda: return id(ct1Amps).state + id(ct6Amps).state - id(ct2Amps).state - id(ct3Amps).state - id(ct4Amps).state - id(ct5Amps).state ;
    accuracy_decimals: 2
    unit_of_measurement: "A"
    device_class: current
    update_interval: ${update_interval_s} 

#Appliance1 Watts (Dryer)
  - platform: template
    name: "${friendly_devicename}: ${appliance1} W"
    id: appliance1TotalWatts
    lambda: return id(ct2Watts).state + id(ct5Watts).state ;
    accuracy_decimals: 1
    unit_of_measurement: "W"
    device_class: energy
    update_interval: ${update_interval_s}

#Appliance1 Amps (Dryer)   
  - platform: template
    name: "${friendly_devicename}: ${appliance1} A"
    id: appliance1TotalAmps
    lambda: return id(ct2Amps).state + id(ct5Amps).state ;
    accuracy_decimals: 2
    unit_of_measurement: "A"
    device_class: current
    update_interval: ${update_interval_s} 

#Appliance2 Total Watts (Oven)
  - platform: template
    name: "${friendly_devicename}: ${appliance2} W"
    id: appliance2TotalWatts
    lambda: return id(ct3Watts).state + id(ct4Watts).state ;
    accuracy_decimals: 1
    unit_of_measurement: "W"
    device_class: energy
    update_interval: ${update_interval_s}

#Appliance2 Total Amps (Oven)   
  - platform: template
    name: "${friendly_devicename}: ${appliance2} A"
    id: appliance2TotalAmps
    lambda: return id(ct3Amps).state + id(ct4Amps).state ;
    accuracy_decimals: 2
    unit_of_measurement: "A"
    device_class: current
    update_interval: ${update_interval_s} 

#Watts - L1 & L2
  - platform: template
    name: "${friendly_devicename}: L1 & L2 W"
    id: l1l2Watts
    lambda: return id(ct1Watts).state + id(ct6Watts).state ;
    accuracy_decimals: 1
    unit_of_measurement: "W"
    device_class: energy
    update_interval: ${update_interval_s}
    
#Amps - L1 & L2
  - platform: template
    name: "${friendly_devicename}: L1 & L2 A"
    id: l1l2Amps
    lambda: return id(ct1Amps).state + id(ct6Amps).state ;
    accuracy_decimals: 2
    unit_of_measurement: "A"
    device_class: current
    update_interval: ${update_interval_s}  

#kWh - L1
  - platform: total_daily_energy
    name: "${friendly_devicename}: L1 kWh"
    power_id: ct1Watts
    filters:
      - multiply: 0.001
    accuracy_decimals: 1
    unit_of_measurement: "kWh"
    device_class: energy
    state_class: total_increasing

#kWh - L2
  - platform: total_daily_energy
    name: "${friendly_devicename}: L2 kWh"
    power_id: ct6Watts
    filters:
      - multiply: 0.001
    accuracy_decimals: 1
    unit_of_measurement: "kWh"
    device_class: energy
    state_class: total_increasing

#kWh - Whole House (L1 + L2)
  - platform: total_daily_energy
    name: "${friendly_devicename}: L1 & L2 kWh"
    power_id: l1l2Watts
    filters:
      - multiply: 0.001
    accuracy_decimals: 1
    unit_of_measurement: "kWh"
    device_class: energy
    state_class: total_increasing

switch:
  - platform: restart
    name: "${friendly_devicename}: Restart"
    
button:
  - platform: safe_mode
    name: "${friendly_devicename}: Restart (Safe Mode)"

time:
  - platform: sntp
    id: sntp_time   
    

To be continued…

2 Likes

I have a similar CircuitSetup system (24 channel so I cover nearly all my heavy use circuits).

In my opinion, there is no need to monitor both legs of most 240V circuits, just a single CT on one side will suffice. The difference is negligible for the HVAC 120V controls compared to the compressor loads. And if you know which phase is used to derive the 120V circuit you can put the CT on that side and always measure the total current.

For example, my heat pump draws 20W idle (controller electronics and thermostat power) but about 3600W when actively cooling.

I do use a CT on both line inputs L1 and L2 since I want to capture the instantaneous total load, looking at a single phase could be off since your 120V loads are divided between the two phases and switching in and out over time.

1 Like

@sillygoose Thank you for the feedback! :slight_smile: I realize there are ways to avoid using two CTs for 240V appliances but what I want to look out for are load imbalances and voltage drops that may be an indicator of some issue. I am not too familiar with the US split phase system so maybe I am off course anyhow I also like to get actual data rather than make up for it mathematically.

1 Like

Thanks for sharing this stuff. It got me looking at the CircuitSetup offerings… it seems to be exactly what I’m after for monitoring my entire house load. I currently have an Enphase Envoy and a Sense module. I’m quite happy with the Envoy but the Sense is a joke. I’ve found that I really want to have more resolution on what my demand looks like… at the individual circuit level would be great!

I really want to monitor Power Factor and I saw that you can get it from the CircuitSetup 6-way device. I’m in the United States so I have 2-phase at my meter/panel and I figure I’ll need to monitor voltage from both legs of the panel. Reading the docs, I see that you can separate the voltage monitoring by cutting JP12 and JP13 and using the VA2 pins with a separate 9-12V AC supply. I’m trying to figure out how best to map out the correct voltage with each CT sensor so this calculation is accurate for every circuit.

Reviewing the design of the boards, it looks like VA (the primary 9-12V AC supply) gets passed down the stack, the bottom-most pins. However, the second supply VA2 does not get passed down the stack. I think this means to monitor two phases most accurately across something like 36-42 circuits, the best method is to leave the JP12/JP13 intact and just build two complete stacks (separate ones for monitoring each phase). Do you agree?

Edit

I just found this alternate method for tracking 3-phase power on the bottom of the GitHub site:

  • Locate the bottom most pins (i.e. those which the ESP32 does not plug into), marked “VA-” and “VA+” for each add-on board. These normally provide the voltage reference to the add-on board from the main board
  • Cut these pins off
  • Solder a connector, or the wires from a transformer, in the bottom right holes marked “VA2” on the add-on boards. Polarity does not matter (if you are getting negative current readings, reverse the CT on the wire)
  • Do not sever JP12 and JP13

I think with this method, I could build a stack like this:

  1. 1x - main board
  2. 2x - add-on boards
  3. 1x - add-on board with VA pins cut
  4. 1x - add-on board with VA2 wired up for second 9V supply
  5. 3x - add-on boards

Then I would have access to all 42 circuits with accurate power factor calculation based on the correct AC voltage supply, I think… the CT sensors on the top 24 inputs would go to one voltage rail in the panel and the bottom 24 inputs would go to the other voltage rail in the panel.

@SpikeyGG - You are likely to get better answers if you post in CircuitSetup’s discussion on Github.

I actually have not yet installed the system as I have not fully resolved a minor issue with reference voltage issues where I see a 0.5V error jumping around. The fact it jumps around leads me to believe it is a sw issue. I believe the voltage error introduces errors on all other measurements that depend on that voltage reference measurement. Anyway that is besides the point…

My install is on the outdoor panel so I only have 5 breakers (240V) and the main split-phase feed to monitor. Therefore, I need 12 CTs which can be handled by a main board and add-on.

Are all 42 circuits in the same panel? If not, I would recommend getting a main board, and needed add-on boards per each panel. With regards to the voltage references… all I can tell you is that the ideal scenario is that you provide a voltage reference for each split phase to ensure the best accuracy in all your other measurements. If I recall correctly, the maximum voltage you can provide is 15Vac as the chips max input is 750mV and there is a 20:1 voltage divider in line. Anything above may saturate or damage the chip. One issue you may encounter is that the main board’s power draw (ESP power draw mostly) causes fluctuations on the main AC voltage reference, while the secondary voltage reference is unaffected due to no load being on it. Another user modified his board to power the ESP separately. My issue is that wiring all this, including the power supplies, is already quite messy (see my attempt trying to address this above) so I am still pondering on the way forward. Right now I don’t have bandwidth so it has been set aside for a few more weeks at least.

2 Likes