Laden...

SerialPort Daten lesen unvollständig/ schreiben nur verzögert möglich

Erstellt von Treter_Peter vor 4 Jahren Letzter Beitrag vor 4 Jahren 2.488 Views
T
Treter_Peter Themenstarter:in
5 Beiträge seit 2019
vor 4 Jahren
SerialPort Daten lesen unvollständig/ schreiben nur verzögert möglich

Hallo zusammen,
ja, einige werden genervt sein, aber ich komme nicht weiter.

Ich bin gerade an einer Ralaiskartensteuerung drann, die über das SerialPort Objekt in einer WinForms anwendung laufen soll.
Ich habe versucht, eine 3-SchichtAnwendung zu schreiben basierend auf dem Template. Allerdings zweifel ich grade daran, ob es wirklich 3 "Schichten" sind.

Grundlegend hab ich 2 Probleme:
Die Relaiskarten erwarten ein 4-Byte Kommando (Byte 1: Kommando, Byte 2: Relaiskartenadresse, Byte 3: Daten, Byte 4: Prüfsumme (XOR Verknüpfung von Byte1-3)),
und sendet einen 4-Byte Antwortrahmen zurück.
Nach Anwendungsstart und dem Init der Schnittstelle löse ich das erste mal ein Click Event aus -> Daten werden erfolgreich gesendet, aber es kommt scheinbar keine Antwort obwohl Relais geschaltet hat.
2ter Click -> Senden nicht erfolgreich, aber es kommt eine Antwort (aber Anzahl der Bytes stimmen of nicht (nur3))
3ter Click -> Senden wieder erfolgreich Anwort kommt, aber falsch.
4ter Click senden wieder erolglos usw.
Nur bei jedem 2ten Click-Event wird das Relais umgeschaltet.

Ich vermute, das kommt daher, das sich das senden und empfangen irgendwie gegenseitig blockieren.

Ich verwende aktuell MS Visual Studio Express 2015 mit .NET Framework 4.7

Danke schonmal dafür, das ihr meine Frage überhaupt gelesen habt. 😃

Code Auszug:
SerialConector.cs


//Daten von ComPort lesen
        private void readDataFromSerialPort(object sender, SerialDataReceivedEventArgs e)
        {
            if(serialPort.BytesToRead >=1)
            {
                while (serialPort.BytesToRead != 0)
                {
                    RecivedData += " Byte" + AnzahlBytes + ":  ";
                    RecivedData += serialPort.ReadChar();
                    RecivedData += "; ";
                    AnzahlBytes += 1;
                }

                if (AnzahlBytes >= 4)
                {
                    RecivedData += Environment.NewLine;
                    gui.OutPutBoxText = RecivedData;
                    gui.SetOutPutOnBox();
                    AnzahlBytes = 1;
                }
            }                                                                
        }

        //Daten an ComPort schreiben
        public void SendDataToSerialPort(/*byte[] Send*/)
        {
            Send[0] = 8;
            Send[1] = 2;
            Send[2] = 3;
            Send[3] ^= Send[0];
            Send[3] ^= Send[1];
            Send[3] ^= Send[2];

            if (serialPort.BytesToWrite == 0)
            {
               serialPort.Write(Send, 0, 4);
            }else
            {
                gui.OutPutBoxText = "Fehler SendePuffer!";
                gui.SetOutPutOnBox();
            }           

StartForm.cs


private void SendCMD_Click(object sender, EventArgs e)
        {
            if (this.serialConnector != null)
            {
                Send[0] = 8;
                Send[1] = 1;
                Send[2] = 255;
                Send[3] ^= Send[0];
                Send[3] ^= Send[1];
                Send[3] ^= Send[2];              

                serialConnector.SendDataToSerialPort(/*Send*/);                
            }
        }
public void SetOutPutOnBox()
        {
            //Call Method from GUI-Thread
            if (this.InfoTextArea.InvokeRequired)
            {
                this.InfoTextArea.Invoke(new MethodInvoker(this.SetOutPutOnBox));
                return;
            }

            //Display Response
            this.InfoTextArea.Text = this.output;
        }

        /// <summary>
        /// Property for OutPutText
        /// </summary>
        public string OutPutBoxText
        {
            get { return this.output; }
            set { this.output = value; }
        }

16.834 Beiträge seit 2008
vor 4 Jahren

Allerdings zweifel ich grade daran, ob es wirklich 3 "Schichten" sind.

Du hast die SerialPort Kommunikation direkt in der Form: nein, es ist daher keine saubere Schichtentrennung.

Daten werden erfolgreich gesendet, aber es kommt scheinbar keine Antwort obwohl Relais geschaltet hat.

Kommen auch wirklich Daten am PC an? Kannst ja prüfen - wir nicht 😃
Gerne auch den Debugger verwenden, damit Du selbst Deinen eigenen Code besser verstehst: [Artikel] Debugger: Wie verwende ich den von Visual Studio?

Ich vermute, das kommt daher, das sich das senden und empfangen irgendwie gegenseitig blockieren. Prinizpiell nicht.
Du verwendest einen Event, der aufgerufen wird, sobald neue Daten eintrudeln.
Es gibt in diesem Fall keine blockierende Methode, die auf "irgendetwas wartet und blockiert".

Dein Code sieht aber durchaus etwas abenteuerlich aus 😃

Ich verwende aktuell MS Visual Studio Express 2015 mit .NET Framework 4.7

Du kannst problemlos auf die Community Edition umschwenken, sofern Du kein riesen Unternehmen mit nem Millionenumsatz bist.
Express hat sehr sehr eingeschränkte Features.

T
Treter_Peter Themenstarter:in
5 Beiträge seit 2019
vor 4 Jahren

Danke für die Antwort,

was genau meinst du mit "Abenteuerlich" und wie trenne ich die schichten denn sauber?

Der Code stammt mehr oder weniger aus dem SerialPort Template hier von der Seite.

Ich habe doch eine Klasse für die Form, und eine 2te Klasse für die Serial Port Kommunikation.

Oder meinst du damit, beide Klassen im selben Thread laufen (wird im Program.cs gestartet).

T
2.224 Beiträge seit 2008
vor 4 Jahren

@Treter_Peter
Das Thema mit dem Schichten kannst du hier nachlesen.

Das Thema kommt ab und an im Forum wieder vor, da lohnt es sich, dass es bereits Artikel darüber gibt 😃

T-Virus

Developer, Developer, Developer, Developer....

99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.

4.939 Beiträge seit 2008
vor 4 Jahren

Für eine 3-Schichten Architektur hast du als mittlere Schicht die (Geschäfts-)Logik-Schicht und darunter dann erst die Datenzugriffsschicht (also bei dir u.a. die Klasse SerialCon(n)ector), s.a. [Artikel] Drei-Schichten-Architektur.

Oh, T-Virus war schneller, aber ich noch ein bißchen ausführlicher 😉

T
Treter_Peter Themenstarter:in
5 Beiträge seit 2019
vor 4 Jahren

Ok, in diesem Beitrag geht es um die Theorie.
Ich weiß aber nicht, wie das hier praktisch umsetzen kann.
Und selbst wenn, wäre nicht sichergestellt, das es das Problem löst, da sich mir die Ursache nicht erschließt.

Das ist das Template. ich habe es nur in den oberen gezeigten Klassen verändert.

cSharp - SerialPort Template

Kann jemand erklären, was da genau das Problem ist?

4.939 Beiträge seit 2008
vor 4 Jahren

Bis du sicher, daß diese Zeile stimmt:


Send[3] ^= Send[0];

?
Ich nehme an, daß in Send[3] die XOR-Verknüpfung der (vorherigen) drei Werte stehen soll.
Mit dieser ersten Zeile führst du ja zusätzlich noch eine XOR-Verknüpfung mit dem bisherigen Wert aus.
=> ändere es also zu


Send[3] = Send[0];

PS: Wir haben dir den Hinweis gegeben, da du ja selber geschrieben hast, daß du eine "3-SchichtAnwendung" schreiben möchtest.
Du erzeugst also eine weitere Klasse ("z.B. RelaisLogic), welche die Logik des Programms enthält. Bisher führst du die Logik direkt in der UI-Klasse (MainForm) aus, d.h. z.B. der Inhalt von SendCMD_Click gehört als Methode in die Logik-Klasse (und wird nur von dieser Methode aufgerufen).
Überlege dir einfach, welche Methoden kannst du übernehmen, wenn du z.B. zusätzlich noch eine Konsolenapplikation (oder ein Web-Projekt oder ...) schreiben möchtest (und diese Klassen und Methoden gehören dann in die Logik-Schicht, da diese dann einfach nur von den jeweiligen UI-Klassen benutzt werden).

T
Treter_Peter Themenstarter:in
5 Beiträge seit 2019
vor 4 Jahren

Bis du sicher, daß diese Zeile stimmt:

  
Send[3] ^= Send[0];  
  

?

Oh man, das mir das nicht aufgefallen ist 8o. Aber ja du hattest da natürlich vollkommen recht, und das war jetzt im Endeffekt auch das Problem, warum das Senden nicht sauber funktioniert hat.

Das Empfangsproblem konnte ich selber finden. Lag daran das ich die Methode ReadChar bzw. ReadByte benutzt hatte. Da das DataReceived Erreignis aber nicht Garantiert von jedem empfangenen Byte aufgerufen wird, war die Methode Read besser, nachdem alle Daten empfangen wurden. Zusätzlich hab ich das Byte direkt in einem String umgewandelt gehabt, was auch falsch war.

Ich habe doch schon eine extra Klasse für die Verabeitung, nämlich "SerialConnector" als Datenverabeitungsschicht, und "StartForm" als GUI.
Oder meinst du, ich sollte das Aufbereiten der Send-Daten auch in der "SerialConnector" Klasse machen?

Hier nochmal der Auszug dem Programm

StartForm Klasse zum eingeben und Anzeigen der Daten:


   public partial class StartForm : Form
    {
        //Schnittstelle / Variablendefinition
        private ComStructure comStructure;
        private SerialConnector serialConnector;
        private Byte[] Send = new byte[4];      // Daten zur RelaisKarte
        private string output = string.Empty;

        //Start Methode
        public StartForm()
        {
            InitializeComponent();
            comStructure = new ComStructure();
           

            // Dialog für ComPort Einstellungen öffnen
            using (ConfigDialog cfgDialog = new ConfigDialog(comStructure))
            {
                if (DialogResult.OK == cfgDialog.ShowDialog())
                {
                    this.comStructure = cfgDialog.ComStructure;
                }
                else
                {
                    MessageBox.Show("Der Dialog mit den UART-Einstellungen wurde nicht mit 'OK' bestätigt.", "WARNUNG", MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1, MessageBoxOptions.ServiceNotification);
                    return;
                }
            }

            try
            {
                this.serialConnector = new SerialConnector(this.comStructure, this);
                this.serialConnector.CreateAndOpenSerialPort();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message + ex.StackTrace, "Fehler", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1, MessageBoxOptions.ServiceNotification);
            }
            
        }

        // ComPort schließen und Programm beenden
        private void Beenden_Click(object sender, EventArgs e)
        {
            if (this.serialConnector != null)
            {
                this.serialConnector.Dispose();
            }
            Application.Exit();
        }

        private void SendCMD_Click(object sender, EventArgs e)
        {
            if (this.serialConnector != null)
            {
                Send[0] = 1;
                Send[1] = 1;
                Send[2] = 0;
                Send[3] = Send[0];
                Send[3] ^= Send[1];
                Send[3] ^= Send[2];              

                serialConnector.SendDataToSerialPort(Send);                
            }
        }
        public void SetOutPutOnBox()
        {
            //Call Method from GUI-Thread
            if (this.InfoTextArea.InvokeRequired)
            {
                this.InfoTextArea.Invoke(new MethodInvoker(this.SetOutPutOnBox));
                return;
            }

            //Display Response
            this.InfoTextArea.Text = this.output;
        }

        /// <summary>
        /// Property for OutPutText
        /// </summary>
        public string OutPutBoxText
        {
            get { return this.output; }
            set { this.output = value; }
        }
    }
}

und die Klasse "SerialConnector" zum Aufbau der ComPortverbindung und das lesen/schreiben der Daten.


    public class SerialConnector: IDisposable
    {
        //Schnittstelle / Variablendefinition
        private static readonly string breakCondition = Environment.NewLine; //CRLF
        private string dataToRecieve = string.Empty;
        private SerialPort serialPort;
        private ComStructure comStructure;
        private StartForm gui;
        private bool isDisposed;

        int AnzahlBytes;
        Byte[] Send = new byte[4];      // Daten zur RelaisKarte
        Byte[] Recived = new byte[4];   // Daten von RelaisKarte
        String RecivedData;             // Empfangene Daten in Textform


        //Start Methode
        public SerialConnector(ComStructure comStructure, StartForm gui)
        {
            this.comStructure = comStructure;
            this.gui = gui;
            AnzahlBytes = 1;
        }

        /// <summary>
        /// Clear Ressources of this instance 
        /// </summary>
        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Finalizer of SerialConnector
        /// </summary>
        ~SerialConnector()
        {
            this.Dispose(false);
        }

        /// <summary>
        /// Clear Ressources of this instance 
        /// </summary>
        /// <param name="disposed">true: Clear managed & unmanaged code.
        /// false: Clear unmanaged code only</param>
        protected virtual void Dispose(bool disposed)
        {
            if (!this.isDisposed)
            {
                if (this.serialPort != null)
                {
                    this.serialPort.Close();
                    this.serialPort.Dispose();
                }

                this.isDisposed = true;
            }
        }

        // ComPort erstellen und öffnen
        public void CreateAndOpenSerialPort()
        {
            //Dispose
            if (this.serialPort != null && this.serialPort.IsOpen)
            {
                this.serialPort.DataReceived -= this.readDataFromSerialPort;
                this.serialPort.Close();
                this.serialPort.Dispose();
            }

            //Open
            this.serialPort = new SerialPort(this.comStructure.PortName, this.comStructure.BaudRate, this.comStructure.Parity, this.comStructure.DataBits, this.comStructure.StopBits);
            this.serialPort.Handshake = this.comStructure.Handshake;
            this.serialPort.DataReceived += this.readDataFromSerialPort;
            this.serialPort.Open();
            gui.InfoTextArea.Text = "ComPort erfolgreich geöffnet." + Environment.NewLine + serialPort.PortName;
        }

        //Daten von ComPort lesen
        private void readDataFromSerialPort(object sender, SerialDataReceivedEventArgs e)
        {
            if(serialPort.BytesToRead >= 4)
            {
                serialPort.Read(Recived, 0, 4);

                RecivedData += "  Byte1: "+Convert.ToString(Convert.ToInt32(Recived[0]));
                RecivedData += "  Byte2: "+Convert.ToString(Convert.ToInt32(Recived[1]));
                RecivedData += "  Byte3: "+Convert.ToString(Convert.ToInt32(Recived[2]));
                RecivedData += "  Byte4: "+Convert.ToString(Convert.ToInt32(Recived[3]));
                RecivedData += Environment.NewLine;
                gui.OutPutBoxText = RecivedData;
                gui.SetOutPutOnBox();
                //serialPort.DiscardInBuffer();           
             }                                                                
        }

        //Daten an ComPort schreiben
        public void SendDataToSerialPort(byte[] Send)
        {
            if (serialPort.BytesToWrite == 0)
            {
                RecivedData = string.Empty;
                serialPort.DiscardInBuffer();
                serialPort.Write(Send, 0, 4);

            }else
            {
                gui.OutPutBoxText = "Fehler SendePuffer!";
                gui.SetOutPutOnBox();
            }           
        }
    }
}

zusätzlich gibt es noch die Klasse "comStructure" in dem die ComSchnittstelle beschrieben wird (BaudRate, ComPortname etc.) Diese wird beim Start durch einen "ConfigDialog" befüllt.

5.658 Beiträge seit 2006
vor 4 Jahren
  1. Bitte keine Fullquotes, siehe [Hinweis] Wie poste ich richtig?

  2. Wenn die Schichten sich gegenseitig kennen (Form kennt SerialPort und Serialport kennt Form), ist das in jedem Fall eine Schichtverletzung, siehe Bild in [Artikel] Drei-Schichten-Architektur. Da hilft es auch nicht, zusätzliche Schichten einzuführen. Wie willst du solchen Code testen?

Weeks of programming can save you hours of planning

T
Treter_Peter Themenstarter:in
5 Beiträge seit 2019
vor 4 Jahren

Ok, jetzt bin ich ganz raus.

Wenn die Logik-Schicht (z.B. SerialConnector) die Präsentationsschicht (z.B. StartForm) nicht kennt, und damit die Ergebnisse nicht mitteilen kann, wie kriege ich die denn dahin?

Hab den Artikel Drei-Schichten-Architektur, jetzt 2 mal gelesen und kapier es nicht. 🙁 🤔

Oder ist damit gemeint, das man eine 3 Schicht einfügt, die "beide" anderen kennt? Dann "komunizieren " die Präsentationsschicht und die Logikschicht jewals nur mit der 3ten, ist das richtig?

Danke für eure Gedult 😉

16.834 Beiträge seit 2008
vor 4 Jahren

Wenn die Logik-Schicht (z.B. SerialConnector) die Präsentationsschicht (z.B. StartForm) nicht kennt, und damit die Ergebnisse nicht mitteilen kann, wie kriege ich die denn dahin?

Steht im Artikel.

Du machst Dir eine Connector-Klasse, die das gesamte Datenhandling übernimmt:

  • Verbindungsaufbau
  • Verbindungsabbau
  • Ereignisbehandlung

In .NET werden Ereignisse über Events umgesetzt: Deine Connector Klasse bietet Events an, wie zB die UI oder die Logikklasse abonnieren kann.
Dadurch kennt immer nur die obere Schicht die Schnittstellen der darunter liegenden.

[Artikel] Drei-Schichten-Architektur