Hello World Microsoft Band


Hace unos días recibí una Microsoft Band, si bien es muy práctica es fácil de usar y lo más interesante, es muy fácil de hacer algún desarrollo para esta, por lo que he decidido indagar en el SDK y hacer algunos experimentos para así finalmente crear mi propia aplicación.

A diferencia de otros post que he hecho en este blog voy a dejar muy separados todos los post para que sean entendibles y no se aten los unos con los otros. Al final haré una aplicación funcional para esta Band.

Ahora bien, para introducirnos a la Microsoft Band debemos saber varias cosas esenciales; sin embargo considero que lo más importante es conocer con cuáles sensores contamos y de qué manera los podemos usar (en los siguientes post detallo esta información).

  • Optical Sensor óptico de pulso cardiaco
  • 3-axis acelerómetro/Girómetro
  • GPS
  • Sensor de luz ambiental
  • Sensor de rayos UV
  • Sensor capacitivo
  • Motor de vibración háptica
  • Micrófono
  • Galvanic skin response

Aquí un ejemplo de la alineación del acelerómetro

Creación de aplicación universal

Lo primero que hay que hacer es crear una aplicación universal ya sea para Windows, Windows Phone o ambas;

  • Para este caso crearé una aplicación vacía Universal con C# y estaré trabajando sobre el proyecto de Windows Phone; el proyecto se llamará “HelloBand“.
  • Cuando haya cargado todo el proyecto, dependencias y demás ubicar el archivo Package.appxmanifest, hacer click derecho y abrir este archivo con un editor de XML.
  • Al final del archivo ubicar la sección “Capabilities“, ahí ubicar el siguiente código
    <m2:DeviceCapability Name="bluetooth.rfcomm">
      <m2:Device Id="any">
        <!-- Used by the Microsoft Band SDK -->
        <m2:Function Type="serviceId:A502CA9A-2BA5-413C-A4E0-13804E47B38F" />
        <!-- Used by the Microsoft Band SDK -->
        <m2:Function Type="serviceId:C742E1A2-6320-5ABC-9643-D206C677E580" />
      </m2:Device>
    </m2:DeviceCapability>
 

Se verá algo parecido a lo siguiente:

  • Ahora cerrar este archivo y abrirlo normalmente (doble click) para poder ver la interfaz gráfica; en este editor agregar la capacidad de proximidad:
  • No sobra decir que en este momento debe estar emparejada nuestra Band con nuestro teléfono mediante Bluetooth y que el teléfono debe estar desbloqueado para poder desarrollar aplicaciones, ya que desplegaremos la aplicación directamente en el teléfono.

Instalar el SDK

  • Abrir la consola de administración de paquetes ubicada en “Herramientas>Administrador de paquetes NuGet>Consola de administrador de paquetes”
  • Escribir el siguiente código:
Install-Package Microsoft.Band
 

Conectándose a la Band

  • Nos iremos a la página donde se quiere hacer la conexión con la Band y agregar la siguiente directiva:
using Microsoft.Band;
 
  • Ahora crear el siguiente método para hacer obtener la Band.
        IBandInfo band;
        bool canSetTile;

        /// <summary>
        /// Used for trying connect to the first band found
        /// </summary>
        /// <returns>Band information</returns>
        private async Task<IBandInfo> GetConnectedBand()
        {
           return (await BandClientManager.Instance.GetBandsAsync()).FirstOrDefault();
        }
 
  • Para obtener información de hardware y firmware es necesario hacer el siguiente método
private async Task<string[]> GetBandInformation()
        {
            using (IBandClient bandClient = await BandClientManager.Instance.ConnectAsync(band))
            {
                try
                {
                    string[] bandInformation = new string[2];
                    // Getting firmware information
                    bandInformation[0] = await bandClient.GetFirmwareVersionAsync();
                    // Getting hardware information
                    bandInformation[1] = await bandClient.GetHardwareVersionAsync();
                    return bandInformation;
                }
                catch (BandException ex)
                {
                    string[] exception = new string[1];
                    exception[0] = ex.Message;
                    return exception;
                }
            }
        }
 

Creando y administrando Tiles en Microsoft Band

  • En el mismo archivo agregar la siguiente directiva:
using Microsoft.Band.Tiles;
 
  • Para obtener información información de todos los Tiles es necesario crear el siguiente método:
        private async Task<BandTile[]> GetBandTiles()
        {
            using (IBandClient bandClient = await BandClientManager.Instance.ConnectAsync(band))
            {
                try
                {     // get the current set of tiles      
                    return (await bandClient.TileManager.GetTilesAsync()).ToArray();
                }
                catch (BandException ex)

                {     // handle a Band connection exception
                    return null;
                }
            }
        } 
  • Ahora, es muy importante conocer si nuestra Band tiene capacidad de almacenar nuestro propio Tile, por lo que es necesario escribir el siguiente método
        private async Task<int> TileCapacity()
        {
            int tileCapacity = 0;
            using (IBandClient bandClient = await BandClientManager.Instance.ConnectAsync(band))
            {
                try
                {
                    // determine the number of available tile slots on the Band     
                    tileCapacity = await bandClient.TileManager.GetRemainingTileCapacityAsync();
                    bandClient.Dispose();
                }
                catch (BandException ex)
                {
                    tileCapacity = 0;
                }
            }
            return tileCapacity;
        } 

Creación de Tiles

Luego de haber obtenido la información necesaria de nuestra Band ya podemos crear un Tile de manera segura y correcta, con esto ya podemos garantizar que nuestra aplicación va a funcionar de maravilla.

  • Como nuestra aplicación tiene logos y algún branding ya definido será fácil asignar el logo que tendrá nuestro Tile, para ello debemos preparar dos imágenes en formato PNG con los tamaños de 46×46 y 24×24 pixeles.
  • Preferiblemente agregar estas imágenes en nuestro proyecto tipo Shared ya que esta es una solución Universal, en este caso crear una capeta llamada Assets y ubicar las dos imágenes ahí.
  • Ahora, para cargar nuestro Tile se necesita el siguiente método.
        private async Task<BandTile> CreateTile()
        {
            // create a new Guid for the tile 
            Guid tileGuid = new Guid("D781F673-6D05-4D69-BCFF-EA7E706C3418");
            // create a new tile with a new Guid 
            BandTile tile = new BandTile(tileGuid)
            {
                // enable badging (the count of unread messages)     
                IsBadgingEnabled = true,
                // set the name     
                Name = "HelloBand",
                // set the icons     
                SmallIcon = await LoadIcon("ms-appx:///Assets/Bat-Man24.png"),
                TileIcon = await LoadIcon("ms-appx:///Assets/Bat-Man46.png")
            };
            return tile;
        }
 
  • Pero para que funcione el método anterior es necesario implementar LoadIcon
        private async Task<BandIcon> LoadIcon(string uri)
        {
            StorageFile imageFile = await StorageFile.GetFileFromApplicationUriAsync(new Uri(uri));

            using (IRandomAccessStream fileStream = await imageFile.OpenAsync(FileAccessMode.Read))
            {
                WriteableBitmap bitmap = new WriteableBitmap(1, 1);
                await bitmap.SetSourceAsync(fileStream);
                return bitmap.ToBandIcon();
            }
        }
 

Crear página en Microsoft Band

  • Agregar la directiva:
using Microsoft.Band.Tiles.Pages;
 
  • Agregar el siguiente método para crear nuestro layout, nuestro mensaje y finalmente agregar el Tile a nuestra querida Band
        private async Task<bool> CreateLayout(BandTile bandTile)
        {
            bool result = false;

            using (IBandClient bandClient = await BandClientManager.Instance.ConnectAsync(band))
            {
                Microsoft.Band.Tiles.Pages.TextBlock myBandTextBlock = new Microsoft.Band.Tiles.Pages.TextBlock()
                {
                    ElementId = 1, // the Id of the TextBlock element; we'll use it later to set its text to "MY CARD"
                    Rect = new PageRect(0, 0, 200, 25)
                };

                FlowPanel panel = new FlowPanel(myBandTextBlock)
                {
                    Orientation = FlowPanelOrientation.Vertical,
                    Rect = new PageRect(60, 50, 250, 100)
                };
                
                bandTile.PageLayouts.Add(new PageLayout(panel));

                await bandClient.TileManager.RemoveTileAsync(bandTile.TileId);
                try
                {
                    // add the tile to the Band  
                    if (await bandClient.TileManager.AddTileAsync(bandTile))
                    {
                        // And create the page with the specified texts and values.
                        PageData page = new PageData(
                            Guid.NewGuid(), // the Id for the page
                            0, // the index of the layout to be used; we have only one layout in this sample app, but up to 5 layouts can be registered for a Tile
                            new TextBlockData(myBandTextBlock.ElementId.Value, "Hello Band!"));

                        await bandClient.TileManager.SetPagesAsync(bandTile.TileId, page);
                        result = true;
                    }
                }
                catch (Exception ex)
                {
                    result = false;
                }
                return result;
            }
        } 

Hacer funcionar todo!

Ahora que he finalizado de escribir todo es justo hacer la interfaz gráfica de mi App y hacer la ejecución de todo, en esta sección no me dedicaré a escribir todos los detalles, solo dejo el resultado final

  • XAML
 <Page
    x:Class="HelloBand.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:HelloBand"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

    <Grid Background="#FFF2F3ED">
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition Width="250"/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Border x:Name="AddTileButton" Height="58" VerticalAlignment="Top" Margin="14.667,34,23.125,0" Grid.Column="1" BorderBrush="#FF105B4D" BorderThickness="1" Tapped="AddTileButton_Tapped">
            <Grid Margin="0,0,0.5,0.333">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="44.167"/>
                    <ColumnDefinition/>
                </Grid.ColumnDefinitions>
                <Viewbox xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" d:LayoutOverrides="Width, TopMargin, BottomMargin, LeftPosition, RightPosition, TopPosition, BottomPosition" Margin="4,0,0.167,0">
                    <Grid>
                        <Grid x:Name="backgroundGrid" Width="46" Height="46" Visibility="Visible">
                            <Ellipse Fill="#FF105B4D" x:Name="Fill" Visibility="Visible" />
                        </Grid>
                        <Path Data="M19.833,0L32.5,0 32.5,19.833999 52.334,19.833999 52.334,32.500999 32.5,32.500999 32.5,52.333 19.833,52.333 19.833,32.500999 0,32.500999 0,19.833999 19.833,19.833999z" Stretch="Uniform" Fill="#FFF2F3ED" Width="20" Height="20" Margin="0,0,0,0" RenderTransformOrigin="0.5,0.5">
                            <Path.RenderTransform>
                                <TransformGroup>
                                    <RotateTransform Angle="0" />
                                    <ScaleTransform ScaleX="1" ScaleY="1" />
                                </TransformGroup>
                            </Path.RenderTransform>
                        </Path>
                    </Grid>
                </Viewbox>
                <Grid Grid.Column="1" Margin="0.833,0,-0.333,0" d:LayoutOverrides="TopMargin, BottomMargin, TopPosition, BottomPosition">
                    <Grid.RowDefinitions>
                        <RowDefinition/>
                        <RowDefinition Height="35"/>
                        <RowDefinition/>
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition/>
                        <ColumnDefinition Width="160"/>
                        <ColumnDefinition/>
                    </Grid.ColumnDefinitions>
                    <TextBlock x:Name="textBlock" TextWrapping="Wrap" Text="Agregar Tile" Foreground="#FF105B4D" d:LayoutOverrides="Width, LeftPosition, RightPosition, TopPosition, BottomPosition" FontSize="29.333" Margin="0" Grid.Column="1" Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center"/>
                </Grid>
            </Grid>
        </Border>

        <Viewbox xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Margin="0,280,0,-36" Grid.ColumnSpan="3" d:LayoutOverrides="TopPosition, BottomPosition" Opacity="0.2">
            <Grid>
                <Grid Name="backgroundGrid2" Width="256" Height="256" Visibility="Collapsed" />
                <Path Data="M142.386962291872,26.3739025949399L142.918029186403,26.3739025949399 142.652404186403,26.3804600595395z M0,0L96.2571557733172,0C96.2571557733172,1.03939237305894E-06 98.3901742669696,33.23882988681 108.789756176149,37.4590866922299 119.18639313904,41.679690635101 131.453414318239,50.6406357644955 132.252959606325,36.9281990884701 133.052459118044,23.215766227142 134.917037365114,7.90997484912077 134.917037365114,7.9099734186093L137.318480846559,27.9616806863705C137.318480846559,27.9616806863705,138.723891613161,26.5661709665219,142.195876476442,26.3917324899594L142.652404186403,26.3804600595395 143.108626720583,26.3917324899594C146.578536388552,26.5661709665219,147.986464855348,27.9616806863705,147.986464855348,27.9616806863705L150.382140514528,7.9099734186093C150.382140514528,7.90997484912077 152.249633190309,23.215766227142 153.05203187195,36.9281990884701 153.851577160036,50.6406357644955 166.11860596863,41.679690635101 176.51524293152,37.4590866922299 186.917678234255,33.23882988681 189.047835704958,1.03939237305894E-06 189.047835704958,0L285.304992077028,0C285.304992077028,1.03939237305894E-06 227.179503795778,34.8151085733334 241.83930909363,111.321414267913 241.83930909363,111.321414267913 155.984588024294,85.4668991922299 142.652495739138,191.500010764496 129.320403453981,85.4668991922299 43.4627794000262,111.321414267913 43.4627794000262,111.321414267913 58.1254838677996,34.8151085733334 0,1.03939237305894E-06 0,0z" Stretch="Uniform" Fill="#FF105B4D" Width="256" Height="256" Margin="0,0,0,0" RenderTransformOrigin="0.5,0.5">
                    <Path.RenderTransform>
                        <TransformGroup>
                            <TransformGroup.Children>
                                <RotateTransform Angle="0" />
                                <ScaleTransform ScaleX="1" ScaleY="1" />
                            </TransformGroup.Children>
                        </TransformGroup>
                    </Path.RenderTransform>
                </Path>
            </Grid>
        </Viewbox>
        <TextBlock x:Name="resultText" Grid.Column="1" HorizontalAlignment="Center" Margin="0,134,0,0" TextWrapping="Wrap" Text="TextBlock" VerticalAlignment="Top" Foreground="#FF105B4D" FontSize="18.667" Height="77" Width="250" TextAlignment="Center"/>
    </Grid>
</Page>
 
  • C#
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
using Microsoft.Band;
using System.Threading.Tasks;
using Microsoft.Band.Tiles;
using Windows.Storage;
using Windows.Storage.Streams;
using Windows.UI.Xaml.Media.Imaging;
using Microsoft.Band.Tiles.Pages;
using Windows.UI;
using System.Diagnostics;

// La plantilla de elemento Página en blanco está documentada en http://go.microsoft.com/fwlink/?LinkId=234238

namespace HelloBand
{
    public sealed partial class MainPage : Page
    {
        IBandInfo band;
        bool canSetTile;

        public MainPage()
        {
            this.InitializeComponent();

            this.NavigationCacheMode = NavigationCacheMode.Required;
        }

        /// <summary>
        /// Used for trying connect to the first band found
        /// </summary>
        /// <returns>Band information</returns>
        private async Task<IBandInfo> GetConnectedBand()
        {
           return (await BandClientManager.Instance.GetBandsAsync()).FirstOrDefault();
        }

        private async Task<string[]> GetBandInformation()
        {
            using (IBandClient bandClient = await BandClientManager.Instance.ConnectAsync(band))
            {
                try
                {
                    string[] bandInformation = new string[2];
                    // Getting firmware information
                    bandInformation[0] = await bandClient.GetFirmwareVersionAsync();
                    // Getting hardware information
                    bandInformation[1] = await bandClient.GetHardwareVersionAsync();
                    return bandInformation;
                }
                catch (BandException ex)
                {
                    string[] exception = new string[1];
                    exception[0] = ex.Message;
                    return exception;
                }
            }
        }

        private async Task<BandTile[]> GetBandTiles()
        {
            using (IBandClient bandClient = await BandClientManager.Instance.ConnectAsync(band))
            {
                try
                {     // get the current set of tiles      
                    return (await bandClient.TileManager.GetTilesAsync()).ToArray();
                }
                catch (BandException ex)

                {     // handle a Band connection exception
                    return null;
                }
            }
        }

        private async Task<int> TileCapacity()
        {
            int tileCapacity = 0;
            using (IBandClient bandClient = await BandClientManager.Instance.ConnectAsync(band))
            {
                try
                {
                    // determine the number of available tile slots on the Band     
                    tileCapacity = await bandClient.TileManager.GetRemainingTileCapacityAsync();
                    bandClient.Dispose();
                }
                catch (BandException ex)
                {
                    tileCapacity = 0;
                }
            }
            return tileCapacity;
        }

        private async Task<BandTile> CreateTile()
        {
            // create a new Guid for the tile 
            Guid tileGuid = new Guid("D781F673-6D05-4D69-BCFF-EA7E706C3418");
            // create a new tile with a new Guid 
            BandTile tile = new BandTile(tileGuid)
            {
                // enable badging (the count of unread messages)     
                IsBadgingEnabled = true,
                // set the name     
                Name = "HelloBand",
                // set the icons     
                SmallIcon = await LoadIcon("ms-appx:///Assets/Bat-Man24.png"),
                TileIcon = await LoadIcon("ms-appx:///Assets/Bat-Man46.png")
            };
            return tile;
        }

        private async Task<BandIcon> LoadIcon(string uri)
        {
            StorageFile imageFile = await StorageFile.GetFileFromApplicationUriAsync(new Uri(uri));

            using (IRandomAccessStream fileStream = await imageFile.OpenAsync(FileAccessMode.Read))
            {
                WriteableBitmap bitmap = new WriteableBitmap(1, 1);
                await bitmap.SetSourceAsync(fileStream);
                return bitmap.ToBandIcon();
            }
        }

        private async Task<bool> CreateLayout(BandTile bandTile)
        {
            bool result = false;

            using (IBandClient bandClient = await BandClientManager.Instance.ConnectAsync(band))
            {
                Microsoft.Band.Tiles.Pages.TextBlock myBandTextBlock = new Microsoft.Band.Tiles.Pages.TextBlock()
                {
                    ElementId = 1, // the Id of the TextBlock element; we'll use it later to set its text to "MY CARD"
                    Rect = new PageRect(0, 0, 200, 25)
                };

                FlowPanel panel = new FlowPanel(myBandTextBlock)
                {
                    Orientation = FlowPanelOrientation.Vertical,
                    Rect = new PageRect(60, 50, 250, 100)
                };
                
                bandTile.PageLayouts.Add(new PageLayout(panel));

                await bandClient.TileManager.RemoveTileAsync(bandTile.TileId);
                try
                {
                    // add the tile to the Band  
                    if (await bandClient.TileManager.AddTileAsync(bandTile))
                    {
                        // And create the page with the specified texts and values.
                        PageData page = new PageData(
                            Guid.NewGuid(), // the Id for the page
                            0, // the index of the layout to be used; we have only one layout in this sample app, but up to 5 layouts can be registered for a Tile
                            new TextBlockData(myBandTextBlock.ElementId.Value, "Hello Band!"));

                        await bandClient.TileManager.SetPagesAsync(bandTile.TileId, page);
                        result = true;
                    }
                }
                catch (Exception ex)
                {
                    result = false;
                }
                return result;
            }
        }
                
        private async void AddTileButton_Tapped(object sender, TappedRoutedEventArgs e)
        {
            if (canSetTile)
            {
                BandTile tile = await CreateTile();
                if (await CreateLayout(tile))
                {
                    resultText.Text = "Tile added successfuly";
                }
                else
                {
                    resultText.Text = "Something wrong, debug this Hello App";
                }
            }
        }

        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            canSetTile = false;
            AddTileButton.Visibility = Visibility.Collapsed;
            SettingUp();
        }

        private async void SettingUp()
        {
            resultText.Text = "Retrieving information";
            band = await GetConnectedBand();
            if (band != null)
            {
                string[] bandInformation = await GetBandInformation();
                BandTile[] bandTiles = await GetBandTiles();
                
                try
                {
                    resultText.Text = "Retrieving capacity";
                    int capacity = await TileCapacity();
                    if (capacity > 0)
                    {
                        canSetTile = true;
                        resultText.Text = "Ok";

                        AddTileButton.Visibility = Visibility.Visible;
                    }
                    else
                    {
                        canSetTile = true;
                        resultText.Text = "You haven't more spaces... but this app delete the Tile :P";
                        AddTileButton.Visibility = Visibility.Visible;
                    }
                }
                catch (Exception ex)
                {
                    canSetTile = false;
                    resultText.Text = ex.Message;
                }

            }
        }        
    }
}

 

Resultado final

Luego de haber escrito toda la aplicación y funcionando, solo queda seguir explorando el SDK y experimentando; en los próximos post me dedicaré a exponer el uso de los sensores y a desarrollar un código mas eficiente.