no message
This commit is contained in:
@@ -12,32 +12,26 @@ class SofarWechselrichter extends IPSModule
|
|||||||
$this->RegisterPropertyInteger('PollInterval', 60);
|
$this->RegisterPropertyInteger('PollInterval', 60);
|
||||||
$this->RegisterPropertyString('Registers', '[]'); // JSON-String
|
$this->RegisterPropertyString('Registers', '[]'); // JSON-String
|
||||||
|
|
||||||
// Timer für zyklische Abfragen
|
// Timer für zyklische Abfragen; ruft per RequestAction ident "Query" auf
|
||||||
$this->RegisterTimer('QueryTimer', 0, 'Sofar_Query($_IPS["TARGET"]);');
|
$script = 'IPS_RequestAction(' . $this->InstanceID . ', "Query", "");';
|
||||||
|
$this->RegisterTimer('QueryTimer', 0, $script);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function ApplyChanges()
|
public function ApplyChanges()
|
||||||
{
|
{
|
||||||
parent::ApplyChanges();
|
parent::ApplyChanges();
|
||||||
|
|
||||||
// Timer-Intervall (ms) setzen
|
// Timer-Intervall (ms) setzen
|
||||||
$intervalSec = $this->ReadPropertyInteger('PollInterval');
|
$intervalSec = $this->ReadPropertyInteger('PollInterval');
|
||||||
$intervalMs = ($intervalSec > 0) ? $intervalSec * 1000 : 0;
|
$intervalMs = ($intervalSec > 0) ? $intervalSec * 1000 : 0;
|
||||||
$this->SetTimerInterval('QueryTimer', $intervalMs);
|
$this->SetTimerInterval('QueryTimer', $intervalMs);
|
||||||
|
|
||||||
// Variable für vorherigen Wert (Register 1160) anlegen, falls noch nicht vorhanden
|
// Variablen für jeden definierten Register-Eintrag anlegen
|
||||||
if (!$this->VariableExists('PrevValue1160')) {
|
|
||||||
$this->RegisterVariableInteger('PrevValue1160', 'Vorheriger Wert (Reg 1160)', '', 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Alle Einträge aus „Registers“-Liste aus Property lesen
|
|
||||||
$registers = json_decode($this->ReadPropertyString('Registers'), true);
|
$registers = json_decode($this->ReadPropertyString('Registers'), true);
|
||||||
if (!is_array($registers)) {
|
if (!is_array($registers)) {
|
||||||
$registers = [];
|
$registers = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Für jeden Eintrag: Variable anlegen, falls nicht existiert
|
$position = 10;
|
||||||
$position = 20;
|
|
||||||
foreach ($registers as $entry) {
|
foreach ($registers as $entry) {
|
||||||
$regNo = (int) $entry['RegisterNumber'];
|
$regNo = (int) $entry['RegisterNumber'];
|
||||||
$label = trim($entry['Label']);
|
$label = trim($entry['Label']);
|
||||||
@@ -58,45 +52,45 @@ class SofarWechselrichter extends IPSModule
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Timer-Callback: wird in Intervallen aufgerufen
|
* Wird aufgerufen durch IPS_RequestAction über Timer
|
||||||
*/
|
*/
|
||||||
public function Query(): void
|
public function RequestAction($Ident, $Value)
|
||||||
|
{
|
||||||
|
switch ($Ident) {
|
||||||
|
case 'Query':
|
||||||
|
$this->Query();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Exception('Ungültiger Ident: ' . $Ident);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Führt eine Abfrage aller in der Tabelle definierten Register durch
|
||||||
|
*/
|
||||||
|
private function Query(): void
|
||||||
{
|
{
|
||||||
$ip = $this->ReadPropertyString('IPAddress');
|
$ip = $this->ReadPropertyString('IPAddress');
|
||||||
$loggerNumber = $this->ReadPropertyInteger('LoggerNumber');
|
$loggerNumber = $this->ReadPropertyInteger('LoggerNumber');
|
||||||
|
|
||||||
// Ohne gültige Seriennummer abbrechen
|
if ($loggerNumber <= 0 || filter_var($ip, FILTER_VALIDATE_IP) === false) {
|
||||||
if ($loggerNumber <= 0) {
|
$this->LogMessage('LoggerNumber ≤ 0 oder IP ungültig, Abbruch', KL_WARNING);
|
||||||
$this->LogMessage('LoggerNumber ist ≤ 0, Abbruch', KL_WARNING);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// (1) Register 1160 als INT16BE auslesen → „Vorheriger Wert“
|
|
||||||
try {
|
|
||||||
$bytes1160 = $this->readRegister($ip, $loggerNumber, 1160, 'BE');
|
|
||||||
$arr1160 = unpack('nvalue', $bytes1160);
|
|
||||||
$raw1160 = $arr1160['value'];
|
|
||||||
// Vorzeichenkorrektur
|
|
||||||
if ($raw1160 & 0x8000) {
|
|
||||||
$raw1160 -= 0x10000;
|
|
||||||
}
|
|
||||||
SetValue($this->GetIDForIdent('PrevValue1160'), $raw1160);
|
|
||||||
} catch (Exception $e) {
|
|
||||||
$this->LogMessage('Fehler Lesen Reg 1160: ' . $e->getMessage(), KL_WARNING);
|
|
||||||
}
|
|
||||||
|
|
||||||
// (2) Alle Einträge aus der Registerliste abfragen
|
|
||||||
$registers = json_decode($this->ReadPropertyString('Registers'), true);
|
$registers = json_decode($this->ReadPropertyString('Registers'), true);
|
||||||
if (!is_array($registers)) {
|
if (!is_array($registers) || count($registers) === 0) {
|
||||||
|
// Keine Register definiert
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
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']);
|
||||||
$scale = (float) $entry['ScalingFactor'];
|
$scale = (float) $entry['ScalingFactor'];
|
||||||
$endian = strtoupper(trim((string)$entry['Endian']));
|
$endian = strtoupper(trim((string)$entry['Endian']));
|
||||||
$ident = 'Reg' . $regNo;
|
$ident = 'Reg' . $regNo;
|
||||||
|
|
||||||
if ($regNo < 0 || $label === '') {
|
if ($regNo < 0 || $label === '') {
|
||||||
continue;
|
continue;
|
||||||
@@ -105,9 +99,9 @@ class SofarWechselrichter extends IPSModule
|
|||||||
try {
|
try {
|
||||||
$bytes = $this->readRegister($ip, $loggerNumber, $regNo, $endian);
|
$bytes = $this->readRegister($ip, $loggerNumber, $regNo, $endian);
|
||||||
if ($endian === 'LE') {
|
if ($endian === 'LE') {
|
||||||
$arr = unpack('vvalue', $bytes); // uint16 little-endian
|
$arr = unpack('vvalue', $bytes); // UINT16 LE
|
||||||
} else {
|
} else {
|
||||||
$arr = unpack('nvalue', $bytes); // uint16 big-endian
|
$arr = unpack('nvalue', $bytes); // UINT16 BE
|
||||||
}
|
}
|
||||||
$valueRaw = $arr['value'];
|
$valueRaw = $arr['value'];
|
||||||
$value = $valueRaw * $scale;
|
$value = $valueRaw * $scale;
|
||||||
@@ -119,14 +113,14 @@ class SofarWechselrichter extends IPSModule
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Liest genau 2 Bytes eines Registers (Modbus-ähnlich über TCP, Port fix 8899).
|
* Liest ein einzelnes Register per Modbus-ähnlichem TCP (2 Bytes zurück)
|
||||||
*
|
*
|
||||||
* @param string $ip IP-Adresse des Inverters
|
* @param string $ip Inverter-IP
|
||||||
* @param int $serial_nr Seriennummer (Logger-Nummer)
|
* @param int $serial_nr Logger-Seriennummer
|
||||||
* @param int $reg Register-Adresse
|
* @param int $reg Register-Adresse
|
||||||
* @param string $endian 'BE' oder 'LE'
|
* @param string $endian 'BE' oder 'LE'
|
||||||
* @return string Binärkette mit genau 2 Bytes
|
* @return string 2 Byte binär
|
||||||
* @throws Exception Bei Kommunikationsfehlern
|
* @throws Exception Bei Kommunikationsfehler
|
||||||
*/
|
*/
|
||||||
private function readRegister(
|
private function readRegister(
|
||||||
string $ip,
|
string $ip,
|
||||||
@@ -137,8 +131,6 @@ class SofarWechselrichter extends IPSModule
|
|||||||
{
|
{
|
||||||
// 1) Out_Frame ohne CRC aufbauen
|
// 1) Out_Frame ohne CRC aufbauen
|
||||||
$oFrame = 'a5170010450000';
|
$oFrame = 'a5170010450000';
|
||||||
|
|
||||||
// Seriennummer (8-stelliges Hex), Byte-Little-Endian pro Byte
|
|
||||||
$hexSN = str_pad(dechex($serial_nr), 8, '0', STR_PAD_LEFT);
|
$hexSN = str_pad(dechex($serial_nr), 8, '0', STR_PAD_LEFT);
|
||||||
$hexSNbytes = [
|
$hexSNbytes = [
|
||||||
substr($hexSN, 6, 2),
|
substr($hexSN, 6, 2),
|
||||||
@@ -147,14 +139,9 @@ class SofarWechselrichter extends IPSModule
|
|||||||
substr($hexSN, 0, 2),
|
substr($hexSN, 0, 2),
|
||||||
];
|
];
|
||||||
$oFrame .= implode('', $hexSNbytes);
|
$oFrame .= implode('', $hexSNbytes);
|
||||||
|
|
||||||
// Data-Field (16 Hex-Zeichen konstant)
|
|
||||||
$oFrame .= '020000000000000000000000000000';
|
$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);
|
$startHex = str_pad(dechex($reg), 4, '0', STR_PAD_LEFT);
|
||||||
$numRegs = 1;
|
$numHex = str_pad(dechex(1), 4, '0', STR_PAD_LEFT);
|
||||||
$numHex = str_pad(dechex($numRegs), 4, '0', STR_PAD_LEFT);
|
|
||||||
$oFrame .= '0103' . $startHex . $numHex;
|
$oFrame .= '0103' . $startHex . $numHex;
|
||||||
|
|
||||||
// 2) CRC16-Modbus (letzte 6 Bytes)
|
// 2) CRC16-Modbus (letzte 6 Bytes)
|
||||||
@@ -163,14 +150,13 @@ class SofarWechselrichter extends IPSModule
|
|||||||
if ($crcInputBin === false) {
|
if ($crcInputBin === false) {
|
||||||
throw new Exception("Ungültiges Hex in CRC-Input: {$crcInputHex}");
|
throw new Exception("Ungültiges Hex in CRC-Input: {$crcInputHex}");
|
||||||
}
|
}
|
||||||
$crcValue = $this->calculateCRC16Modbus($crcInputBin);
|
$crcValue = $this->calculateCRC16Modbus($crcInputBin);
|
||||||
$crcHex = strtoupper(str_pad(dechex($crcValue), 4, '0', STR_PAD_LEFT));
|
$crcHex = strtoupper(str_pad(dechex($crcValue), 4, '0', STR_PAD_LEFT));
|
||||||
// Byte-Swap (Low-Byte zuerst)
|
$crcSwapped = substr($crcHex, 2, 2) . substr($crcHex, 0, 2);
|
||||||
$crcSwapped = substr($crcHex, 2, 2) . substr($crcHex, 0, 2);
|
|
||||||
$oFrameWithCRC = $oFrame . strtolower($crcSwapped);
|
$oFrameWithCRC = $oFrame . strtolower($crcSwapped);
|
||||||
|
|
||||||
// 3) Summen-Checksum (alle 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);
|
||||||
@@ -189,10 +175,9 @@ class SofarWechselrichter extends IPSModule
|
|||||||
$frameBin .= chr($b);
|
$frameBin .= chr($b);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4) TCP-Verbindung öffnen & Paket senden (Port ist fest auf 8899)
|
// 4) TCP-Verbindung öffnen & Paket senden (Port fest 8899)
|
||||||
$port = 8899;
|
$port = 8899;
|
||||||
$timeoutSec = 5;
|
$fp = @stream_socket_client("tcp://{$ip}:{$port}", $errno, $errstr, 5);
|
||||||
$fp = @stream_socket_client("tcp://{$ip}:{$port}", $errno, $errstr, $timeoutSec);
|
|
||||||
if (!$fp) {
|
if (!$fp) {
|
||||||
throw new Exception("Verbindung zu {$ip}:{$port} fehlgeschlagen ({$errno}: {$errstr})");
|
throw new Exception("Verbindung zu {$ip}:{$port} fehlgeschlagen ({$errno}: {$errstr})");
|
||||||
}
|
}
|
||||||
@@ -209,30 +194,25 @@ class SofarWechselrichter extends IPSModule
|
|||||||
$response .= $chunk;
|
$response .= $chunk;
|
||||||
}
|
}
|
||||||
fclose($fp);
|
fclose($fp);
|
||||||
|
|
||||||
if ($response === '') {
|
if ($response === '') {
|
||||||
throw new Exception("Keine Antwort vom Inverter erhalten.");
|
throw new Exception("Keine Antwort vom Inverter erhalten.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6) Slice-Logik: l = 2*(reg–reg)+6 = 6, dann slice(-l, -4) → 2 Bytes
|
// 6) Slice-Logik: l = 2*(reg–reg)+6 = 6 ⇒ 2 Bytes Daten
|
||||||
$lModbus = 2 * ($reg - $reg) + 6; // = 6
|
$lModbus = 6;
|
||||||
$numBytes = $lModbus - 4; // = 2
|
$numBytes = 2;
|
||||||
if (strlen($response) < $lModbus) {
|
if (strlen($response) < $lModbus) {
|
||||||
throw new Exception("Unerwartet kurze Antwort (weniger als {$lModbus} Bytes).");
|
throw new Exception("Unerwartet kurze Antwort (< {$lModbus} Bytes).");
|
||||||
}
|
}
|
||||||
$dataBytes = substr($response, -$lModbus, $numBytes);
|
$dataBytes = substr($response, -$lModbus, $numBytes);
|
||||||
if (strlen($dataBytes) < 2) {
|
if (strlen($dataBytes) < 2) {
|
||||||
throw new Exception("Data-Segment enthält weniger als 2 Bytes.");
|
throw new Exception("Data-Segment enthält weniger als 2 Bytes.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return $dataBytes;
|
return $dataBytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Berechnet CRC16-Modbus (Init=0xFFFF, Polynom=0xA001) über Binärdaten.
|
* CRC16-Modbus (Init=0xFFFF, Polynom=0xA001)
|
||||||
*
|
|
||||||
* @param string $binaryData
|
|
||||||
* @return int
|
|
||||||
*/
|
*/
|
||||||
private function calculateCRC16Modbus(string $binaryData): int
|
private function calculateCRC16Modbus(string $binaryData): int
|
||||||
{
|
{
|
||||||
@@ -253,10 +233,7 @@ class SofarWechselrichter extends IPSModule
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hilfsfunktion: Prüft, ob eine Variable mit gegebenem Ident existiert.
|
* Prüft, ob eine Variable mit Ident existiert
|
||||||
*
|
|
||||||
* @param string $ident
|
|
||||||
* @return bool
|
|
||||||
*/
|
*/
|
||||||
private function VariableExists(string $ident): bool
|
private function VariableExists(string $ident): bool
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user