Kommunikation mit dem Serial Port innerhalb eines Moduls

Hi,

ich beschäftige mich gerade mit den PHP Modulen. Ich möchte ein Modul schreiben, welches z.B. eine Funktion anbietet, bei der das Modul über den seriellen Port Daten abfragen muss.

Das PHP Modul kann mit dem Parent (seriellen Port) mittels SendDataToParent und ReceiveData Daten senden und empfangen. Aber: Wie kann man die Daten so verarbeiten, das eine externe Funktion die Daten zurückliefen kann?

echo TestModul_LeseDaten( $InstanzID );

Die Methode (im PHP Modul) soll nun Daten an den seriellen Port senden (praktisch ein Kommando an ein angeschlossenes Gerät; SendDataToParent), und dann die Antwort des externen Geräts (welche ja via ReceiveData an das PHP Modul gesendet werden müsste) als Ergebnis liefern.

Wie geht das? Der Datenfluss aus der Doku hilft mir hier leider nicht weiter :confused:

Gruss
Coyote

Nimm den PHP Modul Dataflow Generator und wähle dort als IO Standard IO aus und also IO Typ Serial Port dann bekommst Du das passend angelegt als Vorlage.

Das musst du leider per Hand umsetzen.
Also Daten senden und dann warten…bis…
Im ReceiveData die Daten in einen Buffer schieben (SetBuffer).
Jetzt kann deine Senderoutine die Antwort aus dem Buffer lesen.
Michael

Ich wusste gar nicht das dein Generator eine Sync von sende und Empfangsdaten enthält :wink:
Michael

Das erklärt schon die Frage :wink: Scheinbar können die Methoden parallel laufen.

Ja, aber da es verschiedene Threads sind, welche unabhängig voneinander laufen, musst du halt über den InstanceBuffer gehen.
Außerdem empfiehlt es sich, wenn sich Daten überschneiden könnten, auch mit Semaphore und vielleicht sogar mehreren Buffern bzw. Ein Array zu arbeiten.
Kannst du dir in einigen Modulen anschauen. Z.B. xiaomi, Kodi oder Plugwise.
Ganz simpel mit RS232 und nur auf eine Quittung warten ist z.B. hier:
GitHub - Nall-chan/IPSMS35: IPS-Modul for Conrad MS35 RGB-Controller
Michael

Ne das nicht, aber zumindest hat er schon mal die passende GUID ;). Das Modul dann zu schreiben muss er noch selber. Oder macht das Sinn so was in einer Vorlage zu ergänzen, ist das vom Grundsatz immer das gleiche dann könnte man ja auch bei Serial Port hier eine grundsätzliche Vorlage anbieten?

Nein, woher soll dein Modul wissen worauf es nach dem Senden warten muss?
Oder ob es auch spontane Daten gibt, welche empfangen werden ohne das vorher gesendet wurde.

Deshalb ist es ja auch nicht in den IPS IOs integriert :wink:
Sonst könnte man ja (wie z.b. beim HM-Socket oder ModBus-Splitter) gleich die Antwort als Rückgabewert von SendDataToParent erhalten.
Michael

Danke für die vielen Antworten und links. Habe mir mal das RGB Modul angeschaut.

Das senden funktioniert in meinem Modul auch, aber leider meint der FlowHandler immer, das er keine Daten an mein Modul senden kann. die ReceiveData wird einfach nicht aufgerufen…

Ich habe jetzt viel geschaut und gesucht, aber keinen Fehler gefunden.

Könnte mal jemand drüber schauen? Projekt ist hier: GitHub - IPSCoyote/ViessControl: IP-Symcon Modul zur Anbindung von Vitodens/Vitotronic Heizungssteuerungen

Die GUIDs sehen soweit gut aus. Hast du mal die genaue Fehlermeldung?

paresy

„23.11.2017 19:44:05 | FlowHandler | Kann Daten nicht zur Instanz #46050 weiterleiten: Daten…“

Bei der Instanz handelt es sich um mein PHP Modul.

Da sind Echos im ReceiveData :slight_smile:
Das ist der ‚Fehler‘. Kannst du auch sehen, wenn du den Eintrag vom Meldungsfenster mit SPACE vergrößerst.
Für Debugausgabe gibt es SendDebug. Auch IPS_LogMessage ist nicht dafür gedacht.

Zusätzlich ein paar Tips:
__construct brauchst du nicht überschreiben.
Die IO-Instanz nicht umkonfigurieren aus deinem Modul (Ist bei meinem RGB.Modul auch noch falsch)
Du nutzt veraltete Funktionen wie COMPort_SetOpen. Auch bitte nie benutzen.
Um eine Konfig für den IO vorzugeben, gibt es GetConfigurationForParent.
startCommunication oder identifyHeatingControl sollten am besten aufgerufen werden denn der IO verbunden wird. Dafür gibt es MessageSink und RegisterMessage.
Anstatt Hex2String zu nutzen, einfach chr(0x04) oder „\x04“ schreiben.
json_encode im SendDataToParent erwartet UTF8 Strings. Somit immer den Byte-String mit utf8_encode bearbeiten und im ReceiveData mit utf8_decode.

Michael

PS: Mein MS35 RGB Modul braucht auch etwas liebe; ich baue es die Tage mal auf einen etwas um. Dann wäre das fast schon eine universelle Vorlage.

Hehe. Genau. Wie Michael schon sagte - Die Daten wurden weitergeleitet. Aber da du Ausgaben zurück liefert, denkt der Flow Handler, dass etwas schief gelaufen ist.

paresy

Da sage ich mal… :banghead:

Nutz zum Debuggen bitte einfach die SendDebug Funktion :smiley: Ist voll cool und so :slight_smile:

SendDebug — IP-Symcon :: Automatisierungssoftware

paresy

Ups… ich war langsam beim editieren. Was mir noch so aufgefallen ist; weiter oben.
Michael

@Nal-Chan: Danke für die Tips. Bin am Ändern.

Für COMPort_SetOpen habe ich aber auch in neueren Beiträgen keine alternative gefunden :confused:

Und die Methoden sind für andere Zwecke und müssen explizit verwendet werden (der Com-Port zur Viessman-Heizung darf nicht offen bleiben sondern muss bei Bedarf geöffnet werden - er ist sonst nicht stabil).

Was ist denn das? Ein Gerät mit RS232, welches aus dem Tritt kommt wenn der Port ‚offen‘ ist. :confused:

Du suchts die Funktion IPS_SetProperty — IP-Symcon :: Automatisierungssoftware. Ist aber wirklich eine schlechte Idee. Wenn es nicht anders geht…

COMPort heißt schon länger SPRT, eventuell reicht es ja schon mit SPRT_SetBreak, SetDTR oder SetRTS entsprechende Steuersignale an das Gerät zu senden ?

Aber was machst du mit Usern welche keine RS232 nutzen, sondern z.B. einen TCP Clientsocket und eine RS232/LAN-Adapter nutzen ? Oder einen XBee als Funkstrecke ? Das kannst du schlecht alles berücksichtigen.

Grundsätzlich hat sich bei mir folgendes bewährt; auch wenn du es jetzt vielleicht nicht direkt nutzen kannst (habe auch endlich mal mein RGB Modul entsprechend geändert :wink: ):

In der Reihenfolge vom IPS-Systemstart:

  • Create:
    — Definition der Settings der Instanz (RegisterProperty)
    — Instanz ‚verbindet‘ intern den IO-Parent.

  • Applychanges:
    — Registrieren auf IPS ist Betriebsbereit.
    — und auf Änderungen der IPS internen ‚Datenverbindung‘ mit RegisterMessage (Siehe 1)
    — VariableProfile anlegen, Statusvariablen anlegen, wenn statisch.
    — Abbruch der Funktion, da IPS_GetKernelRunlevel meldet dass der Kernel nicht 'bereit ist.

IPS wird ‚betriebsbereit‘ und feuert die Methode MessageSink, ausgelöst durch IPS_KERNELSTARTED

  • MessageSink (siehe 4)
    — Wenn IPS_KERNELSTARTED der Auslöser war, merken wir uns unseren IO und registrieren uns auf IM_CHANGESTATUS (siehe 2) .
    — Und wenn unser IO ‚active‘ ist, dann Kommunikation starten (bei mir IOChangeState, siehe 5)

Nun wird deine Instanz umkonfiguriert und Übernehmen geklickt.
IPS führt ApplyChanges aus.

  • Applychanges:
    — Registrieren auf IPS ist Betriebsbereit. (jetzt egal)
    — und auf Änderungen der IPS internen ‚Datenverbindung‘ mit RegisterMessage (Siehe 1)
    — VariableProfile anlegen, Statusvariablen anlegen, wenn statisch.
    — Da IPS_GetKernelRunlevel nun den Kernel als 'bereit liefert geht es weiter.
    — Wir prüfen ob unser Parent aktiv ist (siehe 3), und starten die Kommunikation (siehe 5)

Der User wählt eine andere IO-Instanz als übergeordnete Instanz aus.
Somit muss die Kommunikation neu gestartet werden. Da wir mit RegisterMessage auf DM_CONNECT und DM_DISCONNECT registriert haben, feuert IPS wieder MessageSink.

  • MessageSink (siehe 4)
    — Wenn DM_CONNECT oder DM_DISCONNECT der Auslöser war, löschen wir die alte Registrierung auf IM_CHANGESTATUS.
    — Merken wir uns den eventuell neuen IO und registrieren uns auf diesen wieder mit IM_CHANGESTATUS (siehe 2).
    — Und wenn unser IO ‚active‘ ist, dann Kommunikation starten (siehe 5)

Nun merkt der User, er hat den falschen ComPort eingestellt. Also schnell ändern und Übernehmen im IO.
Oder der User zieht den USB-Stick mit dem ComPort ab… oder bei einer transparenten / virtuellen COMPort ist die Verbindung weg etc… Der COMPort ist ‚tot‘ und geht in IPS in Fehler.
IPS feuert wieder MessageSink ausgelöst durch IM_CHANGESTATUS.

  • MessageSink (siehe 4)
    — Wenn IM_CHANGESTATUS der Auslöser war, und er ist in Fehler gegangen, dann können wir nicht mehr senden.
    — Ist er wieder in ‚active‘, dann wieder Kommunikation starten (bei mir IOChangeState mit IS_ACTIVE)

1

        $this->RegisterMessage(0, IPS_KERNELSTARTED);
        $this->RegisterMessage($this->InstanceID, DM_CONNECT);
        $this->RegisterMessage($this->InstanceID, DM_DISCONNECT);

2

    protected function RegisterParent()
    {
        $OldParentId = $this->GetBuffer('ParentID');
        $ParentId = @IPS_GetInstance($this->InstanceID)['ConnectionID'];
        if ($ParentId <> $OldParentId)
        {
            if ($OldParentId > 0)
                $this->UnregisterMessage($OldParentId, IM_CHANGESTATUS);
            if ($ParentId > 0)
                $this->RegisterMessage($ParentId, IM_CHANGESTATUS);
            else
                $ParentId = 0;
            $this->SetBuffer('ParentID') = $ParentId;
        }
        return $ParentId;
    }

3

    /**
     * Prüft den Parent auf vorhandensein und Status.
     * 
     * @access protected
     * @return bool True wenn Parent vorhanden und in Status 102, sonst false.
     */
    protected function HasActiveParent()
    {
        $instance = IPS_GetInstance($this->InstanceID);
        if ($instance['ConnectionID'] > 0)
        {
            $parent = IPS_GetInstance($instance['ConnectionID']);
            if ($parent['InstanceStatus'] == 102)
                return true;
        }
        return false;
    }

4


    /**
     * Interne Funktion des SDK.
     *
     * @access public
     */
    protected function MessageSink($TimeStamp, $SenderID, $Message, $Data)
    {
        switch ($Message)
        {
            case IPS_KERNELSTARTED:
            case DM_CONNECT:
                $this->RegisterParent();
                if ($this->HasActiveParent())
                    $this->IOChangeState(IS_ACTIVE);
                else
                    $this->IOChangeState(IS_INACTIVE);
                break;
            case DM_DISCONNECT:
                $this->RegisterParent();
                $this->IOChangeState(IS_INACTIVE);
                break;
            case IM_CHANGESTATUS:
                if ($SenderID == $this->ParentID)
                    $this->IOChangeState($Data[0]);
                break;
        }
    }

5


    /**
     * Wird ausgeführt wenn sich der Status vom Parent ändert und wenn IPS startet.
     * @access protected
     */
    protected function IOChangeState($State)
    {
        // Wenn der IO Aktiv wurde
        if ($State == IS_ACTIVE)
        {
            $this->startCommunication();
        }
        else // und wenn nicht setzen wir uns auf inactive
        {
            $this->SetStatus(IS_INACTIVE);
        }
    }

Hi,

danke für die Tips. Werde sie mir mal anschauen und ggf. einfliessen lassen. Ist halt mein erster Versuch :wink:

Bei dem Gerät handelt es sich um die Vitotronic von Viessmann die über einen RS232 Optolink angeschlossen wird. Meine Erfahrung damals (als ich vor 8 Jahren die Anbindung mit Skripten programmiert habe) war, das die Steuerung nach einiger Zeit meinte nicht mehr reagieren zu müssen. Das kann ggf. auch daran liegen, das ich nur alle 10min. Werte abfrage. Aber wenn ich mich nicht darauf verlassen kann und dann ein „Falls Problem Port Resetten“ Programmieren muss, dann kann ich die Verbindung auch gleich nur bei Bedarf öffnen. Die Verbindung ist auch eher ein Frage/Antwort-Spiel ausgehend von IPS. Die Vitotronic meldet selber nichts aktiv (soweit ich weiss).

Ansonsten hast du natürlich Recht. Mittlerweile gibt es wohl auch LAN-Adapter (im Eigenbau) für die Schnittstelle. Nur: Ich kann die nicht testen und deshalb begebe ich mich da gar nicht erst in den Versuch :wink:

Mein Modul wird die Viessmann Heizung (Vitodens) via RS232 anbinden. Danach schauen wir mal weiter.

Gruss (und nochmals vielen Dank an alle!)
Coyote