Julio de 2016

Volumen 31, número 7

Aplicaciones modernas: Crear un detector Wi-Fi en la UWP

Por Frank La La

En la última década, las redes Wi-Fi se han vuelto omnipresentes. Muchas tiendas y cafeterías ofrecen Wi-Fi gratuito a sus clientes para su disfrute. Prácticamente todos los hoteles ofrecen algún tipo de acceso inalámbrico a Internet a sus huéspedes. La mayoría de nosotros tenemos redes inalámbricas en casa. Como pocos dispositivos móviles y tabletas tienen puertos Ethernet, la conexión Wi-Fi ha pasado a formar una parte fundamental de nuestras vidas modernas. Más allá de eso, raramente pensamos demasiado en ello.

Por lo tanto, abundan las preguntas. ¿Qué pasa con el enorme volumen de redes Wi-Fi que hay a nuestro alrededor? ¿Cuántas hay? ¿Son seguras? ¿En qué canal están? ¿Cómo se llaman? ¿Es posible indicarlas en un mapa? ¿Qué podemos conocer a partir de los metadatos de las redes Wi-Fi?

Hace poco, mientras paseaba a mis perros, me dio por mirar la pantalla de conexiones a redes Wi-Fi de mi teléfono y observé varios nombres de redes realmente ingeniosos. Esto me hizo preguntarme cuántas personas más habrían preferido ser graciosos en lugar de prácticos. Entonces, se me ocurrió la idea de detectar las redes inalámbricas de mi vecindario e indicarlas en un mapa. Si pudiera automatizar el proceso, incluso podría detectar e indicar en un mapa las redes inalámbricas durante mi trayecto al trabajo. Idealmente, podría ejecutar un programa en una Raspberry Pi que de forma periódica realizara una detección inalámbrica y registrara esos datos en un servicio web. Definitivamente esto resultaría más práctico que tener que mirar el teléfono de manera intermitente.

Por suerte, la Plataforma universal de Windows (UWP) ofrece un sofisticado acceso a los datos de las redes inalámbricas mediante las clases del espacio de nombres Windows.Devices.WiFi. Como sabrá, una aplicación para UWP puede ejecutarse no solo en teléfonos y equipos, sino también en dispositivos Raspberry Pi 2 que ejecuten Windows 10 IoT Core. Ahora, ya dispongo de todo lo que necesito para crear el proyecto.

En esta columna, exploraré los conceptos básicos de la detección de redes Wi-Fi mediante las API integradas en la UWP.

El espacio de nombres Windows.Devices.WiFi

Las clases del espacio de nombres Windows.Devices.WiFi contienen todo lo necesario para detectar y explorar adaptadores inalámbricos y redes inalámbricas dentro del alcance. Tras crear un nuevo proyecto UWP en Visual Studio, agregue una nueva clase denominada WifiScanner y agregue la propiedad siguiente:

public WiFiAdapter WiFiAdapter { get; private set; }

Como es posible disponer de varios adaptadores Wi-Fi en un sistema determinado, debe seleccionar el adaptador Wi-Fi que quiera usar. El método InitializeFirstAdapter obtiene el primero que se enumera en el sistema, como se muestra en la Figura 1.

Figura 1. Encontrar el primer adaptador Wi-Fi conectado al sistema e inicializarlo

private async Task InitializeFirstAdapter()
{
  var access = await WiFiAdapter.RequestAccessAsync();
  if (access != WiFiAccessStatus.Allowed)
  {
    throw new Exception("WiFiAccessStatus not allowed");
  }
  else
  {
    var wifiAdapterResults =
      await DeviceInformation.FindAllAsync(WiFiAdapter.GetDeviceSelector());
  if (wifiAdapterResults.Count >= 1)
    {
      this.WiFiAdapter =
        await WiFiAdapter.FromIdAsync(wifiAdapterResults[0].Id);
    }
    else
    {
      throw new Exception("WiFi Adapter not found.");
    }
  }
}

Agregar la funcionalidad Wi-Fi

Podría observar que hay una comprobación para acceder al Wi-Fi y que el código genera una excepción si el método RequestAccessAsync devuelve false. Esto es así porque la aplicación necesita disponer de una funcionalidad del dispositivo que le permita detectar redes Wi-Fi y conectarse a ellas. Esta funcionalidad no se enumera en la pestaña Funcionalidades del editor de propiedades del manifiesto. Para agregar esta funcionalidad, haga clic con el botón derecho en el archivo Package.appxmanager y elija Ver código.

Ahora verá el código XML sin formato del archivo Package.appxmanager. Dentro del nodo Funcionalidades, agregue el código siguiente:

<DeviceCapability Name="wifiControl" />

Ahora guarde el archivo. La aplicación ahora tiene permisos para acceder a las API de Wi-Fi.

Exploración de las redes inalámbricas

Con el código para identificar un adaptador Wi-Fi con el que trabajar y los permisos para acceder a él, el paso siguiente es detectar realmente las redes. Afortunadamente, el código para hacerlo es bastante simple; es tan solo una llamada al método ScanAsync en el objeto WifiAdapter. Agregue el método siguiente a la clase WifiScanner:

public async Task ScanForNetworks()
{
  if (this.WiFiAdapter != null)
  {
    await this.WiFiAdapter.ScanAsync();
  }
  }

Cuando se ejecute ScanAsync, la propiedad NetworkReport de WifiAdapter se rellena. NetworkReport es una instancia de WiFiNetworkReport, que contiene AvailableNetworks, un elemento List<WiFiAvailableNetwork>. El objeto WiFiAvailableNework contiene multitud de puntos de datos sobre una red determinada. Puede descubrir el identificador de red (SSID), la intensidad de la señal, el método de cifrado y el tiempo de actividad del punto de acceso, entre otros puntos de datos, todo sin conectarse a la red.

La iteración a través de las redes disponibles es muy fácil: se crea un objeto CRL estándar (POCO) que contenga algunos de los datos de los objetos WiFiAvailableNetwork, como se ve en el código siguiente:

foreach (var availableNetwork in report.AvailableNetworks)
{
  WiFiSignal wifiSignal = new WiFiSignal()
  {
    MacAddress = availableNetwork.Bssid,
    Ssid = availableNetwork.Ssid,
    SignalBars = availableNetwork.SignalBars,
    ChannelCenterFrequencyInKilohertz =
      availableNetwork.ChannelCenterFrequencyInKilohertz,
    NetworkKind = availableNetwork.NetworkKind.ToString(),
    PhysicalKind = availableNetwork.PhyKind.ToString()
  };
}

Creación de la interfaz de usuario

Aunque mi intención es que la aplicación se ejecute sin una interfaz de usuario en el proyecto final, resulta útil para el desarrollo y la solución de problemas ver las redes dentro del alcance y los metadatos asociados a ellas. También resulta de utilidad para los desarrolladores que quizá no dispongan de una Raspberry Pi en este momento, pero aun así quieran continuar. Como se muestra en la Figura 2, el código XAML del proyecto es sencillo y existe un elemento TextBox multilínea para almacenar la salida de la detección.

Figura 2. El código XAML de la interfaz de usuario

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
  <Grid.RowDefinitions>
    <RowDefinition Height="60"/>
      <RowDefinition Height="60"/>
      <RowDefinition Height="*"/>
  </Grid.RowDefinitions>
  <TextBlock FontSize="36" Grid.RowSpan="2" >WiFi Scanner</TextBlock>
  <StackPanel Name="spButtons" Grid.Row="1" Orientation="Horizontal">
    <Button Name="btnScan" Click="btnScan_Click" Grid.Row="1">Scan For
      Networks</Button>
  </StackPanel>
  <TextBox Name="txbReport" TextWrapping="Wrap" AcceptsReturn="True"
    Grid.Row="2"></TextBox>
  </Grid>
</Page>

Captura de los datos de ubicación

Para ofrecer valor añadido, cada detección de una red inalámbrica también debería anotar su ubicación. Esto permitirá que más tarde sea posible ofrecer visualizaciones de datos e información de interés. Afortunadamente, es sencillo agregar una ubicación a las aplicaciones para UWP. Sin embargo, será necesario agregar la funcionalidad Location a la aplicación. Para ello, haga doble clic en el archivo Package.appxmanifest del Explorador de soluciones, haga clic en la pestaña Funcionalidades y marque la casilla Ubicación de la lista Funcionalidades.

El código siguiente recuperará la ubicación mediante las API integradas en la UWP:

Geolocator geolocator = new Geolocator();
Geoposition position = await geolocator.GetGeopositionAsync();

Ahora que ya tiene una ubicación, querrá almacenar los datos de ella. A continuación se muestra la clase WiFiPointData, que almacena los datos de ubicación junto con información sobre las redes que se encontraron en ella:

public class WiFiPointData
{
  public DateTimeOffset TimeStamp { get; set; }
  public double Latitude { get; set; }
  public double Longitude { get; set; }
  public double Accuracy { get; set; }
  public List<WiFiSignal> WiFiSignals { get; set; }
  public WiFiPointData()
  {
    this.WiFiSignals = new List<WiFiSignal>();
  }
}

En este momento, es importante destacar que, a menos que el dispositivo disponga de GPS, la aplicación requerirá una conexión Wi-Fi a Internet para determinar la ubicación. Sin un sensor GPS incorporado, deberá disponer de una zona con cobertura inalámbrica móvil y asegurarse de que su portátil o Raspberry Pi 2 estén conectados a ella. Esto también implica que la ubicación que se notifique será menos precisa. Para más información sobre los procedimientos recomendados para crear aplicaciones para UWP con reconocimiento de ubicación, consulte el artículo del Centro de desarrollo de Windows "Directrices para las aplicaciones con reconocimiento de ubicación", que puede encontrar en bit.ly/1P0St0C.

Detección de manera repetida

Para un escenario de detección y trazado en mapa mientras se conduce, la aplicación debe explorar de forma periódica si hay redes Wi-Fi. Para ello, deberá usar un elemento DispatchTimer para explorar en busca de redes Wi-Fi en intervalos regulares. Si no está familiarizado con el funcionamiento de DispatchTimer, consulte la documentación disponible en bit.ly/1WPMFcp.

Es importante destacar que una detección Wi-Fi puede tardar varios segundos, en función del sistema. El código siguiente configura un elemento DispatchTimer para que desencadene un evento cada 10 segundos, un tiempo más que suficiente incluso para el sistema más lento:

DispatcherTimer timer = new DispatcherTimer();
timer.Interval = new TimeSpan(0, 0, 10);
timer.Tick += Timer_Tick;
timer.Start();

Cada 10 segundos, el temporizador ejecutará el código del método Timer_Tick. El código siguiente explora en busca de redes Wi-Fi y después anexa los resultados al elemento TextBox de la interfaz de usuario:

private async void Timer_Tick(object sender, object e)
{
  StringBuilder networkInfo = await RunWifiScan();
  this.txbReport.Text = this.txbReport.Text + networkInfo.ToString();
}

Informar de los resultados de la exploración

Como se ha mencionado con anterioridad, cuando se llama al método ScanAsync, los resultados de la exploración se almacenan en un elemento List<WiFiAvailableNetwork>. Lo único necesario para obtener esos resultados es iterar por la lista. El código de la Figura 3 hace justo eso y coloca los resultados en una instancia de la clase WiFiPointData.

Figura 3. Código para iterar por todas las redes encontradas durante una exploración

foreach (var availableNetwork in report.AvailableNetworks)
{
  WiFiSignal wifiSignal = new WiFiSignal()
  {
    MacAddress = availableNetwork.Bssid,
    Ssid = availableNetwork.Ssid,
    SignalBars = availableNetwork.SignalBars,
    NetworkKind = availableNetwork.NetworkKind.ToString(),
    PhysicalKind = availableNetwork.PhyKind.ToString(),
    Encryption = availableNetwork.SecuritySettings.NetworkEncryptionType.ToString()
  };
  wifiPoint.WiFiSignals.Add(wifiSignal);
  }

Para que la interfaz de usuario sea simple a la vez que permita un análisis de datos completo, puede convertir el elemento WiFiPointData a un formato de valores separados por comas (CSV) y establecer el texto del elemento TextBox de la interfaz de usuario. CSV es un formato relativamente sencillo que se puede importar en Excel y Power BI para su análisis. El código para convertir el elemento WiFiPointData se muestra en la Figura 4.

Figura 4. Conversión de WiFiPointData

private StringBuilder CreateCsvReport(WiFiPointData wifiPoint)
{
  StringBuilder networkInfo = new StringBuilder();
  networkInfo.AppendLine("MAC,SSID,SignalBars,Type,Lat,Long,Accuracy,Encryption");
  foreach (var wifiSignal in wifiPoint.WiFiSignals)
  {
    networkInfo.Append($"{wifiSignal.MacAddress},");
    networkInfo.Append($"{wifiSignal.Ssid},");
    networkInfo.Append($"{wifiSignal.SignalBars},");
    networkInfo.Append($"{wifiSignal.NetworkKind},");
    networkInfo.Append($"{wifiPoint.Latitude},");
    networkInfo.Append($"{wifiPoint.Longitude},");
    networkInfo.Append($"{wifiPoint.Accuracy},");
    networkInfo.Append($"{wifiSignal.Encryption}");
    networkInfo.AppendLine();
  }
  return networkInfo;
}

Visualización de los datos

Naturalmente, no pude esperar a configurar mi servicio en la nube para mostrar y visualizar los datos. En consecuencia, tomé los datos CSV que ha generado la aplicación y los copié y pegué en un archivo de texto. Después me aseguré de guardar el archivo con una extensión .CSV. A continuación, importé los datos en Power BI Desktop. Power BI Desktop es una descarga gratuita de powerbi.microsoft.com que facilita la visualización y exploración de datos.

Para importar los datos de la aplicación, haga clic en Obtener datos en la pantalla de presentación de Power Bi Desktop.

En la pantalla siguiente, elija CSV y haga clic en Conectar. En el diálogo del selector de archivos, elija el archivo CSV con los datos de la aplicación copiados y pegados.

Cuando cargue, verá una lista de campos en el lado derecho de la pantalla. Aunque un tutorial completo sobre Power BI Desktop se escapa del ámbito de este artículo, no es demasiado complicado producir una visualización que muestre la ubicación de las redes Wi-Fi, sus SSID y los protocolos de cifrado que usan, como se muestra en la Figura 5.

Visualización en Power BI de los datos que la aplicación de detección de redes Wi-Fi recopiló
Figura 5. Visualización en Power BI de los datos que la aplicación de detección de redes Wi-Fi recopiló

Sorprendentemente, alrededor de un tercio de las redes se encuentran sin ningún tipo de cifrado. Aunque algunas de ellas son redes para invitados establecidas en distintos negocios, otras no lo son.

Aplicaciones prácticas

Aunque el propósito original era simplemente medir la destreza técnica y el sentido común de mis vecinos, el proyecto tiene algunos usos prácticos bastante interesantes. La capacidad de trazar en un mapa la intensidad de señal de una conexión Wi-Fi y su ubicación tiene aplicaciones interesantes. ¿Qué podría hacer una ciudad si cada autobús local se equipara con un dispositivo IoT que ejecutara esta aplicación? Las ciudades podrían medir el predominio de redes Wi-Fi y establecer una correlación entre esos datos y los datos de ingresos de los barrios. Los cargos públicos podrían tomar decisiones políticas fundamentadas en esos datos. Si una comunidad ofrece conexión Wi-Fi pública en la ciudad o en áreas determinadas, se podría medir la intensidad de la señal en tiempo real sin el costo añadido de desplazar técnicos. Las ciudades también podrían determinar los lugares en los que predominaran las redes no seguras y crear programas de concienciación dirigidos para incrementar la ciberseguridad de la comunidad.

A una escala menor, la capacidad de explorar los metadatos de las redes Wi-Fi resulta útil cuando se configura una red propia. Muchos enrutadores ofrecen a los usuarios la posibilidad de modificar el canal de difusión. Un excelente ejemplo sobre esto es una aplicación denominada "WiFi Analyzer" (bit.ly/25ovZ0Q), que entre otras cosas muestra la intensidad y la frecuencia de las redes inalámbricas cercanas. Esto resulta de utilidad cuando se configura una red Wi-Fi en una nueva ubicación.

Resumen

Copiar y pegar datos de texto de la interfaz de usuario no permitirá un escalado. Además, si el objetivo es ejecutar la aplicación en un dispositivo IoT sin ningún tipo de pantalla, la aplicación debe enviar datos a la nube sin ningún tipo de interfaz de usuario. En mi columna del próximo mes, mostraré cómo configurar un servicio en la nube para que reciba todos estos datos. Además, explicaré cómo implementar la solución en un dispositivo Raspberry Pi 2 con Windows IoT Core.


Frank La Vignees un experto en tecnología del equipo Microsoft Technology and Civic Engagement, donde ayuda a que los usuarios aprovechen la tecnología a fin de crear una comunidad mejor. Escribe publicaciones con regularidad en FranksWorld.com y tiene un canal de YouTube llamado Frank’s World TV (youtube.com/FranksWorldTV).

Gracias a los siguientes expertos técnicos por revisar este artículo: Rachel Appel, Robert Bernstein y Jose Luis Manners