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.
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.
- 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
- Edit
/root/.node-red/settings.jsand 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: "*" }]
},
- Restart with
systemctl restart noderedand openhttp://192.168.1.50:1880. - 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.
- In Cloudflare go to Networking → Tunnels → Create Tunnel, name it (e.g.
nodered-home), choose Debian and your architecture. - 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
- 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)
- 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
- In Telegram, message @BotFather, send
/newbotand follow the prompts. Keep the token it gives you; it's a secret. - Send any message to your new bot, then open
https://api.telegram.org/bot<TOKEN>/getUpdatesin a browser and readchat.id. That's your chat ID. - 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'stelegramlink, 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.
- In Octopus, go to Account → Personal details → API access and copy your API key.
- In Node-RED: ☰ → Import, then select
octopus-energy.json, then Import. - 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 |
- 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_tokenandglobal.urlup to date. Without it the flow still runs, but it skips the Insights Hub writes. - 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:00within 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
- Import
tesla-powerwall.json, disable any older Tesla tab, and click Deploy. Apply config turns green. - Check
https://energy.example.co.uk/.well-known/appspecific/com.tesla.3p.public-key.pemshows your public key. - In section 10 · One-off setup:
- Click 1 · Get partner token.
- Click 2 · Register partner domain. You should get HTTP 200 and a
public_keystarting04. - 3 · Verify may return 403 on personal accounts. That's harmless.
5d. Sign in once
- 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.
- Click Products to find your
energy_site_id. Put it inconfig.json, then Deploy. - 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
- 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.
- Import
zappi.jsonand disable any older Zappi tab. Two copies would both switch the charger. - 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.
- Double-click the tab and set the environment variables:
ZAPPI_SERIAL: the serial on the Zappi.MYENERGI_SERVER: the server your account uses, for examples18.myenergi.net.
- Make sure something sets
global.hometoHOMEwhen 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. - 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:
/revokewith @BotFather. myenergi: generate a new API key. Login link: changelogin_keyand 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.