» »

[C#] problem z branjem COM porta

[C#] problem z branjem COM porta

iNN ::

Pozdravljeni!!

Pišem neko aplikacijo, ki naj bi iz seriskega porta pobrala string ki pride vsako sekundo, iz njega pobrala prve tri številke ločene z vejico, jih zapisala v tabelo in mimogrede še v nek textbox na glavnem obrazcu. Stvar zaenkrat na pol deluje tako, da ob dogodku serialdatareceived se sprozi procedura ki naredi zgoraj opisano. Sedaj pa prob:

Stringi ki jih naprava pošilja na drugi strani so približno take oblike: "49.932 ,49.937 ,49.908 , 0.00m, 0.00m, 0.00m,P- 0.00m,+ 0.00m,- 0.00m, 0.00m, 0.00m,50.0,0.00,0.00,0.00,0.00,E& --.-- ,N= ,D* "

Mene zanimajo samo prve 3 vrednosti. Te mi uspe ločiti in zapisati v tabelo ter texbox, vendar se iz meni neznanega razloga aplikacija obesi pri npr. 3 ali 4 ali 5 prejetem stringu.

Koda izgleda takole
        void ComPort2_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
        {
            this.SetText(SetPort.ComPort2.ReadExisting());
        }
        private void SetText(string str_temp_rec)
        {
            if (this.tbtempstatus.InvokeRequired)
            {
                SetTextCallback d = new SetTextCallback(SetText);
                this.Invoke(d, new object[] { str_temp_rec });
            }
            else
            {                
                string[] parts = str_temp_rec.Split(',');
                tabU1[i] = float.Parse(parts[0]);
                tabU2[i] = float.Parse(parts[1]);
                tabU3[i] = float.Parse(parts[2]);
                tbtempstatus.AppendText(System.Convert.ToString("U1 :"+tabU1[i]+"\r\n"));
                tbtempstatus.AppendText("-----------------------------\r\n");
                tbtempstatus.AppendText(System.Convert.ToString("U2 :"+tabU2[i]+"\r\n"));
                tbtempstatus.AppendText("-----------------------------\r\n");
                tbtempstatus.AppendText(System.Convert.ToString("U3 :"+tabU3[i]+"\r\n"));
                tbtempstatus.AppendText("-----------------------------\r\n");
                i++;
                if (i > 10)
                {
                    i = 0;
                }               
            }
        }    


Se pravi, zadeva vedno uspešno zapiše prvi kdaj tudi drugi in tretji string, vendar se potem obesi. Vedno ko se app obesi str_temp_rec nima vrednosti celotnega stringa, npr. njegova vrednost = (, 0.00m,50.0,0.00,0.00,0.00,0.00,E& --.-- ,N= ,D* ) ceprav sem 100% da je bil poslan celoten string.

error ki ga vrne debugger:
System.FormatException was unhandled
Message="Input string was not in a correct format."

Vnaprej hvala za pomoč!
==
  • spremenilo: snow ()

iNN ::

Še en update, zadaj laufam še virtual serial port ki mi poveže COM1 in COM2. Poizkusil sem tako da sem na nek gumb obesil proceduro ki mi na COM1 vpiše zgornji string. In namesto da se ob prejetju stringa na COM2 izvede celotna else veja ki je vidna zgoraj se samo pojavi messagebox. Opazil sem da se kdaj za en poslani string messagebox izpiše dvakrat.
==

iNN ::

Zadevo sem rešil tako, da sem pred vpisovanjem v tabelo in texbox dodal preverjanje stringa, če je le ta pričakovanega formata. Še vedno pa nevem zakaj se je pojavljala prejšnja napaka :)
==

[SkA] ::

Glede na to kar opisuješ bi rekel, da prebriaš podatke še preden so vsi prišli. Datarecive event se zgodi, takoj ko pride prvi znak, kar pa še ne pomeni, da si dobil celoten ukaz.

Če ti je pomembno, da obdelaš vse prejete podatke, potem premisli o kakem flow controlu, da ti naprava ne bo zabasala buffer.
Če ti je pomembno da zgolj ujameš podatke vsake toliko časa, potem flow controla ne rabiš, boš pa verjetno moral malce izboljšati svojo "parsing" funkcijo.

Če imaš možnost da naprava na koncu "serije podatkov" pošlje še LF, potem ga nastavi in uporabljaj funkcijo Readline (seveda z ustreznim timeoutom). Tako boš znotraj Datarecieve vedno obdelal 1 ukaz. Slednje je seveda možno narediti tudi s poljubnim kontrolnim znakom, če naprava omogoča kakšno nastavljanje. Oziroma, lahko da že ima kakšen niz znakov na koncu ukaza, po katerem lahko preverjaš, da si iz bufferja res prebral celoten ukaz. Pa ne pozabi, da imaš lahko v bufferju že celoten ukaz in tudi del novega.

Zgodovina sprememb…

  • spremenil: [SkA] ()

darkolord ::

DataReceived se sproži glede na ReceivedBytesThreshold. Tam nastaviš, koliko bajtov mora biti v input bufferju, preden se dogodek sproži.

iNN ::

Zdravo!

Mislil sem že da sem problemu prišel do dna vendar se je izkazalo da sem se motil. Nastavil sem ReceivedBytesThreshold na velikost stringa. In vse je delovalo vredu, če sem ob zagonu app sprožil pošiljanje stringov iz povezane naprave. Zakomplicira se ko zaženem aplikacijo in naprava že pošilja stringe, z odprtjem portov takoj prejmem neko vsebino v buffer, le ta pa je lahko kateri koli del stringa. In če pobiram po npr. 150 bytov nikoli ne uspem uskladiti prvega znaka z "prvim".

Nato sem poizkusil tako, da bi ob prejetju stringa iskal znak za novo vrstico in se orientiral po njem, nato bi še preveril če se string začne z določenimi tremi znaki, ki predstavljajo ukaz, in glede na ukaz bi potem sledeče vrednosti zapisal v določeno tabelo.

Z tole kodo sem poizkušal prebrati celoten string iz buferja, ga preveriti, razbiti in zapisati v tabelo in textbox.
void ComPort_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
        {           
            this.SetText(SetPort.ComPort.ReadExisting());
        }

        private void SetText(string sText)
        {
            if (this.tbtempstatus.InvokeRequired)
            {
                SetTextCallback d = new SetTextCallback(SetText);
                this.Invoke(d, new object[] { sText });
            }
            else
            {
                if (true == sText.Contains("\r"))
                {
                    sText = sText.Replace("\r", "\n\r");
                    foreach (string sItem in Regex.Split(sText, "\r"))
                    {
                        if ((sItem != "") && (sItem.Contains("\n")) && (sItem.StartsWith("STF")))
                        {
                            if (i < 10)
                            {                                    
                                string[] parts = sItem.Split(',');
                                tabU1[i] = float.Parse(parts[0]);
                                tabU2[i] = float.Parse(parts[1]);
                                tabU3[i] = float.Parse(parts[2]);
                                tbtempstatus.Clear();
                                tbtempstatus.AppendText(System.Convert.ToString("U1 :" + tabU1[i] + "\r\n"));
                                tbtempstatus.AppendText("-----------------------------\r\n");
                                tbtempstatus.AppendText(System.Convert.ToString("U2 :" + tabU2[i] + "\r\n"));
                                tbtempstatus.AppendText("-----------------------------\r\n");
                                tbtempstatus.AppendText(System.Convert.ToString("U3 :" + tabU3[i] + "\r\n"));
                                tbtempstatus.AppendText("-----------------------------\r\n");
                                i++;
                            }
                            else
                            {
                                i = 0;
                                temp_prebran = true;
                            }                           
                        }               
                    }
                }                
            }
        }


Zatakne se pri stavku kjer preverjam "startswith". Ker brez tega ... koda:
        private void SetText(string sText)
        {
            if (this.tbtempstatus.InvokeRequired)
            {
                SetTextCallback d = new SetTextCallback(SetText);
                this.Invoke(d, new object[] { sText });
            }
            else
            {
                if (true == sText.Contains("\r"))
                {
                    sText = sText.Replace("\r", "\n\r");
                    foreach (string sItem in Regex.Split(sText, "\r"))
                    {
                        if ((sItem != "") && (sItem.Contains("\n")))
                        {
                            tbtempstatus.AppendText(sItem.Replace("\n", Environment.NewLine));
                        }
                        else tbtempstatus.AppendText(sItem);
                    }
                }
                else tbtempstatus.AppendText(sText);
            }
        }

...stvar deluje. Vendar moram prepoznati prve tri znake, ker kot sem omenil bom potem razvrščal nekatere vrednosti v tabele glede na ukaz-(prvi trije znaki).

Pri debugingu ima v prvem primeru spremenljivka sItem ponavadi vrednost "\r" ali " ". V drugem (spodnjem) primeru pa je njena vrednost tisti pravi string katerega bi želel v prvem primeru.

Vnaprej hvala za pomoč in nasvete.
==

[SkA] ::

Parkrat sem šel že čez, pa moram priznati, da sem trenutno očitno preutrujen, tako da vzami moj komentar z rezervo (možno da sem kaj banalnega spregledal).

Torej greva kar po vrsti. Ko se tvoj buffer napolni z X znaki, prebereš natanko X + nekaj znakov. Se ti ne zdi, da to morda ni najbolj primerno? Tebi se event sicer zgodi, ko pride ustrezna dolžina v buffer, ampak nikjer ne piše, da ni še nekaj zraven. Kot si sam ugotovil že v prvem primeru, boš imel težave s sinhronizacijo.

Nevem kakšna naprava je na drugi strani ampak, če se da nastavi da pošilja "carrage return" in "newline" ter uproabi readline (Serialport.readline). Readline bo vseeno drugače deloval - blokiral bo nadaljevanje funkcije, dokler ne bo prebral znaka Environment.NewLine, da je prišel konec stringa. To sicer ne pomeni nujno, da je tudi začetek prišel v celoti, ampak vsaj končalo se je kjer bi se moralo in tako nimaš dodatnih potencialnih znakov naslednjega stringa.

Če naprava tega ne podpira, potem predlagam da popraviš svojo funkcijo za branje. Naredi nek začasni buffer (char array, byte array.. lahko tudi kar string, kar ti bolj paše/ustreza) in beri znak po znaku takoj ko pride (bereš dokler buffer ni prazen, potem pa čakaš na nov event). Ko zaznaš znak "\r", sproži obdelavo tega kar imaš v svojem programskem bufferju, programski buffer pa sprazni in nadaljuj z branjen znak po znaku. To je druga varjanta, čeprav tista prva z readline je hitreje izdelana. Pri prvi vstaviš še readtimeout, da ti slučajno zaradi kakšnih težav ne zablokira celega postopka in je zadeva končana. Tu je pač malce več programiranja.

To kar se ti sedaj dogaja, je morda posledica tega, da bereš sicer ustrezno število znakov, ampak nikjer ne piše, da ne bereš prvih X znakov prvega stringa in Y znakov drugega stringa. Vse kar ti preverjaš je, če string vsebuje znak \r, ne pa tudi kje se slednji nahaja. Jasno da pri tem izgubljaš kup podatkov, prav tako pa se ti bo zelo hitro zgodilo, da boš obdeloval string, ki bo sicer vseboval začetne pričakovane in iskane znake, ne pa tudi vsega do konca. Verjetno se ti tu zalomi seckanje.

Nenazadnje - nisem zaenkrat še veliko delal z regular expressioni, čeprav vem da bi jih lahko precej večkrat uporabljal in čemu služijo. Anyway, si prepričan da tisti regular expression split deluje tako kot misliš, da deluje? Mogoče raje uporabi kar string.split.

PS: sicer osebna preferenca, ampak "if (true == sText.Contains("\r"))" lahko zapišeš tudi kot "if (sText.Contains("\r"))". Meni je slednje bolj berljivo. :)

Zgodovina sprememb…

  • spremenil: [SkA] ()

iNN ::

Zdravo!

Najlepsa hvala za nasvet,sedaj tale koda deluje:
        void ComPort_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
        {
            this.SetText(SetPort.ComPort.ReadExisting());
        }
       
        private void SetText(string sText)
        {
            if (this.tbtempstatus.InvokeRequired)
            {
                SetTextCallback d = new SetTextCallback(SetText);
                this.Invoke(d, new object[] { sText });
            }
            else
            {
                baffa = baffa + sText;
                if (baffa.Contains("\r"))
                {
                    if (baffa.StartsWith("STF"))
                    {
                        if (i < 10)
                        {
                            baffa = baffa.Remove(0, 4);
                            string[] parts = baffa.Split(',');
                            tabU1[i] = float.Parse(parts[0]);
                            tabU2[i] = float.Parse(parts[1]);
                            tabU3[i] = float.Parse(parts[2]);
                            tbtempstatus.Clear();
                            tbtempstatus.AppendText(System.Convert.ToString("U1 :" + tabU1[i] + "\r\n"));
                            tbtempstatus.AppendText("-----------------------------\r\n");
                            tbtempstatus.AppendText(System.Convert.ToString("U2 :" + tabU2[i] + "\r\n"));
                            tbtempstatus.AppendText("-----------------------------\r\n");
                            tbtempstatus.AppendText(System.Convert.ToString("U3 :" + tabU3[i] + "\r\n"));
                            tbtempstatus.AppendText("-----------------------------\r\n");
                            i++;
                        }
                        else
                        {
                            i = 0;
                            temp_prebran = true;
                        }  
                        
                    }
                    else
                    {           
                        baffa = "";
                    }
                }

            }
   
        }


Edino kar je, je to da spuščam podatke, predvidevam da zaradi predolge obdelave v zadnjem if stavku, v bistvu mi je zaenkrat vseeno, ker ne rabim sprocesirati vseh.
==


Vredno ogleda ...

TemaSporočilaOglediZadnje sporočilo
TemaSporočilaOglediZadnje sporočilo
»

c# seminarska

Oddelek: Programiranje
112426 (2099) tuned
»

[VB.NET] class

Oddelek: Programiranje
8642 (549) korenje3
»

Java pomoč

Oddelek: Programiranje
131657 (1436) Serial
»

pošiljanje SMS C#

Oddelek: Programiranje
132979 (2472) Neon87
»

[VB] Komunikacija s serijskimi napravami

Oddelek: Programiranje
412221 (1495) mNeRo

Več podobnih tem