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:
-
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…
-
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
-
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;
}