template:
- binary_sensor:
- name: Peak hours
state: >
{{ now().weekday() < 5
and 16 <= now().hour < 21 }}
Looks fine in June. Wrong in January, on a holiday, and the week the peak window moves.
ZIP in, current peak / off-peak period out. Automate EV charging, HVAC, and loads off the tariff clock — not a hardcoded 4–9 PM that drifts every season.
Home Assistant forums are full of now().hour and schedule helpers. They miss holidays, season flips, DST, and the day the utility refiles the tariff.
template:
- binary_sensor:
- name: Peak hours
state: >
{{ now().weekday() < 5
and 16 <= now().hour < 21 }}
Looks fine in June. Wrong in January, on a holiday, and the week the peak window moves.
automation:
- alias: Charge EV when off-peak
triggers:
- trigger: state
entity_id: binary_sensor.off_peak
to: "on"
The period is a sensor. Automations follow the clock the utility actually bills.
How it works
One REST call. Home Assistant already speaks REST. No custom component required.
Any U.S. ZIP, optional street if the ZIP is shared. Residential is the default customer class.
off_peak, on_peak, mid_peak, super_off_peak, or critical_peak — plus when it next changes.
EV charger, HVAC, dishwasher, battery reserve, or the Energy dashboard tariff selector.
Install
Copy into Home Assistant. Swap the ZIP. Reload REST entities and template entities.
Home Assistant reads !secret from secrets.yaml. Include the word Bearer.
tou_api_key: Bearer tou_xxxxxxxxxxxxxxxxxxxxxxxxxxxx
One HTTP call, three entities: the period, a timestamp for the next boundary, and an on/off for cheap hours.
scan_interval is a safety net. The automation in step 3 does the real refresh.
rest:
- resource: https://tou.tools/api/v1/query/current?zip_code=94103
scan_interval: 21600
headers:
Authorization: !secret tou_api_key
Accept: application/json
sensor:
- name: TOU period
unique_id: tou_period
value_template: "{{ value_json.results[0].period }}"
json_attributes_path: "$.results[0]"
json_attributes:
- next_change_at
template:
- sensor:
- name: TOU next change
unique_id: tou_next_change
device_class: timestamp
state: "{{ state_attr('sensor.tou_period', 'next_change_at') }}"
- binary_sensor:
- name: Off-peak
unique_id: tou_off_peak
state: "{{ states('sensor.tou_period') in ['off_peak', 'super_off_peak'] }}"
A time trigger on the timestamp sensor fires just after the tariff changes. Replace switch.ev_charger with your load.
Drop the list into automations.yaml, or paste one automation at a time into the editor.
- alias: Refresh TOU at the next period change
triggers:
- trigger: time
at:
entity_id: sensor.tou_next_change
offset: "00:00:45"
actions:
- action: homeassistant.update_entity
target:
entity_id: sensor.tou_period
- alias: Charge EV when off-peak
triggers:
- trigger: state
entity_id: binary_sensor.off_peak
to: "on"
actions:
- action: switch.turn_on
target:
entity_id: switch.ev_charger
- alias: Sync Energy dashboard tariff
triggers:
- trigger: state
entity_id: sensor.tou_period
actions:
- action: select.select_option
target:
entity_id: select.daily_energy
data:
option: >
{{ 'offpeak' if states('sensor.tou_period') in
['off_peak', 'super_off_peak'] else 'peak' }}
What to automate
Turn the charger on when binary_sensor.off_peak goes on. Hold through super-off-peak. Stop at on-peak.
Drop the setpoint an hour before next_change_at when the next period is on-peak, then coast.
Delay the dishwasher, washer, and water heater until off-peak. Same binary sensor, different switches.
Charge through off-peak, raise reserve at peak start. Period names only — no ¢/kWh from this API.
Drive utility_meter tariff selects from sensor.tou_period instead of time triggers at 16:00.
When sensor.tou_next_change is under an hour away and the current period is off-peak, ping the phone.
A custom Home Assistant component — tou.tools (Time-of-Use) — wraps this exact
flow: API key + ZIP in a config UI, an enum period sensor, an off-peak binary sensor, and a
coordinator that reschedules itself for just after next_change_at.
REST above is the path that works on any Home Assistant today, no extra component. The custom integration is the same sensors without YAML, and it stays inside the free tier by polling only at boundaries. Install from REST; switch later if you want the UI.
Call GET /api/v1/query/current with your ZIP from a REST sensor. results[0].period is the live period. Automate on that, or on a binary sensor that is on during off-peak and super-off-peak. YAML is on this page.
No. tou.tools is the timing layer: period names and boundaries. Prices live in riders, baselines, and customer class. Put your own rates on the Energy dashboard or in utility_meter if you need a dollar figure.
If we have a seeded TOU plan for the utility that serves your ZIP, yes. See coverage. When a ZIP is ambiguous (common in Texas / ERCOT), the API returns every candidate plan instead of guessing — pick the one on your bill, or pass street / city / state.
Refreshing at next_change_at is a handful of calls per day and fits the free plan (250/month, 3 locations). Polling every 15 minutes is ~2,880/month. The YAML on this page does the former.
Yes. On a state change of sensor.tou_period, call select.select_option on your meter’s tariff selector. Map off_peak / super_off_peak to your cheap tariff name. The third automation in the recipe is that mapping.
No credit card. 250 calls/month. Enough for a house if you refresh at next_change_at (a few calls a day).