Sofarmodul fertig hinzugefügt.
This commit is contained in:
@@ -31,7 +31,7 @@
|
||||
{
|
||||
"caption": "Register-Nummer",
|
||||
"name": "RegisterNumber",
|
||||
"width": "100px",
|
||||
"width": "200px",
|
||||
"add": 0,
|
||||
"edit": {
|
||||
"type": "NumberSpinner",
|
||||
@@ -41,7 +41,7 @@
|
||||
{
|
||||
"caption": "Bezeichnung",
|
||||
"name": "Label",
|
||||
"width": "200px",
|
||||
"width": "300px",
|
||||
"add": "",
|
||||
"edit": {
|
||||
"type": "ValidationTextBox"
|
||||
@@ -50,12 +50,11 @@
|
||||
{
|
||||
"caption": "Skalierungs-faktor",
|
||||
"name": "ScalingFactor",
|
||||
"width": "100px",
|
||||
"width": "200px",
|
||||
"add": 1,
|
||||
"edit": {
|
||||
"type": "NumberSpinner",
|
||||
"digits": 4,
|
||||
"minimum": 0
|
||||
"digits": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Sofar Wechselrichter Modul
|
||||
* Sofar Wechselrichter Modul (IP-Symcon)
|
||||
*
|
||||
* Anpassung: LoggerNumber als String behandeln, da Dezimalwert > PHP-Integer
|
||||
* - 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
|
||||
{
|
||||
@@ -13,11 +15,11 @@ class SofarWechselrichter extends IPSModule
|
||||
parent::Create();
|
||||
// Moduleigenschaften
|
||||
$this->RegisterPropertyString('IPAddress', '');
|
||||
$this->RegisterPropertyString('LoggerNumber', '0'); // jetzt String
|
||||
$this->RegisterPropertyString('LoggerNumber', '0'); // als String
|
||||
$this->RegisterPropertyInteger('PollInterval', 60);
|
||||
$this->RegisterPropertyString('Registers', '[]'); // JSON-String
|
||||
|
||||
// Timer für zyklische Abfragen; ruft per RequestAction ident "Query" auf
|
||||
// Timer für zyklische Abfragen (per RequestAction("Query"))
|
||||
$script = 'IPS_RequestAction(' . $this->InstanceID . ', "Query", "");';
|
||||
$this->RegisterTimer('QueryTimer', 0, $script);
|
||||
}
|
||||
@@ -25,16 +27,18 @@ class SofarWechselrichter extends IPSModule
|
||||
public function ApplyChanges()
|
||||
{
|
||||
parent::ApplyChanges();
|
||||
// Timer-Intervall (ms) setzen
|
||||
// Timer-Intervall (ms)
|
||||
$intervalSec = $this->ReadPropertyInteger('PollInterval');
|
||||
$intervalMs = ($intervalSec > 0) ? $intervalSec * 1000 : 0;
|
||||
$this->SetTimerInterval('QueryTimer', $intervalMs);
|
||||
|
||||
// Variablen für jeden definierten Register-Eintrag anlegen
|
||||
// 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'];
|
||||
@@ -43,11 +47,31 @@ class SofarWechselrichter extends IPSModule
|
||||
continue;
|
||||
}
|
||||
$ident = 'Reg' . $regNo;
|
||||
if (!IPS_VariableExists(@IPS_GetObjectIDByIdent($ident, $this->InstanceID))) {
|
||||
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) {
|
||||
if (@IPS_VariableExists($childID)) {
|
||||
$info = IPS_GetVariable($childID);
|
||||
$ident = $info['VariableIdent'];
|
||||
if (substr($ident, 0, 3) === 'Reg') {
|
||||
if (!in_array($ident, $validIdents)) {
|
||||
IPS_DeleteVariable($childID);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function Destroy()
|
||||
@@ -71,35 +95,36 @@ class SofarWechselrichter extends IPSModule
|
||||
}
|
||||
|
||||
/**
|
||||
* Führt eine Abfrage aller in der Tabelle definierten Register durch
|
||||
* Zyklische Abfrage aller definierten Register
|
||||
*/
|
||||
private function Query(): void
|
||||
{
|
||||
$rawIP = $this->ReadPropertyString('IPAddress');
|
||||
$ip = trim($rawIP);
|
||||
$ip = trim($this->ReadPropertyString('IPAddress'));
|
||||
$loggerNumberStr = trim($this->ReadPropertyString('LoggerNumber'));
|
||||
|
||||
// Validierungs-Check: IP und 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;
|
||||
}
|
||||
|
||||
// Tabellen-Einträge holen
|
||||
// 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 = (float) $entry['ScalingFactor'];
|
||||
$scale = (string) $entry['ScalingFactor']; // kann negativ sein
|
||||
$endian = strtoupper(trim((string)$entry['Endian']));
|
||||
$ident = 'Reg' . $regNo;
|
||||
|
||||
@@ -109,14 +134,16 @@ class SofarWechselrichter extends IPSModule
|
||||
|
||||
try {
|
||||
$bytes = $this->readRegister($ip, $loggerNumberStr, $regNo);
|
||||
// Wert als UINT16 (BE oder LE)
|
||||
if ($endian === 'LE') {
|
||||
$arr = unpack('vvalue', $bytes); // UINT16 LE
|
||||
$arr = unpack('vvalue', $bytes); // Little-Endian
|
||||
} else {
|
||||
$arr = unpack('nvalue', $bytes); // UINT16 BE
|
||||
$arr = unpack('nvalue', $bytes); // Big-Endian
|
||||
}
|
||||
$valueRaw = $arr['value'];
|
||||
$value = $valueRaw * $scale;
|
||||
SetValue($this->GetIDForIdent($ident), $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);
|
||||
}
|
||||
@@ -130,7 +157,7 @@ class SofarWechselrichter extends IPSModule
|
||||
* @param string $serial_nr_str Logger-Seriennummer als Dezimal-String
|
||||
* @param int $reg Register-Adresse
|
||||
* @return string 2 Byte binär
|
||||
* @throws Exception Bei Kommunikationsfehler
|
||||
* @throws Exception Bei Kommunikationsfehlern
|
||||
*/
|
||||
private function readRegister(
|
||||
string $ip,
|
||||
@@ -141,10 +168,9 @@ class SofarWechselrichter extends IPSModule
|
||||
// 1) Out_Frame ohne CRC aufbauen
|
||||
$oFrame = 'a5170010450000';
|
||||
|
||||
// Dekimal-String → 8-stellige Hex-String
|
||||
// Dezimal-String → 8-stellige Hex
|
||||
$hexSN8 = $this->decStringToHex8($serial_nr_str);
|
||||
|
||||
// Byte-Little-Endian: je 2 Hex-Zeichen umkehren
|
||||
// Byte-Little-Endian: jeweils 2 Zeichen umkehren
|
||||
$hexSNbytes = [
|
||||
substr($hexSN8, 6, 2),
|
||||
substr($hexSN8, 4, 2),
|
||||
@@ -156,12 +182,12 @@ class SofarWechselrichter extends IPSModule
|
||||
// Data-Field (16 Hex-Zeichen konstant)
|
||||
$oFrame .= '020000000000000000000000000000';
|
||||
|
||||
// Business-Field: 01 03 + Start-Register (2 Bytes) + Anzahl Register (2 Bytes = 1)
|
||||
// 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);
|
||||
$numHex = str_pad(dechex(1), 4, '0', STR_PAD_LEFT);
|
||||
$oFrame .= '0103' . $startHex . $numHex;
|
||||
|
||||
// 2) CRC16-Modbus (letzte 6 Bytes)
|
||||
// 2) CRC16-Modbus über letzte 6 Bytes
|
||||
$crcInputHex = substr($oFrame, -12);
|
||||
$crcInputBin = hex2bin($crcInputHex);
|
||||
if ($crcInputBin === false) {
|
||||
@@ -229,30 +255,29 @@ class SofarWechselrichter extends IPSModule
|
||||
}
|
||||
|
||||
/**
|
||||
* Wandelt einen Dezimal-String in einen 8-stelligen Hex-String um (ohne Präfix).
|
||||
* Wandelt einen Dezimal-String in einen 8-stelligen Hex-String um.
|
||||
*
|
||||
* @param string $decString
|
||||
* @return string 8-stellige Hexadezimal-Notation (Großbuchstaben)
|
||||
* @return string 8-stellige Hex (uppercase)
|
||||
*/
|
||||
private function decStringToHex8(string $decString): string
|
||||
{
|
||||
// BCMath erforderlich. Dezimal-String iterativ in Hex umwandeln.
|
||||
$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);
|
||||
$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);
|
||||
}
|
||||
|
||||
/**
|
||||
* CRC16-Modbus (Init=0xFFFF, Polynom=0xA001)
|
||||
* Berechnet CRC16-Modbus (Init=0xFFFF, Poly=0xA001) über Binärdaten.
|
||||
*/
|
||||
private function calculateCRC16Modbus(string $binaryData): int
|
||||
{
|
||||
@@ -273,7 +298,7 @@ class SofarWechselrichter extends IPSModule
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft, ob eine Variable mit Ident existiert
|
||||
* Prüft, ob eine Variable mit Ident existiert.
|
||||
*/
|
||||
private function VariableExists(string $ident): bool
|
||||
{
|
||||
|
||||
@@ -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