PHP Client Socket Raspberry Pi-IPS / Windows-IPS

Hallo Leute,

ich stehe mal wieder vor einm Rätsel…

Folgender Aufbau eins Client Socket funktioniert auf einem Raspberry Pi problemlos, auf einem Windows-IPS hingegen ist das ganze so extrem träge, dass es bei hoher Belastung Fehler produziert:

	private function CommandClientSocket(String $message, $ResponseLen = 16)
	{
		$Result = -999;
		If (($this->ReadPropertyBoolean("Open") == true) AND ($this->GetParentStatus() == 102)) {
			
			if (IPS_SemaphoreEnter("CommandClientSocket", 100))
			{
				// Socket erstellen
				if(!($sock = socket_create(AF_INET, SOCK_STREAM, 0))) {
					$errorcode = socket_last_error();
					$errormsg = socket_strerror($errorcode);
					IPS_LogMessage("GeCoS_IO Socket", "Fehler beim Erstellen ".$errorcode." ".$errormsg);
					$this->SendDebug("CommandClientSocket", "Fehler beim Erstellen ".$errorcode." ".$errormsg, 0);
					IPS_SemaphoreLeave("CommandClientSocket");
					return $Result;
				}
				// Timeout setzen
				socket_set_option($sock,SOL_SOCKET, SO_RCVTIMEO, array("sec"=>2, "usec"=>0));
				// Verbindung aufbauen
				if(!(socket_connect($sock, $this->ReadPropertyString("IPAddress"), 8888))) {
					$errorcode = socket_last_error();
					$errormsg = socket_strerror($errorcode);
					IPS_LogMessage("GeCoS_IO Socket", "Fehler beim Verbindungsaufbaus ".$errorcode." ".$errormsg);
					$this->SendDebug("CommandClientSocket", "Fehler beim Verbindungsaufbaus ".$errorcode." ".$errormsg, 0);
					IPS_SemaphoreLeave("CommandClientSocket");
					return $Result;
				}
				// Message senden
				if(!socket_send($sock, $message, strlen($message), 0))
				{
					$errorcode = socket_last_error();
					$errormsg = socket_strerror($errorcode);
					IPS_LogMessage("GeCoS_IO Socket", "Fehler beim beim Senden ".$errorcode." ".$errormsg);
					$this->SendDebug("CommandClientSocket", "Fehler beim Senden ".$errorcode." ".$errormsg, 0);
					IPS_SemaphoreLeave("CommandClientSocket");
					return $Result;
				}
				//Now receive reply from server
				if(socket_recv ($sock, $buf, $ResponseLen, MSG_WAITALL ) === FALSE) {
					$errorcode = socket_last_error();
					$errormsg = socket_strerror($errorcode);
					IPS_LogMessage("GeCoS_IO Socket", "Fehler beim beim Empfangen ".$errorcode." ".$errormsg);
					$this->SendDebug("CommandClientSocket", "Fehler beim Empfangen ".$errorcode." ".$errormsg, 0);
					IPS_SemaphoreLeave("CommandClientSocket");
					return $Result;
				}
				// Anfragen mit variabler Rückgabelänge
				$CmdVarLen = array(56, 67, 70, 73, 75, 80, 88, 91, 92, 106, 109);
				$MessageArray = unpack("L*", $buf);
				$Command = $MessageArray[1];
				If (in_array($Command, $CmdVarLen)) {
					$Result = $this->ClientResponse($buf);
					//IPS_LogMessage("IPS2GPIO ReceiveData", strlen($buf)." Zeichen");
				}
				// Standardantworten
				elseIf ((strlen($buf) == 16) OR ((strlen($buf) / 16) == intval(strlen($buf) / 16))) {
					$DataArray = str_split($buf, 16);
					//IPS_LogMessage("IPS2GPIO ReceiveData", strlen($buf)." Zeichen");
					for ($i = 0; $i < Count($DataArray); $i++) {
						$Result = $this->ClientResponse($DataArray[$i]);
					}
				}
				else {
					IPS_LogMessage("GeCoS_IO ReceiveData", strlen($buf)." Zeichen - nicht differenzierbar!");
					$this->SendDebug("CommandClientSocket", strlen($buf)." Zeichen - nicht differenzierbar!", 0);
				}
				
				
				if(!socket_shutdown($sock, 2))
				{
					$errorcode = socket_last_error();
					$errormsg = socket_strerror($errorcode);
					IPS_LogMessage("GeCoS_IO Socket", "Fehler beim beim Schliessen ".$errorcode." ".$errormsg);
					$this->SendDebug("CommandClientSocket", "Fehler beim Schliessen ".$errorcode." ".$errormsg, 0);
				}
				
				socket_close($sock);
				IPS_SemaphoreLeave("CommandClientSocket");
			}
			else {
				$this->SendDebug("CommandClientSocket", "Semaphore Abbruch", 0);
			}
		}	
	return $Result;
	}

Woran liegt es, dass es im Windows-IPS - ichunterstelle mal das der Rechner inder Regel schneller ist als ein Raspberry Pi 3 - für den Client Socket Aufbau so viel länger benötigt??

Joachim

Eine etwas genauere Fehlerbeschreibung und was du schon z.B. beobachtet hast, wäre schön.
Sind vielleicht nur alle PHP-Slots belegt?
Oder sieht man im Debug wirklich wo es diese Latenz gibt?
Brauchst du wirklich die Semaphore? Kann das Ziel nur eine TCP Verbindung? Sonst wirf die raus.
Ein IPS ClientSocket als Alternative? Oder geht das nicht, weil die Verbindung nicht permanent offen bleiben kann?
Michael

…wenn ich das socket_close rausnehme, ändert das beim Raspberry Pi nichts, die Verbindung ist sehr schnell wieder geschlossen, sobald die Funktion verlassen wird. Beim Windows-Rechner wird dann der Fehler 10048 angezeigt - eine Socket-Verbindung kann nicht mehrfach genutzt werden.
Ich gehe davon aus, dass der Socket-Aufbau inkl. Abbau sehr lange beim Windows-IPS benötigt. Dieses führt bei schnellen Funktionsaufrufen dazu, dass die Kommunikation so oder so gestört wird…

Rausschmeißen kann ich das Ding nicht - es ist das zentrale Kommunikationselement für die Befehle zum PIGPIO, den IPS-Client Socket benötige ich für die sonstige (unaufgefordert) eingehenden Daten vom PIGPIO.

Joachim

Wenn das Ziel also mehrere Verbindung kann, wozu dann die Semaphore?
Das kostet dich ja auch Zeit.
Eventuell hilft hier der erste Kommentare zu reuse Socket.
PHP: socket_bind - Manual
socket_set_option($sock, SOL_SOCKET, SO_REUSEADDR, 1)
Michael

Hallo Michael,

ich habe diese Set_Option mal hinzugefügt, vielen Dank für den Tipp.
Es entsteht der Eindruck dass sich die Fehlermeldungen reduziert haben, aber wenn ich die Geschwindigkeit im Debug sehe in der die Funktionen auf dem Raspberry-IPS durchlaufen werden und im Windows-IPS, dann ist das schon ein meilenweiter Unterschied!
Da dieses zum Teil auch beim Öffnendes Konfigurationsformulars passiert, dauert es im Windows gefühlt ewig…:mad:

ich gehe mal davon aus, dass es am PHP liegt?

Joachim

Nutzt du eine IP Adresse oder einen Hostnamen beim Socket?
Eine direkte IP spart die Namensauflösung…
Michael

…die IP…
Der Unterschied ist wirklich krass!:eek:

…die Funktion benötigt auf einem Windows-Rechner ca. acht bis zehnmal so viel Zeit, was bei intensiver Nutzung leider schon sehr ins Gewicht fällt.
Hat jemand vielleicht einen „eigenen“ PHP-Client-Socket im Gebrauch, der performant läuft?
Liegt das Phänomen am PHP oder daran, das der Windows-Rechner durch zu viel Overhead diese zeitliche Verzögerung verursacht?

Joachim

Ich habe gerade einen unter Windows, ich schau heute Abend mal wie ich es gelöst habe.
Probleme sind mir da nicht aufgefallen.
Ich nutze aber nicht direkt den Socket sondern IMO mit fopen.
Michael

Hallo Michael,

diese Funktion ist ja in der Form auch Kern des GPIO-Moduls das ja offensichtlich sehr häufig genutzt wird. Bisher hatte ich diesbezüglich auch keine negativen Rückmeldungen.
Im Modul für die Hardware von Thomas habe ich dieses wieder genutzt und auch intensiven Belastungstest ausgesetzt - alles jedoch auf dem Raspberry Pi. Die Kommunikation zu den I²C-Devices ist von der Datenmenge sehr überschaubar, das aktuelle Server-Modul hat auch einen DS2482 (I²C zu 1-Wire) der - im Verhältnis zu den anderen Anwendungen - sehr viel Traffic produziert.
Auf dem Raspberry Pi hatte ich da keine Probleme feststellen können, bis ein Modul-Nutzer mit Windows-IPS-Server mich darüber informierte. Deshalb habe ich das dann bei mir mal nachgestellt und war schockiert über die Performanz.
Obwohl der Client Socket explizit zum Ende der Funktion geschlossen wird, kommt manchmal die Fehlermeldung, dass eine zweite Verbindung nicht möglich ist („Fehler beim Verbindungsaufbaus 10048 Normalerweise darf jede Socketadresse (Protokoll, Netzwerkadresse oder Anschluss) nur jeweils einmal verwendet werden.“)

Aus dem Windows-IPS-Debug könnte man aber auch lesen, dass sich hier - was beim Raspberry Pi offenbar ohne Probleme läuft - der „CommandClientSocket“ der für die definierten Befehle inkl. definierter Länge der Antwort vorhanden ist, sich mit dem immer parallel erstelltem IPS-Client Socket „beißt“.
Diese Konstellation ist auf Empfehlung des PIGPIO-Entwicklers entstanden, da es ansonsten nur mit „Wahrscheinlichkeitsrechnung“ möglich ist, Antworten der Anfragen von selbst motivierten Datensendungen von PIOGPIO (z.B. Änderungen am Eingang/Interrupt-Meldungen) zu unterscheiden.

Joachim

War natürlich quark mit fopen.
Ich nutze stream_socket_client. Was also das ganze Socket handling alleine erledigt.


       $fp = @stream_socket_client("tcp://" . $this->Host . ":" . $Port, $errno, $errstr, 1);
            if (!$fp)
                throw new Exception($this->Translate('No anwser from LMS'), E_USER_NOTICE);
            else
            {
                stream_set_timeout($fp, 5);
                fwrite($fp, $Data);
                $anwser = stream_get_line($fp, 1024 * 1024 * 2, chr(0x0d));
                fclose($fp);
                $this->SendDebug('Response Direct', $anwser, 0);
            }

Allerdings nutzte ich das nur zusätzlich zum IPS-IO um ‚große‘ Datenmengen (ab 8kB bis ca 1024kB) zu übertragen und dabei den IPS Socket nicht zu blockieren.

Der abbau einer TCP Verbindung dauert auch etwas. Meistens wird noch gewartet bis beide Seiten sich das gegenseitig bestätigt haben. Eventuell ich da etwas anders zwischen Linux & Win. Mit dem stream_socket_client habe ich keine Probleme feststellen können.

Verstehe ich nicht wirklich. Bei abgehenden Verbindungen muß natürlich das lokale binding aus IP+Port (abgehend) einmalig sein. Dafür werden eigentlich dynamisch highports ab 1024 genutzt.
Wüßte jetzt nicht wie sich das beißen kann :confused:

Das geht, ist nur sehr aufwändig.
Erst ‚merken‘ was man gerade gesendet hat.
Beim empfangen nachschauen ob es eine Antwort zu einer Anfrage gewesen sein kann, wenn nein dann ist es ein Event.

Habe das Thema sowohl beim Logitech Media Server als auch den XBees, da hier Events und Antworten teilweise identisch sind.
Beim Logitech Media Server gibt noch nicht mal ein Index Feld welches als Echo zurückkommt, woran ich erkennen könnte ob es eine Antwort oder Event war, wenn ich mir nicht beim senden ‚merken‘ würde was ich zurück erwarte.

Bei den XBees ist es etwas besser, da es FrameIDs gibt welche hochzählen. Immer eine Nummer weiter (oder war es immer 0 :smiley: ) => Event.
Gleiche Nummer Empfangen wie eben versendet => Antwort auf Befehl.

Hatte das Protokoll des PIOGPIO nicht sogar auch so eine FrameID ?

Michael

Hallo Michael,

vielen Dank für diesen alternativen Client Socket Vorschlag.
Ich habe ihn mal so auf meine Gegebenheiten zum Testen angepasst:

$Host = "192.168.178.25";
$Port = 8888;
$Data = pack("L*", 17, 0, 0, 0);

$fp = stream_socket_client("tcp://".$Host.":".$Port, $errno, $errstr, 1);
if (!$fp) {
    throw new Exception("Fehler", E_USER_NOTICE);
}
else {
    stream_set_timeout($fp, 5);
    stream_socket_sendto($fp, $Data);
    $Message = stream_socket_recvfrom($fp, 16);
    fclose($fp);
	$MessageArray = unpack("L*", $Message);
	print_r ($MessageArray);
}  

Läuft in der Raspberry Pi-Version einwandfrei, in der Windows-IPS-Version wird ein leeres Array zurückgegeben.
Irgendwie ist da etwas unterschiedlich in den Versionen…

Kann man bei den Parametern noch etwas anpassen, damit es auch unter Windows laufen würde?:confused:

Joachim

Hallo Michael,

ich habe nach einigen Expirmenten folgende in beiden IPS-Versionen funktionierende Skript gefunden:

$Host = "192.168.178.25";
$Port = 8888;
$Data = pack("L*", 17, 0, 0, 0);

$fp = stream_socket_client("tcp://".$Host.":".$Port, $errno, $errstr, 1);
if (!$fp) {
    throw new Exception("Fehler", E_USER_NOTICE);
}
else {
    stream_set_timeout($fp, 5);
    stream_socket_sendto($fp, $Data);
    $Message = fgets($fp, 16 + 1);
    fclose($fp);
	$MessageArray = unpack("L*", $Message);
	print_r ($MessageArray);
}  

Das „fgets“ war möglicherweise die entscheidende Änderung.

Ich habe zwar immer noch das Gefühl, dass es in Windows-IPS langsamer läuft als auf dem kleinen Raspberry Pi, konnte aber bis dato keine Socket-Fehler mehr feststellen - schauen wir mal…

Erst einmal Dank für diese Alternative!

Joachim

…mal so eine Frage aus dem Gedankenmodell:

Wenn ich eine klassische Funktion nutze, dann kann ich über Return und die Variable die vor dem Funktionsaufruf steht einen Rückgabewert aus der Funktion geben…

Wenn ich eine Anfrage per sentdatatoparent an den I/O sende, kommen die Daten aber im ReceiveData „heraus“.
Wie bekomme ich den gesuchten Rückgabewert dann in meine Variable???

Joachim

Bitte etwas genauer.
Weil, du sehr wohl in der Funktion SendDataToParent einen Rückgabewert (string) hast.
Nur muss der übergeordnete Parent auch wissen was er zurückgeben soll.
Bei deinen eigenen Modulen oder z.B. auch IPS internen Splittern geht das ohne Probleme.

Dazu mußt du natürlich in deinen eigenen übergeordneten Modulen bei ForwardData auch etwas zurückgeben mit return.

Bei den IO’s funktioniert das nicht, er kann ja nicht wissen was du zurück erwartest (und liefert imho null).
Da hast du Recht, das die Daten im ReceiveData landen.

Dort kannst du dann über einen InstanceBuffer die Daten aus dem ‚empfange ReceiveData-Thread‘ über den Buffer in einen ‚wartenden SendDataToParent-Thread‘ überführen.
Hier aber aufpassen, es können ja theoretisch mehrere Anfragen parallel laufen (wenn du x Devices hast, welche quasi zeitgleich (Timer) etwas vom Splitter wollen und der muss dann schauen das er die Daten nicht vermischt)

Michael

Hallo Michael,

geht gerade um den IPS-ClientSocket den ich ja auch schon nutze, aber halt für Dinge die keinen Rückgabewert benötigen.

Bei Deiner oder meiner ClientSocket Variante kann ich eine echte Funktion mit Rückgabewert nutzen.

Beide dieser Varianten laufen unter dem Raspberry Pi -IPS sehr gut, unter dem Windows-IPS ist Deine Variante etwas schneller - aber auch nur etwas…

Daher habe ich mir noch mal Gedanken über den IPS-ClientSocket gemacht und die Auswertung der ankommenden Daten stark überarbeitet.

Unser Varianten haben wahrscheinlich den Nachteil, dass bei einer hohen Anzahl von Funktionsaufrufen die Performance in den Keller geht, weil jedesmal der Anmeldeprozess durchlaufen werden muss, was in der Masse wohl schon relevant ist…

Jedoch wird es wahrscheinlich sehr aufwändig, die ankommenden und „sortierten“ Daten wieder an die aufrufenden Funktionen zurückzugeben…

Joachim

Also bei kleinen Datenpaketen nutze ich nur den IPS-IO.
Da mit den Daten sortieren habe ich ‚relativ‘ komplex gelöst.
Läuft dafür aber absolut Fehlerfrei in mehreren Modulen.
Auch bei den Xbees wo die Daten vom ganzen Netzwerk ja Häppchenweise empfangen werden.

Dazu habe ich ein Array (mit un/serialize) im Buffer.
Da werden gesendete Daten abgelegt und beim Empfang geprüft ob es eine Anfrage war und die Antwort dazu kopiert.

Um es für mich übersichtlich zu halten, speichere ich im ‚Array‘ ganze Objekte.
Diese Objekte baue ich mir je nach Protokoll neu für meine Module. JSON-RPC hat andere Eigenschaften als Squeezebox-CLI oder meine Xbees.

Ich kann gerne mal zwei Beispiele raussuchen.
Michael

Mal eben aus dem Zusammenhang kopiert :smiley:

Hier gibt es ein TXB_API_DataList welches ein Array (items) mit alle meine versendeten Daten (TXB_API_Data) beinhaltet; dieses Objekt wird im Buffer gehalten.


//Im Splitter. TXB_API_Data sind die Objekte welche im im Datenaustausch der Instanzen nutze. Und welche in die DataList geschrieben werden.
    /**
     * Versendet ein TXB_API_Data-Objekt und empfängt die Antwort.
     * 
     * @access private
     * @param TXB_API_Data $APIData Das Objekt welches versendet werden soll.
     * @param TXB_Node|NULL $Node Der Node an welchen der Frame adressiert wird.
     * @result TXB_API_Data|bool Enthält die Antwort oder NULL im Fehlerfall.
     */
    private function Send(TXB_API_Data $APIData, TXB_Node $Node = NULL)
    {
        try
        {
            if ($this->HasActiveParent() === false)
                throw new Exception('Instance has no active Parent Instance!');

            if (!$this->lock('TransmitBuffer')) //Semaphore setzen
                throw new Exception('TransmitBuffer is locked');
            $TransmitBuffer = unserialize($this->GetBuffer('TransmitBuffer')); // Objekt TXB_API_DataList holen aus Buffer
            $TransmitBuffer->Add(); // Einen Slot im 'Array' belegen und die erzeugte FrameID übernehmen.
            $this->SetBuffer('TransmitBuffer',serialize($TransmitBuffer)); // Objekt TXB_API_DataList schreiben in Buffer
            $this->unlock('TransmitBuffer'); // Semaphore aufheben.
            
            // ToFrame macht aus dem Objekt einen Byte-String welchen die Hardware versteht.
            $Frame = $APIData->ToFrame(); 
            $this->SendDataToParent(json_encode(Array("DataID" => "{79827379-F36E-4ADA-8A95-5F8D1DC92FA9}", "Buffer" => utf8_encode($Frame))));

            //WaitForResponse wartet auf die Antwort von FrameID
            $APIResponse = $this->WaitForResponse($APIData->FrameID);
            $this->SendDebug('Response', $APIResponse, 1);
            return $APIResponse;
        }
        catch (Exception $ex)
        {
            trigger_error($ex->getMessage(), E_USER_NOTICE);
            return false;
        }
    }

    /**
     * Wartet auf eine Antwort.
     * 
     * @access private
     * @param int $FrameID Die Frame-ID auf die gewartet wird.
     * @result TXB_API_Data Enthält das API-Paket mit der Antwort.
     */
    private function WaitForResponse(int $FrameID)
    {

            for ($i = 0; $i < 500; $i++)
            {
                $TransmitBuffer = unserialize($this->GetBuffer('TransmitBuffer')); // Objekt TXB_API_DataList holen aus Buffer
                $Data = $TransmitBuffer->Get($FrameID); // Antwort des Frame holen
                if ($Data !== NULL) // Null solange keine Antwort da ist
                {
                    //Antwort ist da, also Frame aus Array löschen
                    if ($this->lock('TransmitBuffer'))
                    {
                        $TransmitBuffer = unserialize($this->GetBuffer('TransmitBuffer')); // Objekt TXB_API_DataList holen aus Buffer
                        $TransmitBuffer->Remove($FrameID);
                        $this->SetBuffer('TransmitBuffer',serialize($TransmitBuffer)); // Objekt TXB_API_DataList schreiben in Buffer
                        $this->unlock('TransmitBuffer');
                        return $Data; // Antwort zurückgeben
                    }
                }
                IPS_Sleep(10);
            }
            // Timeout, keine Antwort erhalten
            if ($this->lock('TransmitBuffer'))
            {
                $TransmitBuffer = unserialize($this->GetBuffer('TransmitBuffer')); // Objekt TXB_API_DataList holen aus Buffer
                $TransmitBuffer->Remove($FrameID);
                $this->SetBuffer('TransmitBuffer',serialize($TransmitBuffer)); // Objekt TXB_API_DataList schreiben in Buffer
                $this->unlock('TransmitBuffer');
            }
            $this->SendDebug('ERROR', 'Wait for response timed out.', 0);
            throw new Exception('Wait for response timed out.');
    }

Um jetzt zu verstehen wie WaitForResponse an die Daten kommt, und diese FrameID gezählt wird; hier noch der
Aufbaue von TXB_API_Data und TXB_API_DataList.


/**
 * Enthält alle Daten eines API Paketes.
 */
class TXB_API_Data
{
    public $APICommand;
    public $NodeName;
    /**
     * Nutzdaten des Paketes.
     * @var string
     * @access public
     */
    public $Data;

    /**
     * FrameID des Paketes.
     * @var Byte  
     * @access public
     */
    public $FrameID = null;

    /**
     * Liefert die Daten welche behalten werden müssen wenn wir das Objekt serialisiert abspeichern.
     * @access public
     */
    public function __sleep()
    {
        return array('APICommand', 'NodeName', 'Data', 'FrameID');
    }

    /**
     * Erzeugt ein API Paket aus den übergeben Daten.
     * Es können wahlweise Rohdaten vom Coordinator,
     * ein API-Kommando mit Nutzdaten,
     * ein utf8-encodiertes Objekt vom IPS Datenaustausch oder
     * zu versendendes AT Kommando (TXB_CMD_DATA) übergeben werden.
     * 
     * @param object|TXB_API_Data|string $Frame
     * @param null|string $Payload
     * @return TXB_API_Data
     */
    public function __construct($Frame = null, $Payload = null)
    {
// Hier passiert also das zerlegen von diversen 'input' Daten und befüllen der Objekt-Eigenschaften
// Brauche ich also nicht im ReceiveData des Splitters machen :)
    }

    /**
     * Liefert den Byte-String für den Versand an den Coordinator
     * 
     * @param bool $Escape True wenn API2 mit maskierten Zeichen.
     * @param TXB_Node $Node Ziel Node oder NULL bei Versand an den Coordinator.
     * @return string Byte-String für den Coordinator.
     */
    public function ToFrame()
    {
// Hier wird das Objekt wieder in einen Byte-Stream zusammengesetzt mit Checksumme maskierung etc.. 
// Das kann die Hardware nachher verstehen.
        return $packet;
    }
}


/**
 * TXB_API_DataList ist eine Klasse welche ein Array von TXB_API_Data Objekten enthält.
 */
class TXB_API_DataList
{

    /**
     * Array mit allen Items.
     * @var array
     * @access public
     */
    public $Items = array();

    /**
     * Aktueller Frame.
     * @var array
     * @access public
     */
    public $FrameID = 1;

    /**
     * Liefert die Daten welche behalten werden müssen wenn wir das serialisieren.
     * @access public
     */
    public function __sleep()
    {
        return array('Items', 'FrameID');
    }

    /**
     * Fügt einen Eintrag in $Items hinzu.
     * @access public
     * @return int FrameID Die FrameID in der Warteschlange.
     */
    public function Add()
    {
        $FrameID = $this->FrameID;
        $this->Items[$FrameID] = null;
        if ($this->FrameID == 255)
            $this->FrameID = 1;
        else
            $this->FrameID++;
        return $FrameID;
    }

    /**
     * Update für einen Eintrag in $Items.
     * @access public
     * @param TXB_API_Data $APIData Das neue Objekt.
     * @return bool True bei erfolg, False wenn FrameID nicht vorhanden.
     */
    public function Update(TXB_API_Data $APIData)
    {
        if (!array_key_exists($APIData->FrameID, $this->Items))
            return false;
        $this->Items[$APIData->FrameID] = $APIData;
        return true;
    }

    /**
     * Löscht einen Eintrag aus $Items.
     * @access public
     * @param int $Index Der Index des zu löschenden Items.
     */
    public function Remove(int $Index)
    {
        if (array_key_exists($Index, $this->Items))
            unset($this->Items[$Index]);
    }

    /**
     * Liefert einen bestimmten Eintrag aus den Items.
     * @access public
     * @param int $Index
     * @return TXB_API_Data APIData der Antwort.
     */
    public function Get(int $Index)
    {
        if (array_key_exists($Index, $this->Items))
            return $this->Items[$Index];
        return false;
    }

}


Und noch mal der Splitter, der ´muss ja die empfangenen Daten da ja irgendwie zuordnen und in der TXB_API_DataList ablegen.


    /**
     * Empfängt Daten vom Parent (IO).
     * Dekodierte API-Pakete werden an ProcessAPIData übergeben.
     * 
     * @access public
     * @param string $JSONString Der empfangene JSON-kodierte Byte-String vom Parent.
     * @result bool Immer True
     */
    public function ReceiveData($JSONString)
    {
        $data = json_decode($JSONString);
        $head = $this->GetBuffer('Buffer'); // Den Rest vom letzen unvollstädigen Datenpaket aus dem Buffer holen.
        $stream = $head . utf8_decode($data->Buffer); // Neue Daten anhängen
        // Anfang suchen
        //Paket suchen
        //usw... hier werden dann Längenfeld, Checksume etc verarbeitet und der 'Rest' kommt wieder in einen Buffer.
        // $packet enthält dann genau 1 Byte-String für einen Befehl
        $APIData = new TXB_API_Data($packet); // Der Konstruktor zerlegt das in Einzelteile und füllt damit seine Eigenschaften
        if ($this->UpdateTransmitBuffer($APIData) === false) // Wenn unser Frame nicht im TXB_API_DataList liegt, dann ist es wohl keine Antwort, sondern ein Event von der Hardware-
        {
            $this->SendDataToDevice($APIData); // Event weitersenden
        }
    }

    /**
     * Fügt ein empfangenes API-Paket in den Transmit-Buffer ein.
     * 
     * @access private
     * @param TXB_API_Data $APIData Das einzufügene API-Paket.
     * @return boolean True wenn erfolgreich, sonst false
     */
    private function UpdateTransmitBuffer(TXB_API_Data $APIData)
    {
        if (!$this->lock('TransmitBuffer'))
        {
            trigger_error('TransmitBuffer is locked', E_USER_NOTICE);
            return false;
        }
        $TransmitBuffer = unserialize($this->GetBuffer('TransmitBuffer')); // Objekt TXB_API_DataList holen aus Buffer 
        if ($TransmitBuffer->Update($APIData))
        {
            $this->SetBuffer('TransmitBuffer',serialize($TransmitBuffer)); // Objekt TXB_API_DataList schreiben in Buffer 
            $this->unlock('TransmitBuffer');
            return true;
        }
        $this->unlock('TransmitBuffer');
        return false;
    }

Der Aufbau ist bei mir immer mal unterschiedlich, je nachdem welches Protokoll ich da behandeln muss.
Bei JSON-RPC suche ich z.B. immer nach der ‚id‘.
Bei dem JSON des Mi ‚Xiaomi‘ Gateways gibt es SIIDs der Geräte und gezielt ein ACK wenn der Befehl verarbeitet wurde.
So bleibt zwar die Idee immer gleich, aber teilweise mit sehr unterschiedlichen Funktionen zum verarbeiten dieser ‚SendWaitResponse‘ Konstruktion.

Michael

…das sieht ja doch schon sehr komplex aus…:frowning:

Einen eigenen Client Socket - der aber vom Modul-Start bis zum Modul-Ende „offen“ bleibt geht nicht?

Joachim

Nein geht nicht.
Sicherlich geht es bestimmt noch besser/eleganter/effizienter aber aktuell ist das mein Mittelmaß, sprich ich verstehe es noch (auch wenn ich Monaten später mir das ansehe).
Michael