Amber Energy Usage Dashboard — daily grid usage + backfill into the Energy Dashboard

For Australian Amber Energy Customers wanting to pull their usage data via Amber’s API into their HA Energy Dashboard

I used to use Solar Analytics, which measured my household circuits’ usage in real time via a 3G unit in my fuse box. Since SA’s ownership changed and the 3G network it relied on was shut down, that’s no longer an option for me.

Looking for a replacement, I found that Home Assistant’s official Amber Electric integration only gives you live pricing — not your actual usage. An older version had a usage sensor, but it was removed as part of merging into HA core (deemed a “duplicate” of the price sensor). Without a Smart Meter monitor or CT-clip setup (perhaps a future project), there’s no built-in way to get real grid import/export into the Energy Dashboard at all.

This kit fills that gap using Amber’s own usage API instead — the same data their app shows you — with two pieces:

  1. A daily automation that keeps your Energy Dashboard updated automatically, one day behind (matching Amber’s own settlement delay), with zero ongoing effort once set up. This was the actual goal — a “set and forget” fix.
  2. An optional one-time backfill script that pulls up to ~90 days of history so your dashboard isn’t starting from a blank slate.

It also wires up accurate cost/compensation tracking against Amber’s live dynamic pricing, rather than a flat estimated rate.

Setup is config YAML + a Python script you run once (needs HACS + the Import Statistics integration) — full instructions in the README.

Tested end-to-end on two separate instances (one HTTPS/DuckDNS setup, one plain HTTP LAN instance) — the README’s troubleshooting section covers the real gotchas hit along the way, including a subtle statistics-corruption bug that took hours to manifest, so hopefully nobody else has to rediscover that one the hard way.

Repo: GitHub - r5e/ha-amber-energy-dashboard: Get Amber Electric usage into Home Assistant's Energy Dashboard, daily and backfilled · GitHub

I’m by no means an expert, and this is my first time putting something like this together — I had Claude help me extensively along the way. If people find this useful, I’m considering spending more time packaging it into a proper integration with fewer manual steps and less YAML-wrangling.

Feedback welcome, especially from anyone with a Controlled Load tariff — I haven’t tested against one and the script would need a small extension to handle that channel.

Version 2.0 is out: now a proper HACS integration

Thanks to everyone who tried the YAML kit (previous v1). Version 2.0 replaces it with a full integration, installed through HACS and set up in the UI: no YAML at all. It imports your actual metered usage and cost from Amber into Home Assistant’s long-term statistics, so the Energy dashboard shows what Amber really billed, hour by hour.




New in 2.0

  • Hourly import and export energy with real per-interval cost and feed-in earnings
  • Automatic backfill of everything Amber still holds (about three months), and automatic catch-up after outages
  • Revision handling: if Amber revises estimated days, history is corrected
  • Controlled load support, detected from your site
  • Optional: price your own energy sensors (CT clamps, smart plugs) at Amber’s real 5-minute prices

Accuracy: checked against a real monthly Amber bill, energy imported and exported match exactly, and import cost matches to the cent.

If you’re using the YAML kit (the version 1 mentioned in the post above): the integration can migrate your history across, so the Energy dashboard keeps one continuous series. It has a dry run first, and an undo. It also fixes a bug in the kit: feed-in earnings were stored as negative, which made the Energy dashboard add them to your cost instead of subtracting them. So after migrating, your dashboard costs will be slightly lower, and correct. Take a backup first, and follow the migration guide.

This is cool!

I haven’t had much success getting it working for me however, I’m just stuck on the “waiting_for_data” status. Looking at the diagnostics, it appears to be checking for data on 2026-07-11 (which was a date where I was not with Amber), so it keeps failing. Maybe the integration could check for the most recent dates first and then go backwards?

It is meant to. Are you using the latest 2.0.1 vers? Did you have the older v1 installed before that? If you can send me any logs or diags I might be able to help. Drop me a private message and I’ll see if I can help.

Actually, there is a bit of a gap here for new users and I think I know what the issue is. I was about to release a v2.1 tomorrow, but I think this fix needs to go into this as there will be other new Amber users (having less than the full 90 day history) and it may also have problems for them.

There is a possible potential work-around: Go to Developer Tools > Actions, run amber_energy_dashboard.probe_retention once. This will force a fresh search for the earliest day with data and this might find the real start date and allow the import to proceed from there. If not, in a couple of days I should have this rolled into the next release. Additional info would help, so PM if you can, otherwise wait for the update. I’ll post here when it’s ready but I’ll be keen to know if it works in the wild.

I just tried the probe retention action and it found the latest date. I then did the full import and it worked! I can try and assist with any logs, just message me with what you’d like!

Really glad that worked! Should have this fixed properly in the next release soon. Have PMed you a request for the diagnostics, just to be sure.
BTW (for anyone else who may need to do this) - the diagnostics file already has any identifiable data (API key, NMI) redacted so it should be safe to post. You can download the diagnostics file under Amber Energy Dashboard (unofficial) >Service info. This should then contain the info needed to properly fix it.

Version 2.1.0 is out

New: bill estimate. Set your billing day and your bill’s daily charges (the guide shows exactly where to find them on an Amber bill), and the integration shows your bill to date, a projected bill, and the average cost per day. On my September bill it came within 3 cents of the actual bill, excluding a one-off card fee.


New button to poll Amber now if you’re too impatient to wait for the schedule. New Diagnostic button in case there are retention problems.
Loads of new useful sensors related to billing which will become more useful in the next version.

New: export allowance (two-way network tariffs). Some networks charge for midday exports, but only beyond a free allowance applied on the bill (for example Endeavour Energy’s N61: 8 kWh a day, over the billing period). Amber’s usage data charges every exported kWh in that window, so feed-in earnings looked lower than on the bill. The integration now detects the tariff automatically, measures the charge from your own data, and provides an adjusted feed-in compensation that matches the bill, plus sensors for the allowance used and remaining. Endeavour N61 is built in; other tariffs can be configured by hand.

Easier setup:

  • An “Add to the Energy dashboard” option at the end of setup, and a Repairs notice if the dashboard isn’t using the integration yet.
  • Run now and Re-check retention buttons on the device page.

Fixes:

  • If you joined Amber recently, the first import could get stuck on “Waiting for Amber data” for a date before you joined. That now recovers automatically. Thanks to @waggles for reporting it.
  • If you added an own energy sensor on 2.0.x, the integration’s other sensors may have disappeared. 2.1.0 restores them automatically.

Updating: through HACS as usual. The bill estimate and export allowance are off until you set them up (Settings > Devices & services > Amber Energy Dashboard > the cog). The installation guide shows each step, including where to find the figures on your Amber bill:

Release notes: Release Amber Energy Dashboard 2.1.0 · r5e/ha-amber-energy-dashboard · GitHub

Also just added a card to better showcase some of the new capabilities. The next version will also have extra items to show you last bill etc.


Lots more to do still, but thought it worth sharing in case it’s useful to anyone.

If anyone wants to use it, create a manual card on your dash and paste in this yaml:

# Amber bill card (current billing cycle) for Amber Energy Dashboard 2.1+
# Requires button-card (HACS > Frontend). Add as a Manual card.
# Check the entity IDs under "variables" match yours (Settings > Entities, search "bill").
type: custom:button-card
show_name: false
show_icon: false
show_state: false
variables:
  bill_to_date: sensor.amber_energy_dashboard_bill_to_date
  projected: sensor.amber_energy_dashboard_projected_bill
  days_into: sensor.amber_energy_dashboard_days_into_billing_cycle
  avg_per_day: sensor.amber_energy_dashboard_average_cost_per_day
  allowance_used: sensor.amber_energy_dashboard_export_allowance_used
  allowance_remaining: sensor.amber_energy_dashboard_export_allowance_remaining
  export_charge: sensor.amber_energy_dashboard_export_charge_after_allowance
  last_bill: sensor.amber_last_bill_estimate
styles:
  card:
    - padding: 16px
  grid:
    - grid-template-areas: '"bill"'
    - grid-template-columns: 1fr
  custom_fields:
    bill:
      - text-align: left
custom_fields:
  bill: |
    [[[
      const st = (e) => states[e];
      const ok = (s) => s && !['unknown', 'unavailable'].includes(s.state);
      const num = (e) => ok(st(e)) ? Number(st(e).state) : NaN;
      const money = (v) => isNaN(v) ? '–' : (v < 0 ? '−$' : '$') + Math.abs(v).toFixed(2);
      const fmtDate = (d) => {
        if (!d) return '';
        const x = new Date(String(d) + 'T00:00:00');
        return isNaN(x) ? String(d) : x.toLocaleDateString(undefined, { day: 'numeric', month: 'short' });
      };

      const billS = st(variables.bill_to_date);
      if (!ok(billS)) {
        return `<div style="font-size:var(--ha-font-size-m,14px);">Bill estimate not available yet.
                Set it up under the integration's options (Bill estimate).</div>`;
      }
      const a = billS.attributes || {};
      const bill = num(variables.bill_to_date);
      const proj = num(variables.projected);
      const avg = num(variables.avg_per_day);

      // Cycle and progress
      const daysIn = Number(a.days_into_cycle ?? num(variables.days_into));
      const daysTotal = Number(a.days_in_cycle);
      const pct = (daysTotal > 0 && !isNaN(daysIn)) ? Math.min(100, Math.max(0, daysIn / daysTotal * 100)) : 0;
      const cycle = (a.cycle_start && a.cycle_end) ? `${fmtDate(a.cycle_start)} – ${fmtDate(a.cycle_end)}` : '';
      const through = a.data_through ? `data to ${fmtDate(a.data_through)}` : '';

      // Line breakdown (labels for the known lines; any others shown by key)
      const labels = {
        usage: 'Usage',
        export_credit: 'Solar export credit',
        network_export_charge: 'Network export charge',
        daily_supply: 'Daily supply',
        amber_subscription: 'Amber subscription',
        other_daily: 'Other daily charges',
      };
      const lines = a.lines || {};
      let rows = '';
      for (const [k, v] of Object.entries(lines)) {
        const val = Number(v);
        if (isNaN(val)) continue;
        if (k === 'network_export_charge' && Math.abs(val) < 0.005) continue;
        const colour = val < 0 ? 'color:var(--success-color,#43a047);' : '';
        rows += `<div style="display:flex;justify-content:space-between;padding:2px 0;">
                   <span>${labels[k] || k.replace(/_/g, ' ')}</span>
                   <span style="${colour}font-variant-numeric:tabular-nums;">${money(val)}</span></div>`;
      }
      const gstNote = (a.gst_percent !== undefined) ? `Daily charges include ${a.gst_percent} % GST.` : '';

      // Export allowance (only if its sensors exist)
      let allowance = '';
      const used = num(variables.allowance_used);
      const rem = num(variables.allowance_remaining);
      if (!isNaN(used) && !isNaN(rem)) {
        const total = used + rem;
        const up = total > 0 ? Math.min(100, used / total * 100) : 0;
        const charge = num(variables.export_charge);
        allowance = `
          <div style="margin-top:12px;">
            <div style="display:flex;justify-content:space-between;">
              <span>Export allowance</span>
              <span style="font-variant-numeric:tabular-nums;">${used.toFixed(1)} of ${total.toFixed(0)} kWh</span>
            </div>
            <div style="height:8px;border-radius:4px;background:var(--divider-color);margin-top:4px;overflow:hidden;">
              <div style="width:${up}%;height:100%;background:${up >= 100 ? 'var(--error-color,#e53935)' : 'var(--warning-color,#fb8c00)'};"></div>
            </div>
            ${!isNaN(charge) && charge > 0.004 ? `<div style="margin-top:4px;color:var(--error-color,#e53935);">Export charge after allowance: ${money(charge)}</div>` : ''}
          </div>`;
      }

      // Last bill (previous cycle), from the "Amber last bill estimate" sensor, if present
      let last = '';
      const lb = st(variables.last_bill);
      if (ok(lb)) {
        const la = lb.attributes || {};
        const lcycle = (la.cycle_start && la.cycle_end) ? `${fmtDate(la.cycle_start)} – ${fmtDate(la.cycle_end)}` : '';
        const ll = la.lines || {};
        let lrows = '';
        for (const [k, v] of Object.entries(ll)) {
          const val = Number(v);
          if (isNaN(val) || (k === 'network_export_charge' && Math.abs(val) < 0.005)) continue;
          lrows += `<span style="white-space:nowrap;margin-right:10px;">${labels[k] || k.replace(/_/g, ' ')} ${money(val)}</span>`;
        }
        const partial = la.complete_from_cycle_start === false ? ' (partial: data started mid-cycle)' : '';
        last = `
          <div style="margin-top:12px;border-top:1px solid var(--divider-color);padding-top:8px;">
            <div style="display:flex;justify-content:space-between;align-items:baseline;flex-wrap:wrap;gap:4px;">
              <span style="font-weight:600;">Last bill estimate</span>
              <span style="color:var(--secondary-text-color);">${lcycle}</span>
            </div>
            <div style="font-size:var(--ha-font-size-xl,20px);font-weight:600;margin-top:2px;">${money(Number(lb.state))}${partial}</div>
            <div style="color:var(--secondary-text-color);font-size:var(--ha-font-size-s,12px);margin-top:2px;">${lrows}</div>
          </div>`;
      }

      return `
        <div style="font-size:var(--ha-font-size-m,14px);line-height:1.45;">
          <div style="display:flex;justify-content:space-between;align-items:baseline;flex-wrap:wrap;gap:4px;">
            <span style="font-weight:600;">Amber bill</span>
            <span style="color:var(--secondary-text-color);">${cycle}</span>
          </div>
          <div style="display:flex;justify-content:space-between;align-items:flex-end;margin-top:6px;flex-wrap:wrap;gap:8px;">
            <div>
              <div style="font-size:var(--ha-font-size-3xl,28px);font-weight:600;line-height:1.1;">${money(bill)}</div>
              <div style="color:var(--secondary-text-color);">so far${through ? ' · ' + through : ''}</div>
            </div>
            <div style="text-align:right;">
              <div style="font-size:var(--ha-font-size-xl,20px);font-weight:600;">${money(proj)}</div>
              <div style="color:var(--secondary-text-color);">projected</div>
            </div>
          </div>
          <div style="height:8px;border-radius:4px;background:var(--divider-color);margin-top:10px;overflow:hidden;">
            <div style="width:${pct}%;height:100%;background:var(--primary-color);"></div>
          </div>
          <div style="display:flex;justify-content:space-between;color:var(--secondary-text-color);margin-top:4px;">
            <span>${isNaN(daysIn) ? '' : `Day ${daysIn}${daysTotal ? ' of ' + daysTotal : ''}`}</span>
            <span>${isNaN(avg) ? '' : money(avg) + ' a day'}</span>
          </div>
          <div style="margin-top:12px;border-top:1px solid var(--divider-color);padding-top:8px;">
            ${rows || '<div style="color:var(--secondary-text-color);">No breakdown available.</div>'}
          </div>
          <div style="color:var(--secondary-text-color);font-size:var(--ha-font-size-s,12px);margin-top:4px;">
            ${gstNote} Estimate from imported days; one-off charges not included.
          </div>
          ${allowance}
          ${last}
        </div>`;
    ]]]