Benutzer mit den meisten Antworten
C# GPS-Protokoll in separaten Thread empfangen

Frage
-
Hallo zusammen,
für ein aktuelles Projekt suche ich nach einer eleganten Möglichkeit GPS-Daten von einer GPS-Maus zu empfangen. Bisher hatte ich dafür einen Timer verwendet, der einmal pro Sekunde die serielle Schnittstelle ausliest und die Daten verarbeitet um so das GPS-Protokoll zu zerlegen.
Das funktioniert auch wunderbar, weswegen ich mich jetzt an den Schritt wagen möchte das Empfangen der GPS-Daten in einem separaten Thread zu verschieben.Ich habe auch schon ein Beispiel gefunden:
http://www.codeproject.com/Questions/228124/serial-port-using-threading
Allerdings verstehe ich dieses Beispiel noch nicht 100%ig. In dem Beispiel wird das komplette Lesen der seriellen Schnittstelle in einer neuen Klasse erledigt. Im Konstruktor wird ein neuer Thread erzeugt, der dann einmalig die serielle Schnittstelle initialisiert und dann so lange ausließt bis die While-Schleife beendet wird.
Der Teil ist kein Problem. Nur was mir noch nicht so ganz einleuchtet ist der Teil:if(DataReceived != null) DataReceived(this, new DataEventArgs(line)); public class DataEventArgs : EventArgs { public string Data { get; private set; } public DataEventArgs(string data) { Data = data; } }
Wenn ich mir das richtig zusammen reime, wird ja jedes mal ein neues Event definiert, wenn die serielle Schnittstelle Daten empfängt und diese Daten werden dann in einer separaten Klasse gespeichert und es wird zudem ein Event ausgelöst (dieses Event muss später im Code noch definiert werden).
Soviel zu der Theorie...
Jetzt habe ich das mal in meinem Code getestet. Meine Klasse sieht wie folgt aususing System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.IO.Ports; namespace GUI { public class GPSReader { private Thread GPS_Thread; public event EventHandler<DataEventArgs> Event_DataReceived; private bool IsClose = false; public GPSReader() { GPS_Thread = new Thread(GPSData); } public void Start() { GPS_Thread.Start(); } public void Stop() { } public void Close() { IsClose = true; } private void GPSData() { SerialPort SerialPort = new SerialPort(); SerialPort.BaudRate = DataContainer.Baudrate; SerialPort.Parity = Parity.None; SerialPort.StopBits = StopBits.One; SerialPort.DataBits = 8; SerialPort.Handshake = Handshake.None; SerialPort.Open(); while(!IsClose) { string ReadLine = SerialPort.ReadLine(); if (Event_DataReceived != null) { Event_DataReceived(this, new DataEventArgs(ReadLine)); } } } } } public class DataEventArgs : EventArgs { public DataEventArgs(string EventData) { Data = EventData; } public string Data { get; private set; } }
In meinem MainWindow habe ich nun einen "Connect"-Button, der alles initialisiert:
private void MenuItem_Click_Connect_GPS(object sender, RoutedEventArgs e) { try { if(DataContainer.Baudrate <= 0) { AddStatus("Keine Baudrate ausgewählt!"); return; } else if(DataContainer.COM_Port == string.Empty) { AddStatus("Kein COM-Port ausgewählt!"); return; } else { ComPort.PortName = DataContainer.COM_Port; ComPort.BaudRate = (int)DataContainer.Baudrate; ComPort.ReadTimeout = 50; ComPort.WriteTimeout = 50; ComPort.Open(); if(ComPort.IsOpen) { AddStatus("Verbinde mit COM-Port..."); if(Reader == null) { AddStatus("GPS-Thread gestartet"); Reader = new GPSReader(); Reader.Event_DataReceived += GetGPS; Reader.Start(); } } } } catch (Exception Error) { if(Error is UnauthorizedAccessException) { AddStatus("COM-Port bereits geöffnet!"); } else { AddStatus("Schwerer Programmfehler! Bitte im Log nachschauen!"); WriteErrorLog(Convert.ToString(Error)); } }
Als Event habe ich mal test weise dieses hier definiert:
public void GetGPS(object s, EventArgs e) { AddStatus("GPS-Event"); }
Theoretisch sollte ja nun bei jeder GPS-Nachricht ein Text "GPS-Event" in meiner Textbox erscheinen.
Das passiert aber leider nicht. Dafür bekomme ich die ganze Zeit solche Ausgaben:
Der Thread 0x2b24 hat mit Code 259 (0x103) geendet. Der Thread 0x214c hat mit Code 259 (0x103) geendet. Der Thread 0x1cfc hat mit Code 259 (0x103) geendet. Der Thread 0x2468 hat mit Code 259 (0x103) geendet.
Wo steckt der Fehler in dem Code? Ich hoffe es kann mir einer helfen :)
Ich sage schon einmal Danke :)
Gruß
Daniel
Antworten
-
Hallo,
ich vermute, dass der Thread nach einer Exception beendet wird.
Sind SerialPort und ComPort unterschiedliche Objekte? Wenn ja, dann sollst Du eine Exception InvalidOperationException kriegen, weil Com-Port schon geöffnet ist. Warum wird Com Port zwei mal geöffnet?Die Methode Open muss immer in try catch Blöcke einpackt werden.
Beim SerialPort Objekt wird kein Port (PortName) gesetzt.
Grüße
- Bearbeitet Iso7 Montag, 27. Juli 2015 11:52
- Als Antwort vorgeschlagen Iso7 Montag, 3. August 2015 11:51
- Als Antwort markiert Dimitar DenkovMicrosoft contingent staff, Administrator Freitag, 7. August 2015 09:00
-
Verwendest Du WinForm oder WPF? Oben ist ein Beispiel für WinForms. Für WPF gibt es Dispatcher.
- Als Antwort vorgeschlagen Dimitar DenkovMicrosoft contingent staff, Administrator Montag, 3. August 2015 10:16
- Als Antwort markiert Dimitar DenkovMicrosoft contingent staff, Administrator Freitag, 7. August 2015 08:59
-
Hallo Daniel,
dein Thread läuft immer in while Schleife und wird nicht beendet.
Die Variable IsClose muss auch gesetzt werden:
IsClose = true;
Grüße
- Als Antwort vorgeschlagen Dimitar DenkovMicrosoft contingent staff, Administrator Montag, 3. August 2015 10:16
- Als Antwort markiert Dimitar DenkovMicrosoft contingent staff, Administrator Freitag, 7. August 2015 08:59
Alle Antworten
-
Hallo,
ich vermute, dass der Thread nach einer Exception beendet wird.
Sind SerialPort und ComPort unterschiedliche Objekte? Wenn ja, dann sollst Du eine Exception InvalidOperationException kriegen, weil Com-Port schon geöffnet ist. Warum wird Com Port zwei mal geöffnet?Die Methode Open muss immer in try catch Blöcke einpackt werden.
Beim SerialPort Objekt wird kein Port (PortName) gesetzt.
Grüße
- Bearbeitet Iso7 Montag, 27. Juli 2015 11:52
- Als Antwort vorgeschlagen Iso7 Montag, 3. August 2015 11:51
- Als Antwort markiert Dimitar DenkovMicrosoft contingent staff, Administrator Freitag, 7. August 2015 09:00
-
Hallo Iso,
danke für das Feedback! Da habe ich den Wald vor lauter Bäumen nicht gesehen (der eigentliche Code im MainWindow ist schon etwas länger).
Der COM-Port sollte natürlich nicht zwei mal geöffnet werden. Ich habe nun erst einmal meine Connect-Routine eingekürzt:private void MenuItem_Click_Connect_GPS(object sender, RoutedEventArgs e) { if (Reader == null) { AddStatus("GPS-Thread gestartet"); Reader = new GPSReader(); Reader.Event_DataReceived += GetGPS; Reader.Start(); } Update.Interval = 100; Update.Tick += new EventHandler(Update_GPS); Update.Start(); AddStatus("Starte Update-Timer"); }
Und meine Klasse um den Namen und einen try-catch Block erweitert.
Jetzt bekomme ich kurz nach dem Connecten eine "InvalidOperationException", da ich über "AddStatus" in eine Textbox schreiben will, die einem anderen Thread angehört.
Das ist schon mal schön, weil dann tut sich wenigstens was :)
Die Lösung des Problems ist jetzt wahrscheinlich so etwas hier:public void AppendTextBox(string Text) { if (!Dispatcher.CheckAccess()) { this.Dispatcher.Invoke(new Action<string>(AppendTextBox), new object[] { value }); return; } TextBox_Status.Text += Text; }
Was ich jetzt nur noch nicht verstehe....
Wie komme ich an den Text aus dem Eventhandler der neu erstellten Klasse dran?
Irgendwie funktioniert das nicht wenn ich schreibepublic void GetGPS(object s, EventArgs e) { TextBox_GPS_Log.AppendText = e.Data; }
Da ist angeblich kein Verweis vorhanden.
- Bearbeitet Kampino89 Montag, 27. Juli 2015 12:00
-
Noch zwei Bemerkungen:
1)Die Controls (z.B. TextBox_GPS_Log) dürfen nicht in einem Thread geändert werden, sondern in UI Thread.
public void Aktualisieren(String text) { if (textBox_GPS_Log.InvokeRequired == false) { textBox_GPS_Log.AppendText(text); } else { textBox_GPS_Log.BeginInvoke(new DelTextAppend(Aktualisieren), new Object[] { text }); } }
2) Event abonnieren :
Reader.Event_DataReceived += new DelegateVonEvent(GetGPS);
- Bearbeitet Iso7 Montag, 27. Juli 2015 12:35
-
Verwendest Du WinForm oder WPF? Oben ist ein Beispiel für WinForms. Für WPF gibt es Dispatcher.
- Als Antwort vorgeschlagen Dimitar DenkovMicrosoft contingent staff, Administrator Montag, 3. August 2015 10:16
- Als Antwort markiert Dimitar DenkovMicrosoft contingent staff, Administrator Freitag, 7. August 2015 08:59
-
Hallo,
ich habe gleich noch einmal eine Frage.
Ich habe meine Klasse für den GPS-Reader nun angepasst:using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.IO.Ports; namespace GUI { public class GPSReader { private Thread GPS_Thread; private Log_Writer Log = new Log_Writer(); public event EventHandler<DataEventArgs> Event_DataReceived; private bool IsClose = false; public GPSReader() { GPS_Thread = new Thread(GPSData); } public void Start() { GPS_Thread.Start(); } public void Stop() { } public void Close() { IsClose = true; } private void GPSData() { SerialPort SerialPort = new SerialPort(); SerialPort.PortName = DataContainer.COM_Port; SerialPort.BaudRate = DataContainer.Baudrate; SerialPort.Parity = Parity.None; SerialPort.StopBits = StopBits.One; SerialPort.DataBits = 8; SerialPort.Handshake = Handshake.None; try { SerialPort.Open(); } catch(Exception Error) { Log.WriteErrorLog(Convert.ToString(Error)); } while(!IsClose) { if(SerialPort.IsOpen) { string ReadLine = SerialPort.ReadLine(); if (Event_DataReceived != null) { Event_DataReceived(this, new DataEventArgs(ReadLine)); } } } } } public class DataEventArgs : EventArgs { public string GPS_Data { get; private set; } public DataEventArgs(string EventData) { GPS_Data = EventData; } } }
Das Abarbeiten des GPS-Events in dem MainWindow klappt nun auch. Nun möchte ich aber auf die Daten aus dem Event, sprich die eingelesene Zeile, zugreifen.
Deswegen habe ich in meinem GPS-Event diese ZeileTextBox_Status.Dispatcher.Invoke(new Action(() => { TextBox_Status.AppendText(Convert.ToString(e.GPS_Data) + "\n"); }));
Allerdings zeigt mir Visual Studio an, dass es kein "GPS_Data" in den EventArgs gibt.
Warum?
-
Hallo,
die komplette Methode schaut so aus:
void GetGPS(object s, EventArgs e) { TextBox_Status.Dispatcher.Invoke(new Action(() => { TextBox_Status.AppendText(Convert.ToString(e.GPS_Data) + "\n"); })); }
Ansonsten tausche ich einfach mal den Namen von der Klasse aus....vielleicht löst das ja schon das Problem.
Edit:
Dein Hinweis war super!
Habe die Klasse nun geändert:public class GPSDataEvents : EventArgs { public string GPS_Data { get; private set; } public GPSDataEvents(string EventData) { GPS_Data = EventData; } }
Und entsprechend das Event ebenfalls:
void GetGPS(object s, GPSDataEvents e) { TextBox_Status.Dispatcher.Invoke(new Action(() => { TextBox_Status.AppendText(Time + " - " + e.GPS_Data + "\n"); })); }
Nun klappt es :)
- Bearbeitet Kampino89 Dienstag, 28. Juli 2015 07:51
-
Das nächste Verständnisproblem was ich habe ist wie ich den Thread für die GPS-Maus ordnungsgemäß beende.
Soweit ich gelesen habe brauche ich die Join-Methode dafür.
Ich habe es nun so gelöstusing System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.IO.Ports; namespace GUI { public class GPSReader { private Thread GPS_Thread; private SerialPort SerialPort; private Log_Writer Log; public event EventHandler<GPSDataEvents> Event_DataReceived; private bool Running = false; private bool IsClose = false; public GPSReader() { GPS_Thread = new Thread(GPSData); } public void Start() { GPS_Thread.Start(); Running = true; } public void Stop() { Running = false; GPS_Thread.Join(); } public void Close() { IsClose = true; } private void GPSData() { while (Running) { SerialPort = new SerialPort(); SerialPort.PortName = DataContainer.COM_Port; SerialPort.BaudRate = DataContainer.Baudrate; SerialPort.DataBits = DataContainer.Databits; SerialPort.Parity = DataContainer.Parität; SerialPort.StopBits = DataContainer.Stop; SerialPort.Handshake = DataContainer.HandShake; try { SerialPort.Open(); } catch (Exception Error) { Log = new Log_Writer(); Log.WriteErrorLog(Convert.ToString(Error)); Log = null; } while (!IsClose) { if (SerialPort.IsOpen) { string ReadLine = SerialPort.ReadLine(); if (Event_DataReceived != null) { Event_DataReceived(this, new GPSDataEvents(ReadLine)); } } } } } } public class GPSDataEvents : EventArgs { public string GPS_Data { get; private set; } public GPSDataEvents(string EventData) { GPS_Data = EventData; } } }
Öffnen kann ich den Thread ganz normal. Nur wenn ich dann die Stop() Methode aufrufe bleibt mein Programm hängen.
Ich habe mich versucht hieran zu halten:https://msdn.microsoft.com/de-de/library/7a2f3ay4%28v=VS.90%29.aspx?f=255&MSPPError=-2147217396
Da wird ja auch mit einem Flag gearbeitet und dieses Flag wird vor der Join()-Methode auf "true" gesetzt, weswegen die Schleife im Thread nicht weiter abgearbeitet wird.
Nur wie gesagt....bei mir hängt sich das Programm dann komplett auf, sprich der Thread wird nicht beendet. -
Hallo Daniel,
dein Thread läuft immer in while Schleife und wird nicht beendet.
Die Variable IsClose muss auch gesetzt werden:
IsClose = true;
Grüße
- Als Antwort vorgeschlagen Dimitar DenkovMicrosoft contingent staff, Administrator Montag, 3. August 2015 10:16
- Als Antwort markiert Dimitar DenkovMicrosoft contingent staff, Administrator Freitag, 7. August 2015 08:59