Propane Tank monitoring

I've been running a Centri MyPropane tank monitor on my 1,000-gallon tank and got it fully integrated into Home Assistant, including the Energy dashboard. Here's the complete setup in case it helps anyone else.


What you get

  • Live tank level (%)
  • Remaining gallons
  • Last/next update timestamps (in local time)
  • Lifetime gallons consumed (running total, survives restarts)
  • Energy dashboard integration via a CCF sensor
  • Deduplication so repeated API responses never double-count consumption
  • Startup automation that fetches fresh data immediately after an HA restart

Prerequisites

You need three credentials:

  • User ID (account-level UUID) - this is found in the My Propane app (Settings -> User Profile)
  • Device ID (printed on the monitor - standard UUID format, e.g. 473fc198-1adf-4b9f-afde-4f3af9f2644e)
  • Device Auth (numeric code)

The last two can be found on your device activation card that came with your device (the one that says "SAVE THIS CARD" at the top. Centri support ([email protected]) may be able to help if you've lost this card, but I had mine. They can also send you the API documentation if you need it - they're very helpful and responsive!


Package file

Create config/packages/propane.yaml (requires packages: !include_dir_named packages in your configuration.yaml). Replace the three placeholder values (YOUR_USER_ID, YOUR_DEVICE_ID, YOUR_DEVICE_AUTH) with your credentials and set tank_size to match your tank.

# CentriConnect Propane Tank Monitor
# API rate limit: 4 requests/device/day — scan_interval: 21600 matches exactly.

sensor:
  - platform: rest
    name: "Centri Tank Level"
    unique_id: centri_tank_level
    resource: "https://api.centriconnect.com/centriconnect/YOUR_USER_ID/device/YOUR_DEVICE_ID/all-data"
    method: GET
    params:
      device_auth: "YOUR_DEVICE_AUTH"
    scan_interval: 21600
    unit_of_measurement: "%"
    state_class: measurement
    value_template: "{{ value_json['YOUR_DEVICE_ID']['TankLevel'] }}"
    json_attributes_path: "$['YOUR_DEVICE_ID']"
    json_attributes:
      - AlertStatus
      - Altitude
      - BatteryVolts
      - DeviceID
      - DeviceName
      - LastPostTimeIso
      - Latitude
      - Longitude
      - NextPostTimeIso
      - SignalQualLTE
      - SolarVolts
      - TankSize
      - TankSizeUnit
      - VersionHW
      - VersionLTE

input_number:
  propane_total_consumed_gal:
    name: "Propane Total Consumed"
    min: 0
    max: 999999
    step: 0.1
    unit_of_measurement: "gal"
    mode: box
    icon: mdi:propane-tank-outline

  propane_last_level_pct:
    name: "Propane Last Known Level"
    min: 0
    max: 100
    step: 0.1
    unit_of_measurement: "%"
    mode: box
    icon: mdi:gauge

input_text:
  propane_last_post_time:
    name: "Propane Last Processed Post Time"
    max: 50
    icon: mdi:clock-check

template:
  - sensor:
      - name: "Propane Tank Remaining Gallons"
        unique_id: propane_tank_remaining_gallons
        unit_of_measurement: "gal"
        state_class: measurement
        icon: mdi:propane-tank
        availability: "{{ has_value('sensor.centri_tank_level') }}"
        state: >
          {% set tank_size = state_attr('sensor.centri_tank_level', 'TankSize') | float(0) %}
          {% set tank_level = states('sensor.centri_tank_level') | float(0) %}
          {{ (tank_size * tank_level / 100) | round(1) }}

      - name: "Propane Tank Last Updated"
        unique_id: propane_tank_last_updated
        icon: mdi:clock-outline
        availability: "{{ state_attr('sensor.centri_tank_level', 'LastPostTimeIso') is not none }}"
        state: >
          {% set ts = state_attr('sensor.centri_tank_level', 'LastPostTimeIso') %}
          {% if ts %}
            {{ (ts | as_datetime).astimezone() | as_timestamp | timestamp_custom('%Y-%m-%d %H:%M') }}
          {% else %}
            Unknown
          {% endif %}

      - name: "Propane Tank Next Update"
        unique_id: propane_tank_next_update
        icon: mdi:clock-check-outline
        availability: "{{ state_attr('sensor.centri_tank_level', 'NextPostTimeIso') is not none }}"
        state: >
          {% set ts = state_attr('sensor.centri_tank_level', 'NextPostTimeIso') %}
          {% if ts %}
            {{ (ts | as_datetime).astimezone() | as_timestamp | timestamp_custom('%Y-%m-%d %H:%M') }}
          {% else %}
            Unknown
          {% endif %}

      - name: "Propane Total Consumed Gallons"
        unique_id: propane_total_consumed_gallons
        unit_of_measurement: "gal"
        state_class: total_increasing
        icon: mdi:propane-tank-outline
        state: "{{ states('input_number.propane_total_consumed_gal') | float(0) | round(1) }}"

      # For Energy dashboard → Gas Consumption section.
      # 1 US gallon liquid propane = 0.3649 CCF (industry standard at 60°F).
      - name: "Propane Total Consumed CCF"
        unique_id: propane_total_consumed_ccf
        unit_of_measurement: "CCF"
        device_class: gas
        state_class: total_increasing
        icon: mdi:fire
        state: >
          {{ (states('input_number.propane_total_consumed_gal') | float(0) * 0.3649) | round(3) }}

automation:
  - id: centri_refresh_on_start
    alias: "Refresh Centri Propane Sensor on Startup"
    trigger:
      - platform: homeassistant
        event: start
    action:
      - delay:
          seconds: 30
      - service: homeassistant.update_entity
        target:
          entity_id: sensor.centri_tank_level
    mode: single

  - id: centri_track_consumption
    alias: "Track Propane Consumption"
    description: >
      Fires on every new sensor reading. Deduplicates via LastPostTimeIso so
      hourly polls or repeated restarts never double-count consumption.
    trigger:
      - platform: state
        entity_id: sensor.centri_tank_level
        not_to:
          - unknown
          - unavailable
    action:
      - variables:
          new_level: "{{ trigger.to_state.state | float(0) }}"
          new_post_time: "{{ state_attr('sensor.centri_tank_level', 'LastPostTimeIso') | string }}"
          last_post_time: "{{ states('input_text.propane_last_post_time') }}"
          prev_level: >
            {% if trigger.from_state is not none
               and trigger.from_state.state not in ['unknown', 'unavailable'] %}
              {{ trigger.from_state.state | float(0) }}
            {% else %}
              {{ states('input_number.propane_last_level_pct') | float(0) }}
            {% endif %}
      - choose:
          - conditions:
              - condition: template
                value_template: "{{ new_post_time != last_post_time and new_post_time not in ['None', 'unknown', ''] }}"
            sequence:
              - variables:
                  drop: "{{ [prev_level - new_level, 0] | max }}"
                  tank_size: "{{ state_attr('sensor.centri_tank_level', 'TankSize') | float(0) }}"
              - choose:
                  - conditions:
                      - condition: template
                        value_template: "{{ drop > 0 and drop <= 30 }}"
                    sequence:
                      - service: input_number.set_value
                        target:
                          entity_id: input_number.propane_total_consumed_gal
                        data:
                          value: >
                            {{ (states('input_number.propane_total_consumed_gal') | float(0)
                                + drop / 100 * tank_size) | round(2) }}
              - service: input_text.set_value
                target:
                  entity_id: input_text.propane_last_post_time
                data:
                  value: "{{ new_post_time }}"
              - service: input_number.set_value
                target:
                  entity_id: input_number.propane_last_level_pct
                data:
                  value: "{{ new_level }}"
    mode: single

First-run bootstrap

After restarting HA, go to Developer Tools → States, find sensor.centri_tank_level, and note its LastPostTimeIso attribute and current state. Then set the two helpers via Developer Tools → Services:

# input_number.set_value
entity_id: input_number.propane_last_level_pct
value: 57.2   # ← your current TankLevel %

# input_text.set_value
entity_id: input_text.propane_last_post_time
value: "2026-05-18 17:01:44.000"   # ← your current LastPostTimeIso

This tells the automation "I've already processed this reading" so the first real consumption event is recorded correctly.


Energy dashboard

Go to Settings → Dashboards → Energy → Gas consumption and add sensor.propane_total_consumed_ccf.

HA's gas section only accepts m³, ft³, or CCF - not gallons directly. The conversion used here (0.3649 CCF/gallon) is the industry-standard figure for US liquid propane vaporised at 60°F.

A note on temperature correction

The API returns TankLevel as a raw percentage with no mention of temperature correction in the documentation. Propane expands roughly 1% per 10°F, so across a large seasonal temperature swing you may see a few percentage points of variance that doesn't represent actual consumption. The gallon figures should be treated as estimates rather than billing-precise measurements.


How deduplication works

The Centri device posts data ~4 times a day. Each posting has a unique LastPostTimeIso timestamp that doesn't change no matter how many times you poll the API afterward. The input_text.propane_last_post_time helper stores the timestamp of the last reading that was actually processed. When the automation fires, it compares the incoming timestamp against the stored one — if they match, the response is a duplicate and everything is skipped. This means you can safely lower scan_interval without risking double-counted consumption.


Dashboard card

@royf007 published a HACS custom card that displays a nice animated horizontal tank with color-coded fill level. Install via HACS → Custom repositories, then:

yaml

type: custom:ha-centri-propane-tank-card
entity: sensor.centri_tank_level
tank_size: 1000   # your tank capacity in gallons