no message
This commit is contained in:
@@ -1,6 +1,11 @@
|
|||||||
<?php
|
<?php
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sofar Wechselrichter Modul
|
||||||
|
*
|
||||||
|
* Anpassung: LoggerNumber als String behandeln, da Dezimalwert > PHP-Integer
|
||||||
|
*/
|
||||||
class SofarWechselrichter extends IPSModule
|
class SofarWechselrichter extends IPSModule
|
||||||
{
|
{
|
||||||
public function Create()
|
public function Create()
|
||||||
@@ -8,7 +13,7 @@ class SofarWechselrichter extends IPSModule
|
|||||||
parent::Create();
|
parent::Create();
|
||||||
// Moduleigenschaften
|
// Moduleigenschaften
|
||||||
$this->RegisterPropertyString('IPAddress', '172.31.70.80');
|
$this->RegisterPropertyString('IPAddress', '172.31.70.80');
|
||||||
$this->RegisterPropertyInteger('LoggerNumber', 0);
|
$this->RegisterPropertyString('LoggerNumber', '0'); // jetzt String
|
||||||
$this->RegisterPropertyInteger('PollInterval', 60);
|
$this->RegisterPropertyInteger('PollInterval', 60);
|
||||||
$this->RegisterPropertyString('Registers', '[]'); // JSON-String
|
$this->RegisterPropertyString('Registers', '[]'); // JSON-String
|
||||||
|
|
||||||
@@ -30,7 +35,6 @@ class SofarWechselrichter extends IPSModule
|
|||||||
if (!is_array($registers)) {
|
if (!is_array($registers)) {
|
||||||
$registers = [];
|
$registers = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
$position = 10;
|
$position = 10;
|
||||||
foreach ($registers as $entry) {
|
foreach ($registers as $entry) {
|
||||||
$regNo = (int) $entry['RegisterNumber'];
|
$regNo = (int) $entry['RegisterNumber'];
|
||||||
@@ -71,17 +75,21 @@ class SofarWechselrichter extends IPSModule
|
|||||||
*/
|
*/
|
||||||
private function Query(): void
|
private function Query(): void
|
||||||
{
|
{
|
||||||
$ip = $this->ReadPropertyString('IPAddress');
|
$rawIP = $this->ReadPropertyString('IPAddress');
|
||||||
$loggerNumber = $this->ReadPropertyInteger('LoggerNumber');
|
$ip = trim($rawIP);
|
||||||
|
$loggerNumberStr = trim($this->ReadPropertyString('LoggerNumber'));
|
||||||
|
|
||||||
if ($loggerNumber <= 0 || filter_var($ip, FILTER_VALIDATE_IP) === false) {
|
// Validierungs-Check: IP und LoggerNumber
|
||||||
$this->LogMessage(
|
if (!filter_var($ip, FILTER_VALIDATE_IP)) {
|
||||||
'Abbruch: LoggerNumber = ' . $loggerNumber . ', IP = "' . $ip . '"',
|
$this->LogMessage('Abbruch: Ungültige IP = "' . $ip . '"', KL_WARNING);
|
||||||
KL_WARNING
|
return;
|
||||||
);
|
}
|
||||||
|
if ($loggerNumberStr === '' || !ctype_digit($loggerNumberStr) || bccomp($loggerNumberStr, '1') < 0) {
|
||||||
|
$this->LogMessage('Abbruch: Ungültige LoggerNumber = "' . $loggerNumberStr . '"', KL_WARNING);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tabellen-Einträge holen
|
||||||
$registers = json_decode($this->ReadPropertyString('Registers'), true);
|
$registers = json_decode($this->ReadPropertyString('Registers'), true);
|
||||||
if (!is_array($registers) || count($registers) === 0) {
|
if (!is_array($registers) || count($registers) === 0) {
|
||||||
// Keine Register definiert
|
// Keine Register definiert
|
||||||
@@ -100,7 +108,7 @@ class SofarWechselrichter extends IPSModule
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$bytes = $this->readRegister($ip, $loggerNumber, $regNo, $endian);
|
$bytes = $this->readRegister($ip, $loggerNumberStr, $regNo);
|
||||||
if ($endian === 'LE') {
|
if ($endian === 'LE') {
|
||||||
$arr = unpack('vvalue', $bytes); // UINT16 LE
|
$arr = unpack('vvalue', $bytes); // UINT16 LE
|
||||||
} else {
|
} else {
|
||||||
@@ -119,30 +127,36 @@ class SofarWechselrichter extends IPSModule
|
|||||||
* Liest ein einzelnes Register per Modbus-ähnlichem TCP (2 Bytes zurück)
|
* Liest ein einzelnes Register per Modbus-ähnlichem TCP (2 Bytes zurück)
|
||||||
*
|
*
|
||||||
* @param string $ip Inverter-IP
|
* @param string $ip Inverter-IP
|
||||||
* @param int $serial_nr Logger-Seriennummer
|
* @param string $serial_nr_str Logger-Seriennummer als Dezimal-String
|
||||||
* @param int $reg Register-Adresse
|
* @param int $reg Register-Adresse
|
||||||
* @param string $endian 'BE' oder 'LE'
|
|
||||||
* @return string 2 Byte binär
|
* @return string 2 Byte binär
|
||||||
* @throws Exception Bei Kommunikationsfehler
|
* @throws Exception Bei Kommunikationsfehler
|
||||||
*/
|
*/
|
||||||
private function readRegister(
|
private function readRegister(
|
||||||
string $ip,
|
string $ip,
|
||||||
int $serial_nr,
|
string $serial_nr_str,
|
||||||
int $reg,
|
int $reg
|
||||||
string $endian
|
|
||||||
): string
|
): string
|
||||||
{
|
{
|
||||||
// 1) Out_Frame ohne CRC aufbauen
|
// 1) Out_Frame ohne CRC aufbauen
|
||||||
$oFrame = 'a5170010450000';
|
$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 = [
|
$hexSNbytes = [
|
||||||
substr($hexSN, 6, 2),
|
substr($hexSN8, 6, 2),
|
||||||
substr($hexSN, 4, 2),
|
substr($hexSN8, 4, 2),
|
||||||
substr($hexSN, 2, 2),
|
substr($hexSN8, 2, 2),
|
||||||
substr($hexSN, 0, 2),
|
substr($hexSN8, 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);
|
||||||
$numHex = str_pad(dechex(1), 4, '0', STR_PAD_LEFT);
|
$numHex = str_pad(dechex(1), 4, '0', STR_PAD_LEFT);
|
||||||
$oFrame .= '0103' . $startHex . $numHex;
|
$oFrame .= '0103' . $startHex . $numHex;
|
||||||
@@ -201,7 +215,7 @@ 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*(reg–reg)+6 = 6 ⇒ 2 Bytes Daten
|
// 6) Slice-Logik: l = 6 ⇒ 2 Bytes Daten
|
||||||
$lModbus = 6;
|
$lModbus = 6;
|
||||||
$numBytes = 2;
|
$numBytes = 2;
|
||||||
if (strlen($response) < $lModbus) {
|
if (strlen($response) < $lModbus) {
|
||||||
@@ -214,6 +228,29 @@ class SofarWechselrichter extends IPSModule
|
|||||||
return $dataBytes;
|
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)
|
* CRC16-Modbus (Init=0xFFFF, Polynom=0xA001)
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user