README.md aktualisiert
This commit is contained in:
@@ -1,2 +1,529 @@
|
|||||||
# ha-timeRatioScript
|
# Home Assistant State Ratio Export
|
||||||
|
|
||||||
|
This project provides a Home Assistant shell-script workflow for exporting hourly and daily time-ratio statistics from the Home Assistant recorder database.
|
||||||
|
|
||||||
|
It evaluates how much of each hour a target entity was in a selected target state. Optionally, the evaluation can be conditioned on a second dependency entity being in a selected dependency state.
|
||||||
|
|
||||||
|
The script writes a CSV file, a summary text file, and a detailed log file into a request-specific output folder under `/config/data`. It can also create a Home Assistant persistent notification via the Home Assistant REST API when the export has finished.
|
||||||
|
|
||||||
|
## What the Script Calculates
|
||||||
|
|
||||||
|
For every hour in the requested interval, the script calculates:
|
||||||
|
|
||||||
|
```text
|
||||||
|
time_ratio = matching_seconds / 3600
|
||||||
|
```
|
||||||
|
|
||||||
|
Without a dependency entity:
|
||||||
|
|
||||||
|
```text
|
||||||
|
matching_seconds = seconds where target_entity == target_state
|
||||||
|
```
|
||||||
|
|
||||||
|
With a dependency entity:
|
||||||
|
|
||||||
|
```text
|
||||||
|
matching_seconds = seconds where target_entity == target_state
|
||||||
|
AND dependence_entity == dependence_state
|
||||||
|
```
|
||||||
|
|
||||||
|
The denominator is always one full hour, even if the requested interval starts or ends within an hour.
|
||||||
|
|
||||||
|
If the target sensor was created inside the requested time interval, the script ignores all days and hours before the target sensor's first recorder entry when calculating averages.
|
||||||
|
|
||||||
|
## Workflow Overview
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
A[Home Assistant Script] --> B[shell_command]
|
||||||
|
B --> C[Bash Export Script]
|
||||||
|
C --> D[SQLite Recorder DB]
|
||||||
|
D --> C
|
||||||
|
C --> E[CSV Output]
|
||||||
|
C --> F[Summary TXT]
|
||||||
|
C --> G[Detailed Log]
|
||||||
|
C --> H[Home Assistant REST API]
|
||||||
|
H --> I[Persistent Notification]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Output Structure
|
||||||
|
|
||||||
|
Each request creates a dedicated folder directly in `/config/data`.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/config/data/20260509-225400_state_ratio_binary_sensor.example_on/
|
||||||
|
├── 20260509-225400_state_ratio_binary_sensor.example_on.csv
|
||||||
|
├── 20260509-225400_state_ratio_binary_sensor.example_on_summary.txt
|
||||||
|
└── 20260509-225400_state_ratio_binary_sensor.example_on.log
|
||||||
|
```
|
||||||
|
|
||||||
|
The request timestamp is placed at the beginning of both the folder name and file names.
|
||||||
|
|
||||||
|
## Generated Files
|
||||||
|
|
||||||
|
### CSV File
|
||||||
|
|
||||||
|
The CSV contains one row per evaluated hour.
|
||||||
|
|
||||||
|
Important columns include:
|
||||||
|
|
||||||
|
| Column | Description |
|
||||||
|
|---|---|
|
||||||
|
| `request_time` | Timestamp when the export was generated |
|
||||||
|
| `target_friendly_name` | Friendly name of the target entity, if available |
|
||||||
|
| `target_entity` | Entity ID of the evaluated target entity |
|
||||||
|
| `target_state` | State that is evaluated for the target entity |
|
||||||
|
| `dependence_entity` | Optional dependency entity |
|
||||||
|
| `dependence_state` | Optional dependency state |
|
||||||
|
| `requested_interval_start` | Original requested interval start |
|
||||||
|
| `requested_interval_stop` | Original requested interval stop |
|
||||||
|
| `effective_interval_start` | Actual start used for averaging, adjusted if the target sensor was created inside the requested interval |
|
||||||
|
| `sensor_created_inside_requested_interval` | Whether pre-creation hours were ignored |
|
||||||
|
| `date` | Date of the hourly row |
|
||||||
|
| `day_name` | Weekday name |
|
||||||
|
| `hour_of_day` | Hour in `HH:00` format |
|
||||||
|
| `hour_start` | Start timestamp of the hour |
|
||||||
|
| `hour_stop` | Stop timestamp of the hour |
|
||||||
|
| `numerator_seconds` | Seconds matching the requested state condition |
|
||||||
|
| `denominator_seconds` | Always `3600.0` |
|
||||||
|
| `time_ratio` | Hourly time ratio |
|
||||||
|
| `daily_average_time_ratio` | Average ratio for the respective day |
|
||||||
|
|
||||||
|
### Summary File
|
||||||
|
|
||||||
|
The summary file contains:
|
||||||
|
|
||||||
|
1. Request metadata
|
||||||
|
2. Overall average time ratio for the effective interval
|
||||||
|
3. Daily average time ratio table
|
||||||
|
4. Hourly time ratio matrix
|
||||||
|
|
||||||
|
The hourly matrix has hours as rows and dates as columns. A second header row shows the weekday name below each date.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Hourly Time Ratio Matrix
|
||||||
|
------------------------
|
||||||
|
Rows are hours of the day. Columns are dates.
|
||||||
|
Hours before target sensor creation are omitted.
|
||||||
|
The second header line contains the weekday name for each date.
|
||||||
|
|
||||||
|
Hour 2026-05-01 2026-05-02 2026-05-03
|
||||||
|
Friday Saturday Sunday
|
||||||
|
--------------------------------------------------
|
||||||
|
00:00 0.000000 0.125000 0.000000
|
||||||
|
01:00 0.250000 0.000000 0.000000
|
||||||
|
02:00 0.100000 0.000000 0.500000
|
||||||
|
```
|
||||||
|
|
||||||
|
### Log File
|
||||||
|
|
||||||
|
The log file contains:
|
||||||
|
|
||||||
|
- Input parameters
|
||||||
|
- Output paths
|
||||||
|
- Database connection status
|
||||||
|
- SQL query information
|
||||||
|
- Entity validation
|
||||||
|
- Target sensor creation detection
|
||||||
|
- State event loading
|
||||||
|
- Segment construction
|
||||||
|
- CSV generation
|
||||||
|
- Summary generation
|
||||||
|
- Home Assistant REST API notification details
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Home Assistant with recorder enabled
|
||||||
|
- SQLite recorder database at `/config/home-assistant_v2.db`
|
||||||
|
- Python 3 available in the Home Assistant environment
|
||||||
|
- Bash-compatible shell
|
||||||
|
- Optional: Home Assistant long-lived access token for REST API notifications
|
||||||
|
|
||||||
|
The script is designed for the default Home Assistant SQLite recorder database.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Create the Script File
|
||||||
|
|
||||||
|
Create this file:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/config/scripts/export_state_ratio_csv.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Paste the shell script into that file.
|
||||||
|
|
||||||
|
Then make it executable:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
chmod +x /config/scripts/export_state_ratio_csv.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### Create the Output Directory
|
||||||
|
|
||||||
|
The script writes to `/config/data` by default.
|
||||||
|
|
||||||
|
If it does not exist yet, create it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p /config/data
|
||||||
|
```
|
||||||
|
|
||||||
|
## Home Assistant Setup
|
||||||
|
|
||||||
|
### shell_command
|
||||||
|
|
||||||
|
Add this to `configuration.yaml`, or to your included shell command YAML file:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
shell_command:
|
||||||
|
export_state_ratio_csv: >-
|
||||||
|
/config/scripts/export_state_ratio_csv.sh
|
||||||
|
"{{ target_entity }}"
|
||||||
|
"{{ target_state }}"
|
||||||
|
"{{ dependence_entity | default('', true) }}"
|
||||||
|
"{{ dependence_state | default('', true) }}"
|
||||||
|
"{{ interval_start }}"
|
||||||
|
"{{ interval_stop }}"
|
||||||
|
```
|
||||||
|
|
||||||
|
After changing `shell_command`, restart Home Assistant.
|
||||||
|
|
||||||
|
### Home Assistant Script
|
||||||
|
|
||||||
|
Add this to `scripts.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
export_state_ratio_csv:
|
||||||
|
alias: Export state ratio CSV
|
||||||
|
description: >
|
||||||
|
Exports hourly time ratios for a target entity being in a target state.
|
||||||
|
Optionally, the ratio is limited to times where another dependence entity
|
||||||
|
is in a defined dependence state. The denominator is always one full hour.
|
||||||
|
|
||||||
|
mode: queued
|
||||||
|
|
||||||
|
fields:
|
||||||
|
target_entity:
|
||||||
|
name: Target entity
|
||||||
|
description: Entity whose state should be evaluated.
|
||||||
|
required: true
|
||||||
|
selector:
|
||||||
|
entity: {}
|
||||||
|
|
||||||
|
target_state:
|
||||||
|
name: Target state
|
||||||
|
description: State of the target entity to evaluate.
|
||||||
|
required: true
|
||||||
|
selector:
|
||||||
|
text: {}
|
||||||
|
|
||||||
|
dependence_entity:
|
||||||
|
name: Dependence entity
|
||||||
|
description: Optional entity that must be in dependence state.
|
||||||
|
required: false
|
||||||
|
selector:
|
||||||
|
entity: {}
|
||||||
|
|
||||||
|
dependence_state:
|
||||||
|
name: Dependence state
|
||||||
|
description: Optional required state of the dependence entity.
|
||||||
|
required: false
|
||||||
|
selector:
|
||||||
|
text: {}
|
||||||
|
|
||||||
|
interval_start:
|
||||||
|
name: Interval start
|
||||||
|
description: "Strict format: DD-MM-YYYY HH:MM:SS"
|
||||||
|
required: true
|
||||||
|
selector:
|
||||||
|
text: {}
|
||||||
|
|
||||||
|
interval_stop:
|
||||||
|
name: Interval stop
|
||||||
|
description: "Strict format: DD-MM-YYYY HH:MM:SS"
|
||||||
|
required: true
|
||||||
|
selector:
|
||||||
|
text: {}
|
||||||
|
|
||||||
|
sequence:
|
||||||
|
- service: shell_command.export_state_ratio_csv
|
||||||
|
data:
|
||||||
|
target_entity: "{{ target_entity }}"
|
||||||
|
target_state: "{{ target_state }}"
|
||||||
|
dependence_entity: "{{ dependence_entity | default('', true) }}"
|
||||||
|
dependence_state: "{{ dependence_state | default('', true) }}"
|
||||||
|
interval_start: "{{ interval_start }}"
|
||||||
|
interval_stop: "{{ interval_stop }}"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Timestamp Format
|
||||||
|
|
||||||
|
The interval timestamps must use this strict format:
|
||||||
|
|
||||||
|
```text
|
||||||
|
DD-MM-YYYY HH:MM:SS
|
||||||
|
```
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```text
|
||||||
|
01-05-2026 00:00:00
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage Examples
|
||||||
|
|
||||||
|
### Evaluate a Target Entity Without Dependency
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
service: script.export_state_ratio_csv
|
||||||
|
data:
|
||||||
|
target_entity: binary_sensor.office_presence
|
||||||
|
target_state: "on"
|
||||||
|
interval_start: "01-05-2026 00:00:00"
|
||||||
|
interval_stop: "07-05-2026 23:59:00"
|
||||||
|
```
|
||||||
|
|
||||||
|
This calculates:
|
||||||
|
|
||||||
|
```text
|
||||||
|
seconds(binary_sensor.office_presence == on) / 3600
|
||||||
|
```
|
||||||
|
|
||||||
|
for every hour in the requested interval.
|
||||||
|
|
||||||
|
### Evaluate a Target Entity With Dependency
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
service: script.export_state_ratio_csv
|
||||||
|
data:
|
||||||
|
target_entity: binary_sensor.office_presence
|
||||||
|
target_state: "on"
|
||||||
|
dependence_entity: binary_sensor.phone_home
|
||||||
|
dependence_state: "on"
|
||||||
|
interval_start: "01-05-2026 00:00:00"
|
||||||
|
interval_stop: "07-05-2026 23:59:00"
|
||||||
|
```
|
||||||
|
|
||||||
|
This calculates:
|
||||||
|
|
||||||
|
```text
|
||||||
|
seconds(binary_sensor.office_presence == on AND binary_sensor.phone_home == on) / 3600
|
||||||
|
```
|
||||||
|
|
||||||
|
for every hour in the requested interval.
|
||||||
|
|
||||||
|
## Home Assistant REST API Notification
|
||||||
|
|
||||||
|
The script can send a persistent notification in Home Assistant after finishing.
|
||||||
|
|
||||||
|
### Create a Long-Lived Access Token
|
||||||
|
|
||||||
|
In Home Assistant:
|
||||||
|
|
||||||
|
```text
|
||||||
|
User profile → Security → Long-lived access tokens → Create token
|
||||||
|
```
|
||||||
|
|
||||||
|
Create a token, for example named:
|
||||||
|
|
||||||
|
```text
|
||||||
|
state_ratio_export
|
||||||
|
```
|
||||||
|
|
||||||
|
### Store the Token
|
||||||
|
|
||||||
|
The script expects the token here:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/config/scripts/.state_ratio_ha_token
|
||||||
|
```
|
||||||
|
|
||||||
|
Create the file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
printf '%s' 'PASTE_YOUR_LONG_LIVED_ACCESS_TOKEN_HERE' > /config/scripts/.state_ratio_ha_token
|
||||||
|
chmod 600 /config/scripts/.state_ratio_ha_token
|
||||||
|
```
|
||||||
|
|
||||||
|
Do not include quotes, Markdown links, labels, or extra text. The file should contain only the token.
|
||||||
|
|
||||||
|
### API Endpoint Used
|
||||||
|
|
||||||
|
The script validates the token with:
|
||||||
|
|
||||||
|
```text
|
||||||
|
GET /api/
|
||||||
|
```
|
||||||
|
|
||||||
|
Then creates a persistent notification with:
|
||||||
|
|
||||||
|
```text
|
||||||
|
POST /api/services/persistent_notification/create
|
||||||
|
```
|
||||||
|
|
||||||
|
Default Home Assistant URL:
|
||||||
|
|
||||||
|
```text
|
||||||
|
http://127.0.0.1:8123
|
||||||
|
```
|
||||||
|
|
||||||
|
If needed, override it through the `HA_URL` environment variable.
|
||||||
|
|
||||||
|
## Recorder Database Access
|
||||||
|
|
||||||
|
The script opens the SQLite recorder database in read-only mode:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/config/home-assistant_v2.db
|
||||||
|
```
|
||||||
|
|
||||||
|
It reads from these recorder tables:
|
||||||
|
|
||||||
|
- `states`
|
||||||
|
- `states_meta`
|
||||||
|
- `state_attributes`
|
||||||
|
|
||||||
|
The script reconstructs state intervals from recorder state-change events.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant HA as Home Assistant Script
|
||||||
|
participant SH as Bash Script
|
||||||
|
participant DB as Recorder SQLite DB
|
||||||
|
participant FS as /config/data
|
||||||
|
participant API as HA REST API
|
||||||
|
|
||||||
|
HA->>SH: Call shell_command with parameters
|
||||||
|
SH->>DB: Validate target and dependency entities
|
||||||
|
SH->>DB: Query first target state timestamp
|
||||||
|
SH->>DB: Query target and dependency state events
|
||||||
|
SH->>SH: Build continuous state segments
|
||||||
|
SH->>SH: Calculate hourly and daily ratios
|
||||||
|
SH->>FS: Write CSV, summary and log
|
||||||
|
SH->>API: Validate token
|
||||||
|
SH->>API: Create persistent notification
|
||||||
|
```
|
||||||
|
|
||||||
|
## Sensor Creation Handling
|
||||||
|
|
||||||
|
If the target entity was first recorded inside the requested interval, the script uses the target sensor's first recorder timestamp as the effective start.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Requested interval: 01-05-2026 00:00:00 to 07-05-2026 23:59:00
|
||||||
|
Target first seen: 03-05-2026 14:23:10
|
||||||
|
Effective start: 03-05-2026 14:23:10
|
||||||
|
```
|
||||||
|
|
||||||
|
All hours before the effective start are omitted from:
|
||||||
|
|
||||||
|
- CSV rows
|
||||||
|
- Daily averages
|
||||||
|
- Overall average
|
||||||
|
- Hourly matrix
|
||||||
|
|
||||||
|
The CSV and summary include metadata showing whether this adjustment was applied.
|
||||||
|
|
||||||
|
## Configuration Variables
|
||||||
|
|
||||||
|
The following variables can be adjusted at the top of the shell script or overridden through the environment.
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `OUT_DIR` | `/config/data` | Base directory for request folders |
|
||||||
|
| `DB_PATH` | `/config/home-assistant_v2.db` | Home Assistant recorder SQLite database |
|
||||||
|
| `HA_TZ` | `Europe/Berlin` | Timezone for timestamp parsing and output |
|
||||||
|
| `HA_URL` | `http://127.0.0.1:8123` | Home Assistant REST API base URL |
|
||||||
|
| `HA_TOKEN_FILE` | `/config/scripts/.state_ratio_ha_token` | File containing the long-lived access token |
|
||||||
|
| `HA_TOKEN` | empty | Optional token from environment variable |
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### No Notification Appears
|
||||||
|
|
||||||
|
Check the generated log file in the request folder. Search for:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Preparing Home Assistant persistent notification
|
||||||
|
```
|
||||||
|
|
||||||
|
If the log says:
|
||||||
|
|
||||||
|
```text
|
||||||
|
WARNING: No HA token found.
|
||||||
|
```
|
||||||
|
|
||||||
|
verify that this file exists:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/config/scripts/.state_ratio_ha_token
|
||||||
|
```
|
||||||
|
|
||||||
|
If the log says:
|
||||||
|
|
||||||
|
```text
|
||||||
|
HTTP 401: Unauthorized
|
||||||
|
```
|
||||||
|
|
||||||
|
the token file was found, but the token is invalid. Create a new long-lived access token and overwrite the token file.
|
||||||
|
|
||||||
|
### Invalid Authentication or IP Ban
|
||||||
|
|
||||||
|
If Home Assistant reports invalid authentication from localhost, remove stale entries from:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/config/ip_bans.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
Then restart Home Assistant.
|
||||||
|
|
||||||
|
### BusyBox date Error
|
||||||
|
|
||||||
|
Some Home Assistant environments use BusyBox `date`, which does not support:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
date --iso-8601=seconds
|
||||||
|
```
|
||||||
|
|
||||||
|
The script uses the BusyBox-compatible form:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
date -Iseconds
|
||||||
|
```
|
||||||
|
|
||||||
|
### Entity Not Found
|
||||||
|
|
||||||
|
The script checks `states_meta` for the target and dependency entities. If an entity is not found, make sure:
|
||||||
|
|
||||||
|
- Recorder is enabled
|
||||||
|
- The entity has existed long enough to be recorded
|
||||||
|
- The entity ID is spelled correctly
|
||||||
|
- The database path is correct
|
||||||
|
|
||||||
|
### Empty Output
|
||||||
|
|
||||||
|
The CSV can contain only headers if:
|
||||||
|
|
||||||
|
- The target entity has no recorder rows
|
||||||
|
- The target entity was created after the requested interval ended
|
||||||
|
- The effective interval is empty
|
||||||
|
|
||||||
|
Check the summary and log for the effective interval.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- The script assumes SQLite recorder storage.
|
||||||
|
- MariaDB or PostgreSQL recorder setups require adapting the SQL connection and query execution code.
|
||||||
|
- The script reads the database in read-only mode.
|
||||||
|
- The denominator is intentionally always `3600`, even for partial first or last hours.
|
||||||
|
- Dependency fields can be left empty. If one dependency field is set, both must be set.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Use and adapt this script freely for your own Home Assistant setup.
|
||||||
|
|||||||
Reference in New Issue
Block a user