[Windows Phone 8.1] Nokia SensorCore SDK

El martes 24 de junio pasado Nokia presentó y puso a nuestra disposición su último SDK (por el momento al menos): SensorCore. Es tanto el último que en la página oficial de su documentación no se aclaran si llamarlo Lumia SensorCore SDK o Microsoft Mobile SensorCore SDK. Aún se encuentra en beta, pero es totalmente usable para publicar apps a la tienda.

Ya lo habían presentado en el Build 2014, pero hasta ahora se encontraba en beta privada y exclusivamente bajo invitación. Por fin está aquí, ¿Qué nos aporta?

SensorCore es una tecnología de sensores de muy bajo consumo, integrada a nivel de hardware en ciertos modelos de la familia Lumia. Por el momento lo podemos encontrar y usar en el Lumia 630/635 y el Lumia 930. El Lumia 1520 y el Lumia Icon también incluyen estos sensores, pero deberemos esperar hasta la actualización de firmware Cyan para que el software necesario quede habilitado. En el resto de dispositivos, no podremos usar este SDK. No por una cuestión de software, si no por la falta de los sensores de hardware de los que hace uso.

Estos sensores de los que hablamos, de bajo consumo, se habilitan y recaban información en segundo plano. Según nos cuentan desde Microsoft, el consumo de energía es totalmente marginal. Aprovechan nuestra actividad de GPS para obtener datos del mismo, no activándolo ellos de forma activa, y monitorizando nuestros pasos gracias a su podómetro. Luego el software crea un histórico de todos estos datos de los últimos 10 días y los procesa de forma que puede indicarnos el tipo de actividad realizada en un momento dado (correr, caminar…), lugares frecuentes como nuestra casa o la oficina, número de pasos en un momento determinado, o rutas comunes que solemos usar.

Como estaréis pensando, esta es una cantidad ingente de datos personales y muy sensibles. En todo momento las aplicaciones tendrán que pedirnos acceso a la información y podremos desactivar totalmente esta tecnología en la configuración del dispositivo. Incluso podremos borrar el histórico de los 10 últimos días cuando deseemos.

Ahora que ya sabemos un poco que es y como funciona SensorCore, vamos a ver como integrarlo en nuestra aplicación. SensorCore SDK se compone de cuatro APIs distintas:

  • Step counter, número de pasos del usuario.
  • Activity monitor, tipo de actividad.
  • Place monitor, identifica la casa y la oficina del usuario así como lugares conocidos.
  • Track point monitor, guarda puntos de geo localización.

Vamos a ver cada una de estas APIs en detalle. Pero primero, veamos algunas buenas practicas relacionadas con el uso del SDK.

Buenas prácticas

Como hemos dicho anteriormente, no todos los dispositivos soportan SensorCore. De echo, solo un pequeño número de ellos lo hace en este momento. Hasta que el Lumia 1520 y el Icon obtengan Cyan, solo el Lumia 630/635 y el 930 soportan este SDK. Debido a esto, es especialmente recomendable que siempre nos aseguremos de que el dispositivo sobre el que se ejecuta nuestra aplicación soporta SensorCore. Para ello cada una de las APIs que hemos comentado incluye un método IsSupportedAsync, al que debemos llamar en primer lugar antes de intentar acceder a los datos.

Otra cosa que debemos tener en cuenta es que, aun estando soportado a nivel de hardware y software, puede que el usuario haya desactivado la opción de Location and motion en la configuración del dispositivo. Si es así, cualquier llamada a un API del SDK fallará con una excepción. Para evitar problemas, deberemos envolver nuestro código que haga uso de SensorCore en un bloque Try/Catch. En el Catch podemos obtener la excepción y haciendo uso de la clase SenseHelper que incluye el SDK extraer el error que se ha producido de la propiedad HRESULT de la propia excepción usando el método GetSenseError, que devuelve un enumerado SenseError con el tipo de error encontrado. SenseDisabled o LocationDisabled pueden ser dos errores muy comunes.

Por último, pero no menos importante, deberemos hacernos cargo de activar y desactivar la monitorización del API desde nuestra aplicación cuando la aplicación esté en primer plano o sea enviada a segundo plano. Para esto lo mejor es controlar la visibilidad de la ventana principal, para activar o desactivar el API que estemos usando dependiendo de si es visible o no.

Instalación

Para empezar a usar el SensorCore SDK solo tendremos que ir al administrador de NuGet y buscar “sensorcore”. Encontraremos dos paquetes. SensorCoreSDK Testing tools y SensorCoreSDK.

image

SensorCore SDK incluye los ensamblados necesarios para usar los sensores y acceder a las diferentes APIs. Por su parte las Testing tools incluye las mismas APIs que SensorCore, pero con datos pregrabados que nos permitirán probar las diferentes APIs aún sin disponer de un móvil compatible. Como siempre digo, si queremos hacer una aplicación comercial, estas herramientas nunca sustituirán totalmente las pruebas en dispositivos. Pero al menos nos ayudarán a desarrollar.

Algo que tenemos que tener en cuenta tras incluir SensorCore en nuestra aplicación es que no podremos compilarla como AnyCPU. Al usar componentes nativos, debemos compilarla en ARM para dispositivos o x86 para los emuladores.

Todas las APIs son muy parecidas, por lo que en este artículo vamos a ver a fondo una de ellas, StepCounter y su simulador StepCounterSimulator. Vamos a hacer una aplicación que tenga las siguientes características:

  • Detecte si nuestro dispositivo soporta o no SensorCore.
  • Dependiendo de la configuración de compilación use el simulador o no.
  • Muestre una gráfica de pasos y nos permita cambiar de día.

Al final, nuestro ejemplo debería parecerse a esto, para ello usaremos VisualStates y Behaviors, aunque en este artículo nos centraremos en SensorCore:

image

Step counter API

Como dijimos anteriormente, esta API nos permite obtener el histórico de los pasos dados por el usuario. Pero antes de aventurarnos a usarla, debemos comprobar que está realmente soportada por nuestro dispositivo. Para ello, tanto StepCounter como el resto de APIs de SensorCore disponen de un método llamado IsSupportedAsync, que devuelve true si está soportado o false si no lo está. Mirando a que más adelante queremos poder usar también el simulador, vamos a crear una interface llamada ISensorService donde crearemos un método IsStepsCounterSupported y otro GetStepsForDate:

ISensorService interface
public interface ISensorService
{
    /// <summary>
    /// Get if the device support or not StepsCounter
    /// </summary>
    /// <returns>true if supported, otherwise false.</returns>
    Task<bool> IsStepsCounterSupported();

    /// <summary>
    /// Get steps for a given date.
    /// </summary>
    /// <param name=«date»></param>
    /// <returns>Steps recorded for given date.</returns>
    Task<StepCounterReading> GetStepsForDate(DateTime date);
}

De esta forma, podremos usar IoC para decidir que implementación usar sin tener que modificar nuestra ViewModel.

La implementación de IsStepsCounterSupported es muy sencilla, simplemente llamamos al método IsSupportedAsync de la clase StepCounter:

IsStepsCounterSupported
public async Task<bool> IsStepsCounterSupported()
{
    return await StepCounter.IsSupportedAsync();
}

Lo siguiente es implementar el método que nos devolverá los pasos para un día concreto. Para ello primero tendremos que obtener acceso a la clase StepCounter, usando el método GetDefaultAsync. A continuación, podremos usar el método GetStepCountAtAsync, que nos permite indicar una fecha y nos devuelve una instancia del tipo StepCounterReading:

GetStepsForDate
public async Task<StepCounterReading> GetStepsForDate(DateTime date)
{
    var stepCounter = await StepCounter.GetDefaultAsync();
    var steps = await stepCounter.GetStepCountAtAsync(date);

    return steps;
}

StepCounterReading contiene propiedades con detalles sobre la actividad para la fecha indicada:

  • WalkingStepCount, el número de pasos caminando.
  • RunningStepCount, el número de pasos corriendo.
  • WalkTime, el tiempo que hemos pasado caminando.
  • RunTime, el tiempo que hemos pasado corriendo.
  • TimeStamp, el momento en el que se creó la lectura actual.

Algo a tener en cuenta es que todos los métodos de SensorCore son nativos y si se produce un error, no nos devuelven el error completo como una excepción. En vez de eso, devuelven una excepción genérica con un valor HRESULT. Para saber exactamente que ha ocurrido debemos usar el método GetSenseError de la clase SenseHelper. Por lo tanto, podríamos reescribir el método anterior de forma segura como se muestra a continuación:

Safe GetStepsForDate
public async Task<StepCounterReading> GetStepsForDate(DateTime date)
{
    Exception failure = null;
    StepCounterReading steps = null;
    try
    {
        var stepCounter = await StepCounter.GetDefaultAsync();
        steps = await stepCounter.GetStepCountAtAsync(date);
    }
    catch (Exception ex)
    {
        failure = ex;
    }

    GetError(failure);

    return steps;
}

Ahora si se produce un error, podremos obtenerlo con GetError y reaccionar a él. El método GetSenseError devuelve un tipo que nos indica que es lo que ha ocurrido y que procesamos en el método GetError del código anterior:

GetError
private static void GetError(Exception failure)
{
    var error = SenseHelper.GetSenseError(failure.HResult);
    switch (error)
    {
        case SenseError.Busy:
            break;
        case SenseError.GeneralFailure:
            break;
        case SenseError.IncompatibleSDK:
            break;
        case SenseError.InvalidParameter:
            break;
        case SenseError.LocationDisabled:
            break;
        case SenseError.NotFound:
            break;
        case SenseError.SenseDisabled:
            break;
        case SenseError.SenseNotAvailable:
            break;
        case SenseError.SensorDeactivated:
            break;
        case SenseError.SensorNotAvailable:
            break;
        default:
            break;
    }
}

Solo tendremos que obtener el error y usando un Switch, ver lo que ha ocurrido para mostrar un mensaje al usuario, llevarlo a una pantalla de configuración o lo que necesitemos.

Ahora que ya es seguro usar nuestro servicio, podemos incluirlo en nuestra ViewModel para obtener los pasos por cada fecha:

VMSteps
IsSupported = await this.sensorService.IsStepsCounterSupported();

if (IsSupported)
{
    var stepsReading = await this.sensorService.GetStepsForDate(this.currentDate);
    if (stepsReading != null)
    {
        Steps = stepsReading.WalkingStepCount + stepsReading.RunningStepCount;
    }
}

Y listo, ya tenemos nuestra app funcionando. Pero solo en terminales que soporten SensorCore. A continuación vamos a ver como usar el simulador para nuestras pruebas en el emulador o en dispositivos sin SensorCore.

Step counter simulator API

Los métodos del simulador son exactamente los mismos que los del API normal, la única diferencia es que la clase no es StepCounter, es StepCounterSimulator y que necesitamos indicarle de donde queremos recoger los datos. Podemos grabar datos reales de un dispositivo o cargar datos ya grabados. En el repositorio de GitHub de Nokia tenemos varios archivos grabados que solo tendremos que descargar y agregar a nuestro proyecto (Content/Copy always). Podéis encontrarlos aquí.

Lo que vamos a hacer es crear una nueva implementación de la interface ISensorService. En este caso IsStepsCounterSupported devolverá true simplemente, sin realizar ninguna comprobación. Después de todo, estamos usando un simulador. Antes de poder implementar GetStepsForDate, crearemos un constructor que se encargue de cargar los datos pre grabados desde disco:

Fake data init
private async Task InitializeAsync()
{
    recording = await SenseRecording.LoadFromFileAsync(«record.txt»);
}

recording es una variable de tipo SenseRecording, que contiene todos los datos pregrabados. Ahora la usaremos para inicializar la clase StepCounterSimulator:

Simulated GetStepsForDate
public async Task<StepCounterReading> GetStepsForDate(DateTime date)
{
    var stepCounter = await StepCounterSimulator.GetDefaultAsync(recording, date.AddDays(10));
    var steps = await stepCounter.GetStepCountAtAsync(date);

    return steps;
}

Además de la grabación de datos, también indicamos una fecha en el método GetDefaultAsync. Esta fecha sobre escribe la que se ha grabado en los datos para permitirnos cambiarla sin tener que editar el archivo de disco. En este caso simplemente le decimos que la fecha de inicio de los datos debe ser hace 10 días. Y no tenemos que hacer nada más, automáticamente llamamos a GetStepCountAtAsync igual que en la implementación real y nos devolverá datos, siempre que la fecha esté dentro de esos 10 días anteriores a hoy.

En este caso no hace falta incluir una manera segura de llamar a los datos, porque el simulador siempre devolverá datos correctos.

Usando IoC para decidir el API a usar

Ahora ya tenemos nuestras dos implementaciones: SenseService y SenseServiceFake. Pero, ¿Como cambiamos entre una y otra? Bien, en primer lugar es importante que lo que consuma nuestra ViewModel sea ISenseService, la interface, para poder cambiar de implementación. Para automatizar, aunque esto puede variar según tus necesidades, podemos usar nuestro Locator de ViewModels en conjunción con la configuración de compilación:

  • Para publicar en la store y hacer pruebas reales, tengo que compilar en Release, por lo que cuando se compile en Release usaré la implementación real SensorService.
  • Para pruebas durante el desarrollo en el emulador uso la configuración de Debug, por lo que cuando compile en Debug usaré la implementación simulada SensorServiceFake.

Con esto, nuestro Locator, podría quedar de la siguiente forma:

VMLocator
public VMLocator()
{
    ContainerBuilder builder = new ContainerBuilder();

#if DEBUG
    builder.RegisterType<SensorServiceFake>().As<ISensorService>().SingleInstance();
#else
    builder.RegisterType<SensorService>().As<ISensorService>().SingleInstance();
#endif
    builder.RegisterType<VMSteps>();

    this.container = builder.Build();
}

Ahora solo tenemos que incluir la interface ISensorService en nuestra ViewModel y voila! Todo debería funcionar correctamente, tanto en Debug como en Release, usando en cada caso la implementación correcta.

VMSteps constructor
public VMSteps(ISensorService sensorService)
{
    this.sensorService = sensorService;

    this.previousDayCommand = new DelegateCommand(ExecutePreviousDayCommand);
    this.nextDayCommand = new DelegateCommand(ExecuteNextDayCommand, CanExecuteNextDayCommand);
}

Y hasta aquí nuestro artículo de hoy. Como siempre, podéis descargar el código del ejemplo aquí para que os sirva de referencia. Espero que os sea de mucha utilidad y ver nuevas apps usando este SDK en la tienda.

Un saludo y Happy Coding!!