Auf Stand V2.001 gebracht, Sofarmodul ist jetzt integriert.
This commit is contained in:
@@ -14,6 +14,7 @@ class HauptManager extends IPSModule
|
||||
$this->RegisterPropertyInteger("Interval", 3); // Recheninterval
|
||||
|
||||
$this->RegisterVariableInteger("Gesamtnetzbezug", "Gesamtnetzbezug", "", 0);
|
||||
$this->RegisterVariableBoolean("Is_Peak_Shaving", "Peakshaving", "", false);
|
||||
|
||||
// Timer registrieren
|
||||
$this->RegisterTimer("Timer_DistributeEnergy",$this->ReadPropertyInteger("Interval")*1000,"IPS_RequestAction(" .$this->InstanceID .', "DistributeEnergy", "");');
|
||||
@@ -53,9 +54,9 @@ class HauptManager extends IPSModule
|
||||
|
||||
$decodedUser = json_decode(GetValue($user["User_Up"]), true);
|
||||
|
||||
if (isset($decodedUser["Timestamp"]) && (($currentTime - $decodedUser["Timestamp"])) < 30) {
|
||||
if (isset($decodedUser["Timestamp"]) && ((($currentTime - $decodedUser["Timestamp"])) < 30) && array_key_exists('User', $decodedUser)) {
|
||||
|
||||
foreach ($decodedUser["Users"] as $subuser) {
|
||||
foreach ($decodedUser["User"] as $subuser) {
|
||||
$subuser['Writeback'] = $user["User_Down"];
|
||||
|
||||
$Verbraucher_Liste_Korr[0]["User"][] = $subuser;
|
||||
@@ -64,7 +65,7 @@ class HauptManager extends IPSModule
|
||||
$Netzbezug += $decodedUser["Netzbezug"];
|
||||
}else{
|
||||
|
||||
RequestAction($user["User_Down"],'{"timestamp":'.time().',"Is_Peak_Shaving":'.true.',"User":[]}');
|
||||
SetValue($user["User_Down"],'{"Timestamp":'.time().',"Is_Peak_Shaving":'.true.',"User":[]}');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -76,8 +77,10 @@ class HauptManager extends IPSModule
|
||||
$Peakleistung = $this->ReadPropertyInteger("Peakleistung");
|
||||
$Ueberschussleistung = $this->ReadPropertyInteger("Ueberschussleistung");
|
||||
$Is_Peak_Shaving = false;
|
||||
|
||||
|
||||
// Fallunterscheidung ob auf Solarladen oder Peakshaving gerregelt wird.
|
||||
if ($Netzbezug < ($Peakleistung - $Ueberschussleistung) / 2) {
|
||||
if ($Netzbezug < ($Peakleistung + $Ueberschussleistung) / 2) {
|
||||
$remainingPower = -1 * (-1 * $Ueberschussleistung + $Netzbezug);
|
||||
$Is_Peak_Shaving = false;
|
||||
} else {
|
||||
@@ -90,7 +93,6 @@ class HauptManager extends IPSModule
|
||||
if (empty($Verbraucher_Liste_Korr[0]["User"])) {
|
||||
// Liste ist leer, daher nichts zu tun
|
||||
IPS_LogMessage("Manager", "aufgerufen leere liste");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -120,7 +122,7 @@ class HauptManager extends IPSModule
|
||||
$remainingPower += $totalAktuelle_Leistung;
|
||||
|
||||
// Wenn nicht alle Benutzer Idle = true sind, rufe SetAktuelle_Leistung mit Aktuelle_Leistung Werten auf, (alle Verbraucher behalten die aktuelle Leistung)
|
||||
if (!$allIdle) {
|
||||
if (!$allIdle || ($Is_Peak_Shaving != $this->GetValue("Is_Peak_Shaving"))) {
|
||||
// Schritt 1: Benutzer nach Writeback-Wert aufteilen
|
||||
$writebackArrays = [];
|
||||
foreach ($Verbraucher_Liste_Korr[0]["User"] as $user) {
|
||||
@@ -154,8 +156,11 @@ class HauptManager extends IPSModule
|
||||
// Schritt 4: RequestAction aufrufen
|
||||
RequestAction($writeback, $resultString);
|
||||
}
|
||||
$this->SetValue("Is_Peak_Shaving", $Is_Peak_Shaving);
|
||||
|
||||
return;
|
||||
}
|
||||
$this->SetValue("Is_Peak_Shaving", $Is_Peak_Shaving);
|
||||
|
||||
// Sortiere die Verbruacher nach Priorität entweder der PV_Prio oder der Peak Prio
|
||||
usort($Verbraucher_Liste_Korr[0]["User"], function ($a, $b) use (
|
||||
@@ -269,14 +274,15 @@ class HauptManager extends IPSModule
|
||||
|
||||
$aktleistung = array_filter($userEnergyProv, function($entry2) use ($user, $manager) {
|
||||
|
||||
|
||||
return $entry2["user"] == $user && $entry2["Writeback"] == $manager;
|
||||
});
|
||||
IPS_LogMessage("Aktuelle LEistung vor foreahc", print_r($aktleistung));
|
||||
|
||||
foreach($aktleistung as $entry){
|
||||
$aktleistung = $entry;
|
||||
}
|
||||
|
||||
IPS_LogMessage("Aktuelle LEistung Mnaagear", print_r($aktleistung));
|
||||
// Überprüfe, ob noch genügend verbleibende Energie für den nächsten Schritt vorhanden ist
|
||||
if ($remainingPower >= $powerstep - $aktleistung['Set_Leistung']) {
|
||||
// Aktualisiere die verbleibende Energie und die bereitgestellte Energie für den Benutzer
|
||||
@@ -359,6 +365,7 @@ class HauptManager extends IPSModule
|
||||
return $entry2["user"] == $user && $entry2["Writeback"] == $manager;
|
||||
});
|
||||
|
||||
|
||||
foreach($aktleistung as $entry){
|
||||
$aktleistung = $entry;
|
||||
}
|
||||
@@ -426,7 +433,7 @@ class HauptManager extends IPSModule
|
||||
// Schritt 2: Foreach-Schleife pro Writeback-Array
|
||||
foreach ($writebackArrays as $writeback => $users) {
|
||||
$resultArray = [
|
||||
'timestamp' => time(),
|
||||
'Timestamp' => time(),
|
||||
'Is_Peak_Shaving' => $Is_Peak_Shaving,
|
||||
'User' => []
|
||||
];
|
||||
@@ -442,7 +449,7 @@ class HauptManager extends IPSModule
|
||||
$resultString = json_encode($resultArray);
|
||||
|
||||
// Schritt 4: RequestAction aufrufen
|
||||
RequestAction($writeback, $resultString);
|
||||
SetValue($writeback, $resultString);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
"value": 4
|
||||
},
|
||||
{
|
||||
"caption": "Easee",
|
||||
"caption": "Easee - Nur Solarladen",
|
||||
"value": 5
|
||||
}
|
||||
]
|
||||
@@ -48,7 +48,7 @@
|
||||
{
|
||||
"type": "NumberSpinner",
|
||||
"name": "Zeit_Zwischen_Zustandswechseln",
|
||||
"caption": "Mindestlaufzeit des Verbrauchers bei Lastschaltung",
|
||||
"caption": "Mindestlaufzeit des Verbrauchers bei Lastschaltung (Für Easee Solarladen zwingend 1 Minute einstellen)",
|
||||
"suffix": ""
|
||||
},
|
||||
{
|
||||
@@ -58,25 +58,37 @@
|
||||
},
|
||||
{
|
||||
"type": "ValidationTextBox",
|
||||
"name": "ID",
|
||||
"name": "Geräte-ID Smart-Me / ECarUp / Easee",
|
||||
"caption": "ID"
|
||||
|
||||
},
|
||||
{
|
||||
"type": "ValidationTextBox",
|
||||
"name": "Seriennummer",
|
||||
"name": "Seriennummer Smart-Me / ECarUp / Easee",
|
||||
"caption": "Seriennummer"
|
||||
|
||||
},
|
||||
{
|
||||
"type": "ValidationTextBox",
|
||||
"name": "Username",
|
||||
"name": "Username / Benutzernahme Solarladen",
|
||||
"caption": "Username"
|
||||
|
||||
},
|
||||
{ "type": "PasswordTextBox",
|
||||
"name": "Password",
|
||||
"name": "Password -> Bei Anwendung mit Pico-Stationen",
|
||||
"caption": "Passwort"
|
||||
},
|
||||
{
|
||||
"type": "SelectVariable",
|
||||
"name": "Token_Easee",
|
||||
"caption": "Variable API-Token für Easee",
|
||||
"suffix": ""
|
||||
},
|
||||
{
|
||||
"type": "SelectVariable",
|
||||
"name": "Token_ECarUp",
|
||||
"caption": "Variable API-Token für ECarUp",
|
||||
"suffix": ""
|
||||
}
|
||||
|
||||
]
|
||||
|
||||
@@ -17,6 +17,8 @@ class Ladestation_v2 extends IPSModule
|
||||
$this->RegisterPropertyInteger("Interval", 5); // Recheninterval
|
||||
$this->RegisterPropertyInteger("Max_Current_abs", 32); // Recheninterval
|
||||
$this->RegisterPropertyInteger("Zeit_Zwischen_Zustandswechseln", 1);
|
||||
$this->RegisterPropertyInteger("Token_Easee", 0); // Recheninterval
|
||||
$this->RegisterPropertyInteger("Token_ECarUp", 0); // Recheninterval
|
||||
|
||||
|
||||
// Ladestationspezifische Variabeln
|
||||
@@ -156,12 +158,18 @@ class Ladestation_v2 extends IPSModule
|
||||
$this->SetValue("Car_detected", true);
|
||||
if($this->GetValue("Max_Current")<6){
|
||||
$this->SetValue("Car_is_full", true);
|
||||
|
||||
}
|
||||
|
||||
}else{
|
||||
$this->SetValue("Car_detected", false);
|
||||
$this->SetValue("Leistung_Delta", 0);
|
||||
$this->SetValue("Car_is_full", false);
|
||||
|
||||
$this->SetValue("IsTimerActive", false);
|
||||
$this->SetValue("Is_1_ph", false);
|
||||
$this->SetValue("Aktuelle_Leistung", 0);
|
||||
$this->SetValue("IdleCounter", 0);
|
||||
$this->SetValue("Idle", true);
|
||||
}
|
||||
}
|
||||
else{
|
||||
@@ -197,6 +205,7 @@ class Ladestation_v2 extends IPSModule
|
||||
|
||||
}else{
|
||||
$this->SetValue("Car_detected", false);
|
||||
$this->SetValue("Leistung_Delta", 0);
|
||||
$this->sendPowerToStation($this->ReadPropertyInteger("Max_Current_abs"));
|
||||
}
|
||||
}
|
||||
@@ -317,16 +326,53 @@ class Ladestation_v2 extends IPSModule
|
||||
break;
|
||||
|
||||
case 4:
|
||||
$this->SetValue("Car_detected", true);
|
||||
$car_on_station = true;
|
||||
break;
|
||||
|
||||
case 5:
|
||||
echo "Fall 5";
|
||||
|
||||
// Benutzer eCarUp abfragen
|
||||
|
||||
$authToken = GetValue($this->ReadPropertyInteger("Token_ECarUp"));
|
||||
$ID = $this->ReadPropertyString("ID");
|
||||
$apiUrl = 'https://public-api.ecarup.com/v1/station/' . $ID . '/conn'.'ectors/'. $ID . '/active-charging';
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $apiUrl);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
'accept: application/json',
|
||||
'Authorization: Bearer ' . $authToken
|
||||
]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($response === false) {
|
||||
IPS_LogMessage("Ladestation", "Fehler beim Abrufen der eCarUp API-Daten");
|
||||
return;
|
||||
}
|
||||
|
||||
$data = json_decode($response, true);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
IPS_LogMessage("Ladestation", "Fehler beim Dekodieren der JSON-Antwort");
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($data['driverIdentifier']) && ($data['driverIdentifier']==$this->ReadPropertyInteger("Username"))) {
|
||||
$this->SetValue("Ladeleistung_Effektiv", $this->GetValue("Power"));
|
||||
$this->SetValue("Fahrzeugstatus", 2);
|
||||
$car_on_station = true;
|
||||
|
||||
} else {
|
||||
$car_on_station = false;
|
||||
$this->SetValue("Fahrzeugstatus", 1);
|
||||
$this->SetValue("Ladeleistung_Effektiv", 0);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
$this->SetValue("Car_detected", false);
|
||||
$this->SetValue("Fahrzeugstatus", -1);
|
||||
$this->SetValue("Car_detected", false);
|
||||
$this->SetValue("Fahrzeugstatus", -1);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -522,6 +568,9 @@ class Ladestation_v2 extends IPSModule
|
||||
case 4:
|
||||
// Nichts zu tun für Dummy station
|
||||
return;
|
||||
case 5:
|
||||
// Keine base Url nötig
|
||||
break;
|
||||
}
|
||||
|
||||
$ch = curl_init();
|
||||
@@ -545,6 +594,21 @@ class Ladestation_v2 extends IPSModule
|
||||
case 4:
|
||||
// Nichts zu tun für Dummy station
|
||||
return;
|
||||
case 5:
|
||||
$url = "https://api.easee.com/api/chargers/".$this->ReadPropertyString("Seriennummer")."/commands/set_dynamic_charger_current";
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_ENCODING => "",
|
||||
CURLOPT_MAXREDIRS => 10,
|
||||
CURLOPT_TIMEOUT => 30,
|
||||
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
|
||||
CURLOPT_CUSTOMREQUEST => "POST",
|
||||
CURLOPT_POSTFIELDS => "{\"amps\":". $value .",\"minutes\":1}",
|
||||
CURLOPT_HTTPHEADER => [
|
||||
"Authorization: Bearer ".GetValue($this->ReadPropertyInteger("Token_Easee"))."",
|
||||
"content-type: application/*+json"
|
||||
],
|
||||
]);
|
||||
|
||||
default:
|
||||
return "Invalid station type.";
|
||||
}
|
||||
@@ -572,6 +636,21 @@ class Ladestation_v2 extends IPSModule
|
||||
case 4:
|
||||
// Nichts zu tun für Dummy station
|
||||
return;
|
||||
case 5:
|
||||
$url2 = "https://api.easee.com/api/chargers/".$this->ReadPropertyString("Seriennummer")."/commands/set_dynamic_charger_current";
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_ENCODING => "",
|
||||
CURLOPT_MAXREDIRS => 10,
|
||||
CURLOPT_TIMEOUT => 30,
|
||||
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
|
||||
CURLOPT_CUSTOMREQUEST => "POST",
|
||||
CURLOPT_POSTFIELDS => "{\"amps\":". $value .",\"minutes\":1}",
|
||||
CURLOPT_HTTPHEADER => [
|
||||
"Authorization: Bearer ".GetValue($this->ReadPropertyInteger("Token_Easee"))."",
|
||||
"content-type: application/*+json"
|
||||
],
|
||||
]);
|
||||
break;
|
||||
default:
|
||||
return "Invalid station type.";
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@ class Manager extends IPSModule
|
||||
$this->RegisterPropertyInteger("DatenZuruck", 0); // Initialisierung mit 0
|
||||
$this->RegisterPropertyInteger("Interval", 2); // Recheninterval
|
||||
|
||||
$this->RegisterVariableBoolean("Is_Peak_Shaving", false);
|
||||
|
||||
// Timer registrieren
|
||||
|
||||
$this->RegisterTimer("Timer_DistributeEnergy",$this->ReadPropertyInteger("Interval")*1000,"IPS_RequestAction(" .$this->InstanceID .', "DistributeEnergy", "");');
|
||||
@@ -38,18 +40,44 @@ class Manager extends IPSModule
|
||||
|
||||
$data = json_decode(GetValue($this->ReadPropertyInteger("DatenZuruck")), true);
|
||||
IPS_LogMessage("Manager", print_r($data));
|
||||
IPS_LogMessage("Manager", $data["timestamp"]);
|
||||
IPS_LogMessage("Manager", $data["Timestamp"]);
|
||||
|
||||
if (isset($data["timestamp"])) {
|
||||
$timestamp = $data["timestamp"];
|
||||
if (isset($data["Timestamp"])) {
|
||||
$timestamp = $data["Timestamp"];
|
||||
$currentTime = time();
|
||||
IPS_LogMessage("Manager", ($currentTime - $timestamp));
|
||||
IPS_LogMessage("Manager", "im here ist so halb gut");
|
||||
|
||||
if (($currentTime - $timestamp) < 3600) {
|
||||
$this->DistributeEnergy_Extern();
|
||||
}
|
||||
} else {
|
||||
IPS_LogMessage("Manager", "im here ist gut");
|
||||
|
||||
} else {
|
||||
|
||||
$sendarray = [
|
||||
"Netzbezug" => GetValue($this->ReadPropertyInteger("Netzbezug")),
|
||||
"Timestamp" => time()
|
||||
];
|
||||
|
||||
SetValue($this->ReadPropertyInteger("DatenHoch"), json_encode($sendarray));
|
||||
|
||||
$this->DistributeEnergy();
|
||||
IPS_LogMessage("Manager", "im here ist schlecht");
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
$sendarray = [
|
||||
"Netzbezug" => GetValue($this->ReadPropertyInteger("Netzbezug")),
|
||||
"Timestamp" => time()
|
||||
];
|
||||
|
||||
SetValue($this->ReadPropertyInteger("DatenHoch"), json_encode($sendarray));
|
||||
|
||||
$this->DistributeEnergy();
|
||||
IPS_LogMessage("Manager", "im here ist schlecht");
|
||||
|
||||
}
|
||||
|
||||
}else{
|
||||
@@ -73,7 +101,7 @@ class Manager extends IPSModule
|
||||
$Ueberschussleistung = $this->ReadPropertyInteger("Ueberschussleistung");
|
||||
|
||||
// Fallunterscheidung ob auf Solarladen oder Peakshaving gerregelt wird.
|
||||
if ($Netzbezug < ($Peakleistung - $Ueberschussleistung) / 2) {
|
||||
if ($Netzbezug < ($Peakleistung + $Ueberschussleistung) / 2) {
|
||||
$remainingPower = -1 * (-1 * $Ueberschussleistung + $Netzbezug);
|
||||
$Is_Peak_Shaving = false;
|
||||
} else {
|
||||
@@ -141,7 +169,7 @@ class Manager extends IPSModule
|
||||
IPS_LogMessage("Manager", "nciht idle");
|
||||
|
||||
}
|
||||
if(in_array(0, $powerSteps, true)){
|
||||
if(in_array(0, $powerSteps, true)){
|
||||
|
||||
// Addiere die aktuell bereits verwendete Leistung auf, um sie bei der verteilung zu berücksichtigen
|
||||
$totalAktuelle_Leistung += ($Aktuelle_Leistung-$delta);
|
||||
@@ -158,16 +186,18 @@ class Manager extends IPSModule
|
||||
return;
|
||||
}
|
||||
|
||||
// Wenn nicht alle Benutzer Idle = true sind, rufe SetAktuelle_Leistung mit Aktuelle_Leistung Werten auf, (alle Verbraucher behalten die aktuelle Leistung)
|
||||
if (!$allIdle) {
|
||||
// Wenn nicht alle Benutzer Idle = true sind, oder sich der zustand von Is_Peak_shaving gerade verändert hat rufe SetAktuelle_Leistung mit Aktuelle_Leistung Werten auf, (alle Verbraucher behalten die aktuelle Leistung)
|
||||
if (!$allIdle || ($Is_Peak_Shaving != $this->GetValue("Is_Peak_Shaving"))) {
|
||||
foreach ($filteredVerbraucher as $user) {
|
||||
IPS_RequestAction($user["InstanceID"],"SetAktuelle_Leistung",$user["Aktuelle_Leistung"]);
|
||||
IPS_LogMessage("Manager", "aufgerufen nicht alle idle");
|
||||
|
||||
}
|
||||
$this->SetValue("Is_Peak_Shaving", $Is_Peak_Shaving);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->SetValue("Is_Peak_Shaving", $Is_Peak_Shaving);
|
||||
// Sortiere die Verbruacher nach Priorität entweder der PV_Prio oder der Peak Prio
|
||||
usort($filteredVerbraucher, function ($a, $b) use (
|
||||
$Is_Peak_Shaving
|
||||
@@ -418,13 +448,13 @@ class Manager extends IPSModule
|
||||
$sendarray = [];
|
||||
|
||||
$sendarray = [
|
||||
"Users" => $filteredVerbraucher,
|
||||
"User" => $filteredVerbraucher,
|
||||
"Netzbezug" => $Netzbezug,
|
||||
"Timestamp" => time()
|
||||
|
||||
];
|
||||
|
||||
RequestAction($this->ReadPropertyInteger("DatenHoch"), json_encode($sendarray));
|
||||
SetValue($this->ReadPropertyInteger("DatenHoch"), json_encode($sendarray));
|
||||
|
||||
$answerArray = json_decode(GetValue($this->ReadPropertyInteger("DatenZuruck")), true);
|
||||
|
||||
|
||||
105
SofarWechselrichter/README.md
Normal file
105
SofarWechselrichter/README.md
Normal file
@@ -0,0 +1,105 @@
|
||||
2. Starte IP-Symcon neu bzw. klicke in der Konsole auf „Module aktualisieren“.
|
||||
|
||||
Anschließend erscheint in der Instanzliste der neue Gerätetyp **Sofar Wechselrichter**.
|
||||
|
||||
---
|
||||
|
||||
## Konfiguration
|
||||
|
||||
1. **Instanz anlegen**
|
||||
- Gehe in der IPS-Konsole auf „Instanzen hinzufügen“ → Hersteller: (falls sichtbar) → Modul: **Sofar Wechselrichter**.
|
||||
- Vergib einen sinnvollen Namen und eine Beschreibung.
|
||||
|
||||
2. **Einstellungen in den Eigenschaften**
|
||||
- **Logger-Seriennummer**:
|
||||
Gib hier die „Logger-Nummer“ deines Sofar-Wechselrichters ein (Dezimal).
|
||||
- **Abfragezyklus (Sekunden)**:
|
||||
Intervall in Sekunden, in dem zyklisch alle eingetragenen Register abgefragt werden.
|
||||
Ein Wert von `0` deaktiviert den Timer (keine zyklischen Abfragen).
|
||||
- **Register-Tabelle**:
|
||||
Hier definierst du beliebig viele Zeilen, jeweils mit:
|
||||
1. **Register-Nummer** (dezimal)
|
||||
2. **Bezeichnung** (z. B. „Gesamtproduktion“ oder „Spannung Phase L1“)
|
||||
3. **Skalierungs-faktor** (z. B. `0.1`, `1`, `10` usw.)
|
||||
|
||||
Wird beispielsweise für Register `1476` als Bezeichnung „Gesamtproduktion“ mit Skalierungsfaktor `1` eingetragen,
|
||||
so liest das Modul alle 60 Sekunden (oder den von dir gewählten Zyklus) Register 1476,
|
||||
multipliziert das rohe UINT16-Ergebnis mit `1` und legt den Wert in einer IPS-Variable an.
|
||||
|
||||
3. **Speichern/Übernehmen**
|
||||
Klicke auf „Übernehmen“, um die Änderungen zu übernehmen.
|
||||
Das Modul legt automatisch untergeordnete IPS-Variablen an:
|
||||
|
||||
- **Vorheriger Wert (Register 1160)**
|
||||
→ INT16BE, unverändert (analog zur alten Node-RED-Logik).
|
||||
Diese Variable heißt intern `Vorheriger Wert` und wird automatisch gepflegt.
|
||||
- **Alle weiteren Einträge aus der Register-Tabelle**
|
||||
→ Für jede Zeile wird eine Float-Variable mit der von dir angegebenen „Bezeichnung“ angelegt.
|
||||
Der Variablen-Identifier lautet automatisch `Reg<Registernummer>` (z. B. `Reg1476`).
|
||||
|
||||
---
|
||||
|
||||
## Funktionsweise
|
||||
|
||||
1. **Initialisierung (ApplyChanges)**
|
||||
- Liest den Abfragezyklus aus den Moduleigenschaften und initialisiert den Timer.
|
||||
- Legt eine Integer-Variable `Vorheriger Wert` (Reg 1160) an.
|
||||
- Legt für jede Zeile in der Register-Tabelle eine Float-Variable an (Ident `Reg<Nummer>`).
|
||||
|
||||
2. **Zyklische Abfrage (Timer-Callback)**
|
||||
- **Register 1160 (INT16BE)**
|
||||
→ Wird als „Vorheriger Wert“ in die Variable geschrieben (signed interpretiert).
|
||||
- **Alle weiteren Register aus der Tabelle**
|
||||
→ Jedes Register wird per Modbus-ähnlichem TCP-Paketaustausch abgefragt, als UINT16 ausgelesen,
|
||||
mit dem angegebenen Skalierungsfaktor multipliziert und in der zugehörigen Float-Variable gespeichert.
|
||||
|
||||
3. **Kommunikation**
|
||||
- TCP-Verbindung zu `192.168.0.0:8899` (feste IP im Code).
|
||||
- Der Aufruf von `readRegister()` baut ein „Out_Frame“ wie in Node-RED,
|
||||
rechnet CRC16-Modbus über die letzten 6 Bytes, hängt eine Summen-Checksum + 0x15 an,
|
||||
sendet das Paket, liest die Antwort, schneidet exakt 2 Daten-Bytes heraus und liefert sie zurück.
|
||||
|
||||
---
|
||||
|
||||
## Beispiel: Register 1476 („Gesamtproduktion“)
|
||||
|
||||
- **Register-Tabelle**
|
||||
| Register‐Nummer | Bezeichnung | Skalierungsfaktor |
|
||||
| --------------: | :------------------ | ----------------: |
|
||||
| 1476 | Gesamtproduktion | 1 |
|
||||
|
||||
- **Ergebnis**
|
||||
- Eine Float-Variable mit der Bezeichnung „Gesamtproduktion“ wird angelegt.
|
||||
- Wenn der PollInterval auf `60` Sekunden steht, liest das Modul alle 60 Sekunden das Register 1476,
|
||||
skaliert mit 1 und schreibt den numerischen Wert in `Reg1476`.
|
||||
|
||||
---
|
||||
|
||||
## Fehlersuche
|
||||
|
||||
- Falls die Variable „Vorheriger Wert“ immer denselben Wert liefert oder ein Lesefehler auftritt, prüfe bitte:
|
||||
1. **Logger-Nummer**: Ist sie korrekt (Dezimal)?
|
||||
2. **Netzwerk/Firewall**: Kann Symcon die Adresse `192.168.0.100:8899` erreichen?
|
||||
3. **Debug-Ausgaben**:
|
||||
– Öffne in der Konsole „Kernel-Log“ → Filter „SofarWechselrichter“.
|
||||
– Dort werden WARNs und ERRs protokolliert, falls z. B. keine Antwort kommt oder das Datenpaket inkorrekt ist.
|
||||
|
||||
- Falls du andere Datentypen brauchst (z. B. INT16 für Register außerhalb 1160), definiere sie analog als separate Zeile:
|
||||
– Trage die `Register‐Nummer` ein, gib als Skalierungsfaktor `1` (oder `0.1` etc.) an.
|
||||
– Der absolute Rohwert wird stets als UINT16 interpretiert (0–65535).
|
||||
– Solltest du negative INT16 benötigen, kannst du nachträglich einfach die Variable „Vorheriger Wert“ (Reg 1160)
|
||||
als Beispiel nehmen und in einem Script umrechnen (Werte über 32767 → –32768 + Rest).
|
||||
|
||||
---
|
||||
|
||||
## Versionshistorie
|
||||
|
||||
- **1.0**
|
||||
- Erstveröffentlichung:
|
||||
• Zyklische Abfrage beliebiger Register in einer Matrix konfigurieren
|
||||
• Automatische Anlage von Variablen für jeden Eintrag
|
||||
• Spezieller „Vorheriger Wert“ (Register 1160 als INT16)
|
||||
|
||||
---
|
||||
|
||||
*Ende der Dokumentation.*
|
||||
79
SofarWechselrichter/form.json
Normal file
79
SofarWechselrichter/form.json
Normal file
@@ -0,0 +1,79 @@
|
||||
{
|
||||
"elements": [
|
||||
{
|
||||
"type": "Label",
|
||||
"caption": "Sofar Wechselrichter Konfiguration"
|
||||
},
|
||||
{
|
||||
"type": "ValidationTextBox",
|
||||
"name": "IPAddress",
|
||||
"caption": "Inverter IP-Adresse"
|
||||
},
|
||||
{
|
||||
"type": "ValidationTextBox",
|
||||
"name": "LoggerNumber",
|
||||
"caption": "Logger-Seriennummer"
|
||||
},
|
||||
{
|
||||
"type": "NumberSpinner",
|
||||
"name": "PollInterval",
|
||||
"caption": "Abfragezyklus (Sekunden)",
|
||||
"minimum": 1,
|
||||
"suffix": "s"
|
||||
},
|
||||
{
|
||||
"type": "List",
|
||||
"name": "Registers",
|
||||
"caption": "Register-Tabelle",
|
||||
"add": "Neues Register hinzufügen",
|
||||
"delete": "Lösche Eintrag",
|
||||
"columns": [
|
||||
{
|
||||
"caption": "Register-Nummer",
|
||||
"name": "RegisterNumber",
|
||||
"width": "200px",
|
||||
"add": 0,
|
||||
"edit": {
|
||||
"type": "NumberSpinner",
|
||||
"minimum": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"caption": "Bezeichnung",
|
||||
"name": "Label",
|
||||
"width": "300px",
|
||||
"add": "",
|
||||
"edit": {
|
||||
"type": "ValidationTextBox"
|
||||
}
|
||||
},
|
||||
{
|
||||
"caption": "Skalierungs-faktor",
|
||||
"name": "ScalingFactor",
|
||||
"width": "200px",
|
||||
"add": 1,
|
||||
"edit": {
|
||||
"type": "NumberSpinner",
|
||||
"digits": 4,
|
||||
"minimum": -999999,
|
||||
"maximum": 999999
|
||||
}
|
||||
},
|
||||
{
|
||||
"caption": "Endian",
|
||||
"name": "Endian",
|
||||
"width": "80px",
|
||||
"add": "BE",
|
||||
"edit": {
|
||||
"type": "Select",
|
||||
"options": [
|
||||
{ "caption": "BE", "value": "BE" },
|
||||
{ "caption": "LE", "value": "LE" }
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"actions": []
|
||||
}
|
||||
12
SofarWechselrichter/module.json
Normal file
12
SofarWechselrichter/module.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"id": "{C26E97C8-BA00-0563-6B14-B807C5ACE17F}",
|
||||
"name": "SofarWechselrichter",
|
||||
"type": 3,
|
||||
"vendor": "Belevo AG",
|
||||
"aliases": [],
|
||||
"parentRequirements": [],
|
||||
"childRequirements": [],
|
||||
"implemented": [],
|
||||
"prefix": "GEF",
|
||||
"url": ""
|
||||
}
|
||||
311
SofarWechselrichter/module.php
Normal file
311
SofarWechselrichter/module.php
Normal file
@@ -0,0 +1,311 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Sofar Wechselrichter Modul (IP-Symcon)
|
||||
*
|
||||
* - LoggerNumber wird als String gehalten, um Overflow zu vermeiden.
|
||||
* - Negative Skalierungen sind erlaubt.
|
||||
* - Beim Löschen eines Registers wird die zugehörige Variable automatisch entfernt.
|
||||
*/
|
||||
class SofarWechselrichter extends IPSModule
|
||||
{
|
||||
public function Create()
|
||||
{
|
||||
parent::Create();
|
||||
// Moduleigenschaften
|
||||
$this->RegisterPropertyString('IPAddress', '');
|
||||
$this->RegisterPropertyString('LoggerNumber', '0'); // als String
|
||||
$this->RegisterPropertyInteger('PollInterval', 60);
|
||||
$this->RegisterPropertyString('Registers', '[]'); // JSON-String
|
||||
|
||||
// Timer für zyklische Abfragen (per RequestAction("Query"))
|
||||
$script = 'IPS_RequestAction(' . $this->InstanceID . ', "Query", "");';
|
||||
$this->RegisterTimer('QueryTimer', 0, $script);
|
||||
}
|
||||
|
||||
public function ApplyChanges()
|
||||
{
|
||||
parent::ApplyChanges();
|
||||
// Timer-Intervall (ms)
|
||||
$intervalSec = $this->ReadPropertyInteger('PollInterval');
|
||||
$intervalMs = ($intervalSec > 0) ? $intervalSec * 1000 : 0;
|
||||
$this->SetTimerInterval('QueryTimer', $intervalMs);
|
||||
|
||||
// Aktuelle Registerliste
|
||||
$registers = json_decode($this->ReadPropertyString('Registers'), true);
|
||||
if (!is_array($registers)) {
|
||||
$registers = [];
|
||||
}
|
||||
|
||||
// 1) Variablen anlegen (neu hinzugekommene)
|
||||
$position = 10;
|
||||
foreach ($registers as $entry) {
|
||||
$regNo = (int) $entry['RegisterNumber'];
|
||||
$label = trim($entry['Label']);
|
||||
if ($regNo < 0 || $label === '') {
|
||||
continue;
|
||||
}
|
||||
$ident = 'Reg' . $regNo;
|
||||
if (!$this->VariableExists($ident)) {
|
||||
$this->RegisterVariableFloat($ident, $label, '', $position);
|
||||
}
|
||||
$position += 10;
|
||||
}
|
||||
|
||||
// 2) Variablen löschen (falls entfernt)
|
||||
// Alle Kinder-IDs durchlaufen, Variablen mit Ident "Reg<Zahl>" entfernen,
|
||||
// wenn sie nicht mehr in $registers stehen.
|
||||
$validIdents = [];
|
||||
foreach ($registers as $entry) {
|
||||
$validIdents[] = 'Reg' . ((int)$entry['RegisterNumber']);
|
||||
}
|
||||
|
||||
$children = IPS_GetChildrenIDs($this->InstanceID);
|
||||
foreach ($children as $childID) {
|
||||
// Nur Variablen berücksichtigen
|
||||
$obj = IPS_GetObject($childID);
|
||||
if ($obj['ObjectType'] !== 2) {
|
||||
continue;
|
||||
}
|
||||
$ident = $obj['ObjectIdent']; // VariableIdent
|
||||
if (substr($ident, 0, 3) === 'Reg') {
|
||||
if (!in_array($ident, $validIdents)) {
|
||||
IPS_DeleteVariable($childID);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function Destroy()
|
||||
{
|
||||
parent::Destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wird aufgerufen durch IPS_RequestAction über Timer
|
||||
*/
|
||||
public function RequestAction($Ident, $Value)
|
||||
{
|
||||
switch ($Ident) {
|
||||
case 'Query':
|
||||
$this->Query();
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Exception('Ungültiger Ident: ' . $Ident);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Zyklische Abfrage aller definierten Register
|
||||
*/
|
||||
private function Query(): void
|
||||
{
|
||||
$ip = trim($this->ReadPropertyString('IPAddress'));
|
||||
$loggerNumberStr = trim($this->ReadPropertyString('LoggerNumber'));
|
||||
|
||||
// 1) Validierung IP
|
||||
if (!filter_var($ip, FILTER_VALIDATE_IP)) {
|
||||
$this->LogMessage('Abbruch: Ungültige IP = "' . $ip . '"', KL_WARNING);
|
||||
return;
|
||||
}
|
||||
// 2) Validierung LoggerNumber (Dezimal-String, > 0)
|
||||
if ($loggerNumberStr === '' || !ctype_digit($loggerNumberStr) || bccomp($loggerNumberStr, '1') < 0) {
|
||||
$this->LogMessage('Abbruch: Ungültige LoggerNumber = "' . $loggerNumberStr . '"', KL_WARNING);
|
||||
return;
|
||||
}
|
||||
|
||||
// 3) Register-Liste einlesen
|
||||
$registers = json_decode($this->ReadPropertyString('Registers'), true);
|
||||
if (!is_array($registers) || count($registers) === 0) {
|
||||
// Keine Register definiert
|
||||
return;
|
||||
}
|
||||
|
||||
// 4) Für jedes Register: auslesen, skalieren, in Variable schreiben
|
||||
foreach ($registers as $entry) {
|
||||
$regNo = (int) $entry['RegisterNumber'];
|
||||
$label = trim((string)$entry['Label']);
|
||||
$scale = (string) $entry['ScalingFactor']; // kann negativ sein
|
||||
$endian = strtoupper(trim((string)$entry['Endian']));
|
||||
$ident = 'Reg' . $regNo;
|
||||
|
||||
if ($regNo < 0 || $label === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$bytes = $this->readRegister($ip, $loggerNumberStr, $regNo);
|
||||
// Wert als UINT16 (BE oder LE)
|
||||
if ($endian === 'LE') {
|
||||
$arr = unpack('vvalue', $bytes); // Little-Endian
|
||||
} else {
|
||||
$arr = unpack('nvalue', $bytes); // Big-Endian
|
||||
}
|
||||
$valueRaw = $arr['value'];
|
||||
// bc* für sichere Multiplikation auch bei großen Zahlen / negativer Skalierung
|
||||
$value = bcmul((string)$valueRaw, (string)$scale, 4);
|
||||
SetValueFloat($this->GetIDForIdent($ident), (float)$value);
|
||||
} catch (Exception $e) {
|
||||
$this->LogMessage("Fehler Lesen Reg {$regNo}: " . $e->getMessage(), KL_WARNING);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Liest ein einzelnes Register per Modbus-ähnlichem TCP (2 Bytes zurück)
|
||||
*
|
||||
* @param string $ip Inverter-IP
|
||||
* @param string $serial_nr_str Logger-Seriennummer als Dezimal-String
|
||||
* @param int $reg Register-Adresse
|
||||
* @return string 2 Byte binär
|
||||
* @throws Exception Bei Kommunikationsfehlern
|
||||
*/
|
||||
private function readRegister(
|
||||
string $ip,
|
||||
string $serial_nr_str,
|
||||
int $reg
|
||||
): string
|
||||
{
|
||||
// 1) Out_Frame ohne CRC aufbauen
|
||||
$oFrame = 'a5170010450000';
|
||||
|
||||
// Dezimal-String → 8-stellige Hex
|
||||
$hexSN8 = $this->decStringToHex8($serial_nr_str);
|
||||
// Byte-Little-Endian: jeweils 2 Zeichen umkehren
|
||||
$hexSNbytes = [
|
||||
substr($hexSN8, 6, 2),
|
||||
substr($hexSN8, 4, 2),
|
||||
substr($hexSN8, 2, 2),
|
||||
substr($hexSN8, 0, 2),
|
||||
];
|
||||
$oFrame .= implode('', $hexSNbytes);
|
||||
|
||||
// Data-Field (16 Hex-Zeichen konstant)
|
||||
$oFrame .= '020000000000000000000000000000';
|
||||
|
||||
// Business-Field: 01 03 + Start-Register + Anzahl Register (1)
|
||||
$startHex = str_pad(dechex($reg), 4, '0', STR_PAD_LEFT);
|
||||
$numHex = str_pad(dechex(1), 4, '0', STR_PAD_LEFT);
|
||||
$oFrame .= '0103' . $startHex . $numHex;
|
||||
|
||||
// 2) CRC16-Modbus über letzte 6 Bytes
|
||||
$crcInputHex = substr($oFrame, -12);
|
||||
$crcInputBin = hex2bin($crcInputHex);
|
||||
if ($crcInputBin === false) {
|
||||
throw new Exception("Ungültiges Hex in CRC-Input: {$crcInputHex}");
|
||||
}
|
||||
$crcValue = $this->calculateCRC16Modbus($crcInputBin);
|
||||
$crcHex = strtoupper(str_pad(dechex($crcValue), 4, '0', STR_PAD_LEFT));
|
||||
$crcSwapped = substr($crcHex, 2, 2) . substr($crcHex, 0, 2);
|
||||
$oFrameWithCRC = $oFrame . strtolower($crcSwapped);
|
||||
|
||||
// 3) Summen-Checksum (Bytes ab Index 1) + 0x15
|
||||
$l = strlen($oFrameWithCRC) / 2;
|
||||
$bArr = [];
|
||||
for ($i = 0; $i < $l; $i++) {
|
||||
$byteHex = substr($oFrameWithCRC, 2 * $i, 2);
|
||||
$bArr[$i] = hexdec($byteHex);
|
||||
}
|
||||
$crcSum = 0;
|
||||
for ($i = 1; $i < $l; $i++) {
|
||||
$crcSum += $bArr[$i];
|
||||
$crcSum &= 0xFF;
|
||||
}
|
||||
$bArr[$l] = $crcSum;
|
||||
$bArr[$l+1] = 0x15;
|
||||
|
||||
$frameBin = '';
|
||||
foreach ($bArr as $b) {
|
||||
$frameBin .= chr($b);
|
||||
}
|
||||
|
||||
// 4) TCP-Verbindung öffnen & Paket senden (Port fest 8899)
|
||||
$port = 8899;
|
||||
$fp = @stream_socket_client("tcp://{$ip}:{$port}", $errno, $errstr, 5);
|
||||
if (!$fp) {
|
||||
throw new Exception("Verbindung zu {$ip}:{$port} fehlgeschlagen ({$errno}: {$errstr})");
|
||||
}
|
||||
fwrite($fp, $frameBin);
|
||||
stream_set_timeout($fp, 2);
|
||||
|
||||
// 5) Antwort einlesen
|
||||
$response = '';
|
||||
while (!feof($fp)) {
|
||||
$chunk = fread($fp, 1024);
|
||||
if ($chunk === false || $chunk === '') {
|
||||
break;
|
||||
}
|
||||
$response .= $chunk;
|
||||
}
|
||||
fclose($fp);
|
||||
if ($response === '') {
|
||||
throw new Exception("Keine Antwort vom Inverter erhalten.");
|
||||
}
|
||||
|
||||
// 6) Slice-Logik: l = 6 ⇒ 2 Bytes Daten
|
||||
$lModbus = 6;
|
||||
$numBytes = 2;
|
||||
if (strlen($response) < $lModbus) {
|
||||
throw new Exception("Unerwartet kurze Antwort (< {$lModbus} Bytes).");
|
||||
}
|
||||
$dataBytes = substr($response, -$lModbus, $numBytes);
|
||||
if (strlen($dataBytes) < 2) {
|
||||
throw new Exception("Data-Segment enthält weniger als 2 Bytes.");
|
||||
}
|
||||
return $dataBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wandelt einen Dezimal-String in einen 8-stelligen Hex-String um.
|
||||
*
|
||||
* @param string $decString
|
||||
* @return string 8-stellige Hex (uppercase)
|
||||
*/
|
||||
private function decStringToHex8(string $decString): string
|
||||
{
|
||||
$num = ltrim($decString, '0');
|
||||
if ($num === '') {
|
||||
return '00000000';
|
||||
}
|
||||
$hex = '';
|
||||
while (bccomp($num, '0') > 0) {
|
||||
$mod = bcmod($num, '16');
|
||||
$digit = dechex((int)$mod);
|
||||
$hex = strtoupper($digit) . $hex;
|
||||
$num = bcdiv($num, '16', 0);
|
||||
}
|
||||
return str_pad($hex, 8, '0', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Berechnet CRC16-Modbus (Init=0xFFFF, Poly=0xA001) über Binärdaten.
|
||||
*/
|
||||
private function calculateCRC16Modbus(string $binaryData): int
|
||||
{
|
||||
$crc = 0xFFFF;
|
||||
$len = strlen($binaryData);
|
||||
for ($pos = 0; $pos < $len; $pos++) {
|
||||
$crc ^= ord($binaryData[$pos]);
|
||||
for ($i = 0; $i < 8; $i++) {
|
||||
if (($crc & 0x0001) !== 0) {
|
||||
$crc >>= 1;
|
||||
$crc ^= 0xA001;
|
||||
} else {
|
||||
$crc >>= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $crc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft, ob eine Variable mit Ident existiert.
|
||||
*/
|
||||
private function VariableExists(string $ident): bool
|
||||
{
|
||||
$vid = @IPS_GetObjectIDByIdent($ident, $this->InstanceID);
|
||||
return ($vid !== false && IPS_VariableExists($vid));
|
||||
}
|
||||
}
|
||||
@@ -68,10 +68,10 @@ class Verbraucher_extern extends IPSModule
|
||||
|
||||
|
||||
private function berechneKombinationen(array $verbraucherListe){
|
||||
$kombinationen = [];
|
||||
$kombinationen = [0];
|
||||
foreach ($verbraucherListe as $verbraucher) {
|
||||
|
||||
if (GetValue($verbraucher['Read_Var']) == 1) {
|
||||
if (GetValue($verbraucher['Read_Var']) == true) {
|
||||
$tempListe = [];
|
||||
if(empty($kombinationen)){
|
||||
$kombinationen[] = $verbraucher['P_Nenn'];
|
||||
@@ -87,6 +87,31 @@ class Verbraucher_extern extends IPSModule
|
||||
return array_values(array_unique($kombinationen));
|
||||
}
|
||||
|
||||
private function findCombinationRecursive($power, $values, $currentCombination, $startIndex, &$result) {
|
||||
if ($power == 0) {
|
||||
$result = $currentCombination;
|
||||
return true;
|
||||
}
|
||||
if ($power < 0) {
|
||||
return false;
|
||||
}
|
||||
for ($i = $startIndex; $i < count($values); $i++) {
|
||||
$currentCombination[] = $values[$i];
|
||||
if ($this->findCombinationRecursive($power - $values[$i], $values, $currentCombination, $i + 1, $result)) {
|
||||
return true;
|
||||
}
|
||||
array_pop($currentCombination);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private function findCombination($power, $values) {
|
||||
$result = [];
|
||||
$found = $this->findCombinationRecursive($power, $values, [], 0, $result);
|
||||
return $found ? $result : null;
|
||||
}
|
||||
|
||||
|
||||
private function find($target, $values, $current, &$result) {
|
||||
if ($target == 0) {
|
||||
@@ -107,18 +132,20 @@ class Verbraucher_extern extends IPSModule
|
||||
$values = json_decode($this->GetValue("PowerSteps"));
|
||||
$result = [];
|
||||
|
||||
$this->find($this->GetValue("Power"), $values, [], $result);
|
||||
$firstCombination = $this->findCombination($this->GetValue("Power"), json_decode($this->GetValue("PowerSteps")));
|
||||
|
||||
//$this->find($this->GetValue("Power"), $values, [], $result);
|
||||
|
||||
$verbraucherListe = json_decode($this->ReadPropertyString("Verbraucher_Liste"), true);
|
||||
|
||||
|
||||
$firstCombination = $result[0];
|
||||
//$firstCombination = $result[0];
|
||||
|
||||
foreach ($verbraucherListe as &$verbraucher) {
|
||||
foreach ($verbraucherListe as $verbraucher) {
|
||||
if (in_array($verbraucher['P_Nenn'], $firstCombination)) {
|
||||
SetValue($verbraucher['Write_Var'], 1);
|
||||
RequestAction($verbraucher['Write_Var'], true);
|
||||
} else {
|
||||
SetValue($verbraucher['Write_Var'], 0);
|
||||
RequestAction($verbraucher['Write_Var'], false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,7 +168,7 @@ class Verbraucher_extern extends IPSModule
|
||||
public function GetCurrentData(bool $Peak)
|
||||
{
|
||||
$this->SetValue("Is_Peak_Shaving", $Peak);
|
||||
SetValue($this->ReadPropertyInteger("Is_Peak"), $Peak);
|
||||
RequestAction($this->ReadPropertyInteger("Is_Peak"), $Peak);
|
||||
|
||||
$verbraucherListe = json_decode($this->ReadPropertyString("Verbraucher_Liste"), true);
|
||||
$kombinationen = $this->berechneKombinationen($verbraucherListe);
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"compatibility": {
|
||||
"version": "8.0"
|
||||
},
|
||||
"version": "2.000",
|
||||
"version": "2.001",
|
||||
"build": 0,
|
||||
"date": 0
|
||||
}
|
||||
Reference in New Issue
Block a user