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