Code: Alles auswählen
function modbus_tcp_lesen2($COM1, $GeraeteAdresse, $FunktionsCode, $RegisterAdresse, $RegisterAnzahl, $DatenTyp, $Retries = 3, $Timeout = 10) {
$Daten = array();
$DatenOK = false;
if (strlen($RegisterAdresse) == 5) {
$Register = dechex($RegisterAdresse);
}
else {
$Register = str_pad(dechex($RegisterAdresse), 4, "0", STR_PAD_LEFT);
}
stream_set_blocking($COM1, false);
$ProtocilIdentifier = "0000";
$RegisterAnzahl = str_pad($RegisterAnzahl, 4, "0", STR_PAD_LEFT);
$MessageLenght = str_pad(dechex(strlen($GeraeteAdresse.$FunktionsCode.$Register.$RegisterAnzahl) / 2), 4, "0", STR_PAD_LEFT);
for ($j = 0; $j < $Retries; $j++) {
$TransactionIdentifier = str_pad(rand(1, 199), 4, "0", STR_PAD_LEFT);
$sendenachricht = hex2bin($TransactionIdentifier.$ProtocilIdentifier.$MessageLenght.$GeraeteAdresse.$FunktionsCode.$Register.$RegisterAnzahl);
$Antwort = "";
$rc = fwrite($COM1, $sendenachricht);
$this->log_schreiben("Befehl => ".bin2hex($sendenachricht), " ", 8);
$LoopEnd = time() + $Timeout;
do {
$Laenge = -1;
// Manche Geräte senden die Daten in Häppchen.
$Chunk = fread($COM1, 1000); // 1000 Bytes lesen
if ($Chunk == false or strlen($Chunk) == 0) {
usleep(100000);
continue;
}
$Antwort .= bin2hex($Chunk);
$this->log_schreiben("Antwort => ".$Antwort, " ", 8);
$Laenge = ((hexdec(substr($Antwort, 8, 4)) * 2) + 12); // 12 = Header Länge
if (substr($Antwort, 0, 4) <> $TransactionIdentifier) {
$Antwort = "";
}
if (substr($Antwort, 14, 2) == "83") {
break;
}
if (substr($Antwort, 14, 2) == "84") {
break;
}
if (substr($Antwort, 14, 2) == "82") {
break;
}
if (strlen($Antwort) > $Laenge) {
$Antwort = substr($Antwort, $Laenge);
$this->log_schreiben("Antwort abgeschnistten => ".$Antwort, " ", 8);
break;
}
} while (strlen($Antwort) <> $Laenge and time() < $LoopEnd and !feof($COM1));
if (strlen($Antwort) == 0) {
$this->log_schreiben("Keine Antwort, nochmal => ", " ", 7);
continue;
}
$Daten["Register"] = $Register;
$Daten["RawDaten"] = $Antwort;
$Daten["Transaction"] = substr($Antwort, 0, 4);
$Daten["Protocol"] = substr($Antwort, 4, 4);
$Daten["Laenge"] = substr($Antwort, 8, 4);
$Daten["Adresse"] = substr($Antwort, 12, 2);
$Daten["Befehl"] = substr($Antwort, 14, 2);
$Daten["DatenLaenge"] = hexdec(substr($Antwort, 16, 2));
$Daten["Wert"] = 0;
$Daten["KeineSonne"] = false;
if ($Daten["Befehl"] == 83) {
// Die Registeradresse kann nicht ausgelesen werden.
$DatenOK = true;
break;
}
elseif ($Daten["Befehl"] == 82) {
// Die Registeradresse kann nicht ausgelesen werden.
$DatenOK = true;
break;
}
elseif ($Daten["Befehl"] == 84) {
// Die Registeradresse kann nicht ausgelesen werden.
$DatenOK = true;
break;
}
elseif ($Daten["Befehl"] == $FunktionsCode and strlen($Antwort) == $Laenge) {
$DatenOK = true;
break;
}
}
stream_set_blocking($COM1, true);
if ($DatenOK == true) {
if ($Daten["Transaction"] == $TransactionIdentifier and $Daten["Adresse"] == strtolower($GeraeteAdresse) and $Daten["Befehl"] == $FunktionsCode) {
// Prüfen ob der Wert richtig sein kann.
switch ($DatenTyp) {
case "String":
$Daten["Wert"] = $this->Hex2String(substr($Antwort, 18, (strpos($Antwort, '00', 18) - 18)));
break;
case "U8":
$Daten["Wert"] = hexdec(substr($Antwort, 18, 2));
break;
case "U16":
$Daten["Wert"] = hexdec(substr($Antwort, 18, $Daten["DatenLaenge"] * 2));
break;
case "U32":
$Daten["Wert"] = hexdec(substr($Antwort, 18, $Daten["DatenLaenge"] * 2));
break;
case "U32S":
$Daten["Wert"] = hexdec(substr($Antwort, 22, $Daten["DatenLaenge"]).substr($Antwort, 18, $Daten["DatenLaenge"]));
break;
case "I16":
$Daten["Wert"] = hexdec(substr($Antwort, 18, $Daten["DatenLaenge"] * 2));
if ($Daten["Wert"] > 32767) {
$Daten["Wert"] = $Daten["Wert"] - 65536;
}
break;
case "I32":
$Daten["Wert"] = $this->hexdecs(substr($Antwort, 18, $Daten["DatenLaenge"] * 2));
break;
case "I32S":
$Daten["Wert"] = $this->hexdecs(substr($Antwort, 22, $Daten["DatenLaenge"]).substr($Antwort, 18, $Daten["DatenLaenge"]));
break;
case "Float32":
$Daten["Wert"] = round($this->hex2float32(substr($Antwort, 18, $Daten["DatenLaenge"] * 2)), 2);
break;
case "Hex":
$Daten["Wert"] = substr($Antwort, 18, $Daten["DatenLaenge"] * 2);
break;
case "ASCII":
$Daten["Wert"] = chr(hexdec(substr($Antwort, 18, $Daten["DatenLaenge"] * 2)));
break;
case "Zeichenkette":
$Daten["Wert"] = hex2bin(substr($Antwort, 18, $Daten["DatenLaenge"] * 2));
break;
default:
$Daten["Wert"] = 0;
break;
}
return $Daten;
}
else {
return $Daten;
}
}
return false;
}
Die Automation habe ich auch nochmal etwas aufgebohrt. Die bekommt aktuell nämlich nicht mit, wenn die Wechselrichter-Kommunikation down geht. Die Automation nimmt einfach stur den letzten Wert in der DB. Wenn man da gerade viel Überschuss hatte, bleiben die Relays permanent an. Solche Fehlerzustände erkenne ich jetzt und schalte sämtliche Relays ab (und schicke eine WhatsApp).
Damit lässt sich jetzt mit Huawei und dem SDongleA wirklich sehr gut arbeiten. Ich setze die aktuelle V200R022C10SPC108 Dongle-Firmware ein. Die Kombination läuft sehr stabil und zuverlässig. Wenn die Cloud mal größere Abfragen tätigt, kann mal die eine oder andere Abfrage per Modbus TCP in die Hose gehen. Damit kann die Automation aber mittlerweile bei mir auch umgehen und prellt bei temporären Aussetzern in der Kommunikation nicht gleich die Relays.