no message

This commit is contained in:
2025-06-04 11:16:46 +02:00
parent 60a70e014e
commit e5b528f778

View File

@@ -1,6 +1,11 @@
<?php
declare(strict_types=1);
/**
* Sofar Wechselrichter Modul
*
* Anpassung: LoggerNumber als String behandeln, da Dezimalwert > 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*(regreg)+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)
*/