Floating point precision problem with gas meter readout via hmc5883l

Hi,

I’m trying to read out my Gasmeter via ESPHome and Home Assistant. At first I tried using a Reed Contact taking this as a starting point.

The problem, however, was that the display value of the physical meter and the calculated values were out of sync after about a day.

I blamed the reed contact and tried a different approach based on an HMC5883L and this guide

The problem, however, persisted. But now I was able to see continuous values from the HMC5883L, no signal loss, no spikes etc so I knew the readout was ok.

So I tried to debug this with Grafana. Below you see

  • the calculated display value from ESPHOME
  • the signal value of the HMC5883L sensor
  • the binary value “pulse” created from the HMC5883L sensor (see yaml below)

You see it works quite well until suddenly (where the ruler /mouse pointer is shown) the HMC5883L signal passes the “HIGH mark”, the binary value changes from 0 → 1 but the Gas meter display value is not incremented.

The ESP log itself shows that the increment has happend.

So my guess is this is a floating point precision problem. As far as I know there is no way to increase the precision with ESPHOME (to double or long) so I wonder what my options are to get this working correctly:

  1. set the initial display value (gv_gas_meter_displayinitial) from 16318.04 to 6318.04 and then somehow add 10000 in HomeAssistant again? (Short term solution, when the meter itself passes 20000 I need to ‘work around’ again…

  2. separate the values at the decimal point into two integers, e.g. INT1=16318 and INT2=04. Then I would need to find a way to “carry over” from INT2 to INT1 when going from 99 → 00 and find a way to combine the values in HomeAssistant again

  3. leave ESPHome and try a Raspberry PI Zero?

I’m really at a loss here how to proceed. Does anyone else already dealt floating point precision problems in ESPHome and can advise a solution?

Is any of my ideas how to circumvent the problem feasible?

Any help would be greatly appreciated!

Below my ESPHome Configuration, heavily inspired by @petsie and @cg089 :

substitutions:
  platform: ESP8266
  board: nodemcuv2
  device_name_short: "heesp18" # used by esp-home config
  friendly_name: "GasMeter:"
  device_description: "ESP8266 with GY-271 for magnetic readout of elster BK-G4 M"
  project_name: socrates.energy_monitor # format: author_name.project_name. Ref: https://esphome.io/components/esphome.html#esphome-creators-project
  update_interval_hmc5883l: 250ms
  main_cycle_interval: 500ms
  ip_address: 192.0.0.1

  impuls_factor: "0.01" # [m3] how much gas was measured during transition from low to high (one revelation of digit with magnet)


esp8266:
    board: ${board}

esphome:
  name: ${device_name_short}
  comment: ${device_description}
  build_path: ./build/${device_name_short}
  on_boot:
    then:
      - logger.log:
          level: info
          format: "BOOTMESSAGE:${device_name_short} API is connected, Device ready!"
      - globals.set:
          id: boot_counter
          value: !lambda "return id(boot_counter)+=1;"
  on_shutdown:
    then:
      - logger.log: ${device_name_short} is down!    


# ---------------------------------------
# (COMPONENT) LOG LEVEL OVERRIDE
# ---------------------------------------
logger:
  level: DEBUG
  logs:
    mqtt.component: DEBUG
    mqtt.client: ERROR
    sensor: INFO
    hmc5883l: INFO
    



# ---------------------------------------
# Base packages wifi, timeserver...
# ---------------------------------------
packages:
  base: !include common/base.yaml
  #base_webserver: !include common/base_webserver.yaml
  base_global: !include common/base_global.yaml



# ----------------------------------------------------------------
# Global variables
# ----------------------------------------------------------------
globals:
  - id: gv_gas_counter # gv_gas_counter (gas_impulse_counter)
    type: int
    restore_value: yes
    initial_value: "0"
  - id: gv_gas_counter_total # gv_gas_counter_total 
    type: float
    restore_value: yes
    initial_value: "0.0"
  - id: gv_m3_to_kwh_factor # gv_m3_to_kwh_factor ( ggf. über service anpassbar?)
    type: float
    restore_value: yes
    initial_value: "10.94"
  - id: gv_gas_meter_displayinital # (initial value when recording began - used to recalculate values upon setting new displayvalue via service)
    type: float
    restore_value: yes
    initial_value: "16318.04"
  - id: gv_gas_meter_display_value # current calculated value on the gas meter device
    type: float
    restore_value: yes
    initial_value: "16318.04"
  - id: gv_gas_meter_total_m3 # (gv_gas_meter_total_m3) total m3 recorded since startup of this device
    type: float
    restore_value: yes
    initial_value: "0.0"
  - id: gv_gas_impulse_high # boolean value set when uT > 50 and reset when uT < 0
    type: bool
    restore_value: no
    initial_value: 'false'
  - id: gv_gas_impulse_high_level_value # float value: level when magnetic field strength is considered HIGH
    type: float
    restore_value: yes
    initial_value: "0.0"
  - id: gv_gas_impulse_low_level_value # float value: level when magnetic field strength is considered !HIGH
    type: float
    restore_value: yes
    initial_value: "-50.0"



# ----------------------------------------------------------------
# Native API Component
#** This provides a service callable from HomeAssistant to re-set
#** or adjust the display value of the gasmeter. This should only
#** be neccessary if the ESP8266 was offline for maintenance.
# ----------------------------------------------------------------
api:
  id: espapi
  port: 6053
  reboot_timeout: 5min
  services:
    - service: set_gasmeterdisplay
      variables:
        my_newdisplayvalue: float
      then:
        - logger.log:
            format: "Setting Gasmeter Display value: %.2f"
            args: [my_newdisplayvalue]
        - globals.set:
            id: gv_gas_meter_display_value
            value: !lambda "return (my_newdisplayvalue);"
        - globals.set:
            id: gv_gas_counter_total
            value: !lambda "return (my_newdisplayvalue);"
        # - globals.set:
        #     id: gv_gas_meter_total_m3
        #     value: !lambda "return ((my_newdisplayvalue) - id(gv_gas_meter_displayinital));"
        # # - globals.set:
        #     id: gv_gas_counter
        #     value: !lambda "return ( ((my_newdisplayvalue) - id(gv_gas_meter_displayinital))/${impuls_factor} );"
        - logger.log:
            format: "Gasmeter Display value: %.2f"
            args: [id(gv_gas_meter_display_value)]


# ----------------------------------------------------------------
# Switch to restart, reset all, reset bootcounter
# ----------------------------------------------------------------
switch:
  - platform: template
    name: ${friendly_name} reset all
    turn_on_action:
      then:
        - lambda: |-
            id(boot_counter) = 0;
            id(gv_gas_counter_total) = 0.0;
            id(gv_gas_counter) = 0;
            id(gv_gas_meter_total_m3)= 0.0;
            id(gv_gas_meter_display_value)= 0.0;
            id(daily_value)= 0.0;
            id(hourly_value)= 0.0;
            id(bootcounter).update();
            id(gas_meter_display).update();
            id(gas_meter_total_m3).update();
            id(gas_meter_total_kw).update();
            id(gas_meter_total_kw_day).update();
        - logger.log: ${device_name_short} all values reset!


# ----------------------------------------------------------------
# Setup of I2C HMC5883L GY-271 magnetic field measurement
# ----------------------------------------------------------------

i2c:
  sda: GPIO4
  scl: GPIO5
  scan: true
  id: bus_a  
  frequency: 800kHz
  


# ----------------------------------------------------------------
# ALL SENSORS
# ----------------------------------------------------------------
sensor:
  - platform: hmc5883l
    address: 0x1E
#    field_strength_x:
#      name: "HMC5883L Field Strength X"
#      id: hmc5883l_x
    field_strength_y:
      name: "HMC5883L Field Strength Y"
      id: hmc5883l_y
#    field_strength_z:
#      name: "HMC5883L Field Strength Z"
#      id: hmc5883l_z
#      heading:
#        name: "HMC5883L Heading"
    #oversampling: 1x
    oversampling: 4x
      #oversampling: 64x
    #range: 130uT
    range: 250uT
      #range: 800uT
    update_interval: ${update_interval_hmc5883l}

    

  - platform: template
    name: ${friendly_name} DIC 0.01 m3 Impulse
    force_update: true
    id: gas_meter_dic_impulse
    accuracy_decimals: 0
    #unit_of_measurement: "m³"
    update_interval: 1s
    icon: "mdi:mdi-numeric-positive-1"
    #device_class: gas
    lambda: |-
      return id(gv_gas_impulse_high);

  - platform: template
    name: ${friendly_name} Meter Display Counter
    force_update: true
    id: gas_meter_display
    accuracy_decimals: 2
    unit_of_measurement: "m³"
    icon: "mdi:counter"
    device_class: gas
    lambda: |-
      return id(gv_gas_meter_display_value);

  - platform: template
    name: ${friendly_name} Acc. Gas Consumption This Day1
    id: gas_meter_m3_today
    state_class: measurement
    device_class: gas
    unit_of_measurement: "m³"
    accuracy_decimals: 2

  - platform: template
    name: ${friendly_name} Registered Impulses Total
    id: gas_meter_impulse_count
    state_class: measurement
    accuracy_decimals: 0
    lambda: |-
      return (id(gv_gas_counter));

  - platform: template
    name: ${friendly_name} Acc. Gas Consumption Total
    id: gas_meter_total_m3
    accuracy_decimals: 1
    unit_of_measurement: "m³"
    device_class: gas
    state_class: total_increasing
    lambda: |-
      return id(gv_gas_meter_total_m3);

  - platform: template
    name: ${friendly_name} Acc. Gas Consumption Total This Day
    id: gas_meter_total_m3_day
    unit_of_measurement: "m³"
    state_class: total_increasing
    accuracy_decimals: 1
    icon: mdi:gauge
    lambda: |-
      return (id(daily_value));

  - platform: template
    name: ${friendly_name} Gas per hour
    id: gas_meter_total_m3_hour
    unit_of_measurement: "m³"
    state_class: total_increasing
    accuracy_decimals: 1
    icon: mdi:gauge
    lambda: |-
      return (id(hourly_value));

  - platform: template
    name: ${friendly_name} Acc. Gas Energy Total 
    id: gas_meter_total_kw
    accuracy_decimals: 2
    device_class: energy
    state_class: measurement
    unit_of_measurement: "kWh"
    lambda: |-
      return (id(gv_gas_meter_total_m3) * id(gv_m3_to_kwh_factor));

  - platform: template
    name: ${friendly_name} Acc. Gas Energy Total This Day
    id: gas_meter_total_kw_day
    unit_of_measurement: "kWh"
    state_class: measurement
    device_class: energy
    accuracy_decimals: 2
    lambda: |-
      return (id(daily_value) * id(gv_m3_to_kwh_factor));

  - platform: template
    name: ${friendly_name} Acc. Gas Energy Total This Hour
    id: gas_meter_total_kw_hour
    unit_of_measurement: "kWh"
    state_class: measurement
    device_class: energy
    accuracy_decimals: 2
    lambda: |-
      return (id(hourly_value) * id(gv_m3_to_kwh_factor));







# ----------------------------------------------------------------
# INTERVAL - run PLC like every "cycle" 
#** This component allows you to run actions at fixed time intervals. 
#** For example if you want to toggle a switch every minute, you can 
#** use this component. Please note that it’s possible to achieve the 
#** same thing with the time.on_time trigger, but this technique is 
#** more light-weight and user-friendly.
# ----------------------------------------------------------------

interval:
  - interval: ${main_cycle_interval}
    then:
    - lambda: |-
       if (id(hmc5883l_y).state >= id(gv_gas_impulse_high_level_value) && !id(gv_gas_impulse_high)) {
          ESP_LOGI("main", "----------------------------------------------------------------------------------------");
           ESP_LOGI("main", " *** Value Update ***");
           
           ESP_LOGD("mein", "       [gv_gas_counter_total] as found: %f", id(gv_gas_counter_total) );
           id(gv_gas_counter_total) += ${impuls_factor};
           ESP_LOGD("mein", "       [gv_gas_counter_total] as left : %f", id(gv_gas_counter_total) );

           ESP_LOGD("mein", "       [gv_gas_meter_display_value] as found: %f", id(gv_gas_meter_display_value) );
           id(gv_gas_meter_display_value) += ${impuls_factor};
           ESP_LOGD("mein", "       [gv_gas_meter_display_value] as left : %f", id(gv_gas_meter_display_value) );

           ESP_LOGD("mein", "       [gv_gas_meter_total_m3] as found: %f", id(gv_gas_meter_total_m3) );
           id(gv_gas_meter_total_m3) += ${impuls_factor};
           ESP_LOGD("mein", "       [gv_gas_meter_total_m3] as left : %f", id(gv_gas_meter_total_m3) );

           ESP_LOGD("mein", "       [daily_value] as found: %f", id(daily_value) );
           id(daily_value) += ${impuls_factor};
           ESP_LOGD("mein", "       [daily_value] as left : %f", id(daily_value) );



           id(hourly_value) += ${impuls_factor};

           id(gv_gas_counter) += 1;	
           id(gv_gas_impulse_high) = true;

           id(gas_meter_display).update();
           id(gas_meter_total_m3).update();
           id(gas_meter_total_kw).update();
           id(gas_meter_total_kw_day).update();
           id(gas_meter_m3_today).publish_state(${impuls_factor});
           ESP_LOGI("main", "----------------------------------------------------------------------------------------");
           
        } else if (id(hmc5883l_y).state <= id(gv_gas_impulse_low_level_value) && id(gv_gas_impulse_high)) {
          id(gv_gas_impulse_high) = false;
        }   
    

Hey, I’m trying the exact same thing with my gas meter and a HMC5883l. Was running this combo with Python on a Raspberry Pi for last few years with very high precision and nearly no drift. The code from Gaszähler auslesen mit Magnetometer HMC5883 und Raspberry Pi

Now after setting up Homeassistant I would include my gas meter with ESPHome. But the counter is way off after just one day. I added logging today and after googling for Float Precision I landed here on your post.

Here is my log from the last few minutes

Time	level	Tag	Message
22:53:18	[I]	[interval:072]	gas_counter_total1: 45545.613281
22:53:18	[I]	[interval:074]	gas_counter_total2: 45545.625000
22:53:40	[I]	[interval:078]	gas_low
22:53:40	[I]	[interval:081]	----
22:55:10	[I]	[interval:071]	gas_high
22:55:10	[I]	[interval:072]	gas_counter_total1: 45545.625000
22:55:10	[I]	[interval:074]	gas_counter_total2: 45545.636719
22:55:30	[I]	[interval:078]	gas_low
22:55:30	[I]	[interval:081]	----
22:57:03	[I]	[interval:071]	gas_high
22:57:03	[I]	[interval:072]	gas_counter_total1: 45545.636719
22:57:03	[I]	[interval:074]	gas_counter_total2: 45545.648438
22:57:25	[I]	[interval:078]	gas_low
22:57:25	[I]	[interval:081]	----
22:58:55	[I]	[interval:071]	gas_high
22:58:55	[I]	[interval:072]	gas_counter_total1: 45545.648438
22:58:56	[I]	[interval:074]	gas_counter_total2: 45545.660156
22:59:15	[I]	[interval:078]	gas_low
22:59:15	[I]	[interval:081]	----
23:00:45	[I]	[interval:071]	gas_high
23:00:45	[I]	[interval:072]	gas_counter_total1: 45545.660156
23:00:45	[I]	[interval:074]	gas_counter_total2: 45545.671875
23:01:08	[I]	[interval:078]	gas_low
23:01:08	[I]	[interval:081]	----
23:02:38	[I]	[interval:071]	gas_high
23:02:38	[I]	[interval:072]	gas_counter_total1: 45545.671875
23:02:38	[I]	[interval:074]	gas_counter_total2: 45545.683594
23:03:00	[I]	[interval:078]	gas_low
23:03:00	[I]	[interval:081]	----
23:04:30	[I]	[interval:071]	gas_high
23:04:30	[I]	[interval:072]	gas_counter_total1: 45545.683594
23:04:30	[I]	[interval:074]	gas_counter_total2: 45545.695312
23:04:50	[I]	[interval:078]	gas_low
23:04:50	[I]	[interval:081]	----
23:06:20	[I]	[interval:071]	gas_high
23:06:20	[I]	[interval:072]	gas_counter_total1: 45545.695312
23:06:20	[I]	[interval:074]	gas_counter_total2: 45545.707031
23:06:43	[I]	[interval:078]	gas_low
23:06:43	[I]	[interval:081]	----
23:08:13	[I]	[interval:071]	gas_high
23:08:13	[I]	[interval:072]	gas_counter_total1: 45545.707031
23:08:13	[I]	[interval:074]	gas_counter_total2: 45545.718750
23:08:35	[I]	[interval:078]	gas_low
23:08:35	[I]	[interval:081]	----
23:10:05	[I]	[interval:071]	gas_high
23:10:05	[I]	[interval:072]	gas_counter_total1: 45545.718750
23:10:05	[I]	[interval:074]	gas_counter_total2: 45545.730469
23:10:25	[I]	[interval:078]	gas_low
23:10:25	[I]	[interval:081]	----
esphome:
  name: gasmeter

esp8266:
  board: esp01_1m
  restore_from_flash: true

# Enable logging
logger:
  level: INFO

#preferences:
#  flash_write_interval: 1min

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

ota:
  password: ""

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

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

captive_portal:

substitutions:
  friendly_name: "Gas"

i2c:
  sda: GPIO0
  scl: GPIO2
  scan: true

globals:
  - id: gas_counter_total
    type: float
    restore_value: yes
  - id: gas_high
    type: bool
    restore_value: no
    initial_value: 'true'

interval:
  - interval: 2500ms
    then:
    - lambda: |-
       if (id(hmc5883ly).state >= 200 && !id(gas_high)) {
          ESP_LOGI("interval", "gas_high");
          ESP_LOGI("interval", "gas_counter_total1: %f", id(gas_counter_total));
          id(gas_counter_total) += 0.01;
          ESP_LOGI("interval", "gas_counter_total2: %f", id(gas_counter_total));
          id(gas_high) = true;
          id(led).turn_on();
        } else if (id(hmc5883ly).state <= 100 && id(gas_high)) {
          ESP_LOGI("interval", "gas_low");
          id(gas_high) = false;
          id(led).turn_off();
          ESP_LOGI("interval", "----");
        }

web_server:
  port: 80

number:
  - platform: template
    name: "${friendly_name} Actual Meter Reading"
    optimistic: True
    min_value: 0
    max_value: 99999.99
    step: 0.01
    id: act_reading
    entity_category: config
    mode: box
    icon: 'mdi:counter'
    set_action:
      then:
        - lambda: |-
            ESP_LOGI("num_temp", "Value of gct befor %f", id(gas_counter_total));
            ESP_LOGI("num_temp", "Value to set %f", x);
            id(gas_counter_total) = x ;
            ESP_LOGI("num_temp", "Value of gct after %f", id(gas_counter_total));

sensor:
  - platform: wifi_signal
    name: "${friendly_name} WiFi Signal"
    update_interval: 60s
    entity_category: diagnostic
  - platform: uptime
    name: "${friendly_name} Uptime"
    id: uptime_sensor
    update_interval: 60s
    entity_category: diagnostic
    on_raw_value:
      then:
        - text_sensor.template.publish:
            id: uptime_human
            state: !lambda |-
              int seconds = round(id(uptime_sensor).raw_state);
              int days = seconds / (24 * 3600);
              seconds = seconds % (24 * 3600);
              int hours = seconds / 3600;
              seconds = seconds % 3600;
              int minutes = seconds /  60;
              seconds = seconds % 60;
              return (
                (days ? to_string(days) + "d " : "") +
                (hours ? to_string(hours) + "h " : "") +
                (minutes ? to_string(minutes) + "m " : "") +
                (to_string(seconds) + "s")
              ).c_str();

  - platform: hmc5883l
#    address: 0x68
    field_strength_x:
      name: "${friendly_name} HMC5883L X"
      id: hmc5883lx
    field_strength_y:
      name: "${friendly_name} HMC5883L Y"
      id: hmc5883ly
    field_strength_z:
      name: "${friendly_name} HMC5883L Z"
      id: hmc5883lz
    range: 250uT
    oversampling: 8
    update_interval: 1s

  - platform: template
    name: "${friendly_name} Gesamt"
    lambda: |-
      return id(gas_counter_total);
    update_interval: 5s
    unit_of_measurement: "m³"
    accuracy_decimals: 2
    icon: 'mdi:fire'
    device_class: gas
    state_class: total_increasing

  - platform: template
    name: "${friendly_name} Gesamt kWh"
    # Gasvolumen in m³ x Zustandszahl x Brennwert = Gasverbrauch in kWh
    # Zustandszahl: ?
    # Brennwert: ?
    # aktueller Wert aus letzter Rechnung Octopus 19.05.2022 10,086
    filters:
    lambda: |-
      return (id(gas_counter_total) * (10.086));
    update_interval: 5s
    unit_of_measurement: "kWh"
    accuracy_decimals: 2
    icon: 'mdi:fire'
    device_class: energy
    state_class: total_increasing

text_sensor:
  - platform: template
    name: "${friendly_name} Uptime Human Readable"
    id: uptime_human
    icon: mdi:clock-start
    entity_category: diagnostic

# Optional: Diese LED soll blinken, sobald ein Signal vom Gaszähler erkannt wird
output:
  - platform: gpio
    pin: GPIO1
    id: 'led'

As you can see I use float for the counter and increment by 0.01 but in the log you see it is always a little bit more or less what is added to the counter.
Have you managed to get this problem fixed? I’m thinking about some sort of offset calculation. Count with an integer, increment by one and divide just once before the output by 100 to get the 2 digits value.

Hi there,
sorry for the late reply.
But you are right on track with your workaround. This is exactly what needs to be done.

relevant configuration parts from my config (just for reference):

# -----------------------------------------------------------------
# Testing float to integers <WHOLE>.<REMAINDER>
  - id: gv_gas_meter_display_value_integer_part
    type: int
    restore_value: yes
    initial_value: "16319"
  
  - id: gv_gas_meter_display_value_fraction_part
    type: int
    restore_value: yes
    initial_value: "46"

# ----------------------------------------------------------------
# -----------------------------------------------------------------
# Testing float to integers <WHOLE>.<REMAINDER>
# This value is reported to HomeAssistant
  - platform: template
    name: ${friendly_name} Meter Display Counter
    force_update: true
    id: gas_meter_display_integer
    accuracy_decimals: 2
    unit_of_measurement: "m³"
    icon: "mdi:counter"
    device_class: gas
    state_class: total_increasing
    lambda: |-
      return ( id(gv_gas_meter_display_value_integer_part) + (id(gv_gas_meter_display_value_fraction_part)*0.01) );

# -----------------------------------------------------------------
interval:
  - interval: ${main_cycle_interval}
    then:
    - lambda: |-
       if (id(hmc5883l_z).state <= ${zero_crossing_value} && !id(gv_gas_impulse_high)) {
          ESP_LOGI("main", "----------------------------------------------------------------------------------------");
           ESP_LOGI("main", " *** Value Update ***");
           
           //ESP_LOGD("main", "       [gv_gas_counter_total] as found: %f", id(gv_gas_counter_total) );
           //id(gv_gas_counter_total) += ${impuls_factor};
           //ESP_LOGD("main", "       [gv_gas_counter_total] as left : %f", id(gv_gas_counter_total) );

           ESP_LOGD("main", "       [gv_gas_meter_display_value_float] as found: %f", id(gv_gas_meter_display_value_float) );
           id(gv_gas_meter_display_value_float) += ${impuls_factor};
           ESP_LOGD("main", "       [gv_gas_meter_display_value_float] as left : %f", id(gv_gas_meter_display_value_float) );

           ESP_LOGD("main", "       [gv_gas_meter_total_m3] as found: %f", id(gv_gas_meter_total_m3) );
           id(gv_gas_meter_total_m3) += ${impuls_factor};
           ESP_LOGD("main", "       [gv_gas_meter_total_m3] as left : %f", id(gv_gas_meter_total_m3) );

           ESP_LOGD("main", "       [daily_value] as found: %f", id(daily_value) );
           id(daily_value) += ${impuls_factor};
           ESP_LOGD("main", "       [daily_value] as left : %f", id(daily_value) );

           // -----------------------------------------------------------------
           // Test with two integers as <WholePartInt>.<RemainderInt>
          
           ESP_LOGD("main", "       [gv_gas_meter_display_value_fraction_part] as found: %i", id(gv_gas_meter_display_value_fraction_part) );
           id(gv_gas_meter_display_value_fraction_part) +=1;
           ESP_LOGD("main", "       [gv_gas_meter_display_value_fraction_part] as left : %i", id(gv_gas_meter_display_value_fraction_part) );

           ESP_LOGD("main", "       [gv_gas_meter_display_value_integer_part] as found: %i", id(gv_gas_meter_display_value_integer_part) );
           if (id(gv_gas_meter_display_value_fraction_part) >= 100) {
             ESP_LOGD("main", " *** CARRY OVER DETECTED ... compensating...");
             id(gv_gas_meter_display_value_integer_part) +=1;
             id(gv_gas_meter_display_value_fraction_part) = id(gv_gas_meter_display_value_fraction_part) - 100;

             ESP_LOGD("main", "       [gv_gas_meter_display_value_integer_part] as left : %i", id(gv_gas_meter_display_value_integer_part) );
             ESP_LOGD("main", "       [gv_gas_meter_display_value_fraction_part] as left : %i", id(gv_gas_meter_display_value_fraction_part) );
           }

           
           id(gas_meter_display_integer).update();
           // -----------------------------------------------------------------

Hope this helps :slight_smile:

3 Likes

Hey, thank you very much for the reply. I managed to get my gasmeter running with integer counting. And it is rock solid for the ~3 days now. I just count with integers and calculate the floating point only once. I also managed to get an input field in the webserver to set the actual reading whenever needed.

Here’s my code:

esphome:
  name: gasmeter

esp8266:
  board: d1_mini
  restore_from_flash: true

# Enable logging
logger:
  level: INFO

preferences:
  flash_write_interval: 1min

# Enable Home Assistant API
api:
  encryption:
    key: "kZenN+1T6/LU9AEwUEG2hzp2OCHmjwtK2brsk15Zv/o="

ota:
  password: "0a8f2df9223b98114d914b425376edea"

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

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Gasmeter Fallback Hotspot"
    password: "8ero5LJdrhQ4"

captive_portal:

substitutions:
  friendly_name: "Gas"

i2c:
  scan: true

globals:
  - id: gas_counter_total
    type: int
    initial_value: '4554851'
    restore_value: yes
  - id: gas_high
    type: bool
    restore_value: no
    initial_value: 'true'

interval:
  - interval: 2500ms
    then:
    - lambda: |-
       if (id(hmc5883ly).state >= 200 && !id(gas_high)) {
          ESP_LOGD("interval", "gas_high");
          ESP_LOGD("interval", "gas_counter_total1: %d", id(gas_counter_total));
          id(gas_counter_total) += 1;
          ESP_LOGD("interval", "gas_counter_total2: %d", id(gas_counter_total));
          id(gas_high) = true;
          id(led).turn_on();
        } else if (id(hmc5883ly).state <= 100 && id(gas_high)) {
          ESP_LOGD("interval", "gas_low");
          id(gas_high) = false;
          id(led).turn_off();
          ESP_LOGD("interval", "----");
        }

web_server:
  port: 80

number:
  - platform: template
    name: "${friendly_name} Set Meter Reading"
    optimistic: True
    min_value: 0
    max_value: 99999.99
    step: 0.01
    id: act_reading
    entity_category: config
    mode: box
    icon: 'mdi:counter'
    set_action:
      then:
        - lambda: |-
            ESP_LOGD("num_temp", "Value of gct before %d", id(gas_counter_total));
            ESP_LOGD("num_temp", "Value to set float %f", x);
            int d;
            d = floor(x*100);
            ESP_LOGD("num_temp", "Value to set int %d", d);
            id(gas_counter_total) = d ;
            ESP_LOGD("num_temp", "Value of gct after %d", id(gas_counter_total));

sensor:
  - platform: wifi_signal
    name: "${friendly_name} WiFi Signal"
    update_interval: 60s
    entity_category: diagnostic
    filters:
      - sliding_window_moving_average:
          window_size: 6
          send_every: 6

  - platform: uptime
    name: "${friendly_name} Uptime"
    id: uptime_sensor
    update_interval: 60s
    entity_category: diagnostic
    on_raw_value:
      then:
        - text_sensor.template.publish:
            id: uptime_human
            state: !lambda |-
              int seconds = round(id(uptime_sensor).raw_state);
              int days = seconds / (24 * 3600);
              seconds = seconds % (24 * 3600);
              int hours = seconds / 3600;
              seconds = seconds % 3600;
              int minutes = seconds /  60;
              seconds = seconds % 60;
              return (
                (days ? to_string(days) + "d " : "") +
                (hours ? to_string(hours) + "h " : "") +
                (minutes ? to_string(minutes) + "m " : "") +
                (to_string(seconds) + "s")
              ).c_str();

  - platform: hmc5883l
#    address: 0x68
#    field_strength_x:
#      name: "${friendly_name} HMC5883L X"
#      id: hmc5883lx
    field_strength_y:
      name: "${friendly_name} HMC5883L Y"
      id: hmc5883ly
#    field_strength_z:
#      name: "${friendly_name} HMC5883L Z"
#      id: hmc5883lz
    range: 250uT
    oversampling: 8
    update_interval: 1s

  - platform: template
    name: "${friendly_name} Gesamt m³"
    lambda: |-
      return ((float)id(gas_counter_total) / (float)100);
    update_interval: 5s
    unit_of_measurement: "m³"
    accuracy_decimals: 2
    icon: 'mdi:fire'
    device_class: gas
    state_class: total_increasing

  - platform: template
    name: "${friendly_name} Gesamt kWh"
    # Gasvolumen in m³ x Zustandszahl x Brennwert = Gasverbrauch in kWh
    # Zustandszahl: ? Brennwert: ?
    # aktueller Wert aus letzter Rechnung Octopus 19.05.2022 10,086
    filters:
    lambda: |-
      return ((float)id(gas_counter_total) / (float)100 * (10.086));
    update_interval: 5s
    unit_of_measurement: "kWh"
    accuracy_decimals: 2
    icon: 'mdi:fire'
    device_class: energy
    state_class: total_increasing

text_sensor:
  - platform: template
    name: "${friendly_name} Uptime Human Readable"
    id: uptime_human
    icon: mdi:clock-start
    entity_category: diagnostic

# Optional: Diese LED soll blinken, sobald ein Signal vom Gaszähler erkannt wird
light:
  - platform: status_led
    pin: D5
    id: 'led'

Hope someone will find this helpful

1 Like