Old analog gas meter located outdoors

Here’s my code minus security stuff

esphome:
  name: "gas-meter"
  friendly_name: Gas Meter

esp32:
  board: esp32dev
  framework:
    type: arduino

# Enable logging
logger:

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

ota:


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

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

captive_portal:

globals:
   - id: gas_counter_total
     type: long
     restore_value: no
     initial_value: '0'
   - id: gas_counter
     type: long
     restore_value: no
     initial_value: '0'
   - id: gas_high
     type: bool
     restore_value: no
     initial_value: 'false'

interval:
  - interval: 0.1s
    then:
    - lambda: |-
       if (id(gasx).state > 15.25 && !id(gas_high)) {
          id(gas_counter_total) += 1;
          id(gas_counter) += 1;	
          id(gas_high) = true;
        } else if (id(gasx).state < 7.75 && id(gas_high)) {
          id(gas_high) = false;
        }   

i2c:
  sda: GPIO21 
  scl: GPIO22
  #scan: true
  frequency: 10kHz


sensor:
  - platform: qmc5883l
    address: 0x0D
    field_strength_x:
      name: "Gas Meter Field Strength X"
      id: gasx
      internal: true
#    field_strength_y:
#      name: "Gas Meter Field Strength Y"
#      id: gasy
#    field_strength_z:
#      name: "Gas Meter Field Strength Z"
#      id: gasz
#      internal: true
#    heading:
#      name: "Gas Meter Heading"
    range: 200uT
    oversampling: 512x
    update_interval: 0.1s

#8 counts per cubic foot, multiplied by 2 to get per minute based on update interval
  - platform: template    
    name: "Gas Rate"
    lambda: |-
      int temp = id(gas_counter);
      id(gas_counter) -= temp;
      float temp2 = temp;
      float temp3 = (temp2/8)*2; 
      return temp3;
    update_interval: 30s
    unit_of_measurement: ft³/min
    device_class: 'gas'

  - platform: template    
    name: "Gas Total"
    lambda: |-
      float temp = id(gas_counter_total);
      return temp/8;
    update_interval: 1s
    unit_of_measurement: 'ft³'
    state_class: 'total_increasing'
    device_class: 'gas'    

Mine generally don’t change either. ALTHOUGH a few weeks ago I happened to notice that my gas wasn’t getting picked up anymore. Nothing about the position of the sensor changed, but it just randomly started reporting different max/min values. I’ve also had a sensor keep reporting values, but essentially stop working (the values change but don’t really track to anything anymore). A quick change out of the sensor and all worked again.

I think these are just really cheap sensors both in price and reliability. :slight_smile:

I’ve changed the code up a bit to handle changes to sensor reading that occur at boot time. As long as you’re reading from the x-axis this code will automatically find your max/min values. It also exposes an option to reset the “calibration” in the event it stop reporting.

This now displays the min and max axis readings (and lets you set them from HA) and lets you set your frequency interval

So here’s everything before it’s fully calibrated (just powered on, waiting for gas to be used)

and here’s everything after the gas started to be used and it was able to find the max and min values

I haven’t found a way for it to recognize when it has drifted away from being calibrated and is no longer reporting, but for the time being I’m satisfied with this code.
Anyone who wants to use it will need to change the wifi info to fit their setup unless you’re using the same name for your !secret, and you’ll need to enter your api encryption information, but I wanted to include the full code (minus my security information) for reference.

substitutions:
  name: "gas-meter"
  friendly_name: "Gas Meter"

esphome:
  name: "${name}"
  friendly_name: "${friendly_name}"

esp32:
  board: esp32dev
  framework:
    type: arduino

# Enable logging
logger:
#  level: DEBUG

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

ota:

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

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

captive_portal:

globals:
   - id: gas_counter_total
     type: long
     restore_value: no
     initial_value: '0'

   - id: gas_counter
     type: long
     restore_value: no
     initial_value: '0'

   - id: gas_high
     type: bool
     restore_value: no
     initial_value: 'false'

   - id: gas_max_mid
     type: float
     restore_value: no
     initial_value: '-2000.0'

   - id: gas_min_mid
     type: float
     restore_value: no
     initial_value: '2000.0'

interval:
  - interval: 0.1s
    then:
      - lambda: |-
          auto maxCall = id(gas_max).make_call();
          auto minCall = id(gas_min).make_call();
          // Set min and max values using the x-axis sensor
          if (id(gasx).state > id(gas_max).state) {
            maxCall.set_value(id(gasx).state);
            maxCall.perform();
            if (id(calibrated).state ) {
              id(gas_max_mid) = (id(gas_max).state-id(gas_min).state)*0.75+id(gas_min).state;
              id(gas_min_mid) = (id(gas_max).state-id(gas_min).state)*0.25+id(gas_min).state;
            }
          } 
          
          if (id(gasx).state < id(gas_min).state) {
            minCall.set_value(id(gasx).state);
            minCall.perform();
            if (id(calibrated).state ) {
              id(gas_max_mid) = (id(gas_max).state-id(gas_min).state)*0.75+id(gas_min).state;
              id(gas_min_mid) = (id(gas_max).state-id(gas_min).state)*0.25+id(gas_min).state;
            }
          }

          // If  the device is calibrated process readings from teh x-axis sensor to calculate gas usage
          if (id(calibrated).state ) {
            if (id(gasx).state > id(gas_max_mid) && !id(gas_high)) {
              id(gas_counter_total) += 1;
              id(gas_counter) += 1;	
              id(gas_high) = true;
            } else if (id(gasx).state < id(gas_min_mid) && id(gas_high)) {
              id(gas_high) = false;
          }   
          
          // If the device is not calibrated, wait until the difference between the min/max value is greater than 10 and less than 1000
          // On first boot when device is calibrating, this might result in a couple seconds of bad readings, but what can you do?
          } else if ((fabs(id(gas_max).state-id(gas_min).state) > 10) && (fabs(id(gas_max).state-id(gas_min).state) < 1000.0)) {
            id(calibrated).publish_state(true);
            id(gas_max_mid) = (id(gas_max).state-id(gas_min).state)*0.75+id(gas_min).state;
            id(gas_min_mid) = (id(gas_max).state-id(gas_min).state)*0.25+id(gas_min).state; 
          }

i2c:
  sda: GPIO21 
  scl: GPIO22
  #scan: true
  frequency: 10kHz

# Reports back to HA when device considers itself to be "calibrated"
binary_sensor:
  - platform: template
    name: "${friendly_name} Calibrated"
    id: calibrated
    device_class: CONNECTIVITY
    publish_initial_state: true

# Uncomment whichever axis gives you the best reading
# For initial setup uncomment all of them and comment out internal: true so you can see historical values in HA
# Then choose the Axis that gives you the greatest variation in it's reading (should look like a sine wave)
# Once you've selected the best axis comment out the rest and set internal: true again.
# If an axis other than X is used you will need to change all the gasx references to gasy or gasz depending on which axis you've selected.
sensor:
  - platform: qmc5883l
    address: 0x0D
    field_strength_x:
      name: "Gas Meter Field Strength X"
      id: gasx
      internal: true
#    field_strength_y:
#      name: "Gas Meter Field Strength Y"
#      id: gasy
#      internal: true
#    field_strength_z:
#      name: "Gas Meter Field Strength Z"
#      id: gasz
#      internal: true
#    heading:
#      name: "Gas Meter Heading"
#      internal: true
    range: 200uT
    oversampling: 512x
    update_interval: 0.1s

#8 counts per cubic foot, multiplied by 2 to get per minute based on update interval
  - platform: template    
    name: "${friendly_name} Gas Rate"
    lambda: |-
      if (id(calibrated).state ) {
        int temp = id(gas_counter);
        id(gas_counter) -= temp;
        float temp2 = temp;
        float temp3 = (temp2/id(frequency).state)*2; 
        return temp3;
      } else {
        return 0;
      }
    update_interval: 30s
    unit_of_measurement: ft³/min
    device_class: 'gas'

  - platform: template    
    name: "${friendly_name} Gas Total"
    lambda: |-
      if (id(calibrated).state ) {
        float temp = id(gas_counter_total);
        return temp/id(frequency).state;
      } else {
        return 0;
      }
    update_interval: 1s
    unit_of_measurement: 'ft³'
    state_class: 'total_increasing'
    device_class: 'gas'

# Used to reset calibration.  If for any reason your device is no longer recording data then try resetting the calibration
button:
  - platform: template
    name: "${friendly_name} Reset Calibration"
    id: Calibrate
    icon: "mdi:chart-histogram"
    on_press:
      then:
        lambda: |-
          auto maxCall = id(gas_max).make_call();
          auto minCall = id(gas_min).make_call();
          maxCall.set_value(-1000.0);
          minCall.set_value(1000.0);
          maxCall.perform();
          minCall.perform();
          id(calibrated).publish_state(false);
          id(gas_max_mid) = 500.0;
          id(gas_min_mid) = -500.0;

# Display some useful variables to HA.
number:
  - platform: template
    optimistic: true
    name: "${friendly_name} Cycle Frequency"
    id: frequency
    update_interval: never
    initial_value: 8
    min_value: 1
    max_value: 500
    step: 1
    restore_value: true
  
  - platform: template
    optimistic: true
    name: "${friendly_name} Max Value"
    id: gas_max
    update_interval: never
    initial_value: -1000.0
    min_value: -5000.0
    max_value: 5000.0
    step: 0.00001
    restore_value: false
    unit_of_measurement: ft³/min
    device_class: 'gas'
  
  - platform: template
    optimistic: true
    name: "${friendly_name} Min Value"
    id: gas_min
    update_interval: never
    initial_value: 1000.0
    min_value: -5000.0
    max_value: 5000.0
    step: 0.00001
    restore_value: false
    unit_of_measurement: ft³/min
    device_class: 'gas'
1 Like

I haven’t really dug in and read through all of your post and code, but what you have done looks awesome, and makes me remember why I’m a part of this community.
I don’t have the expertise to do something like this myself, so seeing others do it, helps me learn and understand the details more.

1 Like

Thanks! I wish I knew enough to have it A) look at all three axes and select the one with the greatest range, and B) have it recognize drift.

And it might also be smart to have to lock in the values after a couple minutes of being “calibrated” so a random bad reading doesn’t skew everything, or do a background calibration while gas is flowing to make sure limits are still accurate.

I just installed my gas meter sensor with the information I learned from this thread. I didn’t realize these types of meters could be read with a magnetometer until I saw this, so thank you!

I started with the code from @brooksben11 but modified it heavily. I had some hysteresis code for calculating sump pump cycles that I leveraged, and I also set it up so that the actual meter reading could be sent via a service call to the ESP device, so that it reflects what you see on your gas bill. But be warned it will revert to zero after an ESP reboot. I also used substitutions and added an uptime timestamp. Code should be able to be leveraged pretty easily by changing the gas meter parameters and changing the three “passwords” (api key, OTA password, and fallback hotspot password).

substitutions:
  # Device Naming
  devicename: gas-meter
  friendly_name: Gas Meter
  device_description: Measurement of gas consumption from diaphragm-style gas meter

  # Gas Meter Parameters
  measurement_unit: ft³
  pulses_per_measurement_unit: '1/0.111' # 0.111 ft³ per rev, and 1 pulse per rev.
  pulse_peak: '-1.6' # µT; the code will use a set point which is 75% of the range to ensure it triggers
  pulse_valley: '-13' # µT; the code will use a set point which is 75% of the range to ensure it triggers
  pulse_min_cycle_duration: '0.1' # seconds; anything shorter than this is assumed to be noise and will be ignored

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

esp8266:
  board: nodemcu

# Enable logging
logger:
  level: INFO #WARN

# Enable Home Assistant API
api:
  encryption:
    key: "your_encryption_key"
  services:
    - service: set_meter_reading
      variables:
        meter_reading: float
      then:
        - logger.log:
            format: "Setting Meter Reading: %.1f"
            args: [meter_reading]
        - globals.set:
            id: counter_total
            value: !lambda |-
                return int(meter_reading * ($pulses_per_measurement_unit * 1.0));
##### Set meter reading from HA using the services tab in developer tools:
## service: esphome.<device_name>_set_meter_reading
## data:
##   meter_reading: "125000"

ota:
  password: "your_ota_password"

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

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

captive_portal:

# Sync time with Home Assistant.
time:
  - platform: homeassistant
    id: time_homeassistant
    on_time_sync:
      - component.update: sensor_uptime_timestamp
    
# I2C bus setup for communication with magnetometer
i2c:
  sda: D1
  scl: D2

globals:
  - id: counter_total
    type: uint32_t
    restore_value: no
    initial_value: '0'

################################################################################
# Binary Sensors
################################################################################
binary_sensor:
  # Hysteresis sensor to trigger pulses
  - platform: template
    name: "${friendly_name} Pulse Trigger"
    id: pulse_trigger
    internal: True
    lambda: |-
      if (id(field_z).state > id(trigger_high_limit).state) {
        return true;
      } else if (id(field_z).state < id(trigger_low_limit).state) {
        return false;
      } else {
        return {};
      }
    filters:
      - delayed_on: !lambda |-
          return $pulse_min_cycle_duration/2.0 * 1000.0; 
      - delayed_off: !lambda |-
          return $pulse_min_cycle_duration/2.0 * 1000.0;
    on_press:
      then:
        # when state transisions from false to true, increment the pulse counter 
        - lambda: |-
            id(counter_total) += 1;
            ESP_LOGI("counter", "Counter is %li", id(counter_total));

################################################################################
# Sensors
################################################################################
sensor:
  # Uptime sensor.
  - platform: uptime
    name: ${friendly_name} Uptime Internal
    id: sensor_uptime
    internal: True
  - platform: template
    id: sensor_uptime_timestamp
    name: "${friendly_name} Uptime"
    device_class: timestamp
    entity_category: diagnostic
    accuracy_decimals: 0
    update_interval: never
    lambda: |-
      static float timestamp = (
        id(time_homeassistant).utcnow().timestamp - id(sensor_uptime).state
      );
      return timestamp;

  # Magnetometer
  - platform: qmc5883l
    address: 0x0D
    range: 200uT
    oversampling: 512x
    update_interval: 0.1s
#    heading:
#      name: "Heading"
#      id: heading
#    field_strength_x:
#      name: "Field Strength X"
#      id: field_x
#    field_strength_y:
#      name: "Field Strength Y"
#      id: field_y
    field_strength_z:
      name: "Field Strength Z"
      id: field_z
      internal: True

  # Trigger limits
  - platform: template
    name: "Trigger High Limit"
    id: trigger_high_limit
    internal: True
    lambda: |-
      return $pulse_peak - 0.25 * ($pulse_peak - $pulse_valley);
  - platform: template
    name: "Trigger Low Limit"
    id: trigger_low_limit
    internal: True
    lambda: |-
      return $pulse_valley + 0.25 * ($pulse_peak - $pulse_valley);

  # Gas meter reading provided to Home Assistant
  - platform: template
    name: None
    id: value
    internal: False
    device_class: "gas"
    unit_of_measurement: $measurement_unit
    state_class: "total_increasing"
    icon: mdi:meter-gas
    accuracy_decimals: 1
    update_interval: 30s
    lambda: |-
      return id(counter_total) / ($pulses_per_measurement_unit * 1.0);

  # Gas meter flow rate provided to Home Assistant (average over past 5 minutes)
  - platform: template
    name: Rate
    id: rate
    internal: False
    device_class: "gas"
    unit_of_measurement: $measurement_unit/min
    state_class: "measurement"
    icon: mdi:meter-gas
    accuracy_decimals: 2
    update_interval: 300s
    lambda: |-
      static uint32 previous_counter = id(counter_total);
      int delta_counts = id(counter_total) - previous_counter;
      previous_counter = id(counter_total);
      return delta_counts / ($pulses_per_measurement_unit * 1.0) * 60.0/300.0;
1 Like

Nice work!, This one will take me some brain cells to understand completely I think. :slight_smile:

The only complicated piece of code is the binary sensor. Think of it like an over-center toggle switch where it takes a certain amount of effort to flip the switch. In this code, the toggle doesn’t become true unless the magnetometer reaches 75% of the max value, and doesn’t become false unless it falls below 75% of the minimum value. Any reading between that won’t change the state of the binary sensor so it remains in whatever state it was already in.

In addition to the hysteresis, there is also a debounce where the value has to remain past the threshold for a set amount of time before the binary sensor is allowed to toggle.

Every time the binary sensor toggles from false to true, the meter reading is incremented by one unit.

From what I’ve seen, the readings from the magnetometer are pretty clean so this stuff isn’t strictly necessary but I already had the code and making the sensor extra robust doesn’t hurt anything.

2 Likes

Hi, I have a question. How does the sensor detect a change in the magnetic field if the moving membrane is not made of metal?

My gas meter is this one: RSE/2001 LA by Samgas - Fiorentini. https://www.fiorentini.com/wp-content/uploads/2021/03/rsepm_manual_ENG.pdf

I made the mistake of asking my gas company whether it is acceptable to attach a small non-invasive device to the side of the meter for purposes of reading its measurement. I showed the hardware, including part numbers of the device and I even pointed to the forum pages. After ignoring me for two weeks, I sent another reminder and within the hour, the answer returned with a resounding “NO”. In fact, I could be liable for penalties and meter tampering which could lead to a criminal charge. Of course they had no alternative method to offer. They said I should purchase and install my own gas meter downstream of the revenue meter, and then attach my gizmo to that. I pushed back explaining that other gas utilities either allow meter reading devices or they offer their own smart gateway that I could lease. In reply, they provided a link to their website explaining the benefits of caulking my windows to reduce energy use. That was their not-so-subtle way of telling me to “f$#k off”. Customer service at its finest.

Who is your gas company?
I saw the tech come around the other day to check the meter reading and was paranoid they would say something, The guy didn’t seem to notice it or care.

To be frank, I’m not sure what you were expecting. There is no way they were going to provide written authorization for you to attach some handmade device onto their meter. Their non-response was probably the best they could offer you, and then you asked again. You’ll have to make a decision for yourself whether there’s truly risk that you’ll face fines or criminal charges for measuring the magnetic field near your meter.

I’m looking into using RTL-SDR monitor to read the AMR signal. I’m still researching. Anyone else try this before?
allangood/rtlamr2mqtt: Docker container to send rtlamr readings to a mqtt broker (github.com)

This thread is more focused on our “dumb” gas meters, we had to go this route since there is no signal to read like the Smart meters have. I know personally my Gas and water meters are both “dumb” or “non-smart” so no signal to read and had to revert to magnetic fields (Gas) and optical recognition (water).

I used that to read my water meter until last year when the water company swapped the meter with a different type; now I use an ESPcam with AI on the edge. I tried to read my gas meter via the same RTL/SDR method but came to the conclusion that my gas meter only sends out a reading when a command is sent to it. So only once/month when the utility company ‘tickles’ it. Not useful for HA consumption. YMMV.

1 Like

I also use AI on the Edge for my water meter. it’s good being in the basement protected, but I can’t see a good setup for the gas meter being outside and vulnerable to the environment.

I made a small improvement to @abishur’s code. I’ve added drift compensation and as a benefit measurement error detection. This is simply achieved by slowly reducing the max value and increasing the min value. If the min and max meet there is an issue with the reading and calibrated gets set back to false.

Edit: I’ve fixed an issue I had with the first value being erroneous. I just added a short delay to the interval.

substitutions:
  device_name: "gas-meter"
  friendly_name: gas-meter

esphome:
  name: ${device_name}
  friendly_name: ${friendly_name}

esp32:
  board: esp32-c3-devkitm-1
  framework:
    type: arduino

# Enable logging
logger:
  logs:
    qmc5883l: INFO

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

ota:
  password: ""

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

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

captive_portal:

web_server:
  port: 80

i2c:
  sda: GPIO8
  scl: GPIO9


globals:
  - id: gas_counter_total
    type: long
    restore_value: no
    initial_value: '0'

  - id: gas_counter
    type: long
    restore_value: no
    initial_value: '0'

  - id: gas_high
    type: bool
    restore_value: no
    initial_value: 'false'

  - id: gas_max_mid
    type: float
    restore_value: no
    initial_value: '-2000.0'

  - id: gas_min_mid
    type: float
    restore_value: no
    initial_value: '2000.0'

  - id: gas_max
    type: float
    restore_value: no
    initial_value: '-1000.0'

  - id: gas_min
    type: float
    restore_value: no
    initial_value: '1000.0'

interval:
  - interval: 0.1s
    startup_delay: 5s
    then:
      - lambda: |-
          // Set min and max values using the x-axis sensor
          if (id(gasz).state > id(gas_max)) {
            id(gas_max) = (id(gasz).state);
            if (id(calibrated).state ) {
              id(gas_max_mid) = (id(gas_max)-id(gas_min))*0.75+id(gas_min);
              id(gas_min_mid) = (id(gas_max)-id(gas_min))*0.25+id(gas_min);
            }
          } 
          
          if (id(gasz).state < id(gas_min)) {
            id(gas_min) = (id(gasz).state);
            if (id(calibrated).state ) {
              id(gas_max_mid) = (id(gas_max)-id(gas_min))*0.75+id(gas_min);
              id(gas_min_mid) = (id(gas_max)-id(gas_min))*0.25+id(gas_min);
            }
          }

          // If  the device is calibrated process readings from the x-axis sensor to calculate gas usage
          if (id(calibrated).state ) {
            if (id(gasz).state > id(gas_max_mid) && !id(gas_high)) {
              id(gas_counter_total) += 1;
              id(gas_counter) += 1;	
              id(gas_high) = true;
            } 
            else if (id(gasz).state < id(gas_min_mid) && id(gas_high)) {
              id(gas_high) = false;
            }  
            // Slowly decrease max and increase min to deal with drift
            float diff = fabs(id(gas_max)-id(gas_min));
            id(gas_max) -= diff*0.00001;
            id(gas_min) += diff*0.00001;
          }
          // If the device is not calibrated, wait until the difference between the min/max value is greater than 10 and less than 1000
          // On first boot when device is calibrating, this might result in a couple seconds of bad readings, but what can you do?
          if ((fabs(id(gas_max)-id(gas_min)) > 10) && (fabs(id(gas_max)-id(gas_min)) < 1000.0)) {
            id(calibrated).publish_state(true);
          }
          // If the sensor isn't reading correctly, the drift compensation above will cause min and max to converge. Setting calibrated to false allows this to be detected.
          else
          {
            id(calibrated).publish_state(false);
          }

# Reports back to HA when device considers itself to be "calibrated"
binary_sensor:
  - platform: template
    name: "Calibrated"
    id: calibrated
    device_class: RUNNING
    publish_initial_state: true

# Uncomment whichever axis gives you the best reading
# For initial setup uncomment all of them and comment out internal: true so you can see historical values in HA
# Then choose the Axis that gives you the greatest variation in it's reading (should look like a sine wave)
# Once you've selected the best axis comment out the rest and set internal: true again.
# If an axis other than X is used you will need to change all the gasx references to gasy or gasz depending on which axis you've selected.
sensor:
  - platform: qmc5883l
    address: 0x0D
    # field_strength_x:
    #   name: "Gas Meter Field Strength X"
    #   id: gasx
    #   internal: true
    #    field_strength_y:
    #      name: "Gas Meter Field Strength Y"
    #      id: gasy
    #      internal: true
    field_strength_z:
     name: "Gas Meter Field Strength Z"
     id: gasz
     internal: true
#    heading:
#      name: "Gas Meter Heading"
#      internal: true
    range: 200uT
    oversampling: 512x
    update_interval: 0.1s

#8 counts per cubic foot, multiplied by 2 to get per minute based on update interval
  - platform: template    
    name: "Gas Rate"
    lambda: |-
      if (id(calibrated).state ) {
        int temp = id(gas_counter);
        id(gas_counter) -= temp;
        float temp2 = temp;
        float temp3 = (temp2*id(ratio).state)*2; 
        return temp3;
      } else {
        return 0;
      }
    update_interval: 30s
    unit_of_measurement: dm³/min
    device_class: 'gas'

  - platform: template    
    name: "Gas Total"
    lambda: |-
      if (id(calibrated).state ) {
        float temp = id(gas_counter_total);
        return temp*id(ratio).state*.001;
      } else {
        return 0;
      }
    update_interval: 1s
    accuracy_decimals: 3
    unit_of_measurement: 'm³'
    state_class: 'total_increasing'
    device_class: 'gas'

  - platform: template
    name: "Max Value"
    lambda: return id(gas_max);
    update_interval: 1s
    unit_of_measurement: uT
  
  - platform: template
    name: "Min Value"
    lambda: return id(gas_min);
    update_interval: 1s
    unit_of_measurement: uT

# Used to reset calibration.  If for any reason your device is no longer recording data then try resetting the calibration
button:
  - platform: template
    name: "Reset Calibration"
    id: Calibrate
    icon: "mdi:chart-histogram"
    on_press:
      then:
        lambda: |-
          id(gas_max) = -1000;
          id(gas_min) = 1000;
          id(calibrated).publish_state(false);
          id(gas_max_mid) = 500.0;
          id(gas_min_mid) = -500.0;

# Display some useful variables to HA.
number:
  - platform: template
    optimistic: true
    name: "dm³/rev"
    id: ratio
    update_interval: never
    initial_value: 8
    min_value: 1
    max_value: 500
    step: 1
    restore_value: true

I’ve noticed recently that the calibration button doesn’t seem to work anymore
…anyone else seeing that?

Here is my project that allows reading a gas meter or a water meter. It supports calibration across all axis. It’s a package that allows easy installation. All configuration is done via the UI.

1 Like

Is this based on the same code from this topic, or are you just providing other options?
I’ve been having trouble with getting readings from mine lately and might try your project.