Power monitoring with BL0937 sensor

I have a device (Arlec PC399HA) that I had previously converted to Tasmota but I’ve been working to move things out of Tasmota and standardise on ESPHome. Calibrating the power metering in Tasmota was pretty easy but I’m having a bit of a struggle with ESPHome.

Without tinkering with the filters and just using the default current_resistor and voltage_divider values (which looking at the board, appears to be correct), the voltage that the device is reporting is 36.6V (should be around 237), current is 2.53a (should be around 0 with nothing plugged in) and power of 395W (again, should be around 0 with nothing plugged in). Looking at the various calibration guides, even without adding any calibration, the values reported back were in the ballpark of where they should be. Mine are well out though.

I have tinkered with the current_resistor and voltage_divider values which changed the reported values but didn’t come close to solving the problem. I’ve also tried adding filters, which did get the voltage reporting mostly correct but the power and current were still well off.

Here is my current config (with some tinkering commented out):

esphome:
  name: plug1

esp8266:
  board: esp01_1m
  

# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: "<password>"

ota:
  password: "<password>"

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Plug1 Fallback Hotspot"
    password: "<password>"

binary_sensor:
  - platform: gpio
    name: "Plug1 Button"
    pin:
      number: GPIO3
      mode:
        input: true
        pullup: true
      inverted: true
    on_press:
      - switch.toggle: relay

switch:
- platform: gpio
  id: blue_led
  pin:
    number: GPIO13
    inverted: true
- platform: gpio
  name: "Plug1 Relay"
  pin: GPIO14
  id: relay
  on_turn_on:
  - switch.turn_on: blue_led
  on_turn_off:
  - switch.turn_off: blue_led

# Use red LED for connectivity status indicator
# status_led:
#   pin:
#     number: GPIO3
#     inverted: true

sensor:
  - platform: hlw8012
    model: BL0937
    sel_pin:
      number: GPIO12
      inverted: true
    cf_pin: GPIO04
    cf1_pin: GPIO05
    # Higher value gives lower watt readout
    #current_resistor: "0.003"
    # Lower value gives lower voltage readout
    #voltage_divider: "10051"
    current:
      name: "Plug1 Current"
      accuracy_decimals: 2
      # filters:
      #   # Map from sensor -> measured value
      #   - calibrate_linear:
      #       - 2.45905 -> 0.0
      #       - 2.25141 -> 5.9
      #       - 2.24197 -> 4.3
      #       - 2.41786 -> 10.0

    voltage:
      name: "Plug1 Voltage"
      # filters:
      #   # Map from sensor -> measured value
      #   - calibrate_linear:
      #       - 33.46 -> 238.02
      #       - 33.11665 -> 235.24
      #       - 33.35223 -> 237.70
      #       - 33.30263 -> 238.82

    power:
      name: "Plug1 Power"
      # filters:
      #   # Map from sensor -> measured value
      #   - calibrate_linear:
      #       - 364.00403 -> 0
      #       - 363.19363 -> 1402.43
      #       - 362.51831 -> 1026.926
      #       - 360.49231 -> 2352.4            
    update_interval: 15s

I have a bunch of power monitoring plugs using BL0937
They are the same model from the same brand.

Non of them could use the default voltage_divider so even tho I used a quality multimeter to measure the resistors and used the exakt values to calculate the voltage_divider, I did’t even get close to real life measurements from the plugs.

My voltage_divider have used these, and its been a trial and error approach, and is close enough for me.
1675
1715
1725
1775

Now my values had to be lowered from the calculated values as it reported a lot higer voltage then real.
Yours appears to be reporting lower.

Now they report correct-ish voltage ± 0.5 volts or even less.
And current seams to be correct, I don’t have a calibrated power meter to test against.
I have not used any other filtering/calibrations then voltage_divider.

So just change voltage_divider until you have the correct reported voltage, then see if you get you need any filters etc on current.

Does the power (watt) read out work correctly for you with the settings you’ve used to get the voltage and current correct?

I believe so, but again I don’t have a power meter to compare to, but I do have a PZEM-004T V3 running with CT clamp on the main power line, comparing that sensor with the outlet, it looks like the outlet load amount matches the amount added on the PZEM-004T.

So my approach was to measure the voltage in the plug, with a good accurate multimeter.
Then checked the value reported in esphome, then change accordingly until i have same or as close as is acceptable.

Here is my config, this is from a common file with the config as I have the same power plug so the same config can be used on all of them. the substitution ${calib} stores the voltage_divider value per device config.
As you can see I don’t have any filters etc.

sensor:
  - platform: hlw8012
    model: BL0937
    sel_pin:
      number: GPIO12
      inverted: true
    cf_pin: GPIO04
    cf1_pin: GPIO05
    voltage_divider: ${calib}
    current:
      name: "${shortname} Current"
    voltage:
      name: "${shortname} Voltage"
    power:
      name: "${shortname} Power"
      id: totalMediaWatts
    energy:
      name: "${shortname} Energy"
    update_interval: 5s

I have poured over the BL0937 datasheet and can’t figure out what is the shortest update interval that it supports, or that Tasmota supports.
Do you know?

I have bought couple of smart plugs based on Tuya bk7231n and BL0937 chips. These plugs reports correct value when we run them with factory firmware + Smart Life App. However after using LibreTiny + esphome the Voltage reported are random and fluctuating. Below given is my complete yaml. Some of the configurations mentioned in this yaml are derived from various topics in this forum, modified for my requirement.

substitutions:
  relay_restore_mode: RESTORE_DEFAULT_OFF 
  devicename: plugvoltage #wpropumpvoltge
  hlw8012_update_interval: 5s
  friendly_name: plugvoltage
  device_description: Wipro Plug with Energy
  current_resistor: "0.0145" "#"0.001" <- Calibrated using this factory value
  voltage_divider: "11350" #"1600" <- Calibrated using this factory value

esphome:
  name: ${devicename}
  friendly_name: ${friendly_name}
  comment: ${device_description}

bk72xx:
  board: generic-bk7231n-qfn32-tuya  

# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: "yourapikeyhere"

ota:
  password: "yourotapassword"

wifi:
  networks:
  - ssid: !secret wifi_ssid
    password: !secret wifi_password
  
  - ssid: !secret wifi2_ssid
    password: !secret wifi2_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "yourplug hotspot"
    password: "yourpassword"

captive_portal:

web_server:
  port: 80

debug:
  update_interval: 10s

globals:
  - id: total_energy
    type: float
    restore_value: yes
    initial_value: '0.0'
  
sensor:
  - platform: uptime
    name: Uptime
    update_interval: 5min

  - platform: wifi_signal
    name: "WiFi Signal"
    update_interval: 5min
    
  - platform: hlw8012
    model: BL0937
    
    cf_pin: # Power Calculations
      number: GPIO7
      inverted: true

    cf1_pin: # Current or Voltage based on Sel Pin value. 
      number: GPIO8
      inverted: true

    sel_pin:
      number: GPIO24
      inverted: true

    change_mode_every: "4294967295"
    update_interval: 5s    

    current_resistor: ${current_resistor}
    voltage_divider: ${voltage_divider}

    power:
      name: "Plug Power"
      id: PlugPower
      filters:
        - skip_initial: 1
        - multiply: 0.97
        - lambda: if (x < 0.01) {return 0;} else {return x;}

    voltage:
      name: "Plug Voltage"
      id: PlugVoltage
      filters:
        - skip_initial: 1

    energy:
      name: "Plug Energy"
      id: PlugEnergy
      filters:
        - skip_initial: 1

  - platform: template
    id: Actual_Voltage
    name: "Actual Voltage"
    lambda: |-
      if (id(PlugPower).state > 1)
      {
        return id(PlugVoltage).state;
      }
      else
      {
        return (id(PlugVoltage).state * 1.14); #This one is not giving the actual but higher than the actual voltage under no load.
      }
    update_interval: 1s
    unit_of_measurement: V
    filters:      
       - sliding_window_moving_average:
          window_size: 5
          send_every: 5

  - platform: template  
    name: "plug Current"
    id: Plugcurrent
    icon: mdi:current-ac
    unit_of_measurement: A
    accuracy_decimals: 2
    update_interval: "5s"
    lambda: |-
      return (id(PlugPower).state / id(PlugVoltage).state * 0.95);
    filters:  
      - skip_initial: 5        

  # Reports the total Power so-far each day
  - platform: total_daily_energy
    name: Total Daily Energy
    icon: mdi:circle-slice-3
    power_id: PlugPower
    filters:
      # Multiplication factor from W to kW is 0.001
      - multiply: 0.001
    unit_of_measurement: kWh

binary_sensor:
  - platform: gpio
    id: binary_switch_1
    device_class: window
    pin:
      number: P10
      inverted: true
      mode: INPUT_PULLUP
    on_press:
      then:
        - switch.toggle: switch_1

output:
  - platform: libretiny_pwm
    id: output_led_1
    pin:
      number: P23
      inverted: true

light:
  - platform: status_led
    id: plug_led
    output: output_led_1

switch:
  - platform: gpio
    id: switch_1
    name: Dongle Power
    pin: P26
    icon: mdi:power-socket-uk
    on_turn_on:
      - light.turn_on: plug_led
    on_turn_off:
      - light.turn_off: plug_led
    restore_mode: ${relay_restore_mode}

status_led:
  pin:
    number: P6
    inverted: true

button:
  - platform: restart
    name: Restart

time:
  - platform: sntp
    id: sntp_time
    servers:
     - yourntpserverip

Looking at the last page - S; When SEL = 1, the CF1 outputs a high frequency pulse
of the voltage RMS. The current and voltage RMS calculation modules of BL0937 are separate
internally. SEL switching time ≤ 10uS.

I have with Arlec PC191HA smart plugs with Power Monitoring (which uses bk72xx WB2S and BL0937). I could not find any combination of voltage_divider and current_resistor which gave reasonable values for voltage and current.

I use these plugs to detect when the washing machine has finished, or if the chest freezer door was left open. I don’t need the readings to be particularly accurate - but the figures from default switching of SEL were just ridiculous.

I ended up choosing to keep the SEL pin at reporting voltage permanently, and calculating current from a template. FWIW, this is my yaml code:

sensor:
    # PC191HA includes a BL0937 chip for measuring power consumption
    #     and BL0937 is a variation of hlw8012, but using inverted SEL pin functionality
    #     Note that the first value reported should be ignored as inaccurate
  - platform: hlw8012
    model: BL0937     # note that the model must be specified to use special calculation parameters
    sel_pin:          # I believe that cf_pin reports either Voltage or Current depending on this select pin
      inverted: true  # determine whether true reports Voltage
      number: P24
    cf_pin:           # current or voltage (ele_pin: 7)
      inverted: true  # the logic of BL0937 is opposite from HLW8012
      number: P7
    cf1_pin:          #  Power (vi_pin: 8)
      inverted: true  # the logic of BL0937 is opposite from HLW8012
      number: P8

    ### Decided that I want Power and Voltage reported each time (not swapping with Current).  
    # I can choose not to keep swapping SEL pin, instead setting change_mode_every to a high value 
    #   This means I will have to calculate the value for current (as a template) from power / voltage
    initial_mode: "VOLTAGE"           # reports VOLTAGE or CURRENT
    change_mode_every: 4294967295     # about 4000 years before swapping.

    # Adjust according to the actual resistor values on board to calibrate the specific unit
    voltage_divider:  770       # LOWER VALUE GIVES LOWER VOLTAGE
    current_resistor: 0.001     # HIGHER VALUE GIVES LOWER WATTAGE

    # how the power monitoring values are returned to ESPHome
    voltage:
      name: $devicename Voltage
      id:   voltage
      unit_of_measurement: V
      accuracy_decimals: 2
      filters:
        - skip_initial: 1

    power:
      name: $devicename Power
      id:   power_sensor
      unit_of_measurement: W
      accuracy_decimals: 3
      filters:
        - skip_initial: 1
        - multiply: 0.97
        - lambda: if (x < 0.01) {return 0;} else {return x;}

    energy:
      name: $devicename Energy
      id: energy
      unit_of_measurement: kWh
      accuracy_decimals: 3
      filters:
        - skip_initial: 1
        - multiply: 0.001  # Multiplication factor from W to kW is 0.001
      on_value:
        then:
          - lambda: |-
              static float previous_energy_value = 0.0;
              float current_energy_value = id(energy).state;
              id(total_energy) += current_energy_value - previous_energy_value;
              previous_energy_value = current_energy_value;

# instead of alternating reporting Voltage and Current, I will report Current from a template 
  - platform: template  
    name: $devicename Current
    id: current
    unit_of_measurement: A
    accuracy_decimals: 3
    lambda: |-
      return (id(power_sensor).state / id(voltage).state );
    filters:  
      - skip_initial: 2     # give time for data to settle to avoid NaN