+ Rispondi al messaggio
Visualizzazione dei risultati da 1 a 7 su 7

[VB2010] Gestione thread e porta seriale

  1. #1
    mynos non è in linea Novello
    Salve a tutti, sono nuovo del forum e questo è il mio primo topic di richiesta aiuto/consiglio.
    Ho spulciato molto del forum da alcune settimane a questa parte.
    Provengo da programmazione abbastanza avanzata in VB6, ho tralasciato purtroppo lo studio ed il passaggio a .NET per motivi di lavoro in quanto da diversi anni programmo in assembly per alcuni microprocessori ST.
    Ora mi trovo a dover creare un software per la gestione di un impianto di un cliente (era l'occasione buona per studiare per bene il .NET, ma ahimè il tempo è davvero tiranno).
    Questo software deve interrogare tramite seriale 485 alcune apparecchiature elettroniche (di nostra produzione) presenti in un impianto.

    Il protocollo di comunicazione è davvero semplice:
    - trasmetto un comando di richiesta dati tramite una stringa contenente appunto il comando, alcuni dati ed il codice del dispositivo (usato dal dispositivo, sempre in ricezione, per capire che la stringa è proprio per lui)
    - il dispositivo, se riceve la stringa col suo codice, risponde sempre con una stessa stringa contenente tutti suoi parametri. Stringa di risposta che, ahime, non contiene il codice del dispositivo.
    - se entro tot tempo (300mS) non ricevo nulla, allora considero un errore di comunicazione e procedo a trasmettere al dispositivo successivo, altrimenti so che si tratta della risposta di quel dispositivo.
    - via dicendo così, fino a spazzolare tutti i dispositivi e tornare al primo

    Ogni volta che incontro un errore di comunicazione, ne tengo conto e proseguo al successivo ... quando arrivo a 6 errori, allora segnalo l'errore per quel dispositivo e proseguo sempre in modo sequenziale a interrogare i dispositivi.

    Oltre al "loop" di trasmissione per la richiesta dati, l'utente può inviare altri comandi di start/stop/etc ... questi comandi andranno ad inserirsi nella sequenza del loop di richiesta dati.

    Dico ahime per quanto riguarda il protocollo, in quanto manca il codice dispositivo nella stringa di risposta. Questo perchè è un protocollo inventato quasi 13-14 anni fa per altri dispositivi di altri produttori ed io per mantenere la compatibilità delle nostre apparecchiature con i dispositivi vecchi ho dovuto continuare ad usarlo (purtroppo i clienti a livello industriale non cambiano le apparecchiature vecchie).

    La gestione delle apparecchiature è collaudata da anni, tramite timer vari in assembly ed interrupt vari ho fatto temporizzare tutto e sincronizzare tutto.

    Il problema è ora. Vengo al programma.
    Ho fatto tutto, user control per ricreare i vari dispositivi e visualizzarne in tempo reale i dati, datagrid per i database delle varie configurazioni, chart per i grafici e lo storico dei dati ... manca la cosa più importante
    La gestione della seriale, in VB6 era un po diversa rispetto a .NET e qui mi trovo un po spaesato, soprattutto con l'ingresso dei threads. Li ho un po capiti anche grazie ad alcuni tutorial e manuali che sto leggendo a tempo di record. Ma un conto sono gli esempi semplici dei tutorial (che fanno sembrare sempre tutto facile e bellissimo), un altro è entrare in qualcosa di un po più complicato (almeno per me lo è, avendo iniziato da poco e soprattutto di botto con il .NET).
    Il problema qual è? che non so bene come fare.
    Ho fatto la gestione della seriale, ma sbaglio qualcosa ... in quanto la spazzolata di richiesta dati verso i dispositivi funziona (alla fine trasmetto e ricevo) ... ma quando durante la spazzolata invio un comando tramite uno dei pulsanti, viene accettato e capito dal dispositivo, ma non sincronizzo bene la trasmissione con la ricezione perchè per qualche attimo visualizzo la risposta di altre apparecchiature che rispondono al loop e di conseguenza visualizzo nel mio software cose che non vanno ... che poi tornano al loro posto in quanto con la successiva spazzolata rivisualizzo tutto correttamente.

    Venendo al codice, io ho fatto così.
    La ricezione della seriale è gestita dall'evento DataReceived della seriale, che tramite un delegate mi salva e gestisce poi la stringa ricevuta.
    La trasmissione invece la faccio in un thread di nome TX, che metto in pausa per 300mS dopo ogni invio per aspettare una risposta e che sospendo e riprendo ogni volta che devo inviare un comando differente dalla richiesta del loop (almeno credo di mettere in pausa il thread TX).

    Imports System
    Imports System.ComponentModel
    Imports System.Threading
    Imports System.IO
    Imports System.IO.Ports
    
    
    Public Class Form1
    
    
    Delegate Sub SetTextCallback(ByVal text As String)
    Dim TX As Thread    ' TX: thread usato per la trasmissione
    
    
    Private Sub Form1_Load(sender As Object, e As System.EventArgs) Handles Me.Load
    
    '
    ' configuro il controllo seriale SerialPort1
    '
    
    TX = New Thread(AddressOf trasmissione)    ' trasmissione è la sub che fa il loop di rischiesta dati
    
    '
    ' codice vario per creare e visualizzare i vari user control, gestione database, etc
    '
    
    SerialPort1.Open()
    
    TX.IsBackground = True
    TX.Start()
    
    End Sub
    
    
    ' SUB usata per la trasmissione della richiesta dati. In pratica effettua la spazzolata di richieste sui dispositivi
    ' (dispositivi messi in un array a cui accedo ciclicamente tramite un contatore)
    Public Sub trasmissione()
       Do
          TX_richiesta = ' creo la stringa (richiesta dati) da trasmettere al dispositivo (indicato dal contatore CONT)
                         ' e in base a parametri vari
          SerialPort1.Write(TX_richiesta)   ' invio la stringa
          Thread.Sleep(300)  ' metto in pausa il thread di trasmissione (è il tempo tra una trasmissione e l'altra)
    
          ' incremento il contatore dei dispositivi
          If cont = numeroDispositivi Then
             cont = 0
          Else
             cont += 1
          End If
       Loop
    End Sub
    
    
    ' evento DataReceived del controllo seriale: esegue la SUB salvaBufferRicezione sulla stringa ricevuta finchè trova il carattere return
    Private Sub SerialPort1_DataReceived(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles
    
    SerialPort1.DataReceived
       salvaBufferRicezione(SerialPort1.ReadTo(vbCr))
    End Sub
    
    
    'SUB salvaBufferRicezione che usa invoke/begininvoke ed il delegate SetTextCallback
    Private Sub salvaBufferRicezione(ByVal text As String)
       If Me.TextBox1.InvokeRequired Then
          Dim x As New SetTextCallback(AddressOf salvaBufferRicezione)
          Me.BeginInvoke(x, New Object() {text})
       Else
          Me.TextBox1.Text = text
    
          ' ho usato una textbox nascosta per inserire la ricezione
          ' in quanto con questo invoke/begininvoke non so passare direttamente a una variabile
    
          
          ' codice per l'analisi della risposta e visualizzazione dati
    
       End If
    End Sub
    
    
    ' SUB richiamata dall'evento click di uno dei pulsanti dell'user control
    ' qui dovrei inviare una stringa di comando ad un determinato dispositivo
    ' (la cosa migliore sarebbe inserire la trasmissione all'interno della spazzolata di richiesta dati, tra una richiesta e l'altra
    ' quindi pensavo di sospendere il thread di di trasmissione, inviare il comando, e riprendere il thread della spazzolata)
    ' Il dispositivo risponde con la stessa stringa di richiesta dati anche a qualsiasi comando inviatogli
    Private Sub btnStart_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
            ' codice vario per determinare quale button dei vari user control ha generato l'evento
    
            TX.Suspend()
            TX_comando = ' creo la stringa per il comando e dispositivo selezionati
            SerialPort1.Write(TX_comando)
            Thread.Sleep(300)
            TX.Resume()
    End Sub
    
    Sicuramente sbaglio a gestire (e innanzitutto a capire) la gestione dei thread e di quel delegate (trovato in molti esempi di seriale) ... sbaglio pure a voler concepire il ragionamento come un'operazione sequenziale come faccio con i miei programmi in assembly.
    Durante gli esempi ed esercizi dei tutorial, ho usato senza problemi thread, backgroundworker, monitor ... ma non riesco a capire come integrarli e farne uso nella mia situazione.
    Disperato e purtroppo non concentrato sull'argomento causa lavoro (non ho solo questo software da fare), chiedo gentilmente qualche consiglio ... la voglia di imparare è tanta, quella di terminare questo lavoro ancor di più (che avere il fiato sul collo dei clienti mette un'ansia ....).
    Ringrazio,
    Vadiliano

  2. #2
    Suspend e Resume sono metodi deprecati.

    Potresti inserire i messaggi "extra" al'interno dello stesso loop principale, sincronizzando l'accesso ad una coda (queue) tramite l'istruzione SyncLock

    L'idea è quella di "accodare" i messaggi, ad una coda di messaggi, e durante il loop verificare la presenza di nuovi messaggi, che eventualmente verranno inviati subito.

    Per sincronizzare gli accessi di lettura/scrittura della coda, come detto precedentemente, ti puoi avvalere di SyncLock, per cui potresti fare:

    Creare la tua coda di messaggi:
    Private MessagesQueue as New Queue
    
    Nell'Evento Click, richiamato dagli UserControls, aggiungere un nuovo messaggio alla coda:
    Private Sub btnStart_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
            ' codice vario per determinare quale button dei vari user control ha generato l'evento
            '-- Acquisizione di un blocco esclusivo sull'oggetto SynRoot della coda
            '-- Questo permette di "bloccare" il thread in loop, quando anch'esso tenterà di acquisire il blocco esclusivo su SynRoot, fino a quando non sarà stato accodato il messaggio
            SyncLock MessagesQueue.SyncRoot
                '-- preparazione  ed accodamento messaggio
                Dim Message As String = "messaggio da inviare"
                MessagesQueue.Enqueue(Message)
            '-- uscita dal blocco
            End SyncLock
     End Sub
    
    Nel Loop del Thread, verificare la presenza di nuovi messaggi, ed eventualmente inviarli:
    Public Sub trasmissione()
       Do
            '-- Acquisizione di un blocco esclusivo sull'oggetto SynRoot della coda
            SyncLock MessagesQueue.SyncRoot
                '-- Verifica la presenza di messaggi da inviare
                If MessagesQueue.Count > 0 Then
                    '-- recupero del primo messaggio (in uscita) dalla coda
                    Dim Message As String = DirectCast(MessagesQueue.Dequeue, String)
                    '-- invio il mesaggio
                    SerialPort1.Write(Message)
                    Thread.Sleep(300)  
                End If
            '-- uscita dal blocco
            End SyncLock
    
          TX_richiesta = ' creo la stringa (richiesta dati) da trasmettere al dispositivo (indicato dal contatore CONT)
                         ' e in base a parametri vari
          SerialPort1.Write(TX_richiesta)   ' invio la stringa
          Thread.Sleep(300)  ' metto in pausa il thread di trasmissione (è il tempo tra una trasmissione e l'altra)
    
          ' incremento il contatore dei dispositivi
          If cont = numeroDispositivi Then
             cont = 0
          Else
             cont += 1
          End If
       Loop
    End Sub
    

    Se c'è la possibilità che la coda contenga più di 1 messaggio, e vuoi poterli inviare contemporaneamente (invece di aspettare un'ulteriore iterazione per inviarli), ti basta eseguire un ciclo all'interno del blocco SyncLock e prelevare/inviare tutti i messaggi dalla coda (sempre 1 alla volta)


    Quote Originariamente inviato da mynos
    'SUB salvaBufferRicezione che usa invoke/begininvoke ed il delegate SetTextCallback
    Private Sub salvaBufferRicezione(ByVal text As String)
    If Me.TextBox1.InvokeRequired Then
    Dim x As New SetTextCallback(AddressOf salvaBufferRicezione)
    Me.BeginInvoke(x, New Object() {text})
    Else
    Me.TextBox1.Text = text

    ' ho usato una textbox nascosta per inserire la ricezione
    ' in quanto con questo invoke/begininvoke non so passare direttamente a una variabile


    ' codice per l'analisi della risposta e visualizzazione dati

    End If
    End Sub
    Invoke e BeginInvoke vanno utilizzati solo quando si opera su oggetti NON THREAD-SAFE, come ad esempio i controlli del Form, se l'intenzione è quella di usare una variabile, non c'è bisogno nè di Invoke, nè di BeginInvoke.

    Altra considerazione da fare è quella sull'uso di BeginInvoke, il quale, essendo un metodo Asincrono, NON BLOCCA il thread chiamante. (sicuro che nel tuo caso sia meglio così?)
    ℹ️ Leggi di più su Fix ...

  3. #3
    mynos non è in linea Novello
    Fix, sei stato davvero gentile, esaustivo ed illuminante.
    Alla coda, per quanto riguarda i messaggi da inviare, ci avevo pensato anch'io e qualche giorno fa stavo scarabocchiando con i vari push e pop non sapendo che nel framework c'era a disposizione la classe bella e pronta ...
    Domani mattina provo ad implementare la tua idea, nel frattempo studio ancora un po gli argomenti che mi hai proposto per quanto riguarda l'uso delle code ed i synclock dei thread.
    Ti ringrazio,
    Vadiliano

  4. #4
    mynos non è in linea Novello
    La soluzione di Fix è molto elegante e mi ha snellito molto il codice di gestione dei comandi da inviare al di fuori del loop, oltre ad avermi insegnato ad usare meglio synclock (ed anche che in .net ci sono già molti dei tipi di dati astratti studiati euni fa all'università che mi dovevo fare a manina con le prime versioni di java ).

    Vorrei chiedere ancora qualcosa, riguardo stavolta alla ricezione della porta seriale.

    1) nella funzione che mi elabora la stringa appena ricevuta (ed aggiorna di conseguenza i vari usercontrol etc etc) avevo usato una textbox invisibile (tramite invokerequired, invoke/begininvoke) su cui salvare la stringa ricevuta dalla seriale e poi analizzavo il testo della textbox (che è sempre la stringa ricevuta); questo perchè se salvo direttamente in una variabile la stringa ricevuta, in fase di compilazione mi escono errori "InvalidOperationException" riguardo operazioni cross-threads, quando vado ad interagire con le proprietà dei controlli.
    C'è un modo per risolvere questo problema? altrimenti mi tengo la textbox.

    2) anche modificando la parte di trasmissione con il codice di Fix e la gestione del thread tramite synclock sulla coda di messaggi, ottengo sempre confusione con le stringhe ricevute ... ovvero quando clicco uno dei pulsanti per inviare un messaggio extra rispetto al loop, visualizzo la risposta ricevuta dal dispositivo a cui ho inviato il comando, come se fosse la risposta di un altro dispositivo ... così per qualche frazione di secondo, finchè non rivengono aggiornati i dati tramite le richieste del loop, visualizzo i dati di un dispositivo al posto di un altro.
    Probabilmente perchè o mi tiro dietro qualche contatore da chissàdove .. oppure perchè anche la porta seriale crea un suo thread ... quindi mi ritrovo con 3 threads? quello principale, quello ho creato per la trasmissione TX e quello che si crea in automatico con la ricezione della porta seriale?
    Se fosse così, come farli sincronizzare?

    Ringrazio ancora

  5. #5
    Credo sia il caso che fornisci ulteriori dettagli, come il codice completo per l'invio (inclusi eventuali metodi richiamati nel loop) ed il codice completo di ricezione dati (incluso eventuali metodi richiamati)

    Ti confermo che l'evento DataReceived viene generato su un thread separato rispetto a quello che ha inviato i dati.
    ℹ️ Leggi di più su Fix ...

  6. #6
    mynos non è in linea Novello
    Il programma è molto lungo, posto quindi solo la parte relativa alla gestione della trasmissione e ricezione (che tutto sommato è quasi uguale a quello che ho allegato nel primo post). Qui vedete anche come creo i dispositivi (in un array) e come gestisco la visualizzazione.
    Non ho allegato il codice dell'user control, molto lungo ed articolato. Ma ho controllato, il problema non risiede li, ho usato molto la finestra di debug per verificarne effettivamente il corretto funzionamento.

    Ho capito proprio ora dove sta il problema (banalissimo tra l'altro ), proprio mentre scrivevo questo post. Devo solo risolverlo.
    In pratica in trasmissione nel loop uso la variabile "cont" che contiene l'indice dell'user control da gestire in quel momento. Quando ricevo una stringa, considero a priori che sia riferita al dispositivo con indice "cont".
    Il mio errore è quando trasmetto un comando manualmente (riferito a un dispositivo con indice diverso da "cont") in quanto quando ricevo, gestisco la stringa di risposta con l'indice "cont" del loop ... e non con l'indice del comando manuale.
    Beh almeno ho trovato l'errore ora penserò a qualcosa ...

    Intanto posto ugualmente parte del codice se magari qualcuno ha da farmi (spero) critiche ed osservazioni per migliorare qualsiasi cosa ...

    Altra cosa nei vari comandi di chiusura dell'applicazione (la X del form, il menu esci, etc), per terminare il thread di trasmissione e anche la porta seriale, è sufficiente scrivere queste tre righe di codice?

    TX.Abort()
    SerialPort1.Close()
    End
    
    oppure dovrei gestire con qualche try-catch i possibili errori? in quanto a volte capita che nella chiusura mi dia un'eccezione sul thread oppure sulla ricezione della seriale o altre volte il programma freeza ...

    Mi vergogno da matti a porre certe domande stupide, ma credetemi che dovermi imparare in 2-3 settimane un linguaggio nuovo venendo da VB6 e assembly ... oltre al normale lavoro .... ma a volte proprio mi sembra di essere un neofita

    Imports System
    Imports System.ComponentModel
    Imports System.Threading
    Imports System.IO
    Imports System.IO.Ports
    
    Public Class Form1
    
        Dim numeroDispositivi As Integer	' numero dei dispositivi
        Public dosatoreFX() As FX	' array che contiene gli user control dei dispositivi (FX è l'user control che rappresenta i dispositivi)
        Public TX As Thread    ' TX: thread usato per la trasmissione
    
        ' SERIALE (stringhe varie di trasmissione e ricezione)
        Dim TX_comando As String
        Dim TX_richiesta As String
        Dim TX_configurazione As String
        Dim stringaRX As String
    
        Public MessagesQueue As New Queue	' coda per i comandi in trasmissione
    
        Delegate Sub SetTextCallback(ByVal text As String)  ' delegate per la ricezione stringhe su textbox invisibile
    
        ' variabili varie per allarmi e contatori
        Dim tentativiComunicazione() As Integer  ' contatore di tentativi di comunicazione
        Dim allarmeComunicazione() As Boolean    ' stato della comunicazione
        Dim ora1() As Integer        ' ora in secondi per inizio timer
        Dim ora2() As Integer        ' ora in secondi per fine timer
        Dim statoPrecedente() As Integer ' stato del dosatore all'ultima lettura 
    
        Dim cont As Integer = 0     ' contatore dispositivi usato nello spazzolamento
    
    
    
        Private Sub Form1_Load(sender As Object, e As System.EventArgs) Handles Me.Load
    
    	' ... codice configurazione porta seriale
    
            TX = New Thread(AddressOf trasmissione)
    
    	' ... codice vario per configurazione dispositivi su datagridview
    		
    	' analizza configurazione e creazione dei controlli
            numeroDispositivi = DGVconfig.Rows.Count - 2	' assegno il numero dei dispositivi
            ReDim dosatoreFX(numeroDispositivi)	' ridimensiono array dispositivi
    
    	' ... ridimensiono array vari sempre per la gestione degli errori, allarmi, etc dei dispositivi
    		
            ReDim statoPrecedente(numeroDispositivi)	' array contenente lo stato di ogni dispositivo in cui si trovava prima della nuova richiesta dati
    
    	' ciclo l'array dispositivi per istanziare gli user control
    	' creo l'oggetto e gli attribuisco già il codice seriale per la comunicazione oltre alle descrizioni inserite dall'utente
    	' gli attribuisco anche un nome del tipo FXx, così da usarlo per determinare quale user control è stato cliccato
            For i As Integer = 0 To numeroDispositivi
                dosatoreFX(i) = New FX
                dosatoreFX(i).Parent = FLP
                dosatoreFX(i).codiceDispositivo = convertiStringaToByte(CInt(DGVconfig.Rows(i).Cells(0).Value), 2)
                dosatoreFX(i).codiceMacchinaUtente = DGVconfig.Rows(i).Cells(1).Value
                dosatoreFX(i).descrizioneUtente = DGVconfig.Rows(i).Cells(2).Value
                dosatoreFX(i).Name = "FX" & i
                AddHandler dosatoreFX(i).btnStart.Click, AddressOf FX_button_Click
                AddHandler dosatoreFX(i).btnStop.Click, AddressOf FX_button_Click
                AddHandler dosatoreFX(i).btnAzzera.Click, AddressOf FX_button_Click
                AddHandler dosatoreFX(i).pannelloTitolo.DoubleClick, AddressOf FX_config_DoubleClick
                tentativiComunicazione(i) = 0
                allarmeComunicazione(i) = False
            Next
    
            ' ... codice vario
    
            SerialPort1.Open()
    
            TX.IsBackground = True
            TX.Start()
        End Sub
    
    
        ' Sub di trasmissione richiamata dal thread TX
        ' loop che cicla tutti i dispositivi inviando una stringa di richiesta dati
        Public Sub trasmissione()
            Do
    	    ' gestione coda comandi manuali
                SyncLock MessagesQueue.SyncRoot
                    If MessagesQueue.Count > 0 Then
                        Dim Message As String = DirectCast(MessagesQueue.Dequeue, String)
                        SerialPort1.Write(Message)
                        Thread.Sleep(300)
                    End If
                End SyncLock
    
    	    ' stringa di richiesta dati usata dal loop (generata a seconda del dispositivo ciclato)
                TX_richiesta = dosatoreFX(cont).TX_creaStringa_02_05(dosatoreFX(cont).codiceDispositivo, FX.TX_comandi.richiestaDati)
    
                SerialPort1.Write(TX_richiesta)
                Thread.Sleep(300)	' pausa tra una trasmissione e la successiva
    	    ' in questo tempo se ricevo qualcosa dalla seriale lo considero come risposta del dispositivo indirizzato da cont
    
                ' incremento il contatore dei dispositivi
                If cont = numeroDispositivi Then
                    cont = 0
                Else
                    cont += 1
                End If
            Loop
    
        End Sub
    
        ' evento DataReceived della porta seriale
        Private Sub SerialPort1_DataReceived(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort1.DataReceived
    	' chiamo la sub passandole i dati ricevuti fino al return
            salvaBufferRicezione(SerialPort1.ReadTo(vbCr))
        End Sub
    
    
    	' qui passo la stringa ricevuta all'user control del dispositivo che la verifica, la analizza
    	' e determina in quale condizione si trova il dispositivo (tipo se sta lavorando, se c'è un allarme, etc etc)
    	' in base alla condizione in cui si trova, visualizzo graficamente
        Private Sub salvaBufferRicezione(ByVal text As String)
    
            ' uso di invokerequired su una textbox invisibile
            If Me.TextBox1.InvokeRequired Then
                Dim x As New SetTextCallback(AddressOf salvaBufferRicezione)
                Me.Invoke(x, New Object() {text})
            Else
                Me.TextBox1.Text = text
    
                If text.Length = 30 Then
                    With dosatoreFX(cont)
                        .stringaRisposta = text
    
                        If .verificaChecksum(.stringaRisposta) = True Then
                            .salvaDati(.stringaRisposta) ' operazione principale che elabora la stringa e decide la condizione in cui si trova il dispositivo
    						
    			' controllo se il dispositivo si trova nello stesso stato di prima
    			' se è lo stesso stato non cambio la grafica, ma aggiorno solo i valori nelle label
    			' altrimenti cambio la grafica e carico le immagini per la nuova condizione
    
                            If Not .condizioneAttuale = statoPrecedente(cont) Then
                                Select Case .condizioneAttuale
                                    Case FX.condizioni.remoto_ok_stop
                                        .pannelloTitolo.BackColor = Color.DimGray
                                        .pannelloMessaggi.BackColor = Color.Gainsboro
                                        .immagine.BackColor = Color.Transparent
                                        .immagine.BackgroundImage = My.Resources.dosatore_STOP
                                        .ledRemoto.BackColor = Color.Red
                                        .ledInserito.BackColor = Color.Transparent
                                        .lblMes1.Text = "STOP"
                                        .btnStart.Enabled = True
                                        .btnStop.Enabled = False
                                        .btnAzzera.Enabled = True
                                    Case FX.condizioni.remoto_ok_dosaggio
                                        .pannelloTitolo.BackColor = Color.DimGray
                                        .pannelloMessaggi.BackColor = Color.Gainsboro
                                        .immagine.BackColor = Color.Transparent
                                        .immagine.BackgroundImage = My.Resources.dosatore_PRODOTTO
                                        .ledRemoto.BackColor = Color.Red
                                        .ledInserito.BackColor = Color.Red
                                        .lblMes1.Text = "DOSAGGIO"
                                        .btnStart.Enabled = False
                                        .btnStop.Enabled = True
                                        .btnAzzera.Enabled = True
                                    Case FX.condizioni.locale_ok_stop
                                        .pannelloTitolo.BackColor = Color.SaddleBrown
                                        .pannelloMessaggi.BackColor = Color.Gold
                                        .immagine.BackColor = Color.Gold
                                        .immagine.BackgroundImage = My.Resources.dosatore_STOP
                                        .ledRemoto.BackColor = Color.Transparent
                                        .ledInserito.BackColor = Color.Transparent
                                        .lblMes1.Text = "STOP"
                                        .btnStart.Enabled = False
                                        .btnStop.Enabled = False
                                        .btnAzzera.Enabled = False
                                    ' ...
    				' ...
    				' tanti altri casi
                                End Select
    
    				' salvo la condizione attuale
                                statoPrecedente(cont) = .condizioneAttuale
                            End If
    
                            .lblTit1.Text = .codiceMacchinaUtente & " | " & .descrizioneUtente
                            .lblTit2.Text = "cod. disp. " & .codiceDispositivo
    
                            ' ... cambio molte altre label
    
                        End If
                    End With
                End If
            End If
        End Sub
    
    
    	' gestione dei pulsanti dell'user control
        Private Sub FX_button_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
            Dim D As Integer	' indice dell'user control da cui è stato generato il click
            Dim btn As String = CType(sender, Button).Name	' indica quale pulsante
    
    	' in base al pulsante cliccato, creo la stringa comando da trasmettere
            Select Case btn
                Case "btnStart"
                    D = CInt((CType(sender, Button).Parent.Parent.Parent.Name).Substring(2))
                    With dosatoreFX(D)
                        TX_comando = .TX_creaStringa_02_05(.codiceDispositivo, FX.TX_comandi.startInserito)
                        .btnStart.Enabled = False
                        .btnStop.Enabled = True
                    End With
                Case "btnStop"
                    D = CInt((CType(sender, Button).Parent.Parent.Parent.Name).Substring(2))
                    With dosatoreFX(D)
                        TX_comando = .TX_creaStringa_02_05(.codiceDispositivo, FX.TX_comandi.stopDisinserito)
                        .btnStart.Enabled = True
                        .btnStop.Enabled = False
                    End With
                Case "btnAzzera"
                    D = CInt((CType(sender, Button).Parent.Parent.Parent.Parent.Name).Substring(2))
                    With dosatoreFX(D)
                        TX_comando = .TX_creaStringa_02_05(.codiceDispositivo, FX.TX_comandi.azzeramentoTotalizzatore)
                    End With
            End Select
    
    	' aggiungo alla coda il comando da inviare
            SyncLock MessagesQueue.SyncRoot
                Dim Message As String = TX_comando
                MessagesQueue.Enqueue(Message)
            End SyncLock
    
        End Sub
    
    End Class
    

  7. #7
    mynos non è in linea Novello
    Ho risolto il problema della sincronizzazione usando una seconda coda in cui salvo gli indici dei dispositivi, ed un flag che mi indica se mi trovo nell'invio "standard" della richiesta dati nel loop oppure nell'invio "straordinario" di un comando.

    dichiaro le 2 code:
    Public MessagesQueue As New Queue   ' coda per le stringhe
    Public MessagesIQueue As New Queue  ' coda per gli indici abbinati alle stringhe
    
    e dichiaro altri contatori ed il flag:
    Dim cont As Integer = 0     ' contatore dispositivi usato nel loop
    Dim cont2 As Integer        ' indice dispositivo che ha inviato il comando nella coda
    Dim contRX As Integer     ' indice usato nell'analisi della ricezione
    Dim flagContCont2 As Boolean = False   ' flag: se false uso cont del loop, se true uso cont2 del comando manuale
    
    mentre nella sub di trasmissione:
    Public Sub trasmissione()
            Do
                SyncLock MessagesQueue.SyncRoot
                    If MessagesQueue.Count > 0 Then
                        Dim Message As String = DirectCast(MessagesQueue.Dequeue, String)
                        cont2 = DirectCast(MessagesIQueue.Dequeue, Integer)
                        flagContCont2 = True
                        SerialPort1.Write(Message)
                        Thread.Sleep(300)
                        flagContCont2 = False
                    End If
                End SyncLock
    
                TX_richiesta = dosatoreFX(cont).TX_creaStringa_02_05(dosatoreFX(cont).codiceDispositivo, FX.TX_comandi.richiestaDati)
    
                SerialPort1.Write(TX_richiesta)   ' invio la stringa
                Thread.Sleep(300)  ' metto in pausa il thread di trasmissione (è il tempo tra una trasmissione e l'altra)
    
                ' incremento il contatore dei dispositivi
                If cont = numeroDispositivi Then
                    cont = 0
                Else
                    cont += 1
                End If
            Loop
    
        End Sub
    
    In ricezione invece dopo aver preso la stringa, controllo il flag per determinare quale indice usare:
    ' ...
    If flagContCont2 = False Then
       contRX = cont
    Else
       contRX = cont2
    End If
    ' ....
    

    Ora è tutto sincronizzato. E' stata un svista che mi è costata cara

    Se posso invece approfittare ancora per chiedere:
    1) come gestire le eccezioni sulla seriale e sul thread, soprattutto in chiusura dell'applicazione
    2) se al posto di usare questo sistema di contatori e flag con 2 code, volessi usare un'unica coda contenente un array di 2 stringhe (stringa da inviare e relativo indice), come dovrei fare?

+ Rispondi al messaggio

Potrebbero interessarti anche ...

  1. [VB2010+]Ricevere dati porta Seriale ComX...
    Da glak nel forum Visual Basic .Net
    Risposte: 2
    Ultimo Post: 13-05-2012, 18:59
  2. Porta seriale
    Da ilcucciolone nel forum Microsoft Word
    Risposte: 1
    Ultimo Post: 04-08-2009, 23:20
  3. Porta seriale
    Da rmfalco89 nel forum Tutto Linux
    Risposte: 1
    Ultimo Post: 05-07-2009, 12:20
  4. Rx Porta Seriale
    Da marco13 nel forum Visual Basic 6
    Risposte: 11
    Ultimo Post: 26-11-2007, 15:11
  5. Porta seriale
    Da Kovacevic83 nel forum Microsoft Windows
    Risposte: 6
    Ultimo Post: 31-01-2005, 15:54