尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证制作者的介绍仅有上面的一句话。 更多详情请阅读仓库 README。 仓库 README ↗
正在加载完整 README。
查看完整 README ↗Persoonlijk dashboard voor een dynamisch stroomcontract. Combineert EPEX day-ahead prijzen, zonne-opbrengst en weersverwachting — en stuurt slimme stekkers via Homey aan om apparaten op het goedkoopste moment te laten draaien.
| Laag | Tools |
|---|---|
| Frontend | Vanilla JS, Chart.js, HTML/CSS |
| Backend | Vercel serverless (api/*.js) |
| State | Upstash Redis (laadplanning per apparaat) |
| Scheduling | Upstash QStash (vertraagde webhooks naar /api/cronLaden) |
| Externe APIs | EnergyZero (EPEX), SolarEdge Monitoring, Growatt OpenAPI, Open-Meteo, Homey cloud webhooks, Home Connect (BSH) |
.
├── index.html hoofdpagina (Energie IQ)
├── css/stijl.css
├── js/
│ ├── config.js tarieven, locatie, panelen, apparaten
│ ├── app.js tab-navigatie, status, init
│ ├── prijsformule.js pure prijsformule (verbruik + parametrische teruglevering), ook in npm test
│ ├── prijzen.js EnergyZero ophalen + grafiek/uurlijst
│ ├── solar.js SolarEdge + Growatt + Open-Meteo
│ └── apparaten.js slim-inplannen logica + UI
├── api/ Vercel serverless functions
│ ├── auth.js login/logout/me (routing via ?action=)
│ ├── growatt.js Growatt plant power
│ ├── solaredge.js SolarEdge overview/power/energy
│ ├── homewizard.js HomeWizard P1 live verbruik (Homey-push → Redis)
│ ├── homey.js Homey webhook proxy + connectivity check
│ ├── homeconnect.js Home Connect OAuth2 + appliance status/start/stop
│ ├── homeconnect/
│ │ └── callback.js OAuth2 redirect-target (code → tokens in Redis)
│ ├── planLaden.js laadplanning aanmaken (QStash publish)
│ └── cronLaden.js QStash callback → Homey webhook (signature-verified)
└── CLAUDE.md instructies voor Claude (versie, deploy, tarieven)
In Vercel → Settings → Environment Variables:
| Variable | Omschrijving |
|---|---|
GROWATT_API_TOKEN | Growatt OpenAPI token |
SOLAREDGE_API_KEY / SOLAREDGE_SITE_ID | SolarEdge Monitoring API |
HOMEY_CLOUD_ID | Homey cloud-id voor <id>.connect.athom.com |
HOMEWIZARD_PUSH_TOKEN_<userId> | Lange random secret die de Homey-flow meestuurt bij de P1-push naar /api/homewizard (#11) |
HOMECONNECT_CLIENT_ID / HOMECONNECT_CLIENT_SECRET | Home Connect OAuth2 app-credentials (globaal, gedeeld) |
APP_URL | Basis-URL voor QStash self-callbacks én Home Connect redirect-URI |
QSTASH_TOKEN | Upstash QStash publish token |
QSTASH_CURRENT_SIGNING_KEY / QSTASH_NEXT_SIGNING_KEY | QStash signing keys voor signature verificatie op /api/cronLaden |
UPSTASH_REDIS_REST_URL / UPSTASH_REDIS_REST_TOKEN | Upstash Redis REST |
DATABASE_URL | Neon PostgreSQL connection string (automatisch gezet door de Vercel ↔ Neon-integratie) |
SESSION_SECRET | Sterk random secret voor HMAC-ondertekening van de sessie-cookie — minimaal 32 tekens. Genereer met node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" |
CREDS_ENCRYPTION_KEY | Optioneel, 64 hex-tekens: sleutel voor de AES-256-GCM-versleuteling van per-user credentials in Redis (HomeWizard-cloud). Niet gezet → afgeleid uit SESSION_SECRET (roteren = users koppelen opnieuw) |
MIGRATE_SECRET | Token voor /api/db/migrate — minimaal 32 tekens, zelfde generator als SESSION_SECRET (maar een andere waarde). Zonder deze var weigert het endpoint (503) |
Gebruikers loggen in met e-mail + wachtwoord; er wordt geen ?u=-parameter meer
gebruikt voor normale toegang (de ?u=-fallback blijft tijdelijk bestaan voor
backwards-compatibiliteit). Auth is stateless: na een geslaagde login zet de
server een HMAC-ondertekende, HttpOnly cookie eq_session (30 dagen geldig) met
{ uid, email, userId }. Elke /api/*-call leidt de userId uit die cookie af
(getValidUserId in api/_helpers.js); bij geen geldige sessie valt hij terug op
?u= — behalve voor bedien-acties (Homey start/stop, laadplanning aanmaken/annuleren,
Home Connect start/stop): die vereisen sinds v2.77.0 een geldige sessie
(requireSessionUserId) en geven anders 401. De vroegere pincode (APP_PINCODE_<id>)
is daarmee vervallen: sinds de echte login (v2.72.0) was hij een dubbele laag die vooral
frictie gaf. Netto blijft er één verplichte beveiligingslaag over: de sessie.
Login, logout en me zitten in één serverless function api/auth.js (routing via
?action=) om onder de Vercel Hobby 12-functie-limiet te blijven.
| Endpoint | Functie |
|---|---|
POST /api/auth?action=login | { email, wachtwoord } → zet eq_session-cookie (rate-limit 10/5min/IP) |
POST /api/auth?action=logout | wist de cookie |
GET /api/auth?action=me | huidige sessie { uid, email, userId } of 401 |
GET /api/db/migrate | maakt de tabellen app_user + user_schedule aan (idempotent). Vereist MIGRATE_SECRET: Authorization: Bearer <token>-header of ?secret=<token>; anders 401. Rate-limit 5/min/IP + brute-force-lockout |
Wachtwoorden worden gehasht opgeslagen (bcrypt, cost 10) in de Neon-tabel app_user.
Eerste keer opzetten:
DATABASE_URL (via Vercel ↔ Neon), SESSION_SECRET en MIGRATE_SECRET in Vercel → redeploy.MIGRATE_SECRET:
curl -H "Authorization: Bearer <MIGRATE_SECRET>" https://energieiq.nl/api/db/migrate
Vanuit de browser kan het ook: https://energieiq.nl/api/db/migrate?secret=<MIGRATE_SECRET>.
Verwacht antwoord: {"success":true,"message":"Database klaar (app_user + user_schedule)"}.
Bij nieuwe tabellen in een latere versie: dezelfde call nog een keer (idempotent).DATABASE_URL in de env):
node scripts/create-user.mjs [email protected] <wachtwoord> "Pieter" 001
Client-side constanten in js/config.js:
LAT / LON — locatie voor Open-MeteoGROWATT_PEAK_KW / SOLAREDGE_PEAK_KW — piekvermogensAPPARATEN — apparatenlijst met vermogen en draaiurenCentraal in api/_helpers.js, gedeeld door alle endpoints:
/api/homey POST, /api/planLaden POST/DELETE en
/api/homeconnect?action=start|stop vereisen een geldige eq_session (geen ?u=-fallback)./api/db/migrate).https://energieiq.nl en https://stroom-dashboard.vercel.app
krijgen een Access-Control-Allow-Origin-header./api/cronLaden valideert calls met de QStash signing keys.state-nonce in Redis.escapeHtml() in js/config.js voor alle 3rd-party/user-data richting innerHTML.GROWATT_API_TOKEN_{userId}SOLAREDGE_API_KEY_{userId} en SOLAREDGE_SITE_ID_{userId}HOMEY_CLOUD_ID_{userId}Voor het aansturen/monitoren van BSH-apparaten.
1. Developer-applicatie aanmaken op https://developer.home-connect.com (registreer een developer-account):
energie-iq (of eigen naam)https://energieiq.nl/api/homeconnect/callbackIdentifyAppliance Monitor Control2. Credentials in Vercel (Settings → Environment Variables):
HOMECONNECT_CLIENT_ID ← Client ID (globaal, niet per user)HOMECONNECT_CLIENT_SECRET ← Client Secret (globaal, niet per user)APP_URL = https://energieiq.nl (de redirect-URI wordt hieruit afgeleid en moet exact matchen)3. In de Home Connect-app op je telefoon:
4. In de Energie IQ-app:
Tokens worden per user in Upstash Redis bewaard (homeconnect_tokens_<userId>).
| Apparaat | Ondersteuning |
|---|---|
| Wasmachine, droger | Volledig — programma's/opties kiezen + starten/inplannen |
| Oven, kookplaat | Alleen-monitoring (API kan ze niet veilig op afstand starten) |
| Vaatwasser | Niet via Home Connect — gebruik Homey of een slimme stekker |
Programma's, opties (temperatuur, centrifuge, droogdoel…) en FinishInRelative ("klaar om") worden volledig dynamisch uit de API gehaald — geen hardcoded waarden, dus het werkt automatisch voor elk merk/model. Bij twee gekoppelde toestellen biedt de app na een wasbeurt aan de droger erna in te plannen (IntelligentDry).
Let op — beperkingen van de Home Connect API:
- Wasmachine & droger: programma + opties kies je in de app; starten op afstand vereist dat "Remote Start" op het toestel aanstaat. Met "klaar om" plant de machine zelf het startmoment (
FinishInRelative).- Oven: vereist per keer fysiek inschakelen van Remote Start; daarom in deze app alleen-monitoring (geen startknop).
- Kookplaat: de API is voor kookplaten monitor-only — op afstand starten is niet mogelijk.
http://[ip-adres]/api/v1/data (geen authenticatie nodig)De P1-meter heeft alleen een lokale API (thuisnetwerk) en is dus niet direct
bereikbaar vanuit Vercel. Daarom pusht een Homey-flow de meetwaarde periodiek naar
/api/homewizard, dat hem in Upstash Redis cachet (homewizard_<userId>, TTL 10 min).
De Zon-tab toont op de tegel "Live verbruik" het actuele net-vermogen
(positief = verbruik uit het net, negatief = teruglevering, in groen).
Setup:
POSThttps://energieiq.nl/api/homewizardContent-Type: application/json{ "userId": "001", "token": "<HOMEWIZARD_PUSH_TOKEN_001>", "vermogenW": [measure_power], "importKwh": [meter_power.imported], "exportKwh": [meter_power.exported] }
(importKwh/exportKwh zijn optioneel; […] zijn Homey-flow-tags van de P1-tegel.)Auth-keuze: een aparte, lange gedeelde secret HOMEWIZARD_PUSH_TOKEN_<userId>
(in Vercel env vars). Homey heeft geen sessie-cookie, dus de gebruikersauth is hier
niet bruikbaar; een lange random push-token is de gangbare machine-to-machine
ingest-aanpak en wordt eenmalig in de Homey-flow gezet.
Genereer er een met bv. openssl rand -hex 32.
De app combineert drie bronnen voor merk/model/specs op basis van een Nederlands kenteken:
RDW Open Data — geen API key nodig
opendata.rdw.nl/resource/m9d7-ebf2.json — basisdata (merk, handelsbenaming, datum eerste toelating)opendata.rdw.nl/resource/8ys7-d773.json — brandstofdata voor PHEV/BEV-detectieEigen curated EV database — ev-database.json in projectroot
rdwHandelsbenaming voor exacte RDW-matchmerk, model, variant (optioneel), rdwHandelsbenaming, bouwjaarVanaf, bouwjaarTot, type (PHEV/BEV), batterijKwh, bruikbaarKwh, laadVermogenAcKw, laadVermogenDcKw (optioneel), elektrischBereikKmOpen EV Data (KilowattApp) — fallback voor onbekende BEVs
github.com/KilowattApp/open-ev-data — MIT licentie, attributie vereistkilowatt-ev-data.json (1,5 MB, server-side gebruikt door api/kenteken.js)kilowatt-meta.json bevat alleen het aantal entries voor snelle frontend-loadMatch-volgorde (in api/kenteken.js):
rdwHandelsbenaming exact → substring → model substringmeerdereVarianten: true + varianten[] array, frontend toont dropdownUpdaten KilowattApp data:
curl -o kilowatt-ev-data.json https://raw.githubusercontent.com/KilowattApp/open-ev-data/master/data/ev-data.json
# regenereer kilowatt-meta.json met:
node -e "const d=require('./kilowatt-ev-data.json'); require('fs').writeFileSync('kilowatt-meta.json', JSON.stringify({count:d.data.length,brands:d.brands.length,updated_at:d.meta.updated_at,source:'https://github.com/KilowattApp/open-ev-data'}, null, 2)+'\n')"
Heb je geen Homey, of hapert de push? Dan kan de app je P1-stand via je HomeWizard-account ophalen. Instellingen → Integraties → rij HomeWizard cloud → "Koppelen" → e-mail + wachtwoord van je HomeWizard Energy-account → Koppelen. De app controleert direct of het inloggen lukt en of er een P1-meter in je account zit, en slaat je gegevens daarna versleuteld op (AES-256-GCM, server-side in Redis; nooit in env vars, nooit zichtbaar in de app). Ontkoppelen wist ze weer.
Hoe het werkt: de server logt in bij HomeWizard, zoekt je P1-meter en opent kort een WebSocket
naar energy-app-ws.homewizard.com, pakt de eerste meetwaarde (active_power_w + meterstanden)
en sluit weer — elke paar minuten, alleen als er geen verse Homey-data is. Homey heeft altijd
voorrang (realtime). De tegel toont de bron: "via Homey" of "via HomeWizard cloud".
Heb je beide routes, dan kun je in hetzelfde paneel de bron handmatig vastzetten (Automatisch /
Alleen Homey / Alleen HomeWizard cloud) — handig om de cloud-route te testen zonder Homey uit te
zetten. De keuze is een persoonlijke weergavevoorkeur in je browser.
⚠️ Dit is een niet-officiële, gereverse-engineerde API (dezelfde die de Homey-apps
homey-homewizard-energy-cloud en com.homewizard gebruiken). HomeWizard kan hem zonder
aankondiging wijzigen; dan toont de tegel "geen live data" tot de koppeling is bijgewerkt.
Parametrisch per gebruiker (users/<id>.js → tarieven), rekenwerk in js/prijsformule.js
(getest met npm test):
verbruiksprijs = (epex + opslag + eb) × btw
terugleverprijs:
methode 'afslag' (Sepa Green): (epex − bedrag) × btw
methode 'opslag' (Zonneplan): (epex + bedrag) × btw
+ zonnebonus: × (1 + zonnebonusPct) als zonnebonusPct gezet is en (epex + bedrag) > 0
teruglevering: { methode: 'afslag', bedrag: 0.0353 } // Sepa Green
teruglevering: { methode: 'opslag', bedrag: 0.01653, zonnebonusPct: 0.10, zonnebonusMaxKwhJaar: 7500 } // Zonneplan
Vereenvoudiging: de zonnebonus geldt op alle uren (Zonneplan: alleen overdag en tot 7500 kWh/jaar). Tarieventabellen per contract: zie CLAUDE.md.
De frontend is een statische pagina — open index.html direct, of serveer de repo-root met bijvoorbeeld:
npx serve .
De API-endpoints werken alleen via een Vercel-deploy (of vercel dev) omdat ze de env vars nodig hebben.
Vercel deployt automatisch bij elke push naar main. Zie CLAUDE.md voor de versienummering- en deploy-conventies.