diff --git a/SofarWechselrichter/module.php b/SofarWechselrichter/module.php index 8a5dfce..f3f50c4 100644 --- a/SofarWechselrichter/module.php +++ b/SofarWechselrichter/module.php @@ -1,6 +1,11 @@ PHP-Integer + */ class SofarWechselrichter extends IPSModule { public function Create() @@ -8,9 +13,9 @@ class SofarWechselrichter extends IPSModule parent::Create(); // Moduleigenschaften $this->RegisterPropertyString('IPAddress', '172.31.70.80'); - $this->RegisterPropertyInteger('LoggerNumber', 0); + $this->RegisterPropertyString('LoggerNumber', '0'); // jetzt String $this->RegisterPropertyInteger('PollInterval', 60); - $this->RegisterPropertyString('Registers', '[]'); // JSON-String + $this->RegisterPropertyString('Registers', '[]'); // JSON-String // Timer für zyklische Abfragen; ruft per RequestAction ident "Query" auf $script = 'IPS_RequestAction(' . $this->InstanceID . ', "Query", "");'; @@ -30,7 +35,6 @@ class SofarWechselrichter extends IPSModule if (!is_array($registers)) { $registers = []; } - $position = 10; foreach ($registers as $entry) { $regNo = (int) $entry['RegisterNumber']; @@ -71,17 +75,21 @@ class SofarWechselrichter extends IPSModule */ private function Query(): void { - $ip = $this->ReadPropertyString('IPAddress'); - $loggerNumber = $this->ReadPropertyInteger('LoggerNumber'); + $rawIP = $this->ReadPropertyString('IPAddress'); + $ip = trim($rawIP); + $loggerNumberStr = trim($this->ReadPropertyString('LoggerNumber')); - if ($loggerNumber <= 0 || filter_var($ip, FILTER_VALIDATE_IP) === false) { - $this->LogMessage( - 'Abbruch: LoggerNumber = ' . $loggerNumber . ', IP = "' . $ip . '"', - KL_WARNING - ); + // Validierungs-Check: IP und LoggerNumber + if (!filter_var($ip, FILTER_VALIDATE_IP)) { + $this->LogMessage('Abbruch: Ungültige IP = "' . $ip . '"', KL_WARNING); + return; + } + if ($loggerNumberStr === '' || !ctype_digit($loggerNumberStr) || bccomp($loggerNumberStr, '1') < 0) { + $this->LogMessage('Abbruch: Ungültige LoggerNumber = "' . $loggerNumberStr . '"', KL_WARNING); return; } + // Tabellen-Einträge holen $registers = json_decode($this->ReadPropertyString('Registers'), true); if (!is_array($registers) || count($registers) === 0) { // Keine Register definiert @@ -100,7 +108,7 @@ class SofarWechselrichter extends IPSModule } try { - $bytes = $this->readRegister($ip, $loggerNumber, $regNo, $endian); + $bytes = $this->readRegister($ip, $loggerNumberStr, $regNo); if ($endian === 'LE') { $arr = unpack('vvalue', $bytes); // UINT16 LE } else { @@ -118,31 +126,37 @@ class SofarWechselrichter extends IPSModule /** * Liest ein einzelnes Register per Modbus-ähnlichem TCP (2 Bytes zurück) * - * @param string $ip Inverter-IP - * @param int $serial_nr Logger-Seriennummer - * @param int $reg Register-Adresse - * @param string $endian 'BE' oder 'LE' - * @return string 2 Byte binär - * @throws Exception Bei Kommunikationsfehler + * @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 Kommunikationsfehler */ private function readRegister( string $ip, - int $serial_nr, - int $reg, - string $endian + string $serial_nr_str, + int $reg ): string { // 1) Out_Frame ohne CRC aufbauen $oFrame = 'a5170010450000'; - $hexSN = str_pad(dechex($serial_nr), 8, '0', STR_PAD_LEFT); + + // Dekimal-String → 8-stellige Hex-String + $hexSN8 = $this->decStringToHex8($serial_nr_str); + + // Byte-Little-Endian: je 2 Hex-Zeichen umkehren $hexSNbytes = [ - substr($hexSN, 6, 2), - substr($hexSN, 4, 2), - substr($hexSN, 2, 2), - substr($hexSN, 0, 2), + 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 (2 Bytes) + Anzahl Register (2 Bytes = 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; @@ -159,7 +173,7 @@ class SofarWechselrichter extends IPSModule $oFrameWithCRC = $oFrame . strtolower($crcSwapped); // 3) Summen-Checksum (Bytes ab Index 1) + 0x15 - $l = strlen($oFrameWithCRC) / 2; + $l = strlen($oFrameWithCRC) / 2; $bArr = []; for ($i = 0; $i < $l; $i++) { $byteHex = substr($oFrameWithCRC, 2 * $i, 2); @@ -201,7 +215,7 @@ class SofarWechselrichter extends IPSModule throw new Exception("Keine Antwort vom Inverter erhalten."); } - // 6) Slice-Logik: l = 2*(reg–reg)+6 = 6 ⇒ 2 Bytes Daten + // 6) Slice-Logik: l = 6 ⇒ 2 Bytes Daten $lModbus = 6; $numBytes = 2; if (strlen($response) < $lModbus) { @@ -214,6 +228,29 @@ class SofarWechselrichter extends IPSModule return $dataBytes; } + /** + * Wandelt einen Dezimal-String in einen 8-stelligen Hex-String um (ohne Präfix). + * + * @param string $decString + * @return string 8-stellige Hexadezimal-Notation (Großbuchstaben) + */ + 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); + } + return str_pad($hex, 8, '0', STR_PAD_LEFT); + } + /** * CRC16-Modbus (Init=0xFFFF, Polynom=0xA001) */