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