Laden...

Einfaches WinForms HMI zum Test der MQTT Schnittstelle

Erstellt von Pippen vor einem Jahr Letzter Beitrag vor einem Jahr 1.342 Views
P
Pippen Themenstarter:in
18 Beiträge seit 2022
vor einem Jahr
Einfaches WinForms HMI zum Test der MQTT Schnittstelle

Salü zäme
Ich bin neu hier im Forum.
Ich programmiere seit 24 Jahren beruflich SPS Steuerungen. Inzwischen auch objektorientiert, da es jetzt diese Möglichkeiten gibt.
Seit 1 Jahr bin ich mich am Einarbeiten in C#, um auch beim bestehenden HMI (VisiWin mit WinForms und C#) mithelfen zu können.

Wir möchten unsere Steuerungen komplett modular aufbauen, mit definierten Schnittstellen gegen aussen. Eine Schnittstelle
wird eine MQTT Kommando-Schnittstelle mit JSON Meldungen sein. Diese Schnittstelle habe ich auf der SPS Seite mal z. T. programmiert.
Sie wird natürlich dauernd um weitere Kommandos erweitert.

Um diese Schnittstelle testen zu können, habe ich mir gedacht, ein kleines, einfaches WinForms HMI zu bauen:
Als Erstes geht es mal darum, eine Seite zu erstellen:
Button "Connect"
Button "Disconnect"
Anzeigeld: "connected" oder "disconnected"
Button: "Get ApplicationInfo"

Ich habe gelernt, dass bei WinForms eigentlich möglichst alles per Event passieren soll. Möglichst nichts zyklisches.
Aber wenn ich jetzt doch etwas zyklisch ausführen muss: Z.Bsp. wenn ich zyklisch den Zustand von SPS Variablen auslesen und auf dem HMI anzeigen will.
Gibt es eine Methode eines WinForms Formulars, das zyklisch aufgerufen wird?

Besten Dank für eure Unterstützung.

16.835 Beiträge seit 2008
vor einem Jahr

Willkommen.

Gibt es eine Methode eines WinForms Formulars, das zyklisch aufgerufen wird?

Nein, weil das im Endeffekt nicht die Verantwortung einer UI ist.

Willst Du Bordmittel nutzen, so nutze einen Timer. Das ist recht simpel.
Timer Class (System.Windows.Forms)
oder zB auch
PeriodicTimer Class (System.Threading)

Im Endeffekt aus moderner Sicht fällt sowas aber durchaus unter das Thema States Handling.
Dafür gibts gewisse Pattern, "Reactive Programming" bzw "Reactive Extensions". Es gibt auch eine .NET Lib dazu, nennt sich System Reactive.
https://github.com/dotnet/reactive


// sample by https://chat.openai.com/chat "Create Code of using System Reactive to requests data from an SPS for a periodic label text update"
using System.Reactive;
using System.Reactive.Linq;
using System.Reactive.Windows.Forms;

// Your form class should inherit from Form
public class Form1 : Form
{
    // Create a label control
    private Label label1 = new Label();

    public Form1()
    {
        // Set the label's text and location
        label1.Text = "Starting text";
        label1.Location = new Point(10, 10);

        // Add the label to the form's Controls collection
        this.Controls.Add(label1);

        // Create a timer that fires every 1 second (1000 milliseconds)
        var timer = Observable
            .Interval(TimeSpan.FromMilliseconds(1000))
            .ObserveOn(this);

        // Subscribe to the timer and request data from the SPS whenever it fires
        timer.Subscribe(_ =>
        {
            // Use a placeholder URL for the SPS
            var url = "http://mysps.com/api/data";

            // Send an HTTP GET request to the SPS and retrieve the data
            var data = SendGetRequest(url);

            // Update the label's text with the data
            label1.Text = data;
        });
    }
}

Das lässt sich auch einfacher in ein UI-System integrieren als zB ein Timer oder ein PeriodicTimer.

Gibt also wie auf so vieles verschiedene Wege, aber WinForms ist für so eine Anforderung nicht verantwortlich.

P
Pippen Themenstarter:in
18 Beiträge seit 2022
vor einem Jahr

Super, vielen Dank. Ich schaue mir das mal genauer an.

P
Pippen Themenstarter:in
18 Beiträge seit 2022
vor einem Jahr

Das sieht sehr vielversprechend aus.

Deshalb habe ich die beiden nuget Packages: System.Reactive und System.Reactive.Windwos.Forms installiert (siehe Anhang).

Trotzdem kommt ein Fehler bei folgender Zeile


using System.Reactive.Windows.Forms;

Windows scheint er im System.Reactive nicht zu kennen (siehe Anhang).
Was ist das Problem?

4.939 Beiträge seit 2008
vor einem Jahr

Die Beispiele unter Rx.NET/Samples/HOL/CS referenzieren auch die System.Reactive.Windows.Forms-Assembly, verwenden aber kein explizites using dafür in den Sourcen - daher laß das mal weg (es scheint einfach nur System.Reactive bzw. System.Reactive.Linq dafür verwendet zu werden - vllt. war das in früheren Versionen mal anders?).

16.835 Beiträge seit 2008
vor einem Jahr

Kann auch sein, dass das ein Fehler im AI Code Generator (https://chat.openai.com/chat) ist.
Es ist schließlich auch nur ein Sample und keine Copy-Paste Vorlage. Da fehlt auch der asynchrone Request etc.
Bisschen anpassen, Doku lesen, nachdenken und Co muss man immer...

P
Pippen Themenstarter:in
18 Beiträge seit 2022
vor einem Jahr

Danke euch beiden für die Hinweise.

Ich ging davon aus, dass Du (@Abt) dieses kleine Bsp. kurz geschrieben hast.
Deshalb schaute ich vorerst nicht über den Tellerrand hinaus 🙂

16.835 Beiträge seit 2008
vor einem Jahr

Deswegen steht in der ersten Zeile vom snippet der Hinweis 😉

P
Pippen Themenstarter:in
18 Beiträge seit 2022
vor einem Jahr

So, jetzt habe ich mal was Einfaches gemacht. Ich bin zwar mit der Architektur noch nicht zufrieden, aber die Links von Dir @Th69 bezüglich Kommunikation zwischen zwei Forms gefallen mir sehr gut. Damit sollte ich eine bessere Architektur hinkriegen.

Nun zum aktuellen Problem:
Wenn der Mqtt Broker beim Connect nicht erreichbar ist, bremst mir dies das ganze UI aus. Warum dies passiert, ist mir klar. Wie löse ich dieses Problem aber elegant und architekturmässig gut?

4.939 Beiträge seit 2008
vor einem Jahr

Schau mal in [FAQ] Warum blockiert mein GUI?
Das beste ist, du informierst dich über asynchrone Programmierung (async/await).

PS: Die Links sind meine Standard-Signatur - aber schön, daß dir der Artikel gefällt und weiterhilft.

P
Pippen Themenstarter:in
18 Beiträge seit 2022
vor einem Jahr

@Th69
Vielen herzlichen Dank. Das sind sehr gute und wichtige Informationen. So kommt man schnell weiter!

P
Pippen Themenstarter:in
18 Beiträge seit 2022
vor einem Jahr

So, nun bin ich schon wieder weiter gekommen mit meinem kleinen UI.

Jetzt wollte ich das Ganze noch ein bisschen sauberer machen und deshalb Events einsetzen zur Kommunikation zwischen meinem MqttHandler und meinem Form.
Dazu habe ich die wunderbare Erklärung in der FAQ gefunden: https://mycsharp.de/forum/threads/8799/loesung-problem-mit-eventhandler-fertige-code-snippets-inkl-erklaerung

Das habe ich dann genau so umgesetzt. Das hat auch funktioniert, wenn ich mit den standard EventArgs arbeite. Sobald ich da eine eigene Klasse als EventArgs verwende, funktioniert das nicht mehr. Gibt es irgendwo dieses Bsp. mit einer eigenen EventArgs Klasse ergänzt?

4.939 Beiträge seit 2008
vor einem Jahr

Kann es sein, daß du den falschen Link gepostet hast und meintest [FAQ] Eigenen Event definieren / Information zu Events (Ereignis/Ereignisse)?

Was genau funktioniert denn dann nicht (Fehlermeldung)?
In meinem Artikel ist unter "2. Ereignisse" auch ein Beispiel mit einer eigenen EventArgs-Klasse.

Aber wenn du schon Reactive einsetzt, dann kannst du auch gleich Reactive Extensions (Rx) - Replacing C# Events einsetzen.

P
Pippen Themenstarter:in
18 Beiträge seit 2022
vor einem Jahr

Komischerweise funktioniert mein Link nicht. Ich weiss aber nicht wieso.

Dein Link funktioniert. Ja, diese FAQ meine ich. Aber wo hast Du denn da was geschrieben.

Der @herbivore hat da ein Bsp. mit eigener EventArgs Klasse gepostet, das er aber nicht empfiehlt.
Deshalb will ich das nicht machen. Sondern ich habe die empfohlene Variante mit Null-conditional operator umsetzen wollen.
Jetzt weiss ich nicht, wie ich das anpassen muss.

4.939 Beiträge seit 2008
vor einem Jahr

Ich meinte in meinem Artikel aus meiner Signatur (Kommunikation von 2 Forms).

Zeige mal deinen Code dazu.

PS: Bei deinem Link ist zwar der Titel korrekt, aber die URL nicht...

P
Pippen Themenstarter:in
18 Beiträge seit 2022
vor einem Jahr

Ah ok. Ja, den Code kann ich frühestens heute Nachmittag zeigen, da ich am Morgen an Sitzungen bin.

Wegen des Links: Wie kann denn das passieren? Ich hatte einfach den Link aus dem Browser kopiert und zwischen den beiden Tags eingefügt.

Ich schaue mir Deinen Vorschlag aus "Kommunikation von 2 Forms" noch an.

P
Pippen Themenstarter:in
18 Beiträge seit 2022
vor einem Jahr

Jetzt hat es funktioniert mit den eigenen EventArgs.

Nun habe ich aber noch folgendes Problem. Es kommt im Formular die Exception System.InvalidOperationException.

Meine einfache Architektur:
Klasse "MqttHandler". Diese enthält eine Instanz der Klasse "MqttClient" vom M2Mqtt Package;
Der MqttHandler hat sich u. a. auf das Event MqttMsgPublishReceived registriert, um die ankommenden Mqtt Meldungen vom Broker empfangen zu können.
Der MqttHandler selber stellt auch ein Event für das Formular zur Verfügung, damit das Formular an die Meldung kommt.
In der Methode des Formulars, die beim Eintreffen des Events aufgerufen wird, wird die Exception geworfen: beim Zugriff auf ein Label, das die Meldung darstellen soll.

Nun meine Frage:
Debuggen an sich, kann ich. Wie erkenne ich beim debuggen aber thread Zugehörigkeiten?
Ich verstehe sowieso nicht, wo es da verschiedene Threads gibt. Ob evtl. der MqttClient des M2Mqtt Packages hier das Problem verursacht.

P
Pippen Themenstarter:in
18 Beiträge seit 2022
vor einem Jahr

Etwas Wichtiges habe ich wahrscheinlich noch vergessen:

Die Connect() Methode des MqttHandler wir in einem Backgroundworker ausgeführt, damit bei Problemen nicht das UI blockiert wird.
Aber eben, es ist nur dieser Aufruf. Der MqttHandler selber wird im Formular instanziiert.

P
Pippen Themenstarter:in
18 Beiträge seit 2022
vor einem Jahr

Das habe ich auch gerade gefunden. Das kann ich auch gerne machen.

Ich möchte aber auch gerne verstehen, wieso diese Methode von einem anderen Thread (wahrscheinlich dem backgroundworker) aufgerufen wird.
Kann mir das jemand erklären?
Und gibt es Möglichkeiten beim Debuggen zu erkennen, was von welchem Thread aufgerufen wird?

4.939 Beiträge seit 2008
vor einem Jahr

Die Empfangsschleife von MTTQ läuft in einem anderen Thread, denn sonst würde sie ja den UI-Thread blockieren.

In VS kannst du über das Menü "Debug / Windows" die Threads-Liste einblenden (wenn das Programm im Debug-Modus ist, d.h. ein Haltepunkt erreicht wurde) - und der aktuelle Thread sollte dann beim Debuggen automatisch selektiert/markiert sein.

Oder du läßt dir den aktuellen Thread anzeigen: Thread.CurrentThread

P
Pippen Themenstarter:in
18 Beiträge seit 2022
vor einem Jahr

Wow, vielen herzlichen Dank.

Das sind super wertvolle Inputs!

Stimmt, das macht Sinn, wegen des Tasks für den Meldungsempfang!

P
Pippen Themenstarter:in
18 Beiträge seit 2022
vor einem Jahr

An verschiedenen Orten ist zu lesen, dass im GUI Thread nichts länger als 1/10s (oder neu sogar 1/20s) dauern soll, damit das GUI nicht blockiert.
Wie messe ich das am einfachsten?
Wie messe ich z. Bsp. die Laufzeit einer bestimmten Methode?

4.939 Beiträge seit 2008
vor einem Jahr

Beim Debuggen sollte dir VS jeweils anzeigen, wie lange ein Methodenaufruf dauert, s. z.B. die Animation der Top-Antwort in How to test functions speed in Visual Studio.

Oder aber per Code mittels der Stopwatch-Klasse.