Down il sito di Apple Developer

A causa di accessi indesiderati il sito web di Apple Developer è stato messo temporaneamente in Down, come visibile qui.

Per chi, come il sottoscritto, lavora anche su dispositivi mobili di Apple è una bella rogna, anche perché, in questi giorni di ferie, contavo proprio di provare il nuovo IOS.

Speriamo si risolva presto.

Gestione delle seriali virtuali. Deep Inside.

Quanto di seguito è il risultato di esperimenti, studi e ricerche effettuati dal sottoscritto e da altri sull’utilizzo delle seriali virtuali e trascende dalle best practice, perché in questo caso queste o non esistono, o sono destinate a fallire miseramente.

Se non vi occupate di firmware, se il vostro concetto di fare hardware consiste nel saper usare il cacciavite a croce per assemblare un pc, allora questo mio sproloquio è per voi assolutamente inutile. Se invece credete che fare hardware sia un’altra cosa, allora potrete trovare nelle righe che seguono qualche informazione utile.

Quando il pc si interfaccia con una qualche periferica esterna, magari custom, via USB, bisogna considerare il sistema complessivo nelle sue varie parti, che vanno dall’applicativo, alle API che permettono all’applicativo di interfacciarsi con il dispositivo fisico, e dalla parte del dispositivo esterno un sistema complementare. Per rendere meglio l’idea si può immaginare il sistema costituito in maniera analoga al classico modello ISO/OSI di derivazione “retistica”.

Grazie al fatto che l’USB è “universal” o “general purpose”, uno sviluppatore può costruirsi ed adattarsi il proprio schema di comunicazione, scrivendosi lo stack sul dispositivo ed anche i device driver così come le api per interfacciarsi a quest’ultimo. Tuttavia, pur non essendo un lavoro impossibile, tale discorso è assolutamente sconsigliato e scarsamente praticato, proprio perché richiede conoscenze molto ampie e molto approfondite, ed anche perché tipicamente ci si può riportare (anche magari con qualche artificio) a situazioni già note, utilizzando ed implementando nel proprio dispositivo e nei propri driver un cosiddetto profilo o classe di dispositivo. I più comuni profili adottati dagli sviluppatori di firmware sono HID (human interface device), MSC (Mass storage device class) e CDC (Communication Device Class), mentre maggiori informazioni possono essere trovate qui.

Il profilo più usato nella fase di transizione tra i dispositivi “classici” e quelli USB è normalmente il profilo CDC grazie al quale dal PC vedo il dispositivo come se comunicassi con una porta seriale. Egualmente, dal dispositivo, se lo stack è fatto almeno decentemente,  comunico con il pc in maniera identica, come se stessi comunicando con una seriale fisica. Questo significa che dal pc posso utilizzare le stesse API per comunicare con il dispositivo che utilizzerei per utilizzare una seriale fisica. Idem dal mio device.

Il .NET framework ci fornisce delle classi per la gestione delle porte seriali che rendono le operazioni decisamente semplici e con pochi sforzi si possono costruire funzioni anche thread safe per la gestione delle seriali stesse.

Dall’altra parte è possibile scrivere, con uno sforzo modesto, uno stack in grado di far riconoscere un dispositivo collegato su USB come porta seriale virtuale. Diciamolo subito, non è una cosa per tutti, c’è chi può e chi non può. Esistono comunque una serie di ASIC (Application Specific Integrated Circuit… parolone che significa… Circuito integrato che si occupa di fare una certa applicazione) quali i dispositivi di FTDI, SILABS, Prolific, Etc… che convertono in maniera automatica e completa una porta USB in una porta di un certo tipo (normalmente una seriale RS232 a 3,3V) e che si possono usare senza grosse modifiche hardware e/o senza grossi tempi di sviluppo ed impegno, anche con microprocessori o microcontrollori di fascia bassa (ma proprio bassa).

A questo punto si può quindi connettere il dispositivo USB al nostro sistema, questi viene riconosciuto come porta seriale virtuale, e quindi utilizzarlo dal nostro programma .NET come una seriale (la faccio facile, ovviamente. In realtà prima di arrivare a questo punto bisogna scriversi lo stack, riprogrammare i driver, che NORMALMENTE nella CDC si trovano già fatti abbastanza ben funzionanti, ma è roba che, fatta una volta, si ricicla)

Tutto a posto, quindi?

Neanche per sogno, perché una porta seriale virtuale non è una porta seriale VERA. E non essendo una seriale vera pone in essere alcuni comportamenti diversi, talora bizzarri, grazie anche a “interazioni umane” non troppo ortodosse.

Vediamo di analizzare e di capire quali sono le differenze tra un dispositivo seriale reale ed uno virtuale.

La prima differenza sta nel numero della porta. Una porta seriale reale ha NORMALMENTE un suo ben preciso indirizzo fisico di I/O e nel numero con il quale la identifico. In realtà non è vera né la prima, né la seconda affermazione, poiché gli indirizzi sono decisi e riprogrammabili dalla logica del Pnp (il famoso Plug and play, conosciuto dai tecnici come Plug and PRAY, e dagli hardwaristi seri come PLUG AND CRY), ed il numero della porta seriale è modificabile.

Tuttavia nella vita di un normale PC questa cambierà BEN POCHE VOLTE.

Nelle seriali virtuali USB, invece, c’è buona probabilità che il numero della porta seriale non sia così… stabile, e questo dipende anche fortemente dalla versione del SO, e dal numero delle periferiche collegate e dall’ordine con cui queste vengono collegate. Non solo, anche cambiando l’ingresso USB fisico al quale il dispositivo viene collegato può cambiare il numero della COMport.

A dire il vero mi è capitato molto spesso su wXP, decisamente meno spesso su w7, segno che le cose su XP erano fatte decisamente più ad mentula canis di quanto non siano state fatte sui moderni sistemi operativi.

Tuttavia, se vengono collegati e scollegati numerosi dispositivi diversi la probabilità che si verifichi è tutt’altro che nulla, anche su w7. Tanto più se si lavora con il debugger e virtual-com attaccati contemporaneamente e si riprogramma il firmware a seriale aperta.

Questo implica quindi che a monte delle funzioni di comunicazione sia “buona pratica” cercare il numero della COMPort “incriminata”. Non solo. Implica pure che la porta seriale virtuale, o più correttamente, il dispositivo fisico possa non essere non connesso al PC, o che magari venga collegato o scollegato a programma aperto, magari mentre la comunicazione è in corso.

Nella mia attività di documentazione non ho mai trovato qualcuno che mi spiegasse come gestire queste situazioni, per le quali mi ero già rassegnato a “tacconare”

Nella mia funzione LeggiEScrivi trattata in uno dei miei post precedenti, ero riuscito a far si che il programma non andasse miseramente in crash in caso di disconnessione nella comunicazione. Tuttavia il problema era stato solo “tacconato” e non risolto.

Infatti, all’uscita del programma, veniva comunque ritornata una segnalazione di errore, dovuto al fatto che si tentava di distruggere un oggetto occupato o altre amenità del genere. Si tratta per lo più di pratiche “limite”, nel senso che bisogna che uno si ingegni per farle capitare. Tuttavia per quanto uno costruisca sistemi a prova di imbecille, esisterà sempre un imbecille a prova di tali sistemi.

La domanda è quindi una. Sono riuscito a risolvere ogni tipo di problema? La risposta è NO. Sono riuscito a risolvere ogni tipo di problema PER ORA NOTO. Rimango in attesa dell’ imbecille che rovini le mie certezze.

OK, adesso basta chiacchiere.

Cerca che ti ricerca ho trovato su CodeProject una bella libreria che svolge un buon 70% del lavoro sporco. Questa è stata scritta da Stéphane LELONG nel 2010 ed è reperibile a questo indirizzo.

Per essere usata c’è bisogno di “ritoccare” il codice. Niente di simile ad un trapianto di cuore, ma la cosa è discretamente invasiva. Le cose IMHO interessanti di questa libreria sono fondamentalmente due. La prima è la possibilità, dati VID e PID di un dispositivo, di recuperare in maniera semplice il numero della seriale virtuale. La seconda è la possibilità di intercettare l’inserimento ed il disinserimento del dispositivo incriminato. L’unico che mi pare realmente interessante è il disinserimento.

Come risalire al numero di porta seriale dal VID e dal PID.

La cosa è ABBASTANZA semplice.

Scaricare la libreria, quindi includerla nel proprio progetto (eventualmente anche in sorgente), quindi, all’interno della Form di comunicazione, dichiarare un oggetto di tipo DeviceProperties

Public USBDeviceProperties As USBClassLibrary.USBClass.DeviceProperties

quindi inizializzarlo con il metodo New

USBDeviceProperties = New USBClassLibrary.USBClass.DeviceProperties

Dopo di che si chiama la funzione shared GetUSBDevice della libreria, che, se il riesce a ritrovare il dispositivo, ci fornisce pure il numero della porta seriale

 If USBClassLibrary.USBClass.GetUSBDevice(CNST_VID_DISPOSITIVO, CNST_PID_DISPOSITIVO, USBDeviceProperties, True) = False Then             
	' Dispositivo non trovato... Messaggio di errore 	
	Exit Sub         
End If ComPortDispositivo = Me.USBDeviceProperties.COMPort

Incredibile ma vero… Tutto qui. Non facciamoci però prendere dai facili entusiasmi. Questa parte della libreria NON FUNZIONA (provato personalmente) con i dispositivi FTDI che tuttavia hanno analoghe librerie scritte apposta. Tuttavia funziona (provato personalmente) con SiLabs CP210X e con i miei dispositivi custom.

Veniamo ora alla parte più divertente. Come intercettare la rimozione del device.

Per prima cosa bisogna dichiarare un oggetto (io lo faccio normalmente a livello di Form) di tipo USBClass, ovviamente WithEvents.

    Public WithEvents USBPort As USBClassLibrary.USBClass

Quindi lo si inizializza, si associa il gestore dell’evento e si registra la Form chiamante per la ricezione dei messaggi da parte del sistema (Tutto fatto, quindi tranquilli)

	Me.USBPort = New USBClassLibrary.USBClass         
	AddHandler Me.USBPort.USBDeviceRemoved, AddressOf USBPort_USBDeviceRemoved         
	USBPort.RegisterForDeviceChange(True, Me)

A questo punto bisogna andare a modificare il ciclo messaggi della Form per inviare all’oggetto USBPort i messaggi che questi deve elaborare.

	Protected Overrides Sub WndProc(ByRef msg As System.Windows.Forms.Message)         
		If Not USBPort Is Nothing Then
             		USBPort.ProcessWindowsMessage(msg)         
		End If         
		MyBase.WndProc(msg)     End Sub

Nel funzionamento quindi, quando arriveranno messaggi da parte di Windows questi verranno interpretati sia dalla Form stessa che, a monte, dall’USBPort.

Questi, nella versione riveduta e corretta (da me) gestisce la rimozione della porta stessa in maniera opportuna e fa scattare l’evento di device rimosso, che io gestisco nella mia Form in maniera semplicissima

	Private Sub USBPort_USBDeviceRemoved(sender As Object, e As USBClassLibrary.USBClass.USBDeviceEventArgs)         
		If USBClassLibrary.USBClass.GetUSBDevice(CNST_VID_DISPOSITIVO, CNST_PID_DISPOSITIVO, USBDeviceProperties, True) = False Then
	        	PortaScollegata = True 	
			' Restante gestione         
		End If     
	End Sub

La funzione ScriviELeggi, deve essere quindi opportunamente modificata per gestire gli eventuali scollegamenti asincroni. Io l’ho quindi modificata così.

	Private Shared Function ScriviELeggi(Messaggio As String, TimeOutMilliseconds As Integer, ByRef Risultato As String) As Integer         
		Dim TimeOutCounter As Integer = 0         
		Dim SB As New StringBuilder         
		Dim c As String = ""         
		If PortaScollegata = False Then             
			If SPDispositivo Is Nothing Then                 
				Return 6             
			End If             
			If SPDispositivo.IsOpen = False Then                 
				Return 7             
			End If             
			SPDispositivo.DiscardInBuffer()             
			SPDispositivo.DiscardOutBuffer()         
		End If          
		Thread.Sleep(10)         
		Try             
			If PortaScollegata = False Then                 
				SPDispositivo.Write(Messaggio)             
			End If         
		Catch ex As Exception             
			Return 1         
		End Try          
		Thread.Sleep(10)         
		Try             
			While True                 
				If SPDispositivo Is Nothing Then                     
					Return 3
				End If                 
				If SPDispositivo.IsOpen = False Then                     
					Return 4                 
				End If                 
				While SPDispositivo.BytesToRead > 0                     
					If PortaScollegata = False Then
						c = Chr(SPDispositivo.ReadChar)
					End If                     
					If CondizioneDiUscita Then                         
						Risultato = SB.ToString
						Return 0                     
					Else                         
						SB.Append(c)                     
					End If                 
				End While                 
				Thread.Sleep(10)                 
				TimeOutCounter = TimeOutCounter + 10                 
				If TimeOutCounter > TimeOutMilliseconds Then 	
					Return 2                 
				End If             
			End While         
		Catch ex As Exception             
			Return 5         
		End Try          
		Return 0     
	End Function

Questa NON SONO RIUSCITO AD INCHIODARLA NEANCHE VOLENDO.

Rimane quindi la gestione dell’eliminazione dell’oggetto SerialPort che va fatta in una maniera un po’ turpe, alla chiusura della Form.

Questo è un risultato PRATICO che funziona

	If Not SPDispositivo Is Nothing Then             
		If Not PortaScollegata Then                 
			If SPDispositivo.IsOpen = True Then                     
				SPDispositivo.Close()                 
			End If              
		End If             
		Try                 
			SPDispositivo.Dispose()             
		Catch ex As Exception              
		End Try             
		SPDispositivo = Nothing         
	End If

Spero qualcuno possa trovare utili queste mie e rimango in attesa di commenti e/o correzioni.

Alla prossima