Condividi tramite


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

Altre lingue

Il presente articolo è disponibile nelle seguenti localizzazioni: