Este sitio utiliza cookies de Google para prestar sus servicios y analizar su tráfico. Tu dirección IP y user-agent se comparten con Google, junto con las métricas de rendimiento y de seguridad, para garantizar la calidad del servicio, generar estadísticas de uso y detectar y solucionar abusos.Más información

View site in english Ir a la página de inicio Contacta conmigo
viernes, 17 de marzo de 2017

ThiefWatcher, un sistema casero de vigilancia en interiores III

Con este artículo termino la serie dedicada a la solución ThiefWatcher, un sistema casero de vídeo vigilancia que se dispara cuando entran intrusos en tu domicilio, te avisa al móvil y te permite obtener en el momento fotografías que puedes utilizar para ayudar a la policía a identificar rápidamente a los ladrones y aumentar las probabilidades de recuperar rápidamente tus pertenencias robadas. En este último artículo voy a explicar las App que se utilizan como clientes remotos del sistema.

Para empezar la serie por el principio, en este enlace tienes el primer artículo de la serie sobre el sistema de vigilancia ThiefWatcher.

En este enlace podéis descargar el código fuente de la solución ThiefWatcher, escrita en CSharp con Visual Studio 2015.

Para crear los clientes he utilizado Xamarin, que permite generar de forma fácil las versiones para Android, iOS, Windows Phone, Windows App y Plataforma Universal de Windows escribiendo la mayoría del código en un solo proyecto.

El tipo de proyecto Xamarin que he utilizado es PCL (Portable Class Library), y se encuentra en TWClientApp. En las aplicaciones cliente solamente se utiliza el protocolo de almacenamiento, para intercambiar comandos e imágenes con el sistema central, y en este caso el protocolo no se puede seleccionar, sino que únicamente lo he implementado para utilizar Dropbox. La implementación se encuentra en la clase DropBoxStorage, que tendréis que sustituir por vuestra propia implementación si queréis usar un protocolo diferente.

El protocolo de almacenamiento en los clientes es algo más reducido que en el servidor. Está definido de la siguiente manera:

public interface IStorageManager
{
Task DownloadFile(string filename, Stream s);
Task DeleteFile(string filename);
Task<bool> ExistsFile(string filename);
Task<List<string>> ListFiles(string model);
Task SendCommand(ControlCommand cmd);
Task SendRequest(List<CameraInfo> req);
Task<List<CameraInfo>> GetResponse(string id);
}

Como podéis ver, es una versión asíncrona del que se utiliza en la aplicación central. En este caso la implementación no es tan sencilla como para la versión del servidor, que simplemente utiliza la carpeta Dropbox para copiar y leer archivos. En la App debemos usar el API de Dropbox para el trasiego de ficheros. Lo primero que debemos hacer es crear una aplicación en el sitio de Dropbox para desarrolladores y obtener una clave de acceso. Con esta cadena de texto, inicializaremos la constante _accessKey de la clase DropBoxStorage para que se puedan realizar las conexiones.

Para llamar a los diferentes métodos asíncronos, el programa hace uso de async / await, por lo que la programación es muy sencilla y no se diferencia gran cosa de una versión síncrona.

Para acceder al API de Dropbox, he utilizado la librería Dropbox.Api del repositorio de paquetes de NuGet.

Toda la implementación de las páginas de contenido de la App se encuentra en la clase CameraPage, derivada de ContentPage. Cuando se inicia la aplicación, aparece un botón para conectar con el servidor:

App cliente en el móvil
App cliente en el móvil

Al pulsar el botón, se crear el objeto DropBoxStorage y se realiza la conexión, obteniendo la lista de cámaras instaladas:

private async void Btn_Connect(object sender, EventArgs e)
{
_storage = new DropBoxStorage();
ControlCommand cmd = new ControlCommand();
cmd.ClientID = _clientId;
cmd.Command = ControlCommand.cmdGetCameraList;
await _storage.SendCommand(cmd);
_camList = await GetResponse();
_sLayout = new StackLayout();
foreach (CameraInfo ci in _camList)
{
Button btn = new Button()
{
Text = ci.ID,
BackgroundColor = Color.Gray,
TextColor = Color.Black,
BorderColor = Color.Black,
BorderWidth = 1,
HorizontalOptions = LayoutOptions.FillAndExpand
};
btn.Clicked += Btn_Camera;
_sLayout.Children.Add(btn);
}
Content = new ScrollView()
{
Content = _sLayout
};
}

A continuación, se crea el diseño definitivo de la página, un StackLayout donde, en primer lugar, se crea un botón para cada una de las cámaras, cada uno con el identificador de cámara correspondiente, que nos permiten seleccionar una de ellas.

Lista de cámaras de la App cliente
Lista de cámaras de la App cliente

Al pulsar uno de estos botones la primera vez, se añade un objeto Image para mostrar la imagen de la cámara, un botón Start / Stop para poner en funcionamiento o parar la cámara, otro botón para tomar una fotografía y otro más para detener el estado de alarma y volver al modo de vigilancia.

Imagen de la cámara en la App cliente
Imágen de la cámara en la App cliente

Debajo de los botones se encuentra la lista de fotos.

Lista de fotografías en la App cliente
Lista de fotos en la App cliente

Cada una de las fotografías puede guardarse en el teléfono o eliminarse por separado, con los botones que se encuentran a su derecha. Como el acceso al dispositivo para guardar los ficheros no es independiente de la plataforma, es necesario implementar la versión correspondiente en cada uno de los proyectos de las diferentes plataformas.

En primer lugar, creamos un interfaz para realizar la operación. Se trata de IImageManager, definido de esta manera:

public interface IImageManager
{
Task SaveImage(string filename, byte[] data);
}

A continuación, es necesario crear una dependencia en cada uno de los proyectos que guarde los datos utilizando las llamadas del código nativo. Esta es, por ejemplo, la implementación para Windows Phone, en el proyecto TWClientApp.WinPhone, en la clase ImageManager_WinPhone:

[assembly: Xamarin.Forms.Dependency(
typeof(TWClientApp.WinPhone.ImageManager_WinPhone))]
namespace TWClientApp.WinPhone
{
public class ImageManager_WinPhone : IImageManager
{
public async Task SaveImage(string filename, byte[] data)
{
StorageFolder picturesLibrary = KnownFolders.PicturesLibrary;
StorageFolder savedPicturesFolder =
await picturesLibrary.CreateFolderAsync(
"TWClient",
CreationCollisionOption.OpenIfExists);
StorageFile imageFile =
await savedPicturesFolder.CreateFileAsync(
filename,
CreationCollisionOption.ReplaceExisting);
await FileIO.WriteBytesAsync(imageFile, data);
}
}
}

Y esta es la versión de Android, en el proyecto TWClientApp.Droid, en la clase ImageManager_Droid:

[assembly: Xamarin.Forms.Dependency(
typeof(TWClientApp.Droid.ImageManager_Droid))]
namespace TWClientApp.Droid
{
public class ImageManager_Droid : IImageManager
{
public async Task SaveImage(string filename, byte[] data)
{
var dir = Android.OS.Environment.GetExternalStoragePublicDirectory(
Android.OS.Environment.DirectoryDcim);
var pictures = dir.AbsolutePath;
string filePath = System.IO.Path.Combine(pictures, filename);
try
{
System.IO.File.WriteAllBytes(filePath, data);
var mediaScanIntent = new Intent(
Intent.ActionMediaScannerScanFile);
mediaScanIntent.SetData(
Android.Net.Uri.FromFile(new File(filePath)));
Xamarin.Forms.Forms.Context.SendBroadcast(mediaScanIntent);
}
catch (System.Exception e)
{
}
}
}
}

Y esta es la forma de llamar a estas funciones, desde la clase CameraPage, en el controlador del evento del botón:

private async void Btn_PhotoSave(object sender, EventArgs e)
{
if (sender is Button)
{
string photo = ((Button)sender).AutomationId;
int ix = _photos.IndexOf(photo);
if (ix >= 0)
{
MemoryStream s = await GetPhoto(photo) as MemoryStream;
byte[] data = s.ToArray();
await DependencyService.Get<IImageManager>().
SaveImage(photo, data);
await DisplayAlert("Image Saved",
"The image has been saved", "OK");
}
}
}

Y esto es todo, espero que esta aplicación os pueda ser útil y os divirtáis realizando el montaje.

Comparte este artículo: Compartir en Twitter Compártelo en Facebook Compartir en Google Plus Compartir en LinkedIn
Comentarios (0):
* (Su comentario será publicado después de la revisión)

E-Mail


Nombre


Web


Mensaje


CAPTCHA
Change the CAPTCHA codeSpeak the CAPTCHA code