pagraham.co.uk

Home energy · Build guide

Build it yourself: Octopus, Powerwall and Zappi on Node-RED

How to build a Node-RED system where Octopus Energy decides when electricity is free, expensive or should be avoided, and a Tesla Powerwall and a myenergi Zappi act on it automatically, with Telegram alerts. All personal data is anonymised: domains, IP addresses, IDs, serials and keys are examples or placeholders.

This is the step-by-step version of my home energy write-up. Every domain, address, ID and key below is an example or a placeholder. Last updated 22 October 2026.

Octopus sets the mode; the Powerwall and Zappi act on it Three Node-RED flows on one box at home. The Octopus flow reads Octopus Energy and sets the mode every minute; the Powerwall and Zappi flows act on it through the Tesla Fleet API and myenergi API. Messages go to Telegram, daily usage to Insights Hub. A Cloudflare Tunnel carries only Tesla's sign-in and key check. Node-RED box at home Octopus flow sets the mode every minute free · powerdown · peak · cheap · normal Powerwall flow free: fill to 100 % powerdown: run the house tokens · summaries Zappi flow free: fast charge peak or away: stop home: solar only saved state on disk mode change Octopus Energy rates · usage · sessions Tesla Fleet API Powerwall + solar myenergi API Zappi charger Insights Hub daily usage + cost Telegram alerts from all three Cloudflare Tunnel energy.example.co.uk Tesla sign-in and key only · 3 paths
Solid lines run all the time. The dashed line is only used when Tesla checks the key or I sign in.

Three flows run on one always-on box. The Octopus flow works out the energy mode every minute; the Powerwall and Zappi flows act on it and talk to their own cloud APIs. The only way in from the internet is a Cloudflare Tunnel that carries Tesla's sign-in and key check.

What you need

Item Used for Notes
Always-on Linux box Runs Node-RED and the tunnel Built on a Siemens IOT2050 (Debian); a Raspberry Pi 4 or any small Debian/Ubuntu box works
Node-RED 3.x or later All three flows Installed as a system service, no Docker
Tesla Powerwall + solar Battery and generation Must be on your Tesla account
myenergi Zappi (+ hub) EV charging Needs a myenergi API key
Octopus Energy account Tariff, usage, events Built on Intelligent Flux; an Octopus Home Mini gives live demand
A domain on Cloudflare Tesla sign-in and public key Free Cloudflare plan is enough; the domain must not contain "tesla"
Tesla developer account Fleet API app developer.tesla.com
Telegram bot Alerts Free, from @BotFather
Siemens Insights Hub tenant (optional) Storing daily usage Only if you want energy data there; needs its own auth flow

Running costs are small: the Cloudflare Tunnel and Telegram are free, and polling the Tesla Fleet API every 10 minutes is about 4,300 data requests a month, which fits inside Tesla's monthly credit for personal use. Tesla's prices can change, so check the developer dashboard after the first week.

Data and secrets map

Every value the build needs, where it lives and whether it must be kept secret. The values shown are examples or placeholders; none are real. The rule throughout: secrets never go in a flow or a comment, because both are exported as plain text.

Value Example used in this guide Where it lives Secret?
Node-RED box address 192.168.1.50:1880 Your LAN No, but keep the editor off the internet
Public hostname energy.example.co.uk Cloudflare Tunnel route No
Cloudflare tunnel token eyJh… Installed into the cloudflared service Yes
Tesla client ID YOUR_TESLA_CLIENT_ID /data/tesla/config.json No
Tesla client secret ta-secret.… /data/tesla/config.json Yes
Tesla energy site ID YOUR_ENERGY_SITE_ID /data/tesla/config.json No
Login link key 48 random hex characters /data/tesla/config.json Yes
Tesla access and refresh tokens written by the flow /data/tesla/state.json Yes
Tesla private key private-key.pem /data/tesla/, never served Yes
Tesla public key com.tesla.3p.public-key.pem /data/tesla/, served to Tesla No
Telegram bot token 123456789:AA… config.json (Tesla) and Octopus tab credential Yes
Telegram chat ID YOUR_TELEGRAM_CHAT_ID config.json and Octopus tab env No
Octopus API key sk_live_… Octopus tab env, credential type Yes
Octopus account number A-XXXXXXXX Octopus tab env Personal
Electricity MPANs, meter serial YOUR_IMPORT_MPAN, YOUR_EXPORT_MPAN, YOUR_ELEC_METER_SERIAL Octopus tab env Personal
Gas MPRN, meter serial YOUR_GAS_MPRN, YOUR_GAS_METER_SERIAL Octopus tab env Personal
myenergi hub serial and API key — Zappi tab, myenergi API node, Digest login Yes (key)
Zappi serial, myenergi server YOUR_ZAPPI_SERIAL, s18.myenergi.net Zappi tab env No
Insights Hub token maintained by your IH auth flow global.access_token at run time Yes
Insights Hub asset IDs YOUR_IMPORT_ASSET_ID etc. Octopus flow, Insights Hub requests node No

Node-RED stores credential-type values encrypted in flows_cred.json. They are not included when you export a flow, so re-enter them after any import.

Step 1: prepare the Node-RED box

The box runs Node-RED as root, as a system service, with three changes to settings.js.

  1. Install Node-RED with the official script and enable it at boot:
bash <(curl -sL https://github.com/node-red/linux-installers/releases/latest/download/update-nodejs-and-nodered-deb)
systemctl enable nodered
  1. Edit /root/.node-red/settings.js and set these three things:
// let function nodes load built-in modules (fs, crypto)
functionExternalModules: true,

// keep flow/global context on disk, so Zappi history survives restarts
contextStorage: {
    default: { module: "localfilesystem" }
},

// password-protect the editor
adminAuth: {
    type: "credentials",
    users: [{ username: "admin", password: "<hash from: node-red admin hash-pw>", permissions: "*" }]
},
  1. Restart with systemctl restart nodered and open http://192.168.1.50:1880.
  2. Create the private folder the Tesla flow uses:
mkdir -p /data/tesla && chmod 700 /data/tesla

If Node-RED runs as a user other than root, chown -R <that user> /data/tesla.

Step 2: domain and Cloudflare Tunnel

Tesla must reach two things on your own domain over HTTPS: a public key file and a sign-in callback. A Cloudflare Tunnel gives the box a public HTTPS name without opening any router ports, and Cloudflare issues the certificate automatically.

Pick a subdomain without the word "tesla" in it, because Tesla's portal rejects those. This guide uses energy.example.co.uk. Your main website on the same domain is not affected.

  1. In Cloudflare go to Networking → Tunnels → Create Tunnel, name it (e.g. nodered-home), choose Debian and your architecture.
  2. On the box, install cloudflared:
mkdir -p --mode=0755 /usr/share/keyrings
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null
echo 'deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared any main' | tee /etc/apt/sources.list.d/cloudflared.list
apt-get update && apt-get install -y cloudflared
  1. Connect it to the tunnel without copying the token by hand:
cloudflared tunnel login        # open the printed link, pick your domain, Authorise
cloudflared service install $(cloudflared tunnel token <TUNNEL_ID>)
systemctl enable cloudflared && systemctl status cloudflared   # active (running)
  1. In the tunnel's Routes, add a Published application:
Field Value
Hostname energy.example.co.uk
Path ^/(powerwall/(login|callback)|\.well-known/appspecific/com\.tesla\.3p\.public-key\.pem)$
Service http://localhost:1880

The path filter is the security boundary: only those three addresses reach Node-RED, and everything else, including the editor, gets a 404. Don't use /auth/... paths, because Node-RED uses /auth/login for its own admin login.

Check from any browser: https://energy.example.co.uk/ should return 404 with a valid padlock.

Step 3: Telegram bot

  1. In Telegram, message @BotFather, send /newbot and follow the prompts. Keep the token it gives you; it's a secret.
  2. Send any message to your new bot, then open https://api.telegram.org/bot<TOKEN>/getUpdates in a browser and read chat.id. That's your chat ID.
  3. You'll paste the token and chat ID into the Tesla config.json (step 5) and the Octopus tab (step 4). The Zappi flow sends through the Tesla tab's telegram link, so it needs neither.

If a token is ever exposed, send /revoke to @BotFather and update both places.

Step 4: Octopus Energy flow

Install this flow first: the Powerwall and Zappi flows read the state it publishes.

  1. In Octopus, go to Account → Personal details → API access and copy your API key.
  2. In Node-RED: ☰ → Import, then select octopus-energy.json, then Import.
  3. Double-click the Octopus Energy v3 tab and fill in its Environment variables:
Name Type Example
OCTOPUS_API_KEY credential sk_live_…
TELEGRAM_BOT_TOKEN credential from step 3
TELEGRAM_CHAT_ID string from step 3
OCTOPUS_ACCOUNT string A-XXXXXXXX
ELEC_IMPORT_MPAN / ELEC_EXPORT_MPAN string 13-digit numbers on your bill
ELEC_SERIAL string electricity meter serial
GAS_MPRN / GAS_SERIAL string gas meter point and serial
GAS_UNITS string m3 (SMETS2) or kWh
GAS_CALORIFIC_VALUE string 39.5
CHEAP_RATE_P string optional, leave empty on a two-rate tariff
FREE_SESSIONS_URL string public Free Electricity feed
  1. Optional: Insights Hub. Set the three asset IDs and the aspect name in the Insights Hub requests node. Make sure your Insights Hub auth flow keeps global.access_token and global.url up to date. Without it the flow still runs, but it skips the Insights Hub writes.
  2. Disable any older Octopus tab, then Deploy.

To check it's working:

  • Output 1: state shows a status such as normal/day 26.52p→16.08p @16:00 within a minute.
  • List products in section F returns data.
  • At 20:00 the outlook arrives on Telegram.

Never paste the API key into a comment node, even temporarily. Comments are exported in plain text.

Step 5: Tesla Powerwall flow

5a. Create the Tesla developer app

At developer.tesla.com, create an app and set:

Setting Value
Allowed Origin URL https://energy.example.co.uk
Allowed Redirect URI https://energy.example.co.uk/powerwall/callback
Scopes openid, offline_access, energy_device_data, energy_cmds

The tunnel from step 2 must already be live: Tesla checks the domain has a valid certificate before it accepts it. UK accounts use the EU region.

5b. Keys and config on the box

cd /data/tesla
openssl ecparam -name prime256v1 -genkey -noout -out private-key.pem
openssl ec -in private-key.pem -pubout -out com.tesla.3p.public-key.pem
chmod 600 private-key.pem && chmod 644 com.tesla.3p.public-key.pem

Create /data/tesla/config.json, entering the secrets without them showing on screen:

read -rsp "Tesla client secret: " CS; echo
read -rsp "Telegram bot token: " BT; echo
LK=$(openssl rand -hex 24)
cat > /data/tesla/config.json <<EOF
{
  "client_id": "YOUR_TESLA_CLIENT_ID",
  "client_secret": "$CS",
  "redirect_uri": "https://energy.example.co.uk/powerwall/callback",
  "partner_domain": "energy.example.co.uk",
  "audience": "https://fleet-api.prd.eu.vn.cloud.tesla.com",
  "auth_base": "https://auth.tesla.com/oauth2/v3",
  "token_url": "https://fleet-auth.prd.vn.cloud.tesla.com/oauth2/v3/token",
  "scope": "openid email offline_access energy_device_data energy_cmds",
  "site_id": "YOUR_ENERGY_SITE_ID",
  "login_key": "$LK",
  "telegram": { "bot_token": "$BT", "chat_id": 0 },
  "billing_day": 1,
  "tariff": {
    "standing_charge_per_day": 0.55,
    "import": { "default": 0.21, "bands": [{ "from": "16:00", "to": "19:00", "rate": 0.28 }] },
    "export": { "default": 0.15, "bands": [{ "from": "16:00", "to": "19:00", "rate": 0.28 }] }
  }
}
EOF
unset CS BT; chmod 600 /data/tesla/config.json
echo "Login link: https://energy.example.co.uk/powerwall/login?k=$LK"

Then edit client_id, chat_id, billing_day and your real tariff rates with nano. Keep the login link in a password manager. If you don't know your site ID yet, set site_id to "0" for now (a YOUR_ value stops the config loading) and fix it in step 5d.

5c. Import and register

  1. Import tesla-powerwall.json, disable any older Tesla tab, and click Deploy. Apply config turns green.
  2. Check https://energy.example.co.uk/.well-known/appspecific/com.tesla.3p.public-key.pem shows your public key.
  3. In section 10 · One-off setup:
    • Click 1 · Get partner token.
    • Click 2 · Register partner domain. You should get HTTP 200 and a public_key starting 04.
    • 3 · Verify may return 403 on personal accounts. That's harmless.

5d. Sign in once

  1. Open the login link, sign in with Tesla, and allow energy information and energy control. You land on "Tesla account linked" and get a Telegram message.
  2. Click Products to find your energy_site_id. Put it in config.json, then Deploy.
  3. Click Today now. A summary should arrive on Telegram.

Without the right ?k= key the login link answers 403, so nobody else can link their own Tesla account in place of yours. After this, the flow refreshes its tokens itself.

Step 6: Zappi flow

  1. In the myenergi app or account portal, generate an API key for your hub (the menu wording changes between app versions). Note your hub serial.
  2. Import zappi.json and disable any older Zappi tab. Two copies would both switch the charger.
  3. Double-click the myenergi API node, then Use authentication → Digest:
    • Username: your hub serial (or the Zappi serial if you have no hub).
    • Password: your API key.
  4. Double-click the tab and set the environment variables:
    • ZAPPI_SERIAL: the serial on the Zappi.
    • MYENERGI_SERVER: the server your account uses, for example s18.myenergi.net.
  5. Make sure something sets global.home to HOME when you're home and anything else when you're away. This is usually a phone presence flow. After a restart, the Zappi waits for it rather than guessing.
  6. Deploy.

To check it's working:

  • Within 10 seconds, Parse status shows something like Eco+ · EV connected · 0 kW.
  • Tap the Stop inject: the charger switches, then hands back to the schedule after 3 hours or when you press Clear override.

The daily summary is sent on the zappi-summary link. Wire it to Telegram if you want it.

Step 7: start on boot and final tests

systemctl enable cloudflared nodered
systemctl list-unit-files | grep -Ei 'node-?red|cloudflared'   # both: enabled
Test Expected result
curl -s -o /dev/null -w '%{http_code}' https://energy.example.co.uk/ 404 (editor not exposed)
curl -s -o /dev/null -w '%{http_code}' https://energy.example.co.uk/powerwall/login 403 (no key given)
curl -s https://energy.example.co.uk/.well-known/appspecific/com.tesla.3p.public-key.pem | head -1 -----BEGIN PUBLIC KEY-----
Click Telegram test in the Tesla tab A message arrives
reboot About 20 s after Node-RED starts: "Node-RED (re)started … Tesla link restored"
Wait for an Octopus mode change, or the 16:00 peak Zappi stops; the Octopus tab's status turns red (peak)

Before you call it done:

  • Editor password set (adminAuth)
  • Older tabs disabled or deleted
  • Login link saved in a password manager
  • No keys in any comment node

How it runs

Every minute the Octopus flow sets one mode, and the Powerwall and Zappi act on it. They react straight away through the Octopus mode change link, and re-check on their own timers in case a message is missed.

Mode When Powerwall Zappi
powerdown A Saving Session (turn-down) that the flow has joined Reserve to 10 %, so the battery runs the house Stop
free Free Electricity session, booked Weekend Happy Hour or power-up, or a zero or negative unit rate Reserve to 100 % and grid charging on, so it fills from the grid Fast (boost), home or away
peak The highest rate on the tariff (Intelligent Flux: 16:00–19:00) Unchanged Stop
cheap The lowest of three rates, or at or below CHEAP_RATE_P Unchanged Normal schedule
normal Anything else Unchanged Eco+ (solar only) when home, Stop when away

The modes are checked in that order, so powerdown beats free. The Zappi also has a manual override, which comes before everything else for 3 hours. When a free or power-down period ends, the Powerwall puts back the reserve and grid-charging setting it had before, even if Node-RED restarted in between.

Schedule

When Flow What happens Telegram
Every 60 s Octopus Live demand from Home Mini; rebuild energy state and mode No
Every 5 min Tesla Token keep-alive Only if a refresh fails (max once a day)
Every 5 min Zappi Read charger, decide mode, switch if needed Octopus-driven changes and failures
Every 10 min Tesla Live status; solar start and finish alerts; Octopus control re-check Solar alerts; Powerwall switches
Every 15 min Tesla Save state to disk No
Every 15 min Zappi Today's kWh No
Every 30 min Octopus Free Electricity feed; Saving Sessions, auto-join power-downs New sessions; joins
Every 6 h Octopus Account, tariffs, unit rates, standing charges No
00:15 Zappi Yesterday's final kWh, week, month and billing totals Daily summary (if wired)
12:00, 21:00 Tesla Energy so far today Yes
18:00 Zappi Backfill missing days (last 32) No
20:00 Octopus Yesterday's usage and cost to Insights Hub; 24 h outlook Outlook
23:55 Tesla Final day stored; billing summary Yes

All times are UK time and follow the clock changes.

Storage, power cuts and security

After a power cut the whole system recovers on its own: no sign-in, no clicks. Both services start at boot, each flow reloads what it saved, and Telegram says the system is back.

What Stored in Survives restart because
Tesla tokens, daily totals, alert flags, export rule /data/tesla/state.json (+ .bak) Written to a temp file, flushed to disk, then swapped in; previous copy kept as backup
Powerwall settings before an Octopus change /data/tesla/octopus-control.json Written on every change
Zappi daily history (400 days) global.zappiHistory localfilesystem context store
Octopus tariffs, sessions, mode Flow context Rebuilt from the API within a minute of start-up
Credentials (Octopus key, Telegram token, myenergi login) flows_cred.json Encrypted by Node-RED

Security model

  • Nothing is exposed except three paths. The tunnel lets through only the public key, the login link and the callback. The editor and all other paths return 404 from outside, and the editor has a password on the LAN.
  • The login link is keyed and single-use per sign-in. Without the long ?k= key it returns 403, and each sign-in carries a random check value that expires after 10 minutes.
  • No secrets in flows or comments. Keys live in config.json (mode 600), credential-type environment variables, or node credentials. A flow can be exported, shared or backed up without leaking anything.
  • A pre-commit check. If you keep the flows in git, run a scanner for key-shaped strings (sk_live_, ta-secret., bot tokens, JWTs, private keys) before every commit.
  • If a key leaks, rotate it. Octopus: regenerate the API key. Tesla: rotate the client secret. Telegram: /revoke with @BotFather. myenergi: generate a new API key. Login link: change login_key and Deploy.

Troubleshooting

Symptom Cause Fix
Tesla portal: "Domain is not valid" Domain contains "tesla", or no live certificate yet Use another subdomain; get the tunnel live first
Login link says Forbidden Missing or wrong ?k= grep login_key /data/tesla/config.json
Login link says Not configured config.json missing, invalid, or a YOUR_ value left in Fix it, then Deploy
Tesla shows a redirect URI error Redirect URI not saved in the portal Add https://energy.example.co.uk/powerwall/callback
Public key URL 404 Key file missing, or route path filter changed Check /data/tesla/ and the tunnel route
"Tesla token refresh failed" Refresh token revoked or expired Open the login link again
Partner step 3 returns 403 Normal on personal accounts Ignore; step 2 confirms it
Octopus: "OCTOPUS_API_KEY not set" Credentials aren't carried by import Re-enter in the tab's environment variables
Zappi: "waiting for home/away state" global.home not set since restart Check the presence flow
Zappi or Powerwall ignore Octopus global.octopus over 10 min old Check the Octopus tab is enabled and updating
Charger flips between modes Two Zappi tabs enabled Disable the old one
Duplicate Telegram or Insights Hub writes Old and new tab both enabled Disable the old one
Powerwall: "retry after …" A Tesla settings change failed Read the Telegram error; it retries after 30 min
Insights Hub writes skipped IH auth flow not running Start the IH auth flow

Lessons learned

  • No "tesla" in the domain. Tesla's portal rejects it outright.
  • Certificate before portal. Tesla checks HTTPS on the domain when you save the allowed origin.
  • Node-RED owns /auth/. Its admin login lives there, so use your own prefix such as /powerwall/.
  • UK Tesla accounts are in the EU region. The North America endpoints give confusing errors.
  • Save the new refresh token first. Tesla's refresh tokens are single use; losing the new one means signing in again.
  • Write state atomically. A plain file write interrupted by a power cut leaves a corrupt file; temp file, fsync and rename doesn't.
  • Credentials don't travel with exports. Re-enter them after every import, and never park a key in a comment "for later".
  • Let Octopus define peak. Working out bands from the actual prices copes with tariff changes and clock changes; fixed times are only a fallback.
  • Only one copy of each flow enabled. Two copies fight over the same charger or the same URLs.
  • Poll gently. Tesla bills data requests and limits energy sites to one every five minutes, plus about five settings changes a day.

Sources