diff --git a/SofarWechselrichter/module.php b/SofarWechselrichter/module.php index d137bfe..9671512 100644 --- a/SofarWechselrichter/module.php +++ b/SofarWechselrichter/module.php @@ -8,6 +8,7 @@ declare(strict_types=1); * - Negative Skalierungen erlaubt. * - Bit-Länge pro Register (16, 32, 64) auswählbar. * - Signed/Unsigned pro Register wählbar. + * - Liest 32/64-Bit-Werte registerweise einzeln und setzt anschließend zusammen. * - Gelöschte Register-Variablen werden entfernt. */ class SofarWechselrichter extends IPSModule @@ -16,7 +17,7 @@ class SofarWechselrichter extends IPSModule { parent::Create(); // Moduleigenschaften - $this->RegisterPropertyString('IPAddress', '172.31.70.80'); + $this->RegisterPropertyString('IPAddress', ''); $this->RegisterPropertyString('LoggerNumber', '0'); // als String $this->RegisterPropertyInteger('PollInterval', 60); $this->RegisterPropertyString('Registers', '[]'); // JSON-String @@ -62,7 +63,6 @@ class SofarWechselrichter extends IPSModule } $children = IPS_GetChildrenIDs($this->InstanceID); foreach ($children as $childID) { - // Nur Variablen berücksichtigen (ObjectType = 2) $obj = IPS_GetObject($childID); if ($obj['ObjectType'] !== 2) { continue; @@ -121,7 +121,7 @@ class SofarWechselrichter extends IPSModule return; } - // 4) Für jedes Register: auslesen, zusammenbauen, Skalierung, speichern + // 4) Für jedes Register: einzeln auslesen, zusammensetzen, skalieren, speichern foreach ($registers as $entry) { $regNo = (int) $entry['RegisterNumber']; $label = trim((string)$entry['Label']); @@ -137,9 +137,13 @@ class SofarWechselrichter extends IPSModule try { $numRegs = $bitLength / 16; // 1, 2 oder 4 - $bytes = $this->readRegisters($ip, $loggerNumberStr, $regNo, $numRegs); - // bytes enthält dann (2 * $numRegs) Bytes - $hex = strtoupper(bin2hex($bytes)); + // Bytes registerweise einzeln abfragen und zusammenfügen: + $dataBytes = ''; + for ($i = 0; $i < $numRegs; $i++) { + $dataBytes .= $this->readRegister($ip, $loggerNumberStr, $regNo + $i); + } + // Nun liegen 2 * $numRegs Bytes in $dataBytes + $hex = strtoupper(bin2hex($dataBytes)); // Endian-Handling: falls LE, kehre gesamte Byte-Reihenfolge um if ($endian === 'LE') { $hex = $this->reverseByteOrder($hex); @@ -149,12 +153,9 @@ class SofarWechselrichter extends IPSModule // Bei "Signed" → Zwei-Komplement-Umrechnung if ($signedness === 'Signed') { - // 2^(bitLength - 1) - $half = bcpow('2', (string)($bitLength - 1), 0); - // 2^bitLength - $fullRange = bcpow('2', (string)$bitLength, 0); + $half = bcpow('2', (string)($bitLength - 1), 0); // 2^(bitLength-1) + $fullRange = bcpow('2', (string)$bitLength, 0); // 2^bitLength if (bccomp($rawDec, $half) >= 0) { - // rawDec - 2^bitLength $rawDec = bcsub($rawDec, $fullRange, 0); } } @@ -163,27 +164,24 @@ class SofarWechselrichter extends IPSModule $valueStr = bcmul($rawDec, $scale, 4); SetValueFloat($this->GetIDForIdent($ident), (float)$valueStr); } catch (Exception $e) { - $this->LogMessage("Fehler Lesen Reg {$regNo} ({$bitLength}bit, {$signedness}): " . $e->getMessage(), KL_WARNING); + $this->LogMessage( + "Fehler Lesen Reg {$regNo} ({$bitLength}bit, {$signedness}): " . $e->getMessage(), + KL_WARNING + ); } } } /** - * Liest $numRegs aufeinanderfolgende Register per Modbus-ähnlichem TCP (2*$numRegs Bytes zurück) + * Liest genau ein Register (16 Bit) 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 Start-Register-Adresse - * @param int $numRegs Anzahl aufeinanderfolgender Register (1..4) - * @return string Binär-String mit genau 2*$numRegs Bytes + * @param int $reg Register-Adresse + * @return string 2-Byte-Binär-String * @throws Exception Bei Kommunikationsfehlern */ - private function readRegisters( - string $ip, - string $serial_nr_str, - int $reg, - int $numRegs - ): string + private function readRegister(string $ip, string $serial_nr_str, int $reg): string { // 1) Out_Frame ohne CRC aufbauen $oFrame = 'a5170010450000'; @@ -201,12 +199,12 @@ class SofarWechselrichter extends IPSModule // Data-Field (16 Hex-Zeichen konstant) $oFrame .= '020000000000000000000000000000'; - // Business-Field: 01 03 + Start-Register + Anzahl Register ($numRegs) - $startHex = str_pad(dechex($reg), 4, '0', STR_PAD_LEFT); - $numHex = str_pad(dechex($numRegs), 4, '0', STR_PAD_LEFT); + // 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 + // 2) CRC16-Modbus (letzte 6 Bytes) $crcInputHex = substr($oFrame, -12); $crcInputBin = hex2bin($crcInputHex); if ($crcInputBin === false) { @@ -218,7 +216,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); @@ -260,9 +258,9 @@ class SofarWechselrichter extends IPSModule throw new Exception("Keine Antwort vom Inverter erhalten."); } - // 6) Slice-Logik: l = 2*$numRegs + 6, dann slice(-l, -4) liefert 2*$numRegs Bytes - $lModbus = 2 * $numRegs + 6; - $numBytes = 2 * $numRegs; + // 6) Slice-Logik: l = 2*1 + 6 = 8, slice(-8, -4) → 2 Bytes + $lModbus = 2 * 1 + 6; // = 8 + $numBytes = 2; if (strlen($response) < $lModbus) { throw new Exception("Unerwartet kurze Antwort (< {$lModbus} Bytes)."); } @@ -298,8 +296,8 @@ class SofarWechselrichter extends IPSModule /** * Kehrt die Byte-Reihenfolge eines Hex-Strings um (2 Hex-Zeichen = 1 Byte). * - * @param string $hex HexRepr (z.B. "A1B2C3D4") - * @return string Umgekehrte ByteReihenfolge (z.B. "D4C3B2A1") + * @param string $hex Hex-Repr. (z.B. "A1B2C3D4") + * @return string Umgekehrte Byte-Reihenfolge (z.B. "D4C3B2A1") */ private function reverseByteOrder(string $hex): string {