Historical sensors for HomeAssistant
Project description
Historical sensors for Home Assistant 
Feed historical statistics into Home Assistant database.
HomeAssistant architecture is built around polling (or pushing) data from devices, or data providers, in "real-time". Some data sources (e.g, energy, water or gas providers) can't be polled in real-time or readings are not accurate. However reading historical data, like last month consumption, is possible and accurate. This module adds support to this.
This module uses the recorder component and Home Assistant's official
statistics API to import historical statistics data.
Current projects using this module:
Important: v3.0 Breaking Changes
Version 3.0 removes state writing entirely. Historical sensors now only write statistics, not individual states. This means:
✅ What still works:
- Energy dashboard integration
- Long-term statistics and trends
- Hourly/daily/monthly aggregated data
- All existing integration code (minimal changes needed)
❌ What no longer works:
- Individual state points in entity history graphs
- Granular state visualization in the UI
Why this change? Directly manipulating Home Assistant's recorder database was extremely complex, fragile, and error-prone. The code for maintaining state chains, handling schema changes, and managing database integrity was unsustainable. Statistics provide everything needed for 99% of use cases while using Home Assistant's official, stable API.
See [https://github.com/ldotlopez/ha-historical-sensor/issues/18] for detailed explanation and migration guide.
What Makes HistoricalSensor Special
HistoricalSensor is not a normal Home Assistant sensor. It exists to import past measurements into Home Assistant statistics, not to expose a live state.
The important differences are:
- The entity state stays
Unknown; the current reading is intentionally not represented as a live state. - You fill
self._attr_historical_statesinasync_update_historical()withHistoricalStateobjects. get_statistic_metadata()must return valid statistics metadata, includingunit_class,unit_of_measurement, and the flags that describe how the statistic should be aggregated.async_calculate_statistic_data()is where you turn historical points intoStatisticDatarows.- The library handles overlap detection and writes statistics through Home Assistant's official external statistics API.
The delorian integration in custom_components/delorian/sensor.py is the best reference for the actual entity shape and data flow.
How to implement a historical sensor
If you only need the entity-level pattern, the steps below are the core sensor implementation details. The delorian integration above combines them with the Home Assistant custom-component scaffolding.
- Import home_assistant_historical_sensor and define your sensor. ⚠️ Don't set the SensorEntity.state_class property. See FAQ below
from homeassistant_historical_sensor import (
HistoricalSensor,
HistoricalState,
)
class Sensor(HistoricalSensor, SensorEntity):
...
- Define the
async_update_historicalmethod and save your historical states into theHistoricalSensor._attr_historical_statesattribute.
async def async_update_historical(self):
self._attr_historical_states = [
HistoricalState(state=x.state, timestamp=x.when.timestamp())
for x in await api.fetch()
]
Note: timestamp expects a Unix timestamp (float).
- Define the
get_statistic_metadatamethod for your sensor.
def get_statistic_metadata(self) -> StatisticMetaData:
meta = super().get_statistic_metadata()
meta["has_sum"] = True # For counters (energy, water, gas)
meta["source"] = self.entity_id.split(".", 1)[0]
meta["unit_class"] = EnergyConverter.UNIT_CLASS
meta["unit_of_measurement"] = UnitOfEnergy.KILO_WATT_HOUR
return meta
- Define the
async_calculate_statistic_datamethod for your sensor.
This method calculates statistics from your historical states. Check the delorian integration for a full example.
async def async_calculate_statistic_data(
self,
hist_states: list[HistoricalState],
*,
latest: StatisticsRow | None = None,
) -> list[StatisticData]:
# Calculate hourly statistics from your states
# Return list of StatisticData with start, state, sum/mean
...
- Done! Besides other Home Assistant considerations, this is everything you need to implement statistics importing into Home Assistant.
Technical details
Q. How does it work?
A. The architecture is straightforward:
-
_attr_historical_statesproperty: Holds a list ofHistoricalStateobjects, each containing astatevalue and atimestamp(Unix timestamp as float). -
async_update_historicalhook: Your implementation updates_attr_historical_stateswith data from your source. This is the only method you must implement. -
async_calculate_statistic_datamethod: Calculates statistics (sum/mean/min/max) from historical states. You must implement this to generate statistics.
4. **`async_write_historical` method**: Implemented by `HistoricalSensor`, handles writing statistics to Home Assistant using the official `async_add_external_statistics` API.
Q. What happened to state writing?
A. Removed in v3.0. Writing states directly to the database was extremely complex and fragile. The module now only writes statistics using Home Assistant's official API, which is:
- Simpler and more maintainable
- Forward-compatible with HA updates
- Sufficient for energy dashboards and long-term trends
Individual state points no longer appear in entity history graphs, but statistics work perfectly for energy monitoring and trend analysis.
Q. Why should I NOT set the state_class property?
A. Because it causes Home Assistant to calculate its own statistics from the sensor's current state, which:
- Doesn't make sense for historical data
- Creates incorrect/duplicate statistics
- Conflicts with your manual statistics calculations
Historical sensors provide statistics through async_calculate_statistic_data, not through state_class.
Q. Why is my sensor in "Unknown" state?
A. This is expected and correct. Historical sensors don't provide current state—they only import past statistics. The sensor will always show "Unknown" as its state because:
- The last historical data point (from hours/days ago) is NOT the current state
- The current state is genuinely unknown
- Only statistics are meaningful for this type of sensor
Q. Why doesn't my sensor show up in the energy panel?
A. Energy dashboard uses statistics, not sensor states. If your sensor doesn't appear:
- Make sure you've implemented
get_statistic_metadata()withhas_sum=True - Make sure you've implemented
async_calculate_statistic_data() - Trigger an update to import data and generate statistics
- Statistics will appear after the first successful import
Q. What should I return from get_statistic_metadata()?
A. Return metadata that describes the imported statistic, not the live entity state. The important fields are:
unit_class: required. Use the matching Home Assistant unit class for the measurement domain, such asenergy.unit_of_measurement: required. Use the exact unit that matches the data being imported, such askWhfor energy.source: usually the integration domain, which the base implementation derives fromentity_id.statistic_id: the external statistic id in<domain>:<object_id>format. Forsensor.delorian, this becomessensor:delorian.has_sum: set this toTruefor cumulative counters such as energy, water, or gas.has_mean: set this toTruewhen average values are meaningful for the statistic.
The default implementation from HistoricalSensor already derives statistic_id and source from entity_id. Override only the fields that describe the measurement itself. For energy sensors, import the unit-class helper explicitly:
from homeassistant.util.unit_conversion import EnergyConverter
Q. Can I provide BOTH current state AND historical statistics?
A. Yes. The sensor can expose real live data, and this library can still generate the appropriate historical statistics. The important constraint is that the sensor should not opt into Home Assistant's native statistics pipeline with state_class or related statistics hooks, because that can conflict with the statistics generated by this library.
Use the sensor state for the live value, and let HistoricalSensor handle historical imports and recorder statistics through get_statistic_metadata() and async_calculate_statistic_data().
If you're using the data coordinator pattern, this should be straightforward.
Q. Can I calculate energy/water/gas costs?
A. Cost calculation must be done in Home Assistant's Energy dashboard configuration, not in the sensor itself. The Energy dashboard has built-in cost calculation features.
The energy websocket API may be useful for advanced use cases.
Q. Do I need to worry about overlapping data when re-importing?
A. No. The library handles this automatically:
- New statistics are only added if they're newer than the last imported statistic
- You can safely re-run imports without creating duplicates
Migration from v2.x to v3.x
Breaking Changes
-
HistoricalStateparameter renamed:ts→timestamp(buttsstill works for backward compatibility) -
group_by_intervalparameter fixed:granurality→granularity(typo fix) -
Minimum Home Assistant version: Now requires HA >= 2025.12.0
-
statistic_idproperty removed: Statistics now derive an external statistic id fromentity_id -
No more state writing: Only statistics are written to the database
What You Need to Update
# Before (v2.x)
HistoricalState(state=value, ts=timestamp)
# After (v3.x) - both work, but timestamp is preferred
HistoricalState(state=value, timestamp=timestamp)
HistoricalState(state=value, ts=timestamp) # Still supported
# Before (v2.x)
group_by_interval(states, granurality=3600)
# After (v3.x)
group_by_interval(states, granularity=3600)
What Stays the Same
✅ The integration API is unchanged
✅ async_update_historical() works the same
✅ async_calculate_statistic_data() works the same
✅ get_statistic_metadata() works the same
✅ Energy dashboard integration works the same
What's Removed (probably not relevant to you)
- All internal state writing methods
- Direct database manipulation utilities
statistic_idproperty (external statistic id derived fromentity_id)patches.pymodule
Helper Functions
group_by_interval
Groups historical states into time intervals (typically hourly) for statistics calculation:
from homeassistant_historical_sensor import group_by_interval
for block_timestamp, states_in_hour in group_by_interval(
hist_states,
granularity=60 * 60 # 1 hour in seconds
):
states = list(states_in_hour)
# Calculate statistics for this hour
...
Importing CSV files
To be implemented: https://github.com/ldotlopez/ha-historical-sensor/issues/3
Licenses
- Logo by Danny Allen (Public domain license) https://publicdomainvectors.org/es/vectoriales-gratuitas/Icono-de-configuraci%C3%B3n-del-reloj/88901.html
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file homeassistant_historical_sensor-3.0.0a5.tar.gz.
File metadata
- Download URL: homeassistant_historical_sensor-3.0.0a5.tar.gz
- Upload date:
- Size: 26.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
66712726bfaec5f5029fffea4b55e7b5551e4bae676c288266168db45f302348
|
|
| MD5 |
1989c5b3c625fbb799f685667cbe8ef3
|
|
| BLAKE2b-256 |
af922dfd981d77a18b2e272c5443e727569afaf9c17ecb24919044372ac8a8ca
|
Provenance
The following attestation bundles were made for homeassistant_historical_sensor-3.0.0a5.tar.gz:
Publisher:
publish.yml on ldotlopez/ha-historical-sensor
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
homeassistant_historical_sensor-3.0.0a5.tar.gz -
Subject digest:
66712726bfaec5f5029fffea4b55e7b5551e4bae676c288266168db45f302348 - Sigstore transparency entry: 1279722801
- Sigstore integration time:
-
Permalink:
ldotlopez/ha-historical-sensor@96e1d70a9e381761a3fdae1f4f4d458df81a2b16 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/ldotlopez
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@96e1d70a9e381761a3fdae1f4f4d458df81a2b16 -
Trigger Event:
workflow_run
-
Statement type:
File details
Details for the file homeassistant_historical_sensor-3.0.0a5-py3-none-any.whl.
File metadata
- Download URL: homeassistant_historical_sensor-3.0.0a5-py3-none-any.whl
- Upload date:
- Size: 23.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
202f2f6cdc16dc2e0ed8858028c7e5f429a58316188847ea623e11a47bd92c7b
|
|
| MD5 |
6f5e6a883cf6cfd381a00fd477ce8ae9
|
|
| BLAKE2b-256 |
a4c4b1d0f687637f70a723f8f065e3e80f2bfea9cc25eaf345610c2c465e1d5a
|
Provenance
The following attestation bundles were made for homeassistant_historical_sensor-3.0.0a5-py3-none-any.whl:
Publisher:
publish.yml on ldotlopez/ha-historical-sensor
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
homeassistant_historical_sensor-3.0.0a5-py3-none-any.whl -
Subject digest:
202f2f6cdc16dc2e0ed8858028c7e5f429a58316188847ea623e11a47bd92c7b - Sigstore transparency entry: 1279722813
- Sigstore integration time:
-
Permalink:
ldotlopez/ha-historical-sensor@96e1d70a9e381761a3fdae1f4f4d458df81a2b16 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/ldotlopez
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@96e1d70a9e381761a3fdae1f4f4d458df81a2b16 -
Trigger Event:
workflow_run
-
Statement type: