Handige tools
Praktische plugins en hulpmiddelen voor iedereen met zonnepanelen of een slimme meter. Gratis te gebruiken, geen account vereist.
Beschikbare plugins
Zonnepanelenvoorspelling via forecast.solar
Haalt elk uur de verwachte opbrengst op via forecast.solar en toont vandaag/morgen/huidig uur als virtuele Domoticz-sensoren.
Installatie
- Kopieer de map
ForecastSolar/naar<domoticz>/plugins/ - Herstart Domoticz
- Setup → Hardware → toevoegen → "Deel je energie — Zonnepanelen Voorspelling"
- Vul breedtegraad, lengtegraad, hellingshoek, oriëntatie en capaciteit in
- Activeer — de drie sensoren worden automatisch aangemaakt
Configuratieparameters
| Parameter | Voorbeeld | Toelichting |
|---|---|---|
| Breedtegraad (lat) | 52.3676 | Geografische breedte van jouw locatie |
| Lengtegraad (lon) | 4.9041 | Geografische lengte van jouw locatie |
| Hellingshoek (°) | 35 | 0 = plat, 90 = verticaal — typisch 30–40° |
| Oriëntatie (°) | 180 | Noord=0, Oost=90, Zuid=180, West=270 |
| Capaciteit (kWp) | 5.0 | Totaal geïnstalleerd vermogen in kilowattpiek |
Breedtegraad en lengtegraad vind je via latlong.net of Google Maps (rechtsklik op je adres).
"""
Deel je energie — forecast.solar Domoticz Plugin
=================================================
Leest elk uur de verwachte zonnepanelenopbrengst via forecast.solar
en werkt drie virtuele Domoticz-sensoren bij:
1. Verwachte opbrengst vandaag (kWh)
2. Verwachte opbrengst morgen (kWh)
3. Verwacht huidig uur (W)
Installatie:
1. Kopieer deze map naar <domoticz>/plugins/ForecastSolar/
2. Herstart Domoticz
3. Ga naar Setup → Hardware → voeg toe: "Deel je energie — Zonnepanelen Voorspelling"
4. Vul breedtegraad, lengtegraad, hellingshoek, oriëntatie en capaciteit in
5. Activeer — de drie virtuele devices worden automatisch aangemaakt
Meer info: https://deeljeenergie.nl/handige-tools
"""
import json
import urllib.request
import urllib.error
from datetime import datetime, timedelta
"""
<plugin key="ForecastSolar" name="Deel je energie — Zonnepanelen Voorspelling" author="deeljeenergie.nl" version="1.0.0">
<description>
Leest elk uur de verwachte opbrengst van je zonnepanelen via forecast.solar
en toont vandaag, morgen en het huidige uur als virtuele Domoticz-sensoren.
Meer info: https://deeljeenergie.nl/handige-tools
</description>
<params>
<param field="Address" label="Breedtegraad (lat)" width="150px" required="true" default="52.3676"/>
<param field="Port" label="Lengtegraad (lon)" width="150px" required="true" default="4.9041"/>
<param field="Mode1" label="Hellingshoek in graden (0=plat, 90=verticaal)" width="120px" required="true" default="35"/>
<param field="Mode2" label="Oriëntatie — Noord=0, Oost=90, Zuid=180, West=270" width="180px" required="true" default="180"/>
<param field="Mode3" label="Capaciteit (kWp)" width="100px" required="true" default="5.0"/>
<param field="Mode6" label="Debug" width="100px">
<options>
<option label="Uit" value="Normal" default="true"/>
<option label="Aan" value="Debug"/>
</options>
</param>
</params>
</plugin>
"""
import Domoticz # noqa — beschikbaar in Domoticz plugin runtime
UNIT_TODAY = 1
UNIT_TOMORROW = 2
UNIT_HOUR = 3
class BasePlugin:
def __init__(self):
self.lat = 52.3676
self.lon = 4.9041
self.tilt = 35
self.compass_az = 180 # Noord=0, Oost=90, Zuid=180, West=270
self.kwp = 5.0
self.debug = False
self.error_count = 0
def onStart(self):
self.lat = float(Parameters.get("Address", "52.3676") or 52.3676)
self.lon = float(Parameters.get("Port", "4.9041") or 4.9041)
self.tilt = int(float(Parameters.get("Mode1", "35") or 35))
self.compass_az = int(float(Parameters.get("Mode2", "180") or 180))
self.kwp = float(Parameters.get("Mode3", "5.0") or 5.0)
self.debug = Parameters.get("Mode6") == "Debug"
# forecast.solar gebruikt Zuid=0, Oost=-90, West=90 (compas - 180)
self._forecast_az = self.compass_az - 180
self._ensure_devices()
Domoticz.Heartbeat(3600) # elk uur ophalen
Domoticz.Log(
"forecast.solar gestart — {}, {} | {} kWp | {}° helling | {}° oriëntatie".format(
self.lat, self.lon, self.kwp, self.tilt, self.compass_az
)
)
self.onHeartbeat() # direct eerste fetch
def onHeartbeat(self):
result = self._fetch()
if result is None:
return
now = datetime.now()
today = now.strftime("%Y-%m-%d")
tomorrow = (now + timedelta(days=1)).strftime("%Y-%m-%d")
cur_hour = now.strftime("%Y-%m-%d %H:00")
wh_day = result.get("watt_hours_day", {})
watts = result.get("watts", {})
today_kwh = round(wh_day.get(today, 0) / 1000, 2)
tomorrow_kwh = round(wh_day.get(tomorrow, 0) / 1000, 2)
hour_w = int(watts.get(cur_hour, 0))
if UNIT_TODAY in Devices: Devices[UNIT_TODAY].Update(nValue=0, sValue=str(today_kwh))
if UNIT_TOMORROW in Devices: Devices[UNIT_TOMORROW].Update(nValue=0, sValue=str(tomorrow_kwh))
if UNIT_HOUR in Devices: Devices[UNIT_HOUR].Update(nValue=0, sValue=str(hour_w))
if self.debug:
Domoticz.Log("Forecast: vandaag {} kWh | morgen {} kWh | dit uur {} W".format(
today_kwh, tomorrow_kwh, hour_w
))
# ── Intern ──────────────────────────────────────────────────────────────
def _ensure_devices(self):
if UNIT_TODAY not in Devices:
Domoticz.Device(
Name="Verwachte opbrengst vandaag",
Unit=UNIT_TODAY,
TypeName="Custom",
Options={"Custom": "1;kWh"},
).Create()
if UNIT_TOMORROW not in Devices:
Domoticz.Device(
Name="Verwachte opbrengst morgen",
Unit=UNIT_TOMORROW,
TypeName="Custom",
Options={"Custom": "1;kWh"},
).Create()
if UNIT_HOUR not in Devices:
Domoticz.Device(
Name="Verwacht huidig uur",
Unit=UNIT_HOUR,
TypeName="Custom",
Options={"Custom": "1;W"},
).Create()
def _fetch(self):
url = "https://api.forecast.solar/estimate/{}/{}/{}/{}/{}".format(
self.lat, self.lon, self.tilt, self._forecast_az, self.kwp
)
try:
req = urllib.request.Request(url, headers={"User-Agent": "DomoticzPlugin/ForecastSolar deeljeenergie.nl"})
with urllib.request.urlopen(req, timeout=15) as resp:
return json.loads(resp.read().decode()).get("result", {})
except urllib.error.HTTPError as e:
self._log_error("forecast.solar HTTP {}: {}".format(e.code, e.reason))
except Exception as e:
self._log_error("forecast.solar niet bereikbaar: " + str(e))
return None
def _log_error(self, msg):
self.error_count += 1
if self.error_count == 1 or self.error_count % 6 == 0:
Domoticz.Error(msg)
_plugin = BasePlugin()
def onStart():
_plugin.onStart()
def onHeartbeat():
_plugin.onHeartbeat()
P1-meter koppeling met Deel je energie
Stuurt elke 10 seconden P1-meterdata (verbruik, teruglevering, zonneopwekking) naar het Deel je energie platform voor realtime inzicht en energiedeling.
Installatie
- Voer
php bin/seed-meter.php "Mijn huis"uit om een API-token aan te maken - Noteer het gegenereerde token
- Kopieer de map
DeeljeEnergieMeter/naar<domoticz>/plugins/ - Herstart Domoticz
- Setup → Hardware → toevoegen → "Deel je energie P1 Meter"
- Vul API URL, token en het IDX van je P1 Smart Meter device in
- Activeer — data stroomt binnen 10 seconden in het admin-dashboard
Configuratieparameters
| Parameter | Voorbeeld | Toelichting |
|---|---|---|
| API URL | https://deeljeenergie.nl | URL van de Deel je energie server |
| API Token | abc123… | Token uit php bin/seed-meter.php |
| Domoticz URL | http://localhost:8080 | Lokale URL van Domoticz zelf |
| P1 Device IDX | 1 | IDX van het DSMR P1 Smart Meter device in Domoticz |
| Zonnepanelen IDX | 0 | IDX van solar device, 0 = geen |
| Domoticz gebruikersnaam | Optioneel, bij beveiligde Domoticz-installatie | |
| Domoticz wachtwoord | Optioneel, bij beveiligde Domoticz-installatie |
Het IDX van een device vind je in Domoticz via Devices → kolom IDX. Voer het seed-script uit op de server waar deeljeenergie.nl op draait.
"""
Deel je energie — P1 Meter Plugin
===================================
Stuurt elke 10 seconden P1-meterdata (en optioneel zonnepanelen) naar de Deel je energie API.
Installatie:
1. Kopieer deze map naar <domoticz>/plugins/DeeljeEnergieMeter/
2. Herstart Domoticz
3. Ga naar Setup → Hardware → voeg toe: "Deel je energie P1 Meter"
4. Vul de configuratie in en activeer
Vereisten:
- Python 3.x (standaard meegeleverd met Domoticz)
- DSMR P1 hardware al geconfigureerd in Domoticz (levert een Smart Meter device)
"""
import base64
import json
import urllib.request
import urllib.error
# Domoticz plugin metadata
"""
<plugin key="DeeljeEnergieMeter" name="Deel je energie P1 Meter" author="deeljeenergie.nl" version="1.1.0">
<description>
Stuurt P1-meterdata elke 10 seconden naar de Deel je energie API.
Optioneel: ook opwekking van zonnepanelen meesturen via een apart Domoticz device.
</description>
<params>
<param field="Address" label="API URL" width="300px" required="true" default="https://deeljeenergie.nl"/>
<param field="Mode1" label="API Token (X-API-Key)" width="300px" required="true" default=""/>
<param field="Mode2" label="Domoticz URL" width="300px" required="true" default="http://localhost:8080"/>
<param field="Mode3" label="P1 Smart Meter device IDX" width="100px" required="true" default="1"/>
<param field="Port" label="Zonnepanelen device IDX (0 = geen)" width="100px" required="false" default="0"/>
<param field="Mode4" label="Domoticz gebruikersnaam" width="200px" required="false" default=""/>
<param field="Mode5" label="Domoticz wachtwoord" width="200px" required="false" password="true" default=""/>
<param field="Mode6" label="Debug" width="100px">
<options>
<option label="Uit" value="Normal" default="true"/>
<option label="Aan" value="Debug"/>
</options>
</param>
</params>
</plugin>
"""
import Domoticz # noqa — beschikbaar in Domoticz plugin runtime
class BasePlugin:
def __init__(self):
self.api_url = ""
self.api_token = ""
self.domoticz_url = ""
self.device_idx = 1
self.solar_idx = 0 # 0 = geen zonnepanelen
self.domoticz_auth = None
self.debug = False
self.error_count = 0
def onStart(self):
self.api_url = Parameters["Address"].rstrip("/")
self.api_token = Parameters["Mode1"].strip()
self.domoticz_url = Parameters["Mode2"].rstrip("/")
self.device_idx = int(Parameters["Mode3"])
self.debug = Parameters["Mode6"] == "Debug"
port_val = Parameters.get("Port", "0").strip()
self.solar_idx = int(port_val) if port_val.isdigit() else 0
user = Parameters["Mode4"].strip()
pwd = Parameters["Mode5"].strip()
if user:
credentials = base64.b64encode("{}:{}".format(user, pwd).encode()).decode()
self.domoticz_auth = "Basic " + credentials
Domoticz.Heartbeat(10)
solar_info = " | Zonnepanelen IDX: {}".format(self.solar_idx) if self.solar_idx else ""
Domoticz.Log("Deel je energie P1 plugin gestart. API: {}{}".format(self.api_url, solar_info))
def onHeartbeat(self):
reading = self._read_device()
if reading is None:
return
if self.solar_idx:
solar_w = self._read_solar()
if solar_w is not None:
reading["solar_w"] = solar_w
self._send_reading(reading)
# ── P1 meter ────────────────────────────────────────────────────────────
def _read_device(self):
"""Leest het P1 Smart Meter device uit Domoticz via de JSON API."""
url = "{}/json.htm?type=command¶m=getdevices&rid={}".format(self.domoticz_url, self.device_idx)
req = urllib.request.Request(url)
if self.domoticz_auth:
req.add_header("Authorization", self.domoticz_auth)
try:
with urllib.request.urlopen(req, timeout=5) as resp:
data = json.loads(resp.read().decode())
except Exception as e:
self._log_error("Kon Domoticz device niet lezen: " + str(e))
return None
if data.get("status") != "OK" or not data.get("result"):
self._log_error("Onverwacht Domoticz antwoord voor IDX {}: {}".format(self.device_idx, data))
return None
return self._parse_p1(data["result"][0])
def _parse_p1(self, device):
"""
Parst een Domoticz P1 Smart Meter device.
Data-veld formaat: "T1_use_Wh;T2_use_Wh;T1_del_Wh;T2_del_Wh;cur_use_W;cur_del_W"
Voorbeeld: "2474426;1652237;1169617;3051901;58;1562"
"""
data_str = device.get("Data", "")
parts = data_str.split(";")
if len(parts) >= 6:
try:
reading = {
"usage_t1_kwh": round(int(parts[0]) / 1000, 3),
"usage_t2_kwh": round(int(parts[1]) / 1000, 3),
"deliver_t1_kwh": round(int(parts[2]) / 1000, 3),
"deliver_t2_kwh": round(int(parts[3]) / 1000, 3),
"power_use_w": int(parts[4]),
"power_del_w": int(parts[5]),
}
if self.debug:
Domoticz.Log("P1 meting: {}W verbruik, {}W teruglevering".format(
reading["power_use_w"], reading["power_del_w"],
))
return reading
except (ValueError, IndexError) as e:
self._log_error("Kon P1 Data-veld niet parsen: '{}' — {}".format(data_str, e))
return None
# Fallback: gebruik Usage/UsageDeliv velden
usage = device.get("Usage", "")
usage_deliv = device.get("UsageDeliv", "")
if usage:
try:
return {
"power_use_w": int(float(usage.split()[0])),
"power_del_w": int(float(usage_deliv.split()[0])) if usage_deliv else 0,
"usage_t1_kwh": 0.0,
"usage_t2_kwh": 0.0,
"deliver_t1_kwh": 0.0,
"deliver_t2_kwh": 0.0,
}
except (ValueError, IndexError):
pass
self._log_error("Kon P1 device niet parsen. Data: '{}' (IDX {})".format(data_str, self.device_idx))
return None
# ── Zonnepanelen ────────────────────────────────────────────────────────
def _read_solar(self):
"""
Leest het zonnepanelen device en geeft het huidige vermogen in W terug.
Ondersteunde Domoticz device-formaten:
- Usage/UsageDeliv veld: "1234 Watt" of "1234.5"
- Data veld (kWh-teller): "vandaag_kWh ; totaal_kWh" → wordt genegeerd (geen W)
- Data veld (W+kWh): "cur_W ; total_Wh" → eerste deel als W
"""
url = "{}/json.htm?type=command¶m=getdevices&rid={}".format(self.domoticz_url, self.solar_idx)
req = urllib.request.Request(url)
if self.domoticz_auth:
req.add_header("Authorization", self.domoticz_auth)
try:
with urllib.request.urlopen(req, timeout=5) as resp:
data = json.loads(resp.read().decode())
except Exception as e:
self._log_error("Kon zonnepanelen device niet lezen (IDX {}): {}".format(self.solar_idx, str(e)))
return None
if data.get("status") != "OK" or not data.get("result"):
self._log_error("Onverwacht Domoticz antwoord voor zonnepanelen IDX {}: {}".format(self.solar_idx, data))
return None
device = data["result"][0]
solar_w = self._parse_solar_w(device)
if self.debug and solar_w is not None:
Domoticz.Log("Zonnepanelen: {} W (IDX {})".format(solar_w, self.solar_idx))
return solar_w
def _parse_solar_w(self, device):
"""
Probeert het huidige vermogen (W) te lezen uit een Domoticz device.
Werkt voor de meeste Solar/PV device-types.
"""
# 1) Usage-veld: "1234 Watt", "1234.5 W", of gewoon "1234"
usage = device.get("Usage", "").strip()
if usage:
try:
return int(float(usage.split()[0]))
except (ValueError, IndexError):
pass
# 2) Data-veld met meerdere delen: "cur_W;total_Wh" of "cur_W;today_kWh;total_kWh"
data_str = device.get("Data", "").strip()
if data_str:
parts = data_str.split(";")
# Alleen het eerste deel gebruiken als vermogen als het er als een getal uitziet
try:
val = float(parts[0].split()[0])
# Waarden > 20 000 zijn waarschijnlijk Wh-tellers, geen W — overslaan
if val <= 20000:
return int(val)
except (ValueError, IndexError):
pass
self._log_error("Kon zonnepanelen-vermogen niet parsen. Device: {} (IDX {})".format(
device.get("Name", "?"), self.solar_idx
))
return None
# ── Versturen ───────────────────────────────────────────────────────────
def _send_reading(self, reading):
"""Stuurt de meting naar de Deel je energie API."""
url = self.api_url + "/api/meter/reading"
payload = json.dumps(reading).encode("utf-8")
req = urllib.request.Request(
url,
data=payload,
headers={
"Content-Type": "application/json",
"X-API-Key": self.api_token,
},
method="POST",
)
try:
with urllib.request.urlopen(req, timeout=10) as resp:
if resp.status == 201:
self.error_count = 0
if self.debug:
solar_str = " | zon: {} W".format(reading.get("solar_w", 0)) if "solar_w" in reading else ""
Domoticz.Log("Meting verstuurd: {} W verbruik, {} W teruglevering{}".format(
reading["power_use_w"], reading["power_del_w"], solar_str
))
else:
self._log_error("API gaf HTTP {} terug".format(resp.status))
except urllib.error.HTTPError as e:
self._log_error("API fout HTTP {}: {}".format(e.code, e.reason))
except Exception as e:
self._log_error("Kon API niet bereiken: " + str(e))
def _log_error(self, msg):
self.error_count += 1
# Log alleen elke 6e fout (= 1x per minuut bij 10s heartbeat) om logspam te voorkomen
if self.error_count == 1 or self.error_count % 6 == 0:
Domoticz.Error(msg)
_plugin = BasePlugin()
def onStart():
_plugin.onStart()
def onHeartbeat():
_plugin.onHeartbeat()
Wil je weten wat jouw panelen morgen opwekken?
Geen Domoticz nodig — vul je locatie in voor een directe voorspelling via forecast.solar.
☀️ Bereken opbrengst morgen