3. Januar 2025 (zuletzt aktualisiert am 4. Januar 2025)
In diesem Artikel zeige ich dir, wie du mithilfe von Python Daten aus einem Kostal-Plenticore-Wechselrichter ausliest und anschließend mit Asterisk über eine Telefonverbindung ansagen lassen kannst. Wir nutzen dazu drei Python-Skripte, ein angepasstes Dialplan-Fragment für die extensions.conf der Telefonsoftware Asterisk und das Python-Modul pymodbus. Voraussetzung sind Grundkenntnisse in Asterisk und Python.
Alle notwendigen Python-Skripte und us-englische Sprachdateien zum Download in einer ZIP: Kostal-Solar-Plenticore-Python-Asteriks-Dateien.zip
Erweiterung um die deutschsprachige Variante: Kostal Deutsche Sprachfassung.zip
Motivation: Das Ziel ist es vorerst, per Telefon Daten wie den aktuellen Hausverbrauch oder die Leistung deiner Solaranlage abzufragen. Dazu greift ein Python-Skript (ähnlich einem IVR-System) über das Modbus-Protokoll auf den Wechselrichter Plenticore von Kostal zu und gibt die Daten über Sprachansagen aus.
Meine Idee für die Zukunft: Die telefonische Ansage der Daten des Wechselrichters ist nur eine Fingerübung, um zu lernen, wie man Daten der Solaranlage weiterverarbeiten kann. Eigentlich geht es darum ein Python-Skript zu schreiben, das zu bestimmten Zeiten große Verbraucher ein- oder ausschaltet, wenn genügend Solarstrom im Überfluss vorhanden ist, der sonst für fast umsonst verkauft werden müsste.
Es geht also um die Heizungssteuerung. Als Heizung dient wie in Schweden üblich eine Erdwärmepumpe. Diese soll sich in der Übergangszeit und im Sommer nur dann einschalten, wenn die Solarbatterie fast voll ist und genügend überschüssiger Solarstrom vorhanden ist.
Auch im Sommer muss die Erdwärmepumpe für die Erhitzung des Brauchwassers sorgen, damit wir angenehm duschen können. Der 80 Liter große Warmwassertank stellt einen genügend großen Puffer dar, sodass das Wasser fast nur in den Sonnenstunden aufgeheizt werden muss. Ähnliche Überlegungen gelten auch für das Heizen des großen Kellers. Bevor ich für umgerechnet 1 Euro-Cent den Strom fast verschenke, heize ich lieber über die Wärmepumpe den Keller damit auf, der dann angenehm warm und trocken bleibt. Das Python-Skript kann auf einem kleinen Raspberry Pi laufen und die Verbraucher über Relais ein- und ausschalten, wenn die Bedingungen erfüllt sind, was das Programm prüft.
Als netter Nebeneffekt bietet es sich dann an auf diesem Raspberry Pi zusätzlich einen Asterisk-Server zu betreiben, um auch telefonisch die aktuellen Daten abfragen zu können. Dieser Asterisk-Server wäre nur rudimentär konfiguriert und besitzt eine IAX2-Verbindung zu dem ursprünglichen Asterisk-Server, der über das Internet erreichbar ist.
Arbeitsumgebung: Entwickelt und getestet habe ich die Skripte auf einem Laptop mit Ubuntu Mate, auf dem Asterisk 18.10.0 und Python 3.10.12 mit Pymodbus 2.5.3 installiert ist. Später ist ein Umzug auf einen Raspberry Pi geplant. Außerdem kamen Sounddateien zum Einsatz, die online mit Hilfe von https://ttsmp3.com/ und https://g711.org/ erstellt wurden.
Vorbereitung des Wechselrichters: Der Wechselrichter ist in das hauseigene LAN mit einer festen IP-Adresse einzubinden. Wir müssen dem Wechselrichter eine statische lokale IP-Adresse vergeben, was über das Webportal des Wechselrichters erfolgt. Dies erfolgt über „Einstellungen“ -> „Netzwerk“. In unserem Beispiel ist es die fiktive 192.168.1.222. Eine statische IP ist auf jeden Fall empfehlenswert, damit wir das Webportal des Wechselrichters auch über das Internet bedienen und abfragen können.
Weiterhin müssen wir über das Webportal des Wechselrichters den Modbus aktivieren. Dies erfolgt über „Einstellungen“ -> „Modbus / Sunspec (TCP)“ wie es in den nachfolgen zwei Screenshots zu sehen ist.
Die drei benötigten Python-Dateien Die 3 Python-Dateien kostal_modbusquery.py, asterisk_agi.py und kostal_agi.py sind im Ordner usr\share\asterisk\agi-bin unterzubringen. Ihnen müssen Rechte vergeben werden, damit sie ausführbar sind:
chmod +x /usr/share/asterisk/agi-bin/*.py
Achte wirklich darauf alle Skripte in demselben Verzeichnis abzulegen, in unserem Beispiel unter /usr/share/asterisk/agi-bin.
kostal_modbusquery.py: Ein Skript, das die Kommunikation mit dem Wechselrichter über Modbus realisiert. Es wurde von Kostal zur Verfügung gestellt (kostal_modbusquery – Read only query of the Kostal Plenticore Inverters using TCP/IP modbus protocol Copyright (C) 2018 Kilian Knoll). Es müssen lediglich zwei Anpassungen vorgenommen, wie es der nachfolgende Ausschnitt zeigt:
import pymodbus from pymodbus.client.sync import ModbusTcpClient # from pymodbus.client import ModbusTcpClient from pymodbus.constants import Endian from pymodbus.payload import BinaryPayloadDecoder class kostal_modbusquery: def __init__(self): #Change the IP address and port to suite your environment: self.inverter_ip="192.168.1.222" self.inverter_port="1502" #No more changes required beyond this point
Die IP des Wechselrichters ist einzutragen. Hier im Beispiel 192.168.1.222 und der Modbus-Port. Die Voreinstellung 1502 stimmt meistens.
Bevor du diese Skript nutzen kannst, installiere bitte das Python-Modul pymodbus in der passenden Version. Es wird für die Kommunikation mit dem Wechselrichter benötigt. Nutze dazu folgenden Befehl im Terminal:
pip install pymodbus==2.5.3
Rufe für die Installation von pymodus das Terminal im Ordner /usr/share/asterisk/agi-bin auf und führe dann den Befehl aus. Achte darauf, Python 3 zu verwenden.
Jetzt kannst du dieses Skript zum Beispiel mit der Python-Entwicklungsumgebung Thonny unter Ubuntu oder Debian ausführen und testen, ob das Skript mit dem Wechselrichter kommunizieren kann und Daten ausgibt:
asterisk_agi.py: Eine Hilfsbibliothek, die die Kommunikation zwischen Python und Asterisk ermöglicht. Ich habe dieses Modul bereits unter https://elektronikbasteln.pl7.de/agi-entwicklung-fuer-asterisk-mit-einem-python-modul-vereinfacht ausführlich vorgestellt. Es stellt grundlegende Funktionen für die Kommunikation mit Asterisk bereit. Es unterstützt Befehle wie sayit und saynumber, um Sprachansagen auszugeben. Eine Anpassung ist in der Regel nicht nötig.
kostal_agi.py: Dieses Skript ist das Herzstück des Projekts. Es verbindet die Wechselrichterdaten mit der Telefonanlage. Es nutzt die Klasse kostal_modbusquery, um die Daten abzufragen und verarbeitet sie weiter. Die Ergebnisse werden über die Asterisk-AGI-Schnittstelle ausgegeben.
#!/usr/bin/env python3 # -*- coding: utf-8 -*- # kostal_agi.py für Asterisk # Dieses Skript ruft Daten vom Kostal-Wechselrichter ab und gibt sie per AGI aus. # Es benötigt kostal_modbusquery.py im selben Verzeichnis. import asterisk_agi as agi # AGI-Module importieren from kostal_modbusquery import kostal_modbusquery # Kostal-Modul importieren def main(): # Initialisiert die AGI-Umgebung env = agi.init_env() # Kostal-Daten abrufen try: Kostalquery = kostal_modbusquery() # Initialisiert die Kostal-Abfrage Kostalquery.run() # Führt die Abfrage aus # Daten berechnen KostalVal = {elements[1]: elements[3] for elements in Kostalquery.KostalRegister} # Dictionary erstellen TotalHomeconsumption = round( KostalVal['Home own consumption from battery'] + KostalVal['Home own consumption from grid'] + KostalVal['Home own consumption from PV'], 1 ) LeftSidePowerGeneration = round((KostalVal['Power DC1'] + KostalVal['Power DC2']), 1) BatteryCharge = round(KostalVal['Battery voltage'] * KostalVal['Actual battery charge -minus or discharge -plus current'], 1) PowertoGrid = round(KostalVal['Inverter Generation Power (actual)'] - TotalHomeconsumption, 1) # Abspielen der Ergebnisse agi.sayit("/usr/share/asterisk/sounds/eigene/kostal/Aktueller_Hausverbrauch_Watt_G711.org_") # Hausverbrauch agi.saynumber(TotalHomeconsumption) # Gesamtverbrauch ausgeben agi.sayit("/usr/share/asterisk/sounds/eigene/kostal/Ladezustand_Batteriespeicher_Prozent_G711.org_") agi.saynumber(KostalVal['Act. state of charge']) agi.sayit("/usr/share/asterisk/sounds/eigene/kostal/Temperatur_Batteriespeicher_Celsius_G711.org_") agi.saynumber(KostalVal['Battery temperature']) agi.sayit("/usr/share/asterisk/sounds/eigene/kostal/Solarzellen_Leistung_Watt_G711.org_") agi.saynumber(LeftSidePowerGeneration) agi.sayit("/usr/share/asterisk/sounds/eigene/kostal/Momentanes_Laden_der_Batterie_Watt_G711.org_") agi.saynumber(BatteryCharge) agi.sayit("/usr/share/asterisk/sounds/eigene/kostal/Momentane_Einspeisung_Watt_G711.org_") agi.saynumber(PowertoGrid) agi.sayit("/usr/share/asterisk/sounds/eigene/kostal/good_bye_G711.org_") except Exception as ex: agi.sayit("/usr/share/asterisk/sounds/eigene/kostal/error_G711.org_") agi.verbose(f"Error: {str(ex)}", level=3) # Fehler im Asterisk-Log anzeigen if __name__ == "__main__": main()
Werte wie zum Beispiel die Temperatur der Batterie (KostalVal[‚Battery temperature‘]) wurden direkt von der modbusquery.py übernommen. Werte wie (BatteryCharge) wurden von der kostal_agi.py berechnet.
Hier ein Beispiel für eine Sprachansage:
agi.sayit("/usr/share/asterisk/sounds/eigene/kostal/Aktueller_Hausverbrauch_Watt_G711.org_") agi.saynumber(TotalHomeconsumption)
Folgende Ansagen auf US Englisch erfolgen der Reihe nach:
1. Aktueller Verbrauch des Hauses in Watt
2. Ladezustand der Batterie in Prozent
3. Temperatur der Batterie in Celsius
4. Aktuelle Leistungserzeugung der Solarpaneele in Watt
5. Aktuelle Leistung, mit der die Batterie geladen wird
6. Aktuelle Leistung, die in das Stromnetz eingespeist wird
(bei negativen Werten wird Leistung vom Stromnetz bezogen)
Was tun, wenn „Inverter Generation Power (actual)“ einen unsinnig hohen Wert liefert? Bei mir zeigte dieser Wert etwa 65 kW an. Er hätte aber aktuell bei Null liegen müssen. Um den aktuellen Stromverbrauch korrekt zu berechnen addierte ich die Wirkleistungen der drei Phasen. Dann mit -1 multiplizieren, um die eingespeiste Leistung zu erhalten. Die neue kostal_agi.py sieht jetzt so aus:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# test_agi.py für Asterisk
# Dieses Skript ruft Daten vom Kostal-Wechselrichter ab und gibt sie per AGI aus.
# Es benötigt kostal_modbusquery.py im selben Verzeichnis.
import asterisk_agi as agi # AGI-Module importieren
from kostal_modbusquery import kostal_modbusquery # Kostal-Modul importieren
def main():
# Initialisiert die AGI-Umgebung
env = agi.init_env()
# Kostal-Daten abrufen
try:
Kostalquery = kostal_modbusquery() # Initialisiert die Kostal-Abfrage
Kostalquery.run() # Führt die Abfrage aus
# Daten berechnen
KostalVal = {elements[1]: elements[3] for elements in Kostalquery.KostalRegister} # Dictionary erstellen
TotalHomeconsumption = round(
KostalVal['Home own consumption from battery'] +
KostalVal['Home own consumption from grid'] +
KostalVal['Home own consumption from PV'], 1
)
LeftSidePowerGeneration = round((KostalVal['Power DC1'] + KostalVal['Power DC2']), 1)
BatteryCharge = round(KostalVal['Battery voltage'] * KostalVal['Actual battery charge -minus or discharge -plus current'], 1)
# PowertoGrid = round(KostalVal['Inverter Generation Power (actual)'] - TotalHomeconsumption, 1)
# Obige Zeile hat den Stromverbrauch falsch berechnet. Inverter Generation Power (actual) unsinnig hoch.
# Vielleicht liegt das an dem Einsatz
# eines Smartmeters, der direkt hinter der Hausssicherung die Ströme und Spannungen der drei Phasen
# misst, was für das Batteriemanagement notwendig ist. Neu:
# Einspeisung berechnen durch Addition der Wirkleistung der drei Phasen und mit -1 multiplizieren:
PowertoGrid = round(
KostalVal['Active power phase 1 (powermeter)'] +
KostalVal['Active power phase 2 (powermeter)'] +
KostalVal['Active power phase 3 (powermeter)'], 1
) * -1
# Abspielen der Ergebnisse
agi.sayit("/usr/share/asterisk/sounds/eigene/kostal/Aktueller_Hausverbrauch_Watt_G711.org_") # Hausverbrauch
agi.saynumber(TotalHomeconsumption) # Gesamtverbrauch ausgeben
agi.sayit("/usr/share/asterisk/sounds/eigene/kostal/Ladezustand_Batteriespeicher_Prozent_G711.org_")
agi.saynumber(KostalVal['Act. state of charge'])
agi.sayit("/usr/share/asterisk/sounds/eigene/kostal/Temperatur_Batteriespeicher_Celsius_G711.org_")
agi.saynumber(KostalVal['Battery temperature'])
agi.sayit("/usr/share/asterisk/sounds/eigene/kostal/Solarzellen_Leistung_Watt_G711.org_")
agi.saynumber(LeftSidePowerGeneration)
agi.sayit("/usr/share/asterisk/sounds/eigene/kostal/Momentanes_Laden_der_Batterie_Watt_G711.org_")
agi.saynumber(BatteryCharge)
agi.sayit("/usr/share/asterisk/sounds/eigene/kostal/Momentane_Einspeisung_Watt_G711.org_")
agi.saynumber(PowertoGrid)
agi.sayit("/usr/share/asterisk/sounds/eigene/kostal/good_bye_G711.org_")
except Exception as ex:
agi.sayit("/usr/share/asterisk/sounds/eigene/kostal/error_G711.org_")
agi.verbose(f"Error: {str(ex)}", level=3) # Fehler im Asterisk-Log anzeigen
if __name__ == "__main__":
main()
Anpassung der extensions.conf: In der Asterisk-Konfiguration /etc/asterisk/extesnions.conf definierst du, wie der Anruf verarbeitet wird. Füge den folgenden Ausschnitt in deine extensions.conf ein:
; extensions.conf Auszug, Asterisk 18.10.0 [general] autofallthrough=no static=yes writeprotect=no [telefone] exten => 507,1,Noop(Anruf erfolgt von: "${CALLERID(name)}" <${CALLERID(num)}>) same => n,Noop(Telefonische Abfrage Solaranlage Kostal-Plenticore-Wechselrichter) same => n,Answer() same => n,AGI(kostal_agi.py) same => n,Hangup()
Sprachdateien: Sie gehören in den Ordner /usr/share/asterisk/sounds/eigene/kostal/ und wurden online mit https://ttsmp3.com/ (US English / Joanna) als MP3 erzeugt und dann mit https://g711.org/ (Voreinstellung) in ein für Asterisk taugliches WAV umgewandelt.
Testen mit dem Telefon: Wenn jemand die Nebenstelle 507 anruft, startet das AGI-Skript kostal_agi.py und gibt die Werte des Wechselrichters aus. Du solltest nun die Werte deiner Solaranlage als Sprachansage erhalten.
Fazit: Ein schönes Projekt, das ausbaufähig ist und zeigt, welche Leistungsfähigkeit Asterisk in Verbindung mit AGI und Python bietet. Umfangreiche AGI-Skripte lassen sich auf verschiedene Dateien unterteilen. Es ist auf jeden Fall von Vorteil die Daten aus einem Wechselrichter auslesen zu können. Asterisk ist dazu nur ein Beispiel der vielen Anwendungsmöglichkeiten.
Weiterführend:
Asterisk-Telefonserver auf einem Raspberry Pi – Installation, Konfiguration, Programmierung, SIP, IAX2, AGI-Skripte, Sicherheit und Tipps zum praktischen Betrieb – 2.11.2022: Diese Seite richtet sich an jene, welche einen Asterisk-Telefon-Server auf einem Raspberry Pi betreiben möchten und später ein kleines Netzwerk aus Asterisk-Servern planen, um ein eigenständiges Telefonnetz aufzubauen. Los geht es mit der Installation von Raspbian und Asterisk auf einem Raspberry Pi und dann nach Lust und Laune immer tiefer in die Programmierung von Asterisk. Die Themen werden laufend erweitert.
Selbstverständlich muss es nicht unbedingt ein Raspberry Pi sein. Andere Linux-Rechner gehen auch. – weiter – |
AGI-Entwicklung für Asterisk mit einem Python-Modul vereinfacht – 29.12.2024: Wenn du dich bereits mit der Telefonsoftware Asterisk auskennst und etwas Erfahrung mit Python mitbringst, hast du den perfekten Startpunkt, um AGI (Asterisk Gateway Interface) zu erkunden. In diesem Beitrag stelle ich dir mein Python-Modul `asterisk_agi.py` vor. Es ist das Herzstück für die einfache und effektive Entwicklung von AGI-Skripten und ermöglicht dir, dynamische und interaktive Funktionen in Asterisk zu integrieren. – weiter – |