VB.NET: Scattare Foto Automatiche da Device per Upload su Webserver (it-IT)
Obiettivo
In questo articolo, vedremo come sviluppare una app per Windows Phone capace di eseguire scatti automatici e temporizzati tramite la fotocamera del dispositivo, per poi inviarli su un web server dal quale possano in seguito essere visualizzati tramite browser. In altre parole, svilupperemo una sorta di network camera / webcam che possa essere eseguita da smartphone o tablet, per monitorare un determinato luogo trovandosi altrove. Utilizzeremo Visual Basic .NET per la parte inerente all'app, mentre la parte relativa al web server verrà sviluppata tramite PHP, così come fatto per un precedente articolo sulla geolocalizzazione (cfr.: Geolocalize a device and store coordinates on Webserver per maggiori informazioni).
Prerequisiti
Per utilizzare e sviluppare quanto illustrato nell'articolo, si necessita di:
- Windows 8.1
- Visual Studio con Windows Phone 8.1 SDK
- Un web server locale o hosting remoto, con Apache e supporto PHP abilitato. Alternativamente, si potrebbe scegliere uno sviluppo che sfrutti le potenzialità di ASP/ASP.NET.
La configurazione di ciascun prerequisito esula dagli scopi finali di questo articolo, e non verranno pertanto trattati in questa sede, presupponendo che quanto indicato sia stato precedentemente configurato correttamente. In caso si abbisogni di una breve referenza relativa all'installazione di Apache, e delle componenti relative a PHP/MySQL, potete consultare l'articolo Quickly installing LAMP server on Debian or Ubuntu Linux
Introduzione
In questo articolo presento un semplice metodo per implementare una sorta di webcam basata su smartphone/tablet, utile per imbastire scenari che potrebbero poi essere ulteriormente sviluppati verso applicazioni di sorveglianza. Un'attenzione sempre crescente verso il mondo IoT ci porta ad essere sempre più preoccupati e consapevoli delle possibilità di interconnessione tra i dispositivi di utilizzo quotidiano, e credo che quanto seguirà possa essere un simpatico - per quanto basilare - esercizio in questa direzione.
Ciò che faremo sarà utilizzare un dispositivo dotato di OS Windows 8.1 come camera automatica, per scattare foto ad intervalli temporali predefiniti, ed inviarli quindi ad uno script PHP remoto, il quale le consoliderà sul versante web server per renderle disponibili ad essere successivamente analizzate da una qualsiasi locazione che permetta una connessione Internet. Iniziamo dalla parte server, in modo da creare alcuni presupposti iniziali che renderanno più semplice lo sviluppo dell'app.
Scripts lato server
Come anticipatamente detto, utilizzeremo PHP (ed un webserver Apache) per imbastire un semplice script capace di ricevere un file, che sarà inviato tramite metodo POST, per salvarlo e renderlo quindi successivamente accessibile mediante richieste HTTP, come una navigazione via browser. Lo script è il più essenziale possibile: consiste nella ricezione di un contenuto tramite POST (il parametro "file"), procedendo quindi nell'apertura di un file arbitrario (qui chiamato "test.jpg"), e scrivendo in esso - in forma binaria - i dati inviati. Vediamo come:
<?php
$content = $_POST['file'];
$sfile = fopen("test.jpg", 'wb'); // 'wb' parameter means "write binary"
fwrite($sfile, $content);
fclose($sfile);
?>
Si tratta di uno snippet piuttosto chiaro con una conoscenza di base di PHP.
In seconda battuta, prepareremo un'altra pagina, qui nominata "index.html": un semplice file HTML in cui mostrare il file "test.jpg".
<title>Network Camera</title>
<!-- Omissis: styles will be present in the full code -->
<h1>Network Camera</h1>
<div id="imgwrapper">
<img src="test.jpg">
</div>
Per riassumere questa parte di progetto, si parla qui di avere un web server equipaggiato con due semplici scripts. Il primo sarà utilizzato dalla nostra app Windows Phone, e riceverà - salvandolo - uno stream di dati inviato dall'app stessa, mentre il secondo è semplicemente un visualizzatore per tali dati, incapsulandoli (in qualità di immagine) in un tag IMG, per consentirne la visione all'utente. Ora possiamo implementare la nostra app per completare il lavoro.
App Windows Phone per acquisizione automatica foto
In questa sezione, svilupperemo la nostra app Windows Phone, che consisterà di quanto segue:
- Acquisizione automatica di foto tramite fotocamera
- Upload automatico della foto (ovvero, submit della foto verso la nostra pagina upload.php)
Per accedere alle funzionalità della fotocamera, faremo uso del namespace Windows.Media.MediaProperties.
Per prima cosa, avviamo Visual Studio e selezioniamo - dal template Visual Basic - la voce Store Apps / Blank App (Windows Phone). Essa creerà per noi un semplice layout basato su singola pagina, di nome MainPage.xaml, sul quale applicheremo i controlli che ci necessitano, assieme alla business logic.
Vediamo ora quali elementi includeremo nel nostro XAML
Come prima cosa abbiamo un CaptureElement, ovvero un controllo sul quale effettuare il rendering dell'anteprima di fotocamera, in modo da osservare ciò che sarà poi acquisito come immagine. Un TextBox di nome txtRemUP conterrà l'indirizzo remoto della nostra pagina upload.php (ed è quindi utile nel caso volessimo fare in modo di comunicare con host differenti, invece di utilizzare una costante stringa nel codice). Un ToggleButton ci permetterà di avviare e fermare l'acquisizione vera e propria, mentre un altro paio di TextBlock ci serviranno per funzioni di logging (la data e l'ora dell'ultima acquisizione e upload), mentre un Canvas mostrerà l'ultima immagine fisicamente acquisita.
Prerequisiti per app
Dal momento che la nostra applicazione deve richiedere l'accesso hardware a dispositivi come la fotocamera, nonché poi alle comunicazioni di rete, dovremo impostare le Capabilities e i Requirements conseguentemente.
Quindi, dovremo eseguire un doppio-click sul file Package.appxmanifest file (creato da Visual Studio), ed accedere poi alle rispettive tab Capabilities e Requirements, specificando i flag di camera e network come segue:
Inizializzazione fotocamera
Dal punto di vista del codice, la prima cosa da fare è inizializzare la fotocamera all'avvio dell'app, impostando la proprietà Source del CaptureElement in modo appropriato. Si noti che tutti gli eventi che verranno presentati hanno esecuzione asincrona. Iniziamo con l'evento OnNavigatedTo, il quale definisce il momento in cui una determinata pagina viene mostrata.
Protected Overrides Async Sub OnNavigatedTo(e As Navigation.NavigationEventArgs)
cam = New Windows.Media.Capture.MediaCapture
Await cam.InitializeAsync()
mCanvas.Source = cam
Await cam.StartPreviewAsync()
End Sub
Ho dichiarato una variabile di tipo MediaCapture, cam, come visibile all'intera pagina. Qui utilizzeremo il metodo InitializeAsync su di essa, per inizializzare la fotocamera ed eseguire il binding della variabile MediaCapture come sorgente del controllo CaptureElement. Una successiva chiamata al metodo StartPreviewAsync risulterà nel rendering grafico dell'acquisizione della camera sul controllo.
Sull'evento Toggled del nostro ToggleButton, lanceremo un Timer (o lo scaricheremo dalla memoria, a seconda dello stato del ToggleButton stesso).
If toggler.IsOn Then
t = New System.Threading.Timer(AddressOf Shoot, Nothing, TimeSpan.Zero, New TimeSpan(0, 0, 10))
t.Change(30000, Threading.Timeout.Infinite)
Else
If Not (t Is Nothing) Then t.Dispose()
End If
Se il ToggleButton è attivo, creeremo una nuova istanza t di Timer, utilizzando un intervallo di 30 secondi. Per ciascun intervallo trascorso, verrà effettuata una chiamata alla sub routine Shoot. In detta sub routine controlleremo l'acquisizione dell'immagine, così come il suo upload.
Acquisizione fotografica
La routine Shoot è come segue:
Public Async Sub Shoot(sender As Object)
Dim imgFormat As ImageEncodingProperties = ImageEncodingProperties.CreateJpeg()
stream = New Streams.InMemoryRandomAccessStream
Await cam.CapturePhotoToStreamAsync(imgFormat, stream)
Await stream.FlushAsync
stream.Seek(0)
Await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, AddressOf SetCanvas)
Await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, AddressOf MarshalToggler)
End Sub
In essa, definiamo un RandomAccessStream che sarà inizializzato attraverso la chiamata al metodo CapturePhotoToStreamAsync (proprio della variabile MediaCapture). In questo modo, semplicemente, la chiamata al metodo esegue un accesso alla fotocamera per acquisire un'immagine, procedendo quindi ad effettuarne la copia sullo stream. Al termine della routine, si possono notare due Dispatchers verso due subroutines, la seconda delle quali semplicemente esegue un controllo sullo stato del ToggleButton, mentre la prima chiama la routine SetCanvas, che sarà utilizzata per salvare l'immagine sul nostro Canvas ma - più importante - soprattutto per inviarla al nostro webserver, attraverso una richiesta POST verso la nostra pagina upload.php.
Private Async Sub SetCanvas()
tmpLab.Text = DateTime.Now.ToString
Dim b As New BitmapImage()
Await b.SetSourceAsync(stream)
pCanvas.Source = b
Dim io As Stream = stream.AsStreamForRead
Dim buf(io.Length) As Byte
io.Position = 0
Await io.ReadAsync(buf, 0, io.Length)
Dim httpClient As New HttpClient
Dim form = New MultipartFormDataContent
form.Add(New ByteArrayContent(buf, 0, buf.Length), "file")
Dim response = Await httpClient.PostAsync(txtRemUP.Text, form)
response.EnsureSuccessStatusCode()
httpClient.Dispose()
Dim sd As String = response.Content.ReadAsStringAsync().Result
lastSent.Text = Date.Now.ToString
End Sub
Ad di là della valorizzazione dei TextBlocks per scopi di logging, ciò che viene fatto dalla routine è:
- Creare un oggetto BitmapImage: tramite il metodo SetSourceAsync, viene creata una nuova Bitmap a partire dal nostro RandomAccessStream, utilizzandolo come sorgente del Canvas, in modo da mostrare l'ultima foto scattata.
- Viene quindi creato un semplice Stream per accedere all'array di Byte del nostro RandomAccessStream originale (ovvero l'immagine stessa). Ciò sarà necessario per eseguire correttamente l'upload dei nostri dati
- Viene creato un MultipartFormDataContent (ossia un form HTTP "simulato", contenente la variabile attesa lato web, che durante la creazione dello script upload.php abbiamo chiamato "file")
- Infine, upload fisico dell'immagine
Il code-behind completo della pagina MainPage sarà quindi come segue:
Imports System.Net.Http
Imports Windows.Storage
Imports Windows.Media.MediaProperties
Public NotInheritable Class MainPage
Inherits Page
Dim t As System.Threading.Timer
Dim cam As Windows.Media.Capture.MediaCapture
Dim stream As Streams.InMemoryRandomAccessStream
Protected Overrides Async Sub OnNavigatedTo(e As Navigation.NavigationEventArgs)
cam = New Windows.Media.Capture.MediaCapture
Await cam.InitializeAsync()
mCanvas.Source = cam
Await cam.StartPreviewAsync()
End Sub
Private Async Sub SetCanvas()
tmpLab.Text = DateTime.Now.ToString
Dim b As New BitmapImage()
Await b.SetSourceAsync(stream)
pCanvas.Source = b
Dim io As Stream = stream.AsStreamForRead
Dim buf(io.Length) As Byte
io.Position = 0
Await io.ReadAsync(buf, 0, io.Length)
Dim httpClient As New HttpClient
Dim form = New MultipartFormDataContent
form.Add(New ByteArrayContent(buf, 0, buf.Length), "file")
Dim response = Await httpClient.PostAsync(txtRemUP.Text, form)
response.EnsureSuccessStatusCode()
httpClient.Dispose()
Dim sd As String = response.Content.ReadAsStringAsync().Result
lastSent.Text = Date.Now.ToString
End Sub
Private Sub MarshalToggler()
If toggler.IsOn Then
t = New System.Threading.Timer(AddressOf Shoot, Nothing, TimeSpan.Zero, New TimeSpan(0, 0, 10))
t.Change(30000, Threading.Timeout.Infinite)
Else
If Not (t Is Nothing) Then t.Dispose()
End If
End Sub
Public Async Sub Shoot(sender As Object)
Dim imgFormat As ImageEncodingProperties = ImageEncodingProperties.CreateJpeg()
stream = New Streams.InMemoryRandomAccessStream
Await cam.CapturePhotoToStreamAsync(imgFormat, stream)
Await stream.FlushAsync
stream.Seek(0)
Await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, AddressOf SetCanvas)
Await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, AddressOf MarshalToggler)
End Sub
Private Sub toggler_Toggled(sender As Object, e As RoutedEventArgs)
MarshalToggler()
End Sub
End Class
Al completamento di tali passi, il nostro file "test.jpg" sarà quindi stato caricato e sarà pertanto pronto ad essere visualizzato, ovviamente conoscendo l'URL della pagina di visualizzazione, da un qualsiasi dispositivo dotato di connessione e browser.
Vediamo un esempio completo dell'esecuzione di entrambi le parti.
Un test complessivo
Un semplice test consiste, in questo caso, nel settaggio del webserver e del dispositivo, lanciando l'app Windows Phone ed attendendo l'esecuzione dell'acquisizione automatica e del conseguente upload, verificando subito dopo attraverso un browser la reale intercomunicazione, visitando la pagina HTML precedentemente preparata a tale scopo:
Codice sorgente di esempio
Il codice sorgente completo qui presentato è disponibile per il download al seguente link: https://code.msdn.microsoft.com/Get-Automatic-Pictures-3b3875dd
Bibliografia
- HttpClient (classe)
- InMemoryRandomAccessStream (classe)
- MultipartFormDataContent (classe)
- Windows.Media.Capture (namespace)
Altre lingue
Il presente articolo è disponibile nelle seguenti localizzazioni: