Home Assistant Bill Series: BC Hydro

Introduction

Next up in the bill series is BC Hydro. This guide will show you how to set up a REST configuration to be able to fetch billing and consumption data and automatically populate them in Home Assistant. Similar to the the previous post for Fortis BC, we will once again use the Browserless Home Assistant Add-On to scrape the BC Hydro web page for the data we need.

Prerequisites

Install Browserless by add the following repository to your add-on store:

alexbelgium/hassio-addons Open in Home Assistant

Once added, search for Browserless Chromium, install and launch it.

HA Sensor Configuration
Note: Similar to the setup for Fortis BC, this is the only section you actually need to configure the BC Hydro data in Home Assistant. The remaining sections are an expanded guide on how we derive this configuration and how it can be maintained.

Copy the following REST sensor configuration in your Home Assistant either in the configuration.yaml or in rest.yaml and replace the EMAIL and PASSWORD parameters in payload section. Feel free to modify the scan_interval parameter to however often you want to fetch data. I suggest about once a day which is how often BC Hydro updates data.

yaml

rest:
    - resource: http://homeassistant.local:3000/function?stealth
        scan_interval: 86400
        method: POST
        timeout: 120
        headers:
        Content-Type: "application/json"
        payload: >-
        {"code": "export default async ({ page }) => { try { const email = \"[EMAIL]\"; const password = \"[PASSWORD]\"; await page.setDefaultTimeout(1200000); await page.setDefaultNavigationTimeout(1200000); await page.goto(\"https://app.bchydro.com/BCHCustomerPortal/web/login.html\"); await page.waitForSelector(\"#email\"); await page.type(\"#email\", email); await page.type(\"#password\", password); await Promise.all([page.waitForNavigation(), page.click(\"#submit-button\")]); await page.waitForSelector(\"#detailCon:not([disabled])\"); const billData = await page.evaluate(() => { return { balance: document.querySelector('.bill_amount')?.innerText.trim().replace('$', '') || null, due_date: document.querySelector('.due_date')?.innerText.trim().replace('Payment due:', '').trim() || null, last_bill_date: document.querySelector('.bill_date span')?.innerText.match(/(.+):/)[1] || null, last_bill_amount: document.querySelector('.bill_date span')?.innerText.match(/:\\s*\\$(.+)/)[1] || null, } }); await Promise.all([page.waitForNavigation(), page.click(\"#detailCon\")]); await page.waitForSelector(\"#tableBtnLabel\"); await page.click(\"#tableBtnLabel\"); await page.waitForSelector(\"table#consumptionTable\"); const consumptionData = await page.evaluate(() => { const billingPeriod = document.querySelector(\"span#titleDateRange\")?.innerText.trim() || null; const costToDate = document.querySelector(\"div.bch-pb-progress-text\")?.innerText.trim().replace(\"$\", \"\").replace(\"*\", \"\") || null; const projectedCost = document.querySelector(\"div.md-value\")?.innerText.trim().replace(\"$\", \"\") || null; const table = document.querySelector(\"table#consumptionTable\"); const rows = table.querySelectorAll(\"tr\"); const headers = [...rows[0].querySelectorAll(\"td\")].map((th) => th.innerText.trim()).slice(1); let firstDate = null; let latestValidEntry = null; let currentConsumption = 0; for (let i = 1; i < rows.length; i++) { const cells = [...rows[i].querySelectorAll(\"td\")].slice(1); const rowData = {}; cells.forEach((cell, index) => { rowData[headers[index]] = cell.innerText.trim(); }); if (rowData[\"Total kWh\"] !== \"N/A\"){ currentConsumption += parseFloat(rowData[\"Total kWh\"]?.replace(/[^0-9.]/g, \"\")); } if (rowData[\"Date\"] && rowData[\"Total kWh\"] !== \"N/A\" && rowData[\"Total cost\"] !== \"N/A\") { latestValidEntry = rowData; } if (i == 1) { firstDate = rowData[\"Date\"]; } } return { cost_to_date: costToDate, projected_cost: projectedCost, current_billing_period: billingPeriod, current_consumption: currentConsumption, latest_date: latestValidEntry ? latestValidEntry[\"Date\"] : firstDate, latest_daily_consumption_kwh: latestValidEntry ? latestValidEntry[\"Total kWh\"] : 0, latest_daily_cost_dollars: latestValidEntry ? latestValidEntry[\"Total cost\"].replace(\"$\", \"\") : 0, }; }); await page.waitForSelector(\"span#dateSelect-button\"); await page.click(\"span#dateSelect-button\"); await page.waitForSelector(\"div.ui-menu-item-wrapper\"); const options = await page.$$(\"div.ui-menu-item-wrapper\"); for (const option of options){ const text = await page.evaluate(el => el.textContent, option); console.log(text); if (text.trim() === \"Last billing period\") { await option.click(); break; } } await page.waitForSelector(\"table#consumptionTable\"); await page.waitForSelector(\"#titleDateRange\"); const lastBillData = await page.evaluate(() => { const lastBillingPeriod = document.querySelector(\"span#titleDateRange\")?.innerText.trim() || null; const table = document.querySelector(\"table#consumptionTable\"); const rows = table.querySelectorAll(\"tr\"); const headers = [...rows[0].querySelectorAll(\"td\")].map((th) => th.innerText.trim()).slice(1); let lastBillingPeriodConsumption = 0; for (let i = 1; i < rows.length; i++) { const cells = [...rows[i].querySelectorAll(\"td\")].slice(1); const rowData = {}; cells.forEach((cell, index) => { rowData[headers[index]] = cell.innerText.trim(); }); if (rowData[\"Total kWh\"] !== \"N/A\"){ lastBillingPeriodConsumption += parseFloat(rowData[\"Total kWh\"]?.replace(/[^0-9.]/g, \"\")); } } return { last_billing_period: lastBillingPeriod, last_billing_period_consumption: lastBillingPeriodConsumption, }; }); const result = { ...billData, ...consumptionData, ...lastBillData, last_run: new Date().toLocaleString(\"en-GB\", { day: \"2-digit\", month: \"short\", year: \"numeric\", hour: \"2-digit\", minute: \"2-digit\", second: \"2-digit\", hour12: false }) }; return result; } catch (error) { return { error: error.message }; } };"}
        sensor:
        - name: "BC Hydro Balance"
            value_template: "{{ value_json.balance | float }}"
            unit_of_measurement: "$"
            device_class: monetary

        - name: "BC Hydro Due Date"
            value_template: "{{ value_json.due_date }}"

        - name: "BC Hydro Last Bill Amount"
            value_template: "{{ value_json.last_bill_amount | float }}"
            unit_of_measurement: "$"
            device_class: monetary

        - name: "BC Hydro Last Bill Date"
            value_template: "{{ value_json.last_bill_date }}"

        - name: "BC Hydro Cost To Date"
            value_template: "{{ value_json.cost_to_date | float }}"
            unit_of_measurement: "$"
            state_class: total
            device_class: monetary

        - name: "BC Hydro Projected Cost"
            value_template: "{{ value_json.projected_cost | float }}"
            unit_of_measurement: "$"
            state_class: total
            device_class: monetary

        - name: "BC Hydro Current Billing Period"
            value_template: "{{ value_json.current_billing_period }}"

        - name: "BC Hydro Current Consumption"
            value_template: "{{ value_json.current_consumption }}"
            device_class: energy
            unit_of_measurement: kWh
            state_class: total

        - name: "BC Hydro Last Billing Period"
            value_template: "{{ value_json.last_billing_period }}"

        - name: "BC Hydro Last Billing Period Consumption"
            value_template: "{{ value_json.last_billing_period_consumption }}"
            device_class: energy
            unit_of_measurement: kWh

        - name: "BC Hydro Latest Daily Consumption"
            value_template: "{{ value_json.latest_daily_consumption_kwh | float }}"
            unit_of_measurement: "kWh"
            device_class: energy

        - name: "BC Hydro Latest Daily Cost"
            value_template: "{{ value_json.latest_daily_cost_dollars | float }}"
            unit_of_measurement: "$"
            device_class: monetary

        - name: "BC Hydro Latest Date"
            value_template: "{{ value_json.latest_date }}"

        - name: "BC Hydro Last Run"
            value_template: "{{ value_json.last_run }}"
                    
The data points being collected are:
  • Balance
  • Due Date
  • Last Bill Amount
  • Last Bill Date
  • Cost To Date
  • Projected Cost
  • Current Billing Period
  • Current Consumption
  • Last Billing Period
  • Last Billing Period Consumption
  • Latest Daily Consumption
  • Latest Daily Cost
  • Latest Latest Date
  • Latest Last Run

Please create an issue on the repository if the payload stops working due to DOM changes.

Puppeteer Payload Script

The Puppeteer script requires modification to email and password. Please refer to the previous post for in the Payload Script to Configuration section to compile it to be used by the Home Assistant rest sensor.

javascript

                        
                    
arcaneiceman