This is a community-maintained integration. It is not affiliated with, endorsed by, or supported by the Ontario Energy Board; it reads their published open data.
This Home Assistant component adds a device for your Ontario, Canada energy company (Electricity or Natural Gas), with sensors for the current rate, the active peak period, and the individual charges that make up your bill. Rates come from the Ontario Energy Board's official open data inventory. Find out more at https://www.oeb.ca/open-data
The current rate sensor can drive cost tracking in Home Assistant's Energy dashboard, following the Time-of-Use or Ultra-Low Overnight schedule through the day.
- Open integrations.
- Click "Explore + Download repositories"
- Search for "Ontario Energy Board" and install the found integration.
Clone or download the repo, and copy the "ontario_energy_board" folder in "custom_components" to the "custom_components" folder in home assistant.
Once installed, use the UI to add the new component to your setup, or click on the button below:
Choose whether you are adding an Electricity or Natural Gas company, then pick your distributor and rate class from the filtered list. Electricity also asks which rate plan you are on — standard Time-of-Use, or Ultra-Low Overnight. Natural gas has no peak periods, so there is nothing further to answer.
You can change either afterwards without losing history: the rate plan from Configure, and the company from Reconfigure.
Each configured company becomes a device carrying its own entities. The default set is deliberately small; everything else is created as a disabled diagnostic, so you can enable exactly what you need from the device page without carrying entities you will never look at.
Current rate is the one to put on a dashboard. It follows the Time-of-Use or Ultra-Low Overnight schedule automatically, including Ontario holidays.
Every period rate is published, for both plans, grouped under the device's diagnostics. That way the plans can be compared, and correcting the plan later takes effect immediately.
Next peak starts is a timestamp, which Home Assistant shows as relative time — "in 2 hours". Together with Next peak and Next peak rate it lets an automation wait for cheaper power rather than poll for it.
Current all-in rate is shaped so Home Assistant can use it as a price source. In Settings → Dashboards → Energy, edit your electricity consumption source, choose Use an entity with current price, and select it. Costs then follow the peak schedule through the day.
Use the all-in rate rather than Current rate here. Current rate is the commodity price the OEB publishes; delivery, regulatory charges, HST and the Ontario Electricity Rebate all land on top of it. Because the delivery charges are flat per kWh, they weigh far more on a cheap kWh than an expensive one:
| Period | Current rate | All-in | |
|---|---|---|---|
| Off-peak | 9.80¢ | 12.08¢ | +23% |
| Mid-peak | 15.70¢ | 17.56¢ | +12% |
| On-peak | 20.30¢ | 21.84¢ | +8% |
Newmarket-Tay Power, residential. The uplift is not a fixed ratio, so it cannot be applied by scaling.
The all-in rate is the cost of the next kWh. Fixed monthly charges — the service charge and the standard supply service charge — are excluded, because they cannot be expressed per kWh without knowing how much you use. It will therefore always read below your bill divided by your consumption.
The arithmetic is verified line by line against the OEB's own bill calculator;
tests/test_billing.py holds that comparison.
If you switch between Time-of-Use and Ultra-Low Overnight with your utility, open the integration's Configure button and change the rate plan there. Home Assistant cannot change your billing; this only tells it which rates apply. Natural gas has no rate plan, so there is nothing to configure.
Ontario distributors are regularly renamed or merged into rate zones. When that happens the old name stops being published and the entry cannot update, so a repair notice appears explaining what to do.
Open the integration and choose Reconfigure to point it at the current name. The likely successor is pre-selected, but check it before saving: rate zones have similar names and genuinely different delivery charges. Doing this keeps your sensors and their history; deleting and re-adding the entry would not.
| Entity | On by default | Unit | OEB key |
|---|---|---|---|
| Current rate | yes | CAD/kWh |
RPPOnP / RPPMidP / RPPOffP / ULO_* / CM |
| Current all-in rate | yes | CAD/kWh |
derived |
| Active peak | yes | — |
— |
| Next peak | yes | — |
— |
| Next peak starts | yes | — |
— |
| Next peak rate | yes | CAD/kWh |
RPPOnP / RPPMidP / RPPOffP / ULO_* |
| Next peak all-in rate | yes | CAD/kWh |
derived |
| Season | yes | — |
— |
| Off-peak rate | yes | CAD/kWh |
RPPOffP |
| Mid-peak rate | yes | CAD/kWh |
RPPMidP |
| On-peak rate | yes | CAD/kWh |
RPPOnP |
| ULO overnight rate | yes | CAD/kWh |
ULO_overnight |
| ULO weekend off-peak rate | yes | CAD/kWh |
ULO_weekendoffp |
| ULO mid-peak rate | yes | CAD/kWh |
ULO_midp |
| ULO on-peak rate | yes | CAD/kWh |
ULO_onp |
| Distribution variable charge | no | CAD/kWh |
DC |
| Distribution volumetric charge | no | CAD/kWh |
VC |
| Other volumetric charges | no | CAD/kWh |
OC |
| Global adjustment | no | CAD/kWh |
PBGA |
| Global adjustment rate rider | no | CAD/kWh |
GA_RR_NONRPP |
| Transmission network rate | no | CAD/kWh |
Net |
| Transmission connection rate | no | CAD/kWh |
Conn |
| Wholesale market service charge | no | CAD/kWh |
WMSR |
| Rural and remote rate protection | no | CAD/kWh |
RRRP |
| Debt retirement charge | no | CAD/kWh |
DRC |
| Lower tier price | no | CAD/kWh |
RPP1 |
| Higher tier price | no | CAD/kWh |
RPP2 |
| Monthly service charge | no | CAD |
SC |
| Standard supply service charge | no | CAD |
SSS |
| Other fixed charges | no | CAD |
OFC |
| Distribution rate protection rate | no | CAD |
DRP_Rate |
| Harmonized sales tax | no | % |
GST |
| Ontario electricity rebate | no | % |
Rebate |
| Tier threshold | no | kWh |
ET1 |
| Loss factor | no | — |
LF |
| Rate year | no | — |
YEAR |
| Distribution rate protection | no | — |
DRP |
| Entity | On by default | Unit | OEB key |
|---|---|---|---|
| Current rate | yes | CAD/m³ |
RPPOnP / RPPMidP / RPPOffP / ULO_* / CM |
| Monthly charge | yes | CAD |
MC |
| Transportation charge | yes | CAD/m³ |
TC |
| Federal carbon charge | yes | CAD/m³ |
FedCC |
| Facility carbon charge | yes | CAD/m³ |
FacCC |
| Storage charge | yes | CAD/m³ |
SC |
| Effective date | yes | — |
ED |
| Delivery charge tier 1 | no | CAD/m³ |
DCT1 |
| Delivery tier 1 start | no | m³ |
DT1Low |
| Delivery tier 1 end | no | m³ |
DT1High |
| Delivery charge tier 2 | no | CAD/m³ |
DCT2 |
| Delivery tier 2 start | no | m³ |
DT2Low |
| Delivery tier 2 end | no | m³ |
DT2High |
| Delivery charge tier 3 | no | CAD/m³ |
DCT3 |
| Delivery tier 3 start | no | m³ |
DT3Low |
| Delivery tier 3 end | no | m³ |
DT3High |
| Delivery charge tier 4 | no | CAD/m³ |
DCT4 |
| Delivery tier 4 start | no | m³ |
DT4Low |
| Delivery tier 4 end | no | m³ |
DT4High |
| Delivery charge tier 5 | no | CAD/m³ |
DCT5 |
| Delivery tier 5 start | no | m³ |
DT5Low |
| Delivery tier 5 end | no | m³ |
DT5High |
| Delivery charge price adjustment | no | CAD/m³ |
DCPA |
| Storage charge price adjustment | no | CAD/m³ |
SCPA |
| Gas supply charge price adjustment | no | CAD/m³ |
CMPA |
| Transportation charge price adjustment | no | CAD/m³ |
TCPA |
| Harmonized sales tax | no | % |
GST |
Four ULO_*_period values (fractions of a day), the three EOffP/EMidP/EOnP
usage percentages, and the twelve monthly gas averages are consumption
assumptions and schedule metadata rather than prices. They stay in
XML_KEY_MAPPINGS so oeb_validation.py keeps checking them against the feed.
scripts/setupCreates a .venv and installs everything from ci_requirements.txt.
There is also a devcontainer (.devcontainer/devcontainer.json) if you prefer a
container — it runs scripts/setup on create and forwards port 8123.
scripts/developStarts Home Assistant on http://localhost:8123 with this integration
symlinked into a generated, git-ignored dev-config/ directory — no copying
required, and edits are picked up on restart. On first run, create a throwaway
account, then add the integration from Settings → Devices & Services → Add
Integration → Ontario Energy Board.
Delete dev-config/ to start from a clean instance.
scripts/test # everything
scripts/test tests/test_peaks.py -q # just the peak rulesThe suite runs against a real (in-process) Home Assistant instance, so no
separate HA install is needed. Every OEB request is served from the trimmed
snapshots in tests/fixtures/, and pytest-socket blocks real network access,
so a missing mock fails loudly rather than silently hitting the live feed.
Layout:
| File | Covers |
|---|---|
tests/test_peaks.py |
The peak rules as pure functions — no Home Assistant, runs in milliseconds |
tests/test_common.py |
Parsing the OEB documents, and resolving the sector from a company name |
tests/test_config_flow.py |
The company picker and duplicate handling |
tests/test_init.py |
Setup, unload, retry on failure, and config entry migration |
tests/test_sensor.py |
The entity end to end, with time frozen in America/Toronto |
tests/fixtures/ holds trimmed snapshots of the two OEB feeds. Re-capture them
when the upstream schema changes:
curl -k -o tests/fixtures/GasBillData.xml https://www.oeb.ca/_html/calculator/data/GasBillData.xmlThe electricity document is trimmed to a handful of rate classes to keep it readable; take the same shape when refreshing it.
- Add the XML key to
XML_KEY_MAPPINGSinconst.py. - If it is a price, charge or rate, add a
SensorEntityDescriptionfor it insensor.pyand a name for itstranslation_keyinstrings.json. - Add a row to the entity table above.
oeb_validation.py runs nightly in CI against the live feeds and fails if the
two ever drift apart, so a new upstream field shows up as a red build without
anyone needing to push a commit.
ruff handles formatting, linting and import
sorting, configured in pyproject.toml.
scripts/lint # format, then fix what can be fixed automatically
scripts/lint --check # report only, as CI runs itThe workspace is preconfigured (.vscode/). Run Setup once, then reload so
the Python extension picks up .venv (or select it via Python: Select
Interpreter).
- Testing sidebar — the suite appears in the Test Explorer; run or debug any
individual test from the gutter. IDE runs pass
--no-cov, because coverage tracing prevents the debugger from hitting breakpoints. - Run and Debug → Home Assistant — starts a real instance on
http://localhost:8123 under the debugger, with breakpoints live in
custom_components/ontario_energy_board. It seedsdev-config/first via a pre-launch task.justMyCodeis off so you can step from a config flow or a coordinator refresh into the integration. - Tasks (
Terminal → Run Task) — Setup, Test, Lint, Run Home Assistant (no debugger), Validate OEB data coverage.
Note that .vscode/ is git-ignored apart from the four shared workspace files,
so personal editor state stays out of the repo.