Hallo,
sitze gerade vor einem Problem, wo ich leider alleine nicht weiter komme.
Mein Problem.
Meine GUI bekommt von einem Mikrocontroller Daten via. RS232 geliefert.
Das ganze triggere ich von einem Button aus an. In dem Button Event, trage ich dann die empfangenen Werte in eine Textbox ein. Soweit so gut..
Nun ist das schreiben in die Textbox deutlich schneller als das empfangen der Daten.
Das heißt erst beim zweiten mal betätigen des Buttons stehen die richtig empfangen Werte drinn, vorher nur "0".
Wie kann ich das jetzt am klügsten lösen? warten?
Hi,
das klingt als würdest du nur einmalig, direkt nach dem Button Klick die Daten lesen?
Generell würde ich das lesen der Daten in den Hintergrund auslagern. Dafür müsstest du allerdings erkennen, wann der µC fertig geschrieben hat - ausser du kannst davon ausgehen, dass wann immer etwas gesendet wird, du es in die Textbox schreiben kannst.
Ich würde dir empfehlen dir die Drei-Schichten-Architektur anzuschauen, dann wärst du vermutlich gar nicht in diese Lage gekommen:
[Artikel] Drei-Schichten-Architektur
warten?
"Warten" ist in jeder Art von Software ein potentiell falscher Weg.
Die Kommunikation Deiner Software gehört in den entsprechenden Software Layer, was Dir Papst schon verlinkt hat.
Aktionen werden aber nicht durch Warten gelöst, sondern durch Events, auf die dann reagiert werden kann.
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code
Ich lese die Daten nur nach der Aktion. Ganz genau. Das mache
Ich aber auch nur damit der BUS vom Mikrokontroller nicht belastet wird.
Das mit dem Drei Schichten Model habe ich schon mehr oder weniger so strukturiert.
Keine Full Quotes. Danke.
[Hinweis] Wie poste ich richtig?
Eine serielle Schnittstelle ist kein Bus sondern eine P2P Verbindung. Einzig allein, wenn du darauf liest erzeugst du keine Last auf dem angeschlossenen zweiten Teilnehmer.
Die SerialPort Klasse von .NET ist dabei ein Wrapper um das Serielle Interface und bietet dir zwei Möglichkeiten Daten zu lesen:
-> Ein Event wird ausgelöst, wenn ein neuer Char anliegt
-> Eine Blockierende Methode, die bis zu einem bestimmten Punkt lesen kann (z.B. 1 Char, EOF, EOL)
Was du davon verwendest liegt an dir, aber wenn du nur einmal kurz liest, nachdem du ein Steuerzeichen/-kommando auf die Verbindung gelegt hast, beachtest du nicht die Verarbeitungszeit auf deinem µC und auch nicht die Übertragungszeigt.
P.S.: Der Artikel mit der drei Schichten Architektur bezog sich darauf, dass du den Klick als auslöser genommen hast um auf der Schnittstelle zu schreiben. Die Datenkommunikationssicht deiner Anwendung sollte nichts von einem Klick wissen, sondern lediglich, dass sie jetzt Daten abrufen soll.
Das heißt doch wiederum das ich die Daten
Zyklisch gelesen werden müssen und wenn ich
Den Button klicke, müssten die Daten aus dem Speicher übernommen werden oder wie verstehe ich das?
Naja, das sagt doch Papst, Du hast zwei Möglichkeiten:
-> Ein Event wird ausgelöst, wenn ein neuer Char anliegt
-> Eine Blockierende Methode, die bis zu einem bestimmten Punkt lesen kann (z.B. 1 Char, EOF, EOL)
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code
Das habe ich doch bereits erledigt.
Derzeit nutze ich das DataReceived Event dafür.
Wenn ich meine Anzahl an bytes zusammen habe, läuft mein Parser drüber.
Mit einem Event liest du nicht zyklisch sondern event getriggert.
Sobald du die SerialPort Instanz mit einem seriellen Port verbindest lauscht dieser auf eingehenden Datenverkehr.
Zyklisch wäre, wenn du immer versuchst einen char zu lesen, allerdings ist dann der zyklus nicht zeitbasiert sondern ebenfalls eventbasiert.
Im Endeffekt ist das SerialPort.Read() also nur ein ausprogrammieren des Eventgetriebenen Ansatzes.
Ist es das was du meinst?
Also. Wenn ich auf den Button klicke, sende ich einen String zu meinem Mikrocontroller. Dieser wertet den Befehl aus und schickt ggf. eine Antwort. Diese kann je nach Befehl von der Länge und vom Inhalt variieren.
Den ganzen Stream den der Mikrocontroller sendet und der Pc empfängt wird Event gesteuert.
Hier das Event
private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
cmd parser = new cmd();
/* Puffer anlegen..
*/
byte[] buffer = new byte[ 50 ];
/* Empfangene Daten abholen
*/
length += Client.Read(buffer, 0, buffer.Length);
/* Ist die mindest Anzahl an Bytes empfangen worden?
*/
if ( length < (int)cmd.Answer_Byte_Position_enum.CMD_ANSWER_DATA_BYTE_BEGINN )
{
return;
}
length = 0;
/* Sollen die empfangenen Daten angezeigt werden?
*/
if ( showInStreams.CheckState == CheckState.Checked )
{
string __debug__ = null;
for (uint x = 0; x < 10; x++)
{
__debug__ += Convert.ToByte(buffer[x]).ToString() + " ";
}
MessageBox.Show(__debug__ , "Eingehender Stream" , MessageBoxButtons.OK , MessageBoxIcon.Information);
}
richtxtbx_rawdata.Invoke(new Action(() => richtxtbx_rawdata.Clear()));
/* Kommando untersuchen..
*/
ReceiveCommando = parser.ParseCommandoReceive( buffer );
/* Wurde kein gültiges Kommando gefunden, zurück!
*/
if( ReceiveCommando.noBeginn == true )
{
return;
}
/* Checksummen vergleich..
*/
if ( ReceiveCommando.ChecksummOk )
{
CommandoCrcOkCounter++;
txtbx_id.Invoke(new Action(() => txtbx_crc_ok.Text = CommandoCrcOkCounter.ToString()));
}
else
{
CommandoCrcErrorCounter++;
txtbx_crc_error.Invoke(new Action(() => txtbx_crc_error.Text = CommandoCrcErrorCounter.ToString()));
}
/* Wenn Checksumme Ok, dann Bytes abspeichern..
*/
if ( ReceiveCommando.ChecksummOk )
{
txtbx_id.Invoke(new Action(() => txtbx_id.Text = ReceiveCommando.Id.ToString()));
txtbx_exitcode.Invoke(new Action(() => txtbx_exitcode.Text = ReceiveCommando.Exitcode.ToString()));
txtbx_rawdatabytes.Invoke(new Action(() => txtbx_rawdatabytes.Text = ReceiveCommando.DataLen.ToString()));
txtbx_length.Invoke(new Action(() => txtbx_length.Text = ReceiveCommando.AnswerLen.ToString()));
txtbx_crc.Invoke(new Action(() => txtbx_crc.Text = ReceiveCommando.Checksumm.ToString()));
if ( ReceiveCommando.DataLen > 0 )
{
for( uint x = 0; x < ReceiveCommando.DataLen; x++ )
{
richtxtbx_rawdata.Invoke(new Action(() => richtxtbx_rawdata.AppendText
(
"[" + x.ToString() + "]: " + ReceiveCommando.Data[x] + "\r\n"
)));
}
}
}
}
Hi Janiiix3,
Sagtest du nicht:
Das mit dem Drei Schichten Model habe ich schon mehr oder weniger so strukturiert.
Du hast da aber alles zusammen: UI, Logik zur Auswertung der Daten, Logik zur Kommunikation mit dem Microcontroller. Alles in einer Methode. So läßt sich das nur schwer lesen, debuggen und testen.
Mein Vorschlag: Schreib dir eine Klasse, die die Kommunikation mit dem Controller übernimmt, eine Methoden zum Senden von Befehlen anbietet, und über eine Antwort des Controllers per Event informiert. Dann kannst du diesen Teil debuggen und testen. Dann brauchst du nur noch eine Klasse, die die empfangenen Daten auswertet, und dann kannst die diese in der UI verwenden, um die die Steuerung zu übernehmen und die Antworten anzuzeigen.
Weeks of programming can save you hours of planning
Das ist eine gute Idee. Nur löst es nicht das Problem was ich eigentlich habe.
Ich möchte gerne wissen wann die Übertragung abgeschlossen ist und ein Kommando empfangen wurde.
Wie bekomme ich das innerhalb des Buttons raus?
Es sind keine großen Anforderung an die GUI.
Muss ich irgendwie solange in dem Button
Ereignis warten bis ein solches Kommando
Empfangen wurde...
Die Fragen wurden dir ja bereits beantwortet. Wenn du das noch nicht verstanden hast, schau mal in diese Artikel:
SerialPort.DataReceived Event
Events (C# Programming Guide)
Es geht darum: Du mußt nicht warten. Du wirst benachrichtigt!
Weeks of programming can save you hours of planning
Wir schreiben aneinander vorbei.
Das Serialport Event nutze ich doch bereits..
Ist dein Problem, dass beim auslösen des SerialPort.DataReceived Events noch nicht alle Daten, die du benötigst im Buffer liegen?
Wenn dem so ist, dann musst du dir einfach das zwischenergebnis merken und die Auswertung erst dann starten, wenn alles da ist. --> Das wäre durch eine eigene Klasse zum Handling mit dem SerialPort einfacher gelöst.
Merke: Ein DataReceived Event könnte auch zwei unterschiedliche Informationen vom µC enthalten.
Da du aber nicht genau sagst, was der Fehler oder das Fehlverhalten ist können wir nur ein wenig reinstochern...
Interessant wäre:
-> Was kommt an?
-> Was sollte ankommen?
Also anbei mal ein Foto von meiner GUI.
Empfangenes Kommando: Dort stehen die ganzen Daten die angekommen sind.
Multimeter: Volt -> Hier sollte das Ergebnis der "Roh Daten" eingetragen sein.
In dem Beispiel "[0]: 42 [1]: 1" sind das meine Roh Daten. Diese werden noch berechnet und dann sollten diese in der Textbox neben "Volt" eingetragen werden.
Beim zweiten mal "Lesen" stehen dann die entsprechenden Daten drin, da sie dann ja schon im Speicher vorhanden sind. Bei dem ersten "klick" jedoch noch nicht weil ich nicht auf die Daten warten kann..
Da ist mein Problem.. Ich muss warten bis diese angekommen sind.
Erstaunlich, dass das überhaupt funktioniert.
Du schmeißt nämlich so den Inhalt deines Buffers weg, wenn nicht genug daten eingetroffen sind.
Bei jedem DataReceived
löscht du doch die TextBox-Daten:
richtxtbx_rawdata.Invoke(new Action(() => richtxtbx_rawdata.Clear()));
Und da aber nicht sichergestellt ist, daß bei jedem Ereignisaufruf die kompletten Daten vorliegen, mußt du eben einen eigenen Puffer benutzen.
Du kannst dir auch mal Template SerialPort anschauen.
Das ist nur ein Zähler. Der zeigt mir bei jedem neuen Kommando an wie viele "Nutzdaten" mit gesendet wurden.
Wo schmeiße ich denn den Inhalt weg?
*/
byte[] buffer = new byte[ 50 ];
/* Empfangene Daten abholen
*/
length += Client.Read(buffer, 0, buffer.Length);
/* Ist die mindest Anzahl an Bytes empfangen worden?
*/
if ( length < (int)cmd.Answer_Byte_Position_enum.CMD_ANSWER_DATA_BYTE_BEGINN )
{
return;
}
Du legst den Buffer in der Methode an. Beim Verlassen dieser, werden die Daten weggeschmissen. Du speicherst die nirgends.
Pre-Post: panicJonny war schneller, aber ich denke ich bin eine Idee ausführlicher 😉
Wo schmeiße ich denn den Inhalt weg?
Hier:
private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
/* Puffer anlegen..
*/
byte[] buffer = new byte[ 50 ];
Du erstellst den Puffer innerhalb des Datenempfangs.
Wenn Du nun erwartest, dass "Test123" eingeht, der Port aber schnell genug reagiert, kommt nur "Tes" an und in dem nächsten ausgelösten Event "t123".
Beides ist von der Länge her kleiner als Deine erwarteten Daten. Also springst Du aus der Methode und die Daten sind futsch.
Eine mögliche Lösung ist es einen Empfangspuffer außerhalb Deiner Methode anzulegen und solange zusammensetzen, bis Deine Länge erreicht ist. Dann kannst Du diesen auswerten und wieder löschen.
Zum Aufbau:
Verwende eine eigene Klasse, die sich genau um das o.g. kümmert. Lerne wie man selbst Events erstellt und nutze es, wenn Deine Daten komplett sind.
Erstelle Dir eine Daten-Klasse, die Deine Werte beinhaltet und verknüpfe diese per Databinding an die Controls.
Nun füllst/änderst Du den Wert in Deiner Datenklasse aus dem Event aus Deiner SerialPort-Klasse mit der Pufferungs-Logik.
Verstehst du deinen eigene Code nicht?
richtxtbx_rawdata
ist doch das selbe Control, in der du die ReceiveCommando.Data
-Werte einträgst:
richtxtbx_rawdata.Invoke(new Action(() => richtxtbx_rawdata.AppendText
(
"[" + x.ToString() + "]: " + ReceiveCommando.Data[x] + "\r\n"
)));
Ich kann dir nur raten, deinen Code noch mal komplett neu aufzusetzen (wie von den anderen schon geschrieben).
@panicJonny:
Er parst jedesmal den Puffer:
ReceiveCommando = parser.ParseCommandoReceive( buffer );
Das führt aber im Endeffekt zum gleichen Ergebnis wie bei meiner Aussage oben.
Wie genau gehe ich vor?
Ich empfange Daten via Serial Port Event.
Von wo aus soll ich den Parser aufrufen? Noch in dem Event?
Im Prinzip ist der serielle Port noch Teil Deiner Kommunikationsschicht.
Den Parser kannst Du ja erst aufrufen - soweit ich das verstehe - wenn Du alle notwendigen Informationen hast.
Ergo muss nun die Logik, die entscheidet, wann Du alle Informationen hast, dann den Parser aufrufen.
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code
Wie genau gehe ich vor?
a) eine eigene Klasse erstellen, die sich um die Kommunikation mit dem Seriellen Port kümmert
b) darin eine globale Variable Klassenvariable (Feld) erstellen, die den gesamten Buffer einer Anfrage-Antwort darstellt
c) wenn der Button im ViewModel betätigt wird, kann die Anfrage in deiner neuen Klasse gestellt werden (Methode die du dafür erzeugt hast)
d) während in deiner neuen Klasse von der Anfrage immer wieder Antworten reinkommen, alle Teile in den globalen Buffer die Klassenvariable (Buffer) schreiben, solange bis alles da ist
e) sind nun alle Daten eingetrudelt, können sie geparst werden und am besten danach den globalen Buffer leeren/null'en (wie auch immer)
f) Deinem ViewModel mitteilen, daß die Anfrage beendet wurde und sie mittels Event benachrichtigen
g) Im ViewModel kann dann der fertige Wert angezeigt werden (Im Event können alle möglichen Informationen mitgegeben werden, Rohdaten, geparste Daten,...)
Wie was wo funktioniert, siehe andere Beiträge...
SG Thomas
Ich habe den Titel mal angepasst, so dass Suchende auch etwas damit anfangen können. EDIT: Ich sollte beim Wort "Shift" im Titel das "f" nicht vergessen... 😄
eine globale Variable erstellen
Bitte nicht.
Weeks of programming can save you hours of planning
Naja, er schrieb "darin eine globale Variable erstellen", womit er wahrscheinlich ein Feld der Klasse meint. Aber ja, wording ist wichtig 😉.
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)
a) eine eigene Klasse erstellen, die sich um die Kommunikation mit dem Seriellen Port kümmert
b) **:::
Ich wüßte nicht was man da falsch verstehen kann wenn man richtig liest 😉
Ich habe den Titel mal angepasst, so dass Suchende auch etwas damit anfangen können. EDIT: Ich sollte beim Wort "Shift" im Titel das "f" nicht vergessen... 😄
In computer programming, a global variable is a variable with global scope
Weeks of programming can save you hours of planning
Da muss ich Abt ausnahmsweise recht geben.
Eine Globale Variable und eine Klassenvariable sind zwei verschiedene paar Schuhe
Gemeint ist wohl:
b) darin eine öffentliche Eigenschaft erstellen, die den gesamten Buffer einer Anfrage-Antwort darstellt
Hallo,
ok ich gebe mich geschlagen 😉
Ich meinte eine Klassenvariable die Klassenintern global ist. Wußte ehrlich gesagt gar nicht, daß dieser Ausdruck so gar nicht verwendet werden kann, da ich noch nie eine 'globale Variable' selbst erstellen mußte oder damit direkt zu tun hatte.
Danke für die Info 😁
b) darin eine öffentliche Eigenschaft erstellen, ...
Nein, mit Variable meine ich ein 'Feld' und keine Eigenschaft, ist ja dann doch ein eindeutiger Unterschied. Zudem sind bei mir Felder immer 'private'.
Ich habe den Titel mal angepasst, so dass Suchende auch etwas damit anfangen können. EDIT: Ich sollte beim Wort "Shift" im Titel das "f" nicht vergessen... 😄
@panicJonny:
Er parst jedesmal den Puffer:ReceiveCommando = parser.ParseCommandoReceive( buffer );
Das führt aber im Endeffekt zum gleichen Ergebnis wie bei meiner Aussage oben.
Nein, tut er nicht. Wenn nicht genügend Daten ankommen, springt er vorher aus der Methode mit return raus.
Mit öffentliche Eigenschaft erstellen dachte ich eher an sowas:
public class Communicator : IDisposable
{
//öffentliche Eigenschaft
private MemoryStream _buffer = new MemoryStream();
public byte[] buffer { get { return _buffer.ToArray(); } }
public void AddBuffer(byte[] toAdd)
{
_buffer.Write(toAdd, 0, toAdd.Length);
}
private void _FreeManaged()
{
if (_buffer != null)
{
_buffer.Dispose();
_buffer = null;
}
}
private void _FreeUnmanaged()
{
//
}
public bool isDisposed { get { return this._disposed; } }
#region Dispose
// Track whether Dispose has been called.
private bool _disposed = false;
// Implement IDisposable.
// Do not make this method virtual.
// A derived class should not be able to override this method.
public void Dispose()
{
Dispose(true);
// This object will be cleaned up by the Dispose method.
// Therefore, you should call GC.SupressFinalize to
// take this object off the finalization queue
// and prevent finalization code for this object
// from executing a second time.
GC.SuppressFinalize(this);
}
// Dispose(bool disposing) executes in two distinct scenarios.
// If disposing equals true, the method has been called directly
// or indirectly by a user's code. Managed and unmanaged resources
// can be disposed.
// If disposing equals false, the method has been called by the
// runtime from inside the finalizer and you should not reference
// other objects. Only unmanaged resources can be disposed.
private void Dispose(bool disposing)
{
// Check to see if Dispose has already been called.
if (!this._disposed)
{
// If disposing equals true, dispose all managed
// and unmanaged resources.
if (disposing)
{
// Dispose managed resources.
_FreeManaged();
}
// Call the appropriate methods to clean up
// unmanaged resources here.
// If disposing is false,
// only the following code is executed.
{
//Unmanaged code like Handles
_FreeUnmanaged();
}
// Note disposing has been done.
_disposed = true;
}
}
// Use C# destructor syntax for finalization code.
// This destructor will run only if the Dispose method
// does not get called.
// It gives your base class the opportunity to finalize.
// Do not provide destructors in types derived from this class.
~Communicator()
{
// Do not re-create Dispose clean-up code here.
// Calling Dispose(false) is optimal in terms of
// readability and maintainability.
Dispose(false);
}
#endregion
}
Aufruf:
using (Communicator communicator = new Communicator())
{
communicator.AddBuffer(Encoding.Default.GetBytes("Hallo "));
communicator.AddBuffer(Encoding.Default.GetBytes("Welt."));
//Ausgabe: "Hallo Welt."
byte[] current = communicator.buffer;
MessageBox.Show(Encoding.Default.GetString(current, 0, current.Length));
}
Mit öffentliche Eigenschaft erstellen dachte ich eher an sowas:
Das ist dann eher eine Design-Frage, mein Beispiel hätte anders ausgesehen 😉
Gemeint ist wohl:
b) darin eine öffentliche Eigenschaft erstellen....
Hätte eher gesagt werden müssen, Meine Idee wäre:
Aber offtopic..
Ich habe den Titel mal angepasst, so dass Suchende auch etwas damit anfangen können. EDIT: Ich sollte beim Wort "Shift" im Titel das "f" nicht vergessen... 😄