Laden...

Datenübergabe Serialport -> eigene Datenstruktur korrekt implementieren

Erstellt von ismirschlecht vor 7 Jahren Letzter Beitrag vor 7 Jahren 2.388 Views
I
ismirschlecht Themenstarter:in
45 Beiträge seit 2012
vor 7 Jahren
Datenübergabe Serialport -> eigene Datenstruktur korrekt implementieren

Hallo,

ich stecke bei folgendem Problem fest:

  1. Ich möchte N Serialports erzeugen, initialisieren und starten (geht schon);
    Jede Serialportinstanz verweist dabei auf den gleichen Eventhandler DataReceived()

  2. Die Daten kommen "unvorhersehbar" rein und sind byte - Werte

  3. Jedem Serialport wird eine Datenstruktur mit Namen "Paket" zugeordnet; dieses "Paket" schiebt sequenziell Bytes rein (Schieberegister) und prüft nach jedem neuen Byte, ob ein Paket erkannt wurde (Anfengssequenz, Checksumme). Auch das geht.

  4. Das Problem: Wie gelangen die Bytes aus dem jeweiligen Eventhandler
    KORREKT in ihr zugeordnetes Paket ?
    Ich habe so ein Programm schonmal in Delphi geschrieben und möchte nun Designsünden von vornherein vermeiden.
    Natürlich könnte ich im Tag des Serialports einen Verweis auf das zugeordnete Paket unterbringen, das kommt mir aber sehr dreckig vor...

  5. Wenn 4. erledigt ist: Wie stößt ein erkanntes Paket korrekt die Weiterverarbeitung seiner selbst an ?

Ich will keinen Code sondern eher Strategien, um den Paradigmenwechsel zu NET hinzubekommen.

ism.

EDIT:

Habe gerade festgestellt, daß Serialport gar keinen tag hat.
Also entfällt auch dieser Schleichpfad...
(4.)

Hinweis von Coffeebean vor 7 Jahren

Ich habe deinen Beitrag mal verschmolzen, da das Thema sonst nicht mehr in "Themen ohne Antwort" erscheint.

1.029 Beiträge seit 2010
vor 7 Jahren

Hi,

ich würde vermutlich einfach vom SerialPort erben - so zum Beispiel:

using System;
using System.IO.Ports;

namespace TrashForms
{
	public class Temp
	{
		void Test()
		{
			IPackage packageV1 = new PackageV1();
			PackagingSerialPort port1 = new PackagingSerialPort(packageV1);

			IPackage packageV2 = new PackageV2();
			PackagingSerialPort port2 = new PackagingSerialPort(packageV2);
		}
	}

	public class PackagingSerialPort : SerialPort
	{
		public IPackage Package { get; private set; }

		public PackagingSerialPort(IPackage package)
		{
			Package = package;

			DataReceived += PackagingSerialPort_DataReceived;
		}

		private void PackagingSerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
		{
			for (int i = 0; i < BytesToRead; i++)
			{
				Package.AddByte(ReadByte());
			}
		}
	}

	public interface IPackage
	{
		event EventHandler<EventArgs> PackageReceived;

		void AddByte(int data);
	}

	public class PackageV1 : IPackage
	{
		public event EventHandler<EventArgs> PackageReceived;

		public void AddByte(int data)
		{
			throw new NotImplementedException();
		}
	}

	public class PackageV2 : IPackage
	{
		public event EventHandler<EventArgs> PackageReceived;

		public void AddByte(int data)
		{
			throw new NotImplementedException();
		}
	}
}

LG

3.003 Beiträge seit 2006
vor 7 Jahren

Jede Serialportinstanz verweist dabei auf den gleichen Eventhandler DataReceived()

Ist etwas unklar. Benutzt jede Instanz dieselbe Referenz, oder nur jede eine spezifische Instanz einer Klasse mit dem EventHandler?

Davon unabhängig ist die Signatur von DataReceived:


public delegate void SerialDataReceivedEventHandler(
	object sender,
	SerialDataReceivedEventArgs e
)

, das heisst der Sender wird sowieso übergeben, und damit eine Zuordnung zur SerialPort-Instanz, die das Ereignis ausgelöst hat. (ist dann im EventHandler SerialPort sp = (SerialPort)sender;)

Wenn du den Serialport hast, hast du auch dein "Paket" dazu.
Weiterverarbeitung entsprechend wieder mit einem Event in "Paket", das du auslöst, wenn sich die Daten im Paket verändert haben.

LaTino

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)

W
872 Beiträge seit 2005
vor 7 Jahren

Ich würde einiges anderes machen, wenn ich Dich richtig verstanden habe.
Erstmal solltest Du nur mit einer Instanz/Singleton arbeiten, um Synchronisationsprobleme zu vermeiden, damit gleichzeitiges Senden funktioniert.
Dann würde ich mir überlegen, ob Du beim Senden das delegate oder den EventHandler mitgeben möchtest oder nicht.
Intern würde ich auf jeden Fall eine Sequenz-Nummer mitgeben, wenn es geht, so daß Du Request und Response miteinander verknüpfen kannst - auch bei der Fehlersuche hilft Dir das. Das halte ich für ein sauberes Vorgehen.

I
ismirschlecht Themenstarter:in
45 Beiträge seit 2012
vor 7 Jahren

Hallo,

danke erstmal an alle.
Genug Stoff zum Rumprobieren.

Bis zum Punkt 4 bin ich jetzt gekommen:


public class Einheit : SerialPort
        {
        public Paket P=null;
        public string Bez="";
        public Einheit(string cname, int Baud, Parity party, int Bits, int Plength,   
                            string   _bez)
            : base (cname,Baud,party,Bits)
            {
            this.Bez = _bez;
            this.P = new Paket(Plength);
            }

        }

Diese "Einheit" besitzt ein "Paket", und im Ereignishandler kann ich das über Casting ansprechen:


 private void OnDataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
            {
            Einheit iP= ( (Einheit) sender);
            while (iP.BytesToRead > 0)
                {
                iP.P.Push(  iP.ReadByte()  );
                // Push(int) schiebt das nächste Byte in die 
                // Warteschlange des Paketes
                // hier dann Paketerkennung usw.
                }
       
            }

Bis dahin scheint es mir relativ sauberer Stil zu sein.
Die Weiterverarbeitung (bei erkanntem Paket) würde ich gerne nach dem Motto
"fire and forget" abhandeln, dazu werde ich mir aber wohl noch userdefinierte Events ansehen müssen...
ism

3.003 Beiträge seit 2006
vor 7 Jahren

Gibt dazu in den Artikeln schon etwas (mal die Forumssuche bemühen), aber im Endeffekt läuft es darauf hinaus:


class Paket
{
    public event EventHandler DataChanged;
    private void OnDataChanged()
    {
        var handler = DataChanged;
        if(handler != null) handler(this, EventArgs.Empty);

        //C# 6 -> Visual Studio 2015
        DataChanged?.Invoke(this, EventArgs.Empty);
    }

    void Push(byte data)
    {
          //was immer da jetzt auch steht...
         OnDataChanged(); //Ereignis auslösen
    }
}

Der Hinweis von weismat ist sehr gut, was die Synchronisation angeht.

LaTino

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)

I
ismirschlecht Themenstarter:in
45 Beiträge seit 2012
vor 7 Jahren

Hallo,

erstmal zwei Informationen:

  1. Ich arbeite mit VS2010 unter NET4.0
  2. Zu

Jede Serialportinstanz verweist dabei auf den gleichen Eventhandler DataReceived() :
Das heißt einfach nichts anderes als daß ich jedem Serialport den gleichen Datareceived-Handler zuweise. Über "sender" komme ich ja an den Absender ran.

Ist ein Datenpaket erkannt (klappt) übergebe ich die Nutzdaten in ein Hilfsobjekt und starte einen Einmal-Thread (klappt), welcher die spezifische Arbeit erledigt (klappt).

Aber jetzt kommt eine Stelle, bei der ich mich unwohl fühle:
Wie bekommt die GUI eine Rückmeldung vom Thread, sprich seinem Hilfsobjekt ?
Das Hilfsobjekt steckt in einer separat deklarierten Quelldatei und soll vom Hauptformular nichts wissen, ich will das Zeugs je vielleicht weiterverwenden.

Dazu habe ich im Hauptformular einen Delegaten und eine konkrete Instanz definiert:


        public delegate void Nowosti( string nobui);
        public void konkNowosti(string nowui)
            {
            // stelle die Nachricht irgendwo dar
            }

Weiterhin bekommt die Helferklasse den gleichen Delegaten und einen Verweis für ein Besitzer-Objekt:


        public delegate void nowo(String nachri);
        public nowo Nowohandler = null; 
        public object Elter = null;    // das wird das Hauptformular

Im Hauptthread, wo das Hilfsobjekt erstellt wird, weise ich Besitzer (Elter) und Delegat zu:


  // Ka heißt mein Hilfsobjekt
  Ka.Nowohandler = konkNowosti;
  Ka.Elter = this;

... und zum guten Schluß rufe ich im Arbeitsthread den Delegaten auf:


   Nachr = "Es hat geklappt";
   ((Form) this.Elter).Invoke(new nowo(Nowohandler),Nachr);

So, Code zur Kritik freigegeben. Der Compiler hat Ja gesagt, das Programm läuft...

Gruß ism

3.003 Beiträge seit 2006
vor 7 Jahren

Mach's über events, statt direkt mit Delegaten zu arbeiten, die sind darauf spezialisiert.


public event EventHandler<string> Nowosti;
private void OnNowosti(string msg)
{
    var handler = Nowosti;
    if(handler != null) handler(msg);
}

//da, wo du jetzt Elter zuweist
Ka.Nowosti += konkNowosti;

//Arbeitsthread:
Nachr = "Es hat geklappt";
OnNowosti(Nachr);

LaTino

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)

I
ismirschlecht Themenstarter:in
45 Beiträge seit 2012
vor 7 Jahren

Hallo,

danke, das kam mir auch schon in den Sinn.
ism

I
ismirschlecht Themenstarter:in
45 Beiträge seit 2012
vor 7 Jahren

Nachdem ersten Versuch über eine eigene Eventklasse wurde ich von der Runtime vollgenölt, Thread A würde in Thread B (GUI) schreiben.
Fazit: Ich mußte mir eine üble Invoke-Konstruktion trotz Events besorgen.
Wo soll nun der Vorteil der Events liegen ?
Da sind Delegaten doch ein wenig einfacher zu verstehen.

Und: Zieht ein Event, der aus einem Thread etwas an der GUI drehen will, zwangsläufig ein Invoke nach sich ?

Gruß ism

1.029 Beiträge seit 2010
vor 7 Jahren

Hi,

du solltest grundsätzlich nicht von einem anderen Thread als dem UI-Thread selbst Änderungen an der UI durchführen lassen. Geht schief! Und ja - im Endeffekt sind das dann Invokes.

Nachfolgend ein Link zur entsprechenden FAQ:
[FAQ] Controls von Thread aktualisieren lassen (Control.Invoke/Dispatcher.Invoke)

LG

Edit: Das mit den Delegates lässt sich auch leicht schöner schreiben wie im "Standard", unter folgendem Link wird gezeigt, wie das aussehen kann:
http://stackoverflow.com/questions/253138/anonymous-method-in-invoke-call

3.003 Beiträge seit 2006
vor 7 Jahren

Events sind Delegaten. Der Unterschied in der Ausführung ist minimal zu dem, was du mit den Delegaten gemacht hast. Event setzt nur nochmal eine Zwischenschicht auf delegate, die verhindert, dass der delegate und seine Aufrufliste versehentlich überschrieben werden - indem nur noch hinzufügen und entfernen von der Aufrufliste erlaubt werden. Örks. War das irgendwie annähernd verständlich? 😄

Im Prinzip war dein Vorgehen aus meiner Sicht völlig in Ordnung. Weil es aber ein typischer Anwendungsfall von event ist, sollte man das dann eben auch nutzen.

LaTino

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)

I
ismirschlecht Themenstarter:in
45 Beiträge seit 2012
vor 7 Jahren

Hallo,

die Dinge haben sich weiterentwickelt.

an Taipi88: Geht klar, deshalb mein erster Ansazu über Delegates (Nowosti() )
Ich dachte eben, Events seien genau das Mittel, das Invoke zu vermeiden.

an LaTino: Äh ja, ich bin am Verstehen. 😎

Der Artikel von Herbivore ist wirklich gut.
Man bleint dran,
ism