none
gestione seriale RRS feed

  • Domanda

  • Ciao,

    voglio gestire la seriale RS232 in modo da avere un buffer dei comandi dati e un timeout se non ricevo risposta.

    Ho implementato la gestione della seriale, e funziona, apro la porta, invio un comando e ricevo risposta dal dispositivo.

    Pero vorrei fare qualcosa di piu "evoluto", mi spiego, vorrei che il comando sucessivo venisse inviato solo se ho ricevuto risposta dal precedente, o time out nel caso che non risponda dopo x tempo. Inoltre vorrei che la lista dei comandi che voglio inviare venissero messi in una "coda": per esempio, ho 3 comandi che mando di continuo, perche devo tenere aggiornati dei dati da visualizzare. Poi voglio avere un pulsante che quando lo premo mi invii un ulteriore comando(che fa qualcosa), per fare un bel lavoro non lo devo mandare cosi', ma deve "inserirsi" in un contesto di messaggio inviato e ricevuto.

    Posto quello che ho fatto:

      public partial class Fseriale : Form
        {
            SerialPort ComPort = new SerialPort();
            internal delegate void SerialDataReceivedEventHandlerDelegate(object sender, SerialDataReceivedEventArgs e);
            internal delegate void SerialPinChangedEventHandlerDelegate(object sender, SerialPinChangedEventArgs e);
            private SerialPinChangedEventHandler SerialPinChangedEventHandler1;
            delegate void SetTextCallback(string text);
            string InputData = String.Empty;      
    
            public Fseriale()
            {
                InitializeComponent();
                this.FormBorderStyle = FormBorderStyle.FixedSingle;     // rende i bordi del form non spostabili
                this.MaximizeBox = false;                               // toglie il pulsante massimize dal form
    
                SerialPinChangedEventHandler1 = new SerialPinChangedEventHandler(PinChanged);
                ComPort.DataReceived += new System.IO.Ports.SerialDataReceivedEventHandler(port_DataReceived_1);
            }
    
            private void bcercaSeriali_Click(object sender, EventArgs e)
            {
    
                string[] ArrayComPortsNames = null;
                int index = -1;
                string ComPortName = null;
    
                try
                {
                    ArrayComPortsNames = SerialPort.GetPortNames();
                    do
                    {
                        index += 1;
                        cbCOM.Items.Add(ArrayComPortsNames[index]);
                    }
                    while (!((ArrayComPortsNames[index] == ComPortName) || (index == ArrayComPortsNames.GetUpperBound(0))));
     
                    Array.Sort(ArrayComPortsNames);
    
                    cbCOM.Items.Clear();
                    int a = 0;
                    do
                    {
                        cbCOM.Items.Add(ArrayComPortsNames[a]);
                        index--;
                        a++;
                    }
                    while (!(index == -1));
    
                cbCOM.Text = cbCOM.Items[0].ToString();
                //Baud Rate
                cbBAUD.Items.Add(300);
                cbBAUD.Items.Add(600);
                cbBAUD.Items.Add(1200);
                cbBAUD.Items.Add(2400);
                cbBAUD.Items.Add(9600);
                cbBAUD.Items.Add(14400);
                cbBAUD.Items.Add(19200);
                cbBAUD.Items.Add(38400);
                cbBAUD.Items.Add(57600);
                cbBAUD.Items.Add(115200);
                cbBAUD.Items.ToString();
                //get first item print in text
                cbBAUD.Text = cbBAUD.Items[4].ToString();
                //Data Bits
                cbBIT.Items.Add(7);
                cbBIT.Items.Add(8);
                //get the first item print it in the text 
                cbBIT.Text = cbBIT.Items[1].ToString();
    
                //Stop Bits
                cbSTOP.Items.Add("One");
                cbSTOP.Items.Add("OnePointFive");
                cbSTOP.Items.Add("Two");
                //get the first item print in the text
                cbSTOP.Text = cbSTOP.Items[0].ToString();
                //Parity 
                cbPARITY.Items.Add("None");
                cbPARITY.Items.Add("Even");
                cbPARITY.Items.Add("Mark");
                cbPARITY.Items.Add("Odd");
                cbPARITY.Items.Add("Space");
                //get the first item print in the text
                cbPARITY.Text = cbPARITY.Items[0].ToString();
                //Handshake
                cbHAND.Items.Add("None");
                cbHAND.Items.Add("XOnXOff");
                cbHAND.Items.Add("RequestToSend");
                cbHAND.Items.Add("RequestToSendXOnXOff");
                //get the first item print it in the text 
                cbHAND.Text = cbHAND.Items[0].ToString();
                }
                catch
                {
                    MessageBox.Show("Nessuna seriale disponibile  !");
                }
            }
    
    
            private void bopenPort_Click(object sender, EventArgs e)
            {
                try
                {
                    if (bopenPort.Text == "Closed" && cbCOM.Text!= "")
                    {
                        bopenPort.Text = "Open";
                        ComPort.PortName = Convert.ToString(cbCOM.Text);
                        ComPort.BaudRate = Convert.ToInt32(cbBAUD.Text);
                        ComPort.DataBits = Convert.ToInt16(cbBIT.Text);
                        ComPort.StopBits = (StopBits)Enum.Parse(typeof(StopBits), cbSTOP.Text);
                        ComPort.Handshake = (Handshake)Enum.Parse(typeof(Handshake), cbHAND.Text);
                        ComPort.Parity = (Parity)Enum.Parse(typeof(Parity), cbPARITY.Text);
                        ComPort.Open();
                    }
                    else if (bopenPort.Text == "Open")
                    {
                        bopenPort.Text = "Closed";
                        ComPort.Close();
                    }
                    // Set the read/write timeouts
                    ComPort.ReadTimeout = 100;
                    ComPort.WriteTimeout = 100;
                }
                catch
                {
                    MessageBox.Show("Nessuna seriale selezionata");
                }
            }
    
            private void button2_Click(object sender, EventArgs e)
            {
                try
                {
                    ComPort.Write("*0001RD\r\n");
                }
                catch
                {
                    MessageBox.Show("Nessuna seriale selezionata");
                }
            }
    
    
            private void port_DataReceived_1(object sender, SerialDataReceivedEventArgs e)
            {
                InputData = ComPort.ReadExisting();
                if (InputData != String.Empty)
                {
                    this.BeginInvoke(new SetTextCallback(SetText), new object[] { InputData });
                }
            }

    martedì 7 ottobre 2014 11:36

Risposte

  • Ciao,

    non è una cosa semplicissima, ma è fattibile, eventualmente ho già del codice sviluppato comprensivo di documentazione (help in linea) ma eviterei di postarlo perché se no non è una vera e propria risposta ma più una "fornitura".

    Partendo dal presupposto che il lavoro da fare è abbastanza "grosso" (in realtà relativamente) direi che è il caso di creare una dll separata e non usare il form direttamente, questo ti consentirebbe di poter riutilizzare il codice. Inoltre dovrai gestire l'apertura e chiusura (e relative risposte) della seriale tramite una classe centralizzata che sarà l'unica a fare da tramite tra l'applicazione che usa la dll e la seriale stessa.

    La prima classe la chiamiamo "Switch" e sarà statica, essa avrà il compito di raccogliere le richieste verso la seriale eseguendo dei comandi, ma senza comunicare direttamente col la seriale, in pratica gestisce la coda delle richieste fungendo però solo da semaforo e gestendo l'arrivo e l'uscita dei "comandi" progressivi tramite un thread che esegue i comandi (vedi successivamente) uno dietro l'altro, oltre a gestire anche la porta (tramite incapsulamento della classe successiva) sulla quale inviare i comandi.

    La seconda invece dovrà ereditare dalla SerialPort, essa avrà il compito di attivare un thread in lettura continua sulla porta, questo ovviamente sarà attivato dopo una eventuale scrittura sulla porta del comando, in pratica riceve il comando da scrivere sulla seriale ed in base alla configurazione di una classe che vedremo dopo, procederà ad attivare (oppure no) il thread in lettura, che a sua volta sulla base della configurazione data, agirà in un modo oppure l'altro. Terminata questa attività (gestita praticamente dalla configurazione che vedremo in seguito) il suo scopo cesserà d'esistere liberando la risorsa (la porta).

    La classe di configurazione invece avrà il compito di inglobare il lavoro da fare sulla seriale, questo sarà possibile grazie ad un sistema a piramide che vede al suo vertice il comando da scrivere sulla seriale, una volta scritto il "comando" la classe, che dovrà essere provvista di più liste di classi uguali a se stessa, selezionerà da un elenco di possibili risposte (magari generate da pattern) quella data dalla seriale, quindi essendo un tipo uguale a se stesso fa ripartire il ciclo, ovvero continua fin tanto che ci sarà una scrittura/lettura o quando il comando selezionato sarà escape (nello stesso modo gestisci anche gli errori).

    A questo punto dovrai dapprima inserire una lista di porte nello switch (quindi una derivata da SerialPort per ogni device collegato alla corrispondente porta seriale che vorrai gestire), dopo di che la classe rimane in attesa dei command (quelli che inizialmente ho chiamato configurazioni).

    Creo un command con scrittura di avvio, ad esempio : AT+\n, a questo punto creo una lista (sempre di tipo uguale a questo) che contiene un elenco di possibili altri comandi corrispondenti alle possibili risposte dalla seriale, ad esempio : OK+\n, ERROR\n ecc.. In questo caso entrambi i comandi (che saranno scelti in base all'uguaglianza della risposta dalla seriale) avranno un comando di escape (che non necessariamente deve essere ritrasmesso alla seriale, potrebbe semplicemente interrompere il ciclo) che terminerà tutto, in altri casi potrebbe continuare a più livelli, quindi ci sarà scrittura->avvio lettura->ricezione lettura->scelta comando successivo(o uscita)->scrittura nuovo comando-> (e rincomincia il ciclo).

    questo a grandi linee. poi magari approfondiamo.

    mercoledì 8 ottobre 2014 13:51

Tutte le risposte

  • Ciao Marco,
    Sto cercando di darti una mano, ma fino al momento non sono riuscito a trovare la soluzione. Spero che qualcuno con più esperienza dia un suggerimento, cosi capiremo come viene inserito il comando. 

    Microsoft offre questo servizio gratuitamente, per aiutare gli utenti e aumentare il database dei prodotti e delle tecnologie. Il contenuto viene fornito “così come è” e non comporta alcuna responsabilità da parte dell'azienda.

    mercoledì 8 ottobre 2014 07:32
  • Ciao,

    non è una cosa semplicissima, ma è fattibile, eventualmente ho già del codice sviluppato comprensivo di documentazione (help in linea) ma eviterei di postarlo perché se no non è una vera e propria risposta ma più una "fornitura".

    Partendo dal presupposto che il lavoro da fare è abbastanza "grosso" (in realtà relativamente) direi che è il caso di creare una dll separata e non usare il form direttamente, questo ti consentirebbe di poter riutilizzare il codice. Inoltre dovrai gestire l'apertura e chiusura (e relative risposte) della seriale tramite una classe centralizzata che sarà l'unica a fare da tramite tra l'applicazione che usa la dll e la seriale stessa.

    La prima classe la chiamiamo "Switch" e sarà statica, essa avrà il compito di raccogliere le richieste verso la seriale eseguendo dei comandi, ma senza comunicare direttamente col la seriale, in pratica gestisce la coda delle richieste fungendo però solo da semaforo e gestendo l'arrivo e l'uscita dei "comandi" progressivi tramite un thread che esegue i comandi (vedi successivamente) uno dietro l'altro, oltre a gestire anche la porta (tramite incapsulamento della classe successiva) sulla quale inviare i comandi.

    La seconda invece dovrà ereditare dalla SerialPort, essa avrà il compito di attivare un thread in lettura continua sulla porta, questo ovviamente sarà attivato dopo una eventuale scrittura sulla porta del comando, in pratica riceve il comando da scrivere sulla seriale ed in base alla configurazione di una classe che vedremo dopo, procederà ad attivare (oppure no) il thread in lettura, che a sua volta sulla base della configurazione data, agirà in un modo oppure l'altro. Terminata questa attività (gestita praticamente dalla configurazione che vedremo in seguito) il suo scopo cesserà d'esistere liberando la risorsa (la porta).

    La classe di configurazione invece avrà il compito di inglobare il lavoro da fare sulla seriale, questo sarà possibile grazie ad un sistema a piramide che vede al suo vertice il comando da scrivere sulla seriale, una volta scritto il "comando" la classe, che dovrà essere provvista di più liste di classi uguali a se stessa, selezionerà da un elenco di possibili risposte (magari generate da pattern) quella data dalla seriale, quindi essendo un tipo uguale a se stesso fa ripartire il ciclo, ovvero continua fin tanto che ci sarà una scrittura/lettura o quando il comando selezionato sarà escape (nello stesso modo gestisci anche gli errori).

    A questo punto dovrai dapprima inserire una lista di porte nello switch (quindi una derivata da SerialPort per ogni device collegato alla corrispondente porta seriale che vorrai gestire), dopo di che la classe rimane in attesa dei command (quelli che inizialmente ho chiamato configurazioni).

    Creo un command con scrittura di avvio, ad esempio : AT+\n, a questo punto creo una lista (sempre di tipo uguale a questo) che contiene un elenco di possibili altri comandi corrispondenti alle possibili risposte dalla seriale, ad esempio : OK+\n, ERROR\n ecc.. In questo caso entrambi i comandi (che saranno scelti in base all'uguaglianza della risposta dalla seriale) avranno un comando di escape (che non necessariamente deve essere ritrasmesso alla seriale, potrebbe semplicemente interrompere il ciclo) che terminerà tutto, in altri casi potrebbe continuare a più livelli, quindi ci sarà scrittura->avvio lettura->ricezione lettura->scelta comando successivo(o uscita)->scrittura nuovo comando-> (e rincomincia il ciclo).

    questo a grandi linee. poi magari approfondiamo.

    mercoledì 8 ottobre 2014 13:51
  • Non credo di avere capito tutto quello che mi hai detto.....

    Forse mi basta una cosa piu semplice, tipo un oggetto queue, dove inserisco i pacchetti che voglio trasmettere, poi sto sempre in ricezione, e se scade il tempo dopo che ho spedito il pacchetto do il timeout, se tutto bene do il sucessivo pacchetto.

    non so..

    *a146464646/r/n

    *a246464646/r/n

    *a346464646/r/n

    ecc

    mercoledì 8 ottobre 2014 15:17
  • Si, in realtà è un po quello che ti ho detto, solo che è organizzato diversamente, ed al fatto che sia necessario creare un infrastruttura di un certo tipo intorno ci sono arrivato quando mi sono trovato a dover affrontare le diverse problematiche legate ad una struttura fissa (quindi senza infrastruttura) prevista già nel codice (in pratica giochini di if che sarai costretto a scrivere e ripetere spesso per prevedere le risposte). Poi magari esistono modi migliori, ma per il momento il mio ha sempre fatto il suo dovere. Tra l'altro l'infrastruttura di cui ti parlo è una specie di automazione degli "if" e gestione automatica dei thread di lavoro, in modo da non dover aspettare la risposta sulla porta "freezzando" l'interfaccia e potendo accodare comandi senza preoccuparsi di aspettare la risposta a quelli precedenti, infatti quelli precedenti una volta elaborati chiameranno i metodi che hai impostato tu tramite configurazione. Un pò come dire: ho finito di elaborare questa scrittura, che faccio? oppure questo è il risultato della lettura, o ancora questo è il risultato di tutto il comando (globale con tutte le sue scritture e letture).

    In sostanza comunque si tratta sempre di scrivere->leggere->reagire, oppure leggere->reagire->scrivere-> (non puoi fare le due cose insieme, devi alternarle), l'importante che ogni passaggio sia gestito singolarmente e siano previste tutte le sue variabili (compreso l'errore), perché diversamente potresti trovarti bloccato con un comando a metà. Immagina se la seriale sta attendendo una certa risposta ad una procedura già iniziata, ma magari c'è stato un errore (per errore intendo anche una risposta di errore dal device, non solo errori sulla porta) e il device rimane nel limbo, i comandi successivi andrebbero a "schifiu", quindi per ogni riga immessa direttamente dovresti prevedere anche l'errore potenziale con degli if annidati in altri if che a loro volta magari stanno in un altra istruzione switch ecc. alla fine credimi che ti perderesti.

    mercoledì 8 ottobre 2014 17:03