desarrolloMobile.NET Noticias

jueves, enero 29, 2009

Webcast sobre File Sync Provider

MSF Se ha publicado un nuevo Webcast de poco más de un 1 hora de duración, en el que Ashish Shah, Principal Development Lead del equipo de desarrolo de Sync Framework, habla sobre los diferentes escenarios soportados este built-in provider de Microsoft Sync Framework.

Los escenarios tratados son:

  • Sincronización de archivos multi-master entre PC de una red.
  • Sincronización de archivos entre PC fuera de una red corporativa a través de USB.
  • Mantenimiento de las copias de seguridad de archivos.

Y además sobre los aspectos más destacados sobre este proveedor en cuanto a requisitos y características del mismo…

 

Salud!!

martes, enero 27, 2009

¿Cuán precisa es la señal de nuestros receptores GPS?

NOTA: Este post es Cross Posting desde http://geeks.ms/blogs/jmtorres/archive/2009/01/21/cuan-preciso-es-la-senyal-de-nuestros-receptores-gps.aspx. Si tiene problema de visualización, por favor visite el original.

Quién más o quién menos ha utilizado o utiliza un receptor GPS para uso lucrativo o personal, sean receptores compactos, con pantalla y software o sean a través de PDA’s con receptores conectado por Bluetooth, por ejemplo. Si es así, seguro que en muchas ocasiones hemos estado a punto de perder la paciencia debido a que los satélites aquél día, parecen no estar surcando los cielos.

En este post pretendo mostrar el porqué de esos comportamiento y cómo podemos evaluar la dilución de la precisión nosotros mismo con un dispositivos Windows Mobile y el SDK del mismo.

NOTA: Algunos de los términos explicados aquí son aproximados debido a la complejidad de su comprensión. Éstos pueden tener alguna interpretación errónea por mi parte así que si encontráis algún dato incorrecto, por favor, contactadme.

Conceptos previos

Antes de analizar la exactitud y tasa de errores que podemos encontrarnos en nuestros receptores GPS vamos a introducir unos cuantos conceptos que deberían tenerse en cuenta.

El sistema de posicionamiento global es un sistema arto complicado cuyos fundamentos matemáticos y físicos describen el comportamiento y aplicación del mismo. La parte fundamental del GPS son los satélites y éstos (unos 24 más lo de respaldo) andan por sus anchas describiendo planos orbitales alrededor de la esfera terrestre a una altitud de unos 20.200 kilómetros lo que permite que hasta 10 satélites sean visibles en un radio de unas 11.000 millas náuticas.

Triangulación

Para que un receptor obtenga un posicionamiento (sea cual sea su exactitud y tasa de error) se requiere la detección de 4 satélites, lo cual da lugar a la triangulación 3D. Con éstas cuatro señales el receptor es capaz de determinar la longitud (x), la latitud(y), la altitud (y) y el tiempo (t) con lo que en un entorno ideal sin errores, el receptor GPS se situará en la intersección de las 4 esferas definidas por las señales de los satélites. El uso de únicamente 3 satélites deriva en el posicionamiento en 2D (exento de z, altitud).

Es de vital importancia el llamado cuarto satélite puesto que éste es que determinará el valor de t. Esto es debido a que se determina la distancia del receptor al satélite en base a la velocidad de la luz más una constante de rectificación de errores y la sincronización de todos los satélites mediante un reloj atómico que incorporan.

Fuentes de error

Efectos atmosféricos

Como dije anteriormente la señales que emiten los satélites lo hacen a la velocidad de la luz, pero en el espacio exterior. De esta forma, existen errores relacionados a la refracción con la ionosfera y con la troposfera.

En la ionosfera es la capa dónde se produce una mayor tasa de error pudiendo llegar a ser de +/- 5 metros. Esto es debido a que en esta capa de la atmósfera se encuentra un alto nivel de electrones e iones positivos, producidos por la radiación solar, que interfieren negativamente sobre las señales. La Troposfera, por su parte, concentra una gran cantidad de vapor de agua lo que da lugar a otro tipo de refracción de las ondas con unos valores que oscilan entre los +/- 0.5 metros.

Errores de reloj

Pese a que los relojes equipados en los satélites son atómicos, no son 100% precisos y experimentan errores por ruidos o desfases con los errores del receptor, que no son atómicos. Estos errores pueden llega a producir una desviación de + o – 2 metros.

Relatividad

Por un lado tenemos que los satélites recorren la esfera terrestres a una altitud de más de 20.000 km a una velocidad de unos 12.000km/h lo que provoca que, según la teoría de la relatividad, los relojes de los satélites (el tiempo) corra más lentamente en los satélites que en la tierra por su velocidad (relatividad espacial) y por su altitud (relatividad potencial) lo que generará retardos del orden de 7,2 y 38 nanosegundos/día respectivamente.

Efecto Sagnac

Otro efecto relativístico es el efecto Sagnac que es causado por el posicionamiento, del receptor/observador, en la superficie terrestre relativo a la velocidad de rotación del globo terrestre. Este efecto, sin embargo, tiene una tasa de error ínfimo.

Efecto Multipath

Es muy común andar o circular por ciudades cuya altura de los edificios superan unas cuantas plantas, de la misma forma que se produce por cuestiones orográficas, montañas etc. Pese a que el receptor puede no obtener una señal con la calidad SNR (señal/ruido) óptima, puede ocurrir algo imprevisto, que la señal llegue con SNR optima pero provenga rebotada de un edificio o montaña cercana.

multipath

Este tipo de error es muy común en receptores en movimiento, pudiendo llegar a generar una tasa de error de hasta +/- 1 metro.

Disponibilidad Selectiva

Esta fuente de error es la inducida mediante la alteración o manipulación de la señal del satélite para que envíe datos erróneos en forma de mensajes NMEA al receptor, intencionadamente. Este tipo de fuente fue utilizada durante las guerras del Golfo de principio de los 90 y, evidentemente fueron inducidas adrede por el gobierno de los EEUU. A partir de mayo de 2000, el entonces presidente de los EEUU Bill Clinton, eliminó la disponibilidad selectiva haciendo público el uso del GPS sin alteración humana.

Geometría de los satélites y DOP

Tan importante es el posicionamiento de satélites como la geometría que describen entre ellos y emiten sus señales, me explico. En la siguiente imagen podemos observar como cuatro satélites emiten señales válidas a un receptor GPS.

DOP alto

Sin embargo la geometría de sus señales no es óptima debido a a la proximidad que existe entre ellos. En la siguiente imagen, ocurre todo lo contrario.

DOP bajo

La señales provenientes de los satélites geométricamente mejor posicionados generarán un mayor fiabilidad del posicionamiento. Así pues:

La dilución de precisión (DOP) se emplea en cartografía y describe la precisión del GPS en base a la geometría de los satélites. Cuando la señal DOP es alta, los satélites están muy cerca entre sí con lo que la precisión disminuye y el valor DOP aumenta. Si por el contrario los satélites son distantes, la precisión aumenta y con ello el valor DOP disminuye. Existen diluciones de precisión para el posicionamiento horizontal (HDOP) , vertical (VDOP), el de posicionamiento (PDOP) y el de tiempo (TDOP) con lo que dentro del cada uno de dichos aspectos puede ofrecer un valor distinto. Los obstáculos urbanos y naturales puede aumentar el DOP de la señal del GPS—> Ver efecto Multipath.

¿Como se controlan todos estos errores?

La corrección de estos errores se llevan a cabo mediante complejas formulas fisicomatemáticas específicas para cada fuente de error. La mayoría de estos errores son transparente para mostros y ni siquiera debemos tenerlos en cuenta, en la mayoría de circunstancias.

Muchos de estas correcciones podríamos obtenerlas mediante las sentencias NMEA recibidas (pero nunca intervenir), sin embargo, el barómetro más común utilizado son los valores del DOP (Dilución de la precisión) y que podemos ver en este mismo post/ejemplo.

Uso de la clase GPSPosition del SDK de Windows Mobile 6

Junto con el SDK de Windows Mobile 6 podemos encontrar un ejemplo en código administrado de uso del GPS Intermediate Driver. Pese a que esta librería está en código nativo y forma parte, por lo tanto, del core de Windows CE para las versiones 5.01 y superiores, el ejemplo en cuestión incorpora un wrapper de todas estas funciones, enumeradores y estructuras en .NET.

SDK

Nos vamos a centrar en la clase GPSPosition la cual nos retorna los valores más significativos y necesarios para el posicionamiento y valores de navegación así como el estado de las señales, satélites y valores DOP. Veámoslos.

Valores que retorna

En la siguiente tabla se detallan todos los valores de la estructura/clase GPSPosition.

PropiedadDescripción
EllipsoidAltitudeAltitud elipsoidal (para saber que es la altitud elipsoidal ver Visualizando las altitudes)
EllipsoidAltitudeValidIndica si el valor de la altitud elipsoidal es valido/fiable.
HeadingRumbo en grados de 001-360
HeadingValidIndica si el valor del rumbo es valido/fiable.
HorizontalDilutionOfPrecisionDilución de precisión horizontal (1-50)
HorizontalDilutionOfPrecisionValidIndica si el valor del HDOP es valido/fiable.
LatitudeLatitud en grados
LatitudeValidIndica si el valor de la latitud es valido/fiable.
LatitudeInDegreesMinutesSecondsLatitud en grados/minutos/segundos
LongitudeLongitud en grados
LongitudeValidIndica si el valor de la longitud es valido/fiable.
LongitudeInDegreesMinutesSecondsLongitud en grados/minutos/segundos
PositionDilutionOfPrecisionDilución de precisión (1-50)
PositionDilutionOfPrecisionValidIndica si el valor del PDO es valido/fiable.
SatelliteCountNúmero de satélites
SatelliteCountValidIndica si el valor del recuento de satélites es valido/fiable.
SatellitesInSolutionNúmero de satélites utilizados
SatellitesInSolutionValidIndica si el valor del recuento de satélites utilizado es valido/fiable.
SatellitesInViewCountNúmero de satélites vistos
SatellitesInViewCountValidIndica si el valor del recuento de satélites visibles es valido/fiable.
SeaLevelAltitudeAltitud MSL a nivel del mar
SeaLevelAltitudeValidIndica si el valor de la altitud es valido/fiable.
SpeedVelocidad en nudos
SpeedValidIndica si el valor de la velocidad es valido/fiable.
TimeHora/Fecha
TimeValidIndica si el valor de la fecha es valido/fiable.
VerticalDilutionOfPrecisionDilución de precisión vertical (1-50)
VerticalDilutionOfPrecisionValidIndica si el valor del VDOP es valido/fiable.
MétodosDescripción
GetSatellitesInSolution()Retorna un array del tipo Satellite con los satélites que se están utilizando en ese momento.
GetSatellitesInView()Retorna un array del tipo Satellite con los satélites que se están viendo en ese momento.

Visualizando el tipo de señal (GPS_FIX_QUALITY)

Dentro de la clases GPSPosition encontramos un atributo (selectionType) del tipo FixSelection cuyo valor se define en el enumerador:

enum FixQuality : int
{
Unknown = 0,
Gps,
DGps
}




Dichos valores equivalen al tipo de señal que se obtiene. En este punto cabe remarcar la existencia de los GPS Diferenciales (DGPS) que son antenas que complementan y se comunican con los receptores y cuyas tasas de error son aun menor a las del receptor GPS tradicional. Por lo tanto selectionType determinará el tipo de señal i por consiguiente la calidad de la misma.



Visualizando el número de satélites



En cuanto a la información de los satélites contamos con dos métodos que nos retornan tanto los satélites en vista (GetSatellitesInView()) como los utilizados (GetSatellitesInSolution()). Los satélites descritos por la clase Satellite, contienen, así mismo, 4 propiedades:














































PropiedadDescripcion
IdIdentificador del satélite.
SignalStrenghCalidad de la señal/ruido (SNR) en decibelios.
AzimuthDescribe la posición radial relativa al receptor en grados. Un valor de 90 indica al Este de nuestra posición mientras que un valor 270 o 180, al Oeste o Sur.
ElevationEl ángulo descrito entre el satélite y el plano del receptor.







Visualizando las altitudes



Altitud elipsoidal


Antes de comprender la fiabilidad de la altitud, trataré de explicar que es la altitud elipsoidal.



La esfera terrestre no tiene una forma -geométricamente- esférica perfecta, mas bien tiene forma “de pera”. A partir de esta premisa, es obvio que en los cálculos de altitud de un receptor se harán en base al tiempo que tarde en viajar la señal desde el satélite al receptor, teniendo en cuenta la velocidad de la luz más las constantes y cálculos de rectificación de errores. Supongamos que un receptor está al nivel del mar en una magnífica playa de Argentina mientras que un segundo receptor esta al nivel del mar en el norte de Noruega. Ambos están a una altitud de 0 metros, sin embargo y debido a la forma de la tierra, la señal del receptor argentino indicará valores de altitud relativa a los satélites más altos que la real y el de Noruega valores más bajos (probablemente bajo 0 metros) que la real.



Para subsanarlo, el GPS representa la tierra de forma geométrica perfecta con una elipsoide referencial o Datum estándar utilizado en otras áreas como la topografía. Dicho estándar lo describe WGS84 en la actualidad y probablemente sea substituido en breve.



Conociendo la posición exacta relativa del receptor y satélite sobre la línea imaginaria que describe el datum, conoce las correcciones que debe realizar para conocer la altitud exacta del receptor. De esta forma, en España, por ejemplo, los valores de la altitud elipsoidal oscilan entre los 45 y 50 metros (* datos aproximados)



En la siguiente imágenes podemos ver como altitud elipsoidal puede ser distinta en una Lugar A (Noruega por ejemplo) y un Lugar B (Argentina por ejemplo).



altelipsoidal



Altitud (MSL)


La altitud, pues, es la distancia del receptor respecto al nivel del mar. Un valor mínimo de VDOP nos dará una mayor fiabilidad.



Visualizando el posicionamiento y velocidad


Los valores del posicionamiento (latitud + longitud) son absolutos y se especifican en grados/minutos/segundos o en únicamente en grados. La velocidad, por su parte, es enviada en nudos (esto es millas náutica por hora).



Conversiones nudos (fuente Wikipedia.)




  • 0,514444 metros por segundo (m·s−1)





  • 1,150779 milla (estatutaria) por hora (mph)





  • 1,852 kilómetros por hora



    Un valor mínimo de HDOP nos ofrecerá una mayor fiabilidad.



    Por último el rumbo describe la dirección en las agujas del reloj en forma de grados de nuestro movimiento. Dirección 360 es Norte, 90 es Este, 180 es Sur y 270 es Oeste.



    Visualizando los DOP



    Los indicadores de Dilución de Precisión de la posición (además del vertical y el horizontal) los podemos encontrar en la clase GPSPosition, con lo que dado un posicionamiento y altitud podemos evaluar su precisión según los valores:
















































    ValorDescripción
    1Ideal
    2-3Excelente. En este punto la fiabilidad de la posición es muy fiable.
    4-6Buena. La fiabilidad es buena pero con algún error (muy pequeño, sin embargo) de posicionamiento.
    7-8Moderada. La señal debe ser revisada. El posicionamiento es aproximado pero no fiable.
    9-20Pobre. Los posicionamientos deben ser descartados.
    21-50Muy Pobre. Los posicionamientos pueden tener un rango de error de +/- 300 metros.



    El ejemplo practico



    Ahora solo necesitamos dos cosas, bueno tres: el SDK de Windows Mobile 6, un nuevo proyecto Smart Device, y configurar el Fake GPS que viene con el SDK o el GPS con nuestro dispositivo móvil a través del GPS Intermediate Driver.



    Para configurar el Fake GPS, podemos ver cómo hacerlo en desarrolloMobile.NET, sección GPS Library. Hecho esto y creado un proyecto Smart Device para Windows Mobile 6 (indistintamente Classic o Professional) empezamos por agregar el proyecto Microsoft.WindowsMobile.Samples.Location.csproj que encontraremos en %PROGRAM_FILES%\Windows Mobile 6 SDK\Samples\PocketPC\CS\GPS. Seguidamente añadimos la referencia a dicho proyecto desde el nuestro y empezamos.



    Diseñamos una interfaz con un TabControl y un par de TabPage además de unos menús para abrir y cerrar el GPS y cerrar la aplicación. (Aquí a gusto de cada uno)



    app diseño



    Vamos a mostrar en el tab POSICION todos los datos de posicionamiento así como los DOP, y en el tab de SATELITES un ListView con los satelites, información y cuales son utilizados.










    app posicion app satelites



    Utilizaremos los siguiente atributos dentro de la clase Form1 e añadimos en el evento Form_Load el siguiente código para suscribir el EventHandler a los cambios de estado del dispositivo GPS y de posicionamiento.



    public partial class Form1 : Form
    {
    EventHandler _updateDataHandler;
    GpsDeviceState _dispositivo;
    GpsPosition _posicion;

    Gps gps = new Gps();

    public Form1()
    {
    InitializeComponent();

    _updateDataHandler = RefreshUI;
    }


    Con lo que toda la lógica la trasladamos al delegado del tipo EventHandler _updateDataHandler. Éste delegará sobre el método RefreshUI.



    public Form1()
    {
    InitializeComponent();

    _updateDataHandler = RefreshUI;
    }


    El método RefreshUI contempla las siguientes partes a destacar:



    Para el control de los datos de latitud, longitud, altitud, altitud elipsoidal, velocidad, rumbo y fecha lo haremos de la siguiente forma:



    if (_posicion.LatitudeValid)
    {
    str[0] += string.Format("Lat:\t{0}\n", _posicion.LatitudeInDegreesMinutesSeconds);
    }

    if (_posicion.LongitudeValid)
    {
    str[0] += string.Format("Lon:\t{0}\n", _posicion.LongitudeInDegreesMinutesSeconds);
    }

    if (_posicion.SpeedValid)
    str[0] += string.Format("Velocidad:\t\t{0} nudos\n", _posicion.Speed.ToString());

    if (_posicion.SeaLevelAltitudeValid)
    str[0] += string.Format("Altitud:\t\t{0} metros\n", _posicion.SeaLevelAltitude.ToString());

    if (_posicion.EllipsoidAltitudeValid)
    str[0] += string.Format("Altitud elipsoidal:\t{0} metros\n", _posicion.EllipsoidAltitude.ToString());

    if (_posicion.HeadingValid)
    str[0] += string.Format("Rumbo:\t\t{0} grados\n\n", _posicion.Heading.ToString());

    if (_posicion.TimeValid)
    {
    Text = _posicion.Time.ToString();
    }


    Para toda la información de satélites jugaremos con LINQ de cara a mostrar en un ListView, de forma ordenada descendente por Señal, los satélites vistos, resaltando con una Imagen en color los utilizados. El código es:



    //satelites
    if (_posicion.SatellitesInSolutionValid &&
    _posicion.SatellitesInViewValid &&
    _posicion.SatelliteCountValid)
    {
    lstSatelites.Items.Clear();
    _posicion.GetSatellitesInView()
    .OrderByDescending(s => s.SignalStrength)
    .ToList().ForEach(s =>
    {
    var lvi =
    new ListViewItem(
    new[]
    {
    s.Id.ToString(),
    s.SignalStrength.ToString(),
    s.Azimuth.ToString(),
    s.Elevation.ToString()
    })
    {
    ImageIndex =
    _posicion.GetSatellitesInSolution()
    .Contains(s, new SatelliteComparer())
    ? 0
    : 1,
    Selected = true
    };
    lstSatelites.Items.Add(lvi);
    });

    str[0] += string.Format("Satelites vistos: {0}\n", _posicion.GetSatellitesInView().Length);
    str[0] += string.Format("Satelites en uso: {0}\n\n", _posicion.GetSatellitesInSolution().Length);


    }


    Fijémonos que hago uso de un IEqualityComparer para la clase Satellite de cara a poder identificar los que están usados dentro de la lista de vistos. El SatelliteComparer es:



    class SatelliteComparer : IEqualityComparer<Satellite>
    {
    public bool Equals(Satellite x, Satellite y)
    {

    if (ReferenceEquals(x, y)) return true;

    if (ReferenceEquals(x, null) ReferenceEquals(y, null))
    return false;

    return x.Id == y.Id && x.Id == y.Id;
    }

    #region IEqualityComparer<Satellite> Members

    public int GetHashCode(Satellite obj)
    {
    if (ReferenceEquals(obj, null)) return 0;

    int hashSat = obj.Id.ToString() == null ? 0 : obj.Id.GetHashCode();


    int hashSatId = obj.Id.GetHashCode();

    return hashSat ^ hashSatId;
    }

    #endregion
    }


    Por último controlamos los DOP, HDOP y VDOP. He jugado con varios paneles y labels de forma que represente una especie de ProgressBar vertical en color. Si la señal del DOP esta entre 1 y 3 el color del panel será verde; entre 4 y 8 amarillo y superior a 8 hasta 50 rojo, de forma que el código quedaría tal que así:



    //ststus
    if (_posicion.HorizontalDilutionOfPrecisionValid)
    {
    lHDOP.Text = _posicion.HorizontalDilutionOfPrecision.ToString();
    pHDOPValue.UpdatePanel(_posicion.HorizontalDilutionOfPrecision);
    str[0] += string.Format("HDOP:\t\t{0}\n", _posicion.HorizontalDilutionOfPrecision.ToString());
    }

    if (_posicion.VerticalDilutionOfPrecisionValid)
    {
    lVDOP.Text = _posicion.VerticalDilutionOfPrecision.ToString();
    pVDOPValue.UpdatePanel(_posicion.HorizontalDilutionOfPrecision);
    str[0] += string.Format("VDOP:\t\t{0}\n", _posicion.VerticalDilutionOfPrecision.ToString());
    }

    if (_posicion.PositionDilutionOfPrecisionValid)
    {
    lPODValue.Text = _posicion.PositionDilutionOfPrecision.ToString();
    pPODValue.UpdatePanel(_posicion.HorizontalDilutionOfPrecision);
    str[0] += string.Format("DOP:\t\t{0}\n\n", _posicion.PositionDilutionOfPrecision.ToString());
    }


    He creado un método extensor para la clase Panel llamado UpdatePanel al cual le paso el valor float de DOP para que configure el comportamiento del Panel como hemos descrito anteriormente. El método en cuestión es:



    public static class ExtensionMethods
    {
    public static void UpdatePanel(this Panel panel, float value)
    {
    panel.Size =
    new Size(panel.Width, 20 + (int)value * 2);
    if (value > 8 && value <= 50)
    panel.BackColor = Color.Red;
    else if (value > 3 && value <= 8)
    panel.BackColor = Color.Yellow;
    else if (value > 0 && value <= 3)
    panel.BackColor = Color.Green;
    }
    }


    En definitiva, con la gestión de información en labels y control de estado del GPS el método RefreshUI quedaría así:



    void RefreshUI(object sender, EventArgs args)
    {
    if (gps.Opened)
    {
    string[] str = {""};
    if (_dispositivo != null)
    {
    str[0] = string.Format("Info Dispositivo:\n{0} {1}, {2}\n\n", _dispositivo.FriendlyName, _dispositivo.ServiceState, _dispositivo.DeviceState);
    }

    if (_posicion != null)
    {

    if (_posicion.LatitudeValid)
    {
    str[0] += string.Format("Lat:\t{0}\n", _posicion.LatitudeInDegreesMinutesSeconds);
    }

    if (_posicion.LongitudeValid)
    {
    str[0] += string.Format("Lon:\t{0}\n", _posicion.LongitudeInDegreesMinutesSeconds);
    }

    if (_posicion.SpeedValid)
    str[0] += string.Format("Velocidad:\t\t{0} nudos\n", _posicion.Speed.ToString());

    if (_posicion.SeaLevelAltitudeValid)
    str[0] += string.Format("Altitud:\t\t{0} metros\n", _posicion.SeaLevelAltitude.ToString());

    if (_posicion.EllipsoidAltitudeValid)
    str[0] += string.Format("Altitud elipsoidal:\t{0} metros\n", _posicion.EllipsoidAltitude.ToString());

    if (_posicion.HeadingValid)
    str[0] += string.Format("Rumbo:\t\t{0} grados\n\n", _posicion.Heading.ToString());

    if (_posicion.TimeValid)
    {
    Text = _posicion.Time.ToString();
    }


    //satelites
    if (_posicion.SatellitesInSolutionValid &&
    _posicion.SatellitesInViewValid &&
    _posicion.SatelliteCountValid)
    {
    lstSatelites.Items.Clear();
    _posicion.GetSatellitesInView()
    .OrderByDescending(s => s.SignalStrength)
    .ToList().ForEach(s =>
    {
    var lvi =
    new ListViewItem(
    new[]
    {
    s.Id.ToString(),
    s.SignalStrength.ToString(),
    s.Azimuth.ToString(),
    s.Elevation.ToString()
    })
    {
    ImageIndex =
    _posicion.GetSatellitesInSolution()
    .Contains(s, new SatelliteComparer())
    ? 0
    : 1,
    Selected = true
    };
    lstSatelites.Items.Add(lvi);
    });

    str[0] += string.Format("Satelites vistos: {0}\n", _posicion.GetSatellitesInView().Length);
    str[0] += string.Format("Satelites en uso: {0}\n\n", _posicion.GetSatellitesInSolution().Length);


    }

    //ststus
    if (_posicion.HorizontalDilutionOfPrecisionValid)
    {
    lHDOP.Text = _posicion.HorizontalDilutionOfPrecision.ToString();
    pHDOPValue.UpdatePanel(_posicion.HorizontalDilutionOfPrecision);
    str[0] += string.Format("HDOP:\t\t{0}\n", _posicion.HorizontalDilutionOfPrecision.ToString());
    }

    if (_posicion.VerticalDilutionOfPrecisionValid)
    {
    lVDOP.Text = _posicion.VerticalDilutionOfPrecision.ToString();
    pVDOPValue.UpdatePanel(_posicion.HorizontalDilutionOfPrecision);
    str[0] += string.Format("VDOP:\t\t{0}\n", _posicion.VerticalDilutionOfPrecision.ToString());
    }

    if (_posicion.PositionDilutionOfPrecisionValid)
    {
    lPODValue.Text = _posicion.PositionDilutionOfPrecision.ToString();
    pPODValue.UpdatePanel(_posicion.HorizontalDilutionOfPrecision);
    str[0] += string.Format("DOP:\t\t{0}\n\n", _posicion.PositionDilutionOfPrecision.ToString());
    }

    lblPosition.Text = str[0];

    }
    }
    }


    Para hacer pruebas con el Fake GPS de la aplicación, podemos utilizar el conjunto de sentencias NMEA que viene con el FAKE GPS mediante dos archivos o podemos insertar una captura de un GPS real tomada por mí mismo y que podéis encontrar aquí. El archivo se llama outgps2.txt y solo tenéis que copiarlo a vuestro emulador, en la carpeta Fake GPS de archivos de programa, junto a los otros dos y configurarlo para que lo utilice.



    Si queréis probarlo en un PDA real, lo único que tenéis que hacer es configurar apropiadamente el GPS Intermediate Driver desde la configuración –> Settings.



    El código completo lo podéis obtener desde aquí, y se llama GPSStatus.



    Fuentes:


    Uso de GPS desde Windows Mobile – dotNetMania num .49



    http://www.isa.cie.uva.es/gps/GPSerrores.html



    http://www.kowoma.de/en/gps/errors.htm



    http://www.upv.es/satelite/trabajos/pracGrupo4/errors.htm



    http://msdn.microsoft.com/en-us/library/ms850332.aspx












  • miércoles, enero 07, 2009

    Microsoft Sync Framework - FAQ -

     

    NOTAS:

    A fecha de 6 de enero de 2009.

    Versiones:

    • Microsoft Sync Framework 1.0
      • Sync Services for ADO.NET 2.0
    • Microsoft Sync Framework 2.0 CTP1
    • Sync Services for ADO.NET for Devices 1.0
    • Microsoft Sync Framework for devices 1.0 CTP1

    General

    1.- ¿Que es Microsft Sync Framework 1.0?

    Es la respuesta de Microsoft a los escenarios ocasionalmente conectados. MSF 1.0 es la primera versión de un marco de desarrollo que permite el desarrollo de cualquier tipo de solución que requiera de algún tipo de sincronización en escenarios off-line de cualquier tipo de origen de datos sobre cualquier protocolo o tipo de red y exponerlos como servicios WCF.

     2.- ¿Con que producto se distribuye? / ¿Dónde puedo obtenerlo?

    Finalmente, MSF 1.0 se distribuye con SQL Server 2008 debido a su afinidad en contextos de sincronización de datos y con Visual Studio 2008 .NET SP1. Sin embargo, también podemos descargarlo desde el siguiente enlace.

    3.- ¿Es gratuito?

    Si, sin embargo contiene unas cláusulas especiales que deben ser consultadas con Microsoft si MSF va a ser utilizado en plataformas que no estén basadas en Windows mediante licencias comerciales y kits de portabilidad.

    4.- ¿Puedo utilizar Microsoft Sync Framework sobre sistemas operativos Windows 64 bits?

    Sí. Soporta además de x86, AMD64 y IA64.

    4.- ¿Qué relación tienen Sync Services for ADO.NET y Microsoft Sync Framework?

    Sync Services for ADO.NET es uno de los tres proveedores Built-In de MSF y está orientada específicamente a la sincronización de orígenes de datos accesibles mediante ADO.NET.

    5.- ¿Y los File Sync Services?

    Idem. Sin embargo File Sync está orientado a la sincronización de archivos y carpetas.

    6.- Pero, ¿Qué son los proveedores de sincronización?

    Son orígenes susceptibles de sincronización y pueden ser de naturaleza heterogénea. MSF provee tres proveedores listos para usar, orientados a datos, archivos y SSE, agrupados en Sync Services for ADO.NET, File Sync y RSS Sync Feed, respectivamente.

    7.- En definitiva, ¿qué incluye Microsoft Sync Framework 1.0?

    MSF incluye:

    • Un proveedor específico para la sincronización de orígenes de datos agrupados en Sync Services for ADO.NET 2.0 de los que hay que distinguir dos tipos diferentes de escenarios: off-line y peer-to-peer.
    • Un proveedor específico para la sincronización de archivos y carpetas agrupados en File Sync Services.
    • Un proveedor específico para la sincronización de SSE(Simple Sharing Extensions) que permite la sincronización de orígenes RSS y Atom.
    • El SDK completo con todas las clases que intervienen en los sistemas de sincronización utilizadas para la creación de proveedores específicos distintos a los tres descritos anteriormente.
    • Los Metadata Services, los cuales permiten el almacenamiento de la metadata en los procesos de sincronización sin la cual, ésta no seria posible.

    8.- ¿Podemos crear proveedores customizados?

    Si. Todas y cada una de las clases necesarias están definidas en MSF, sin embargo, la creación de proveedores customizados implica un conocimiento más a bajo nivel sobre las características, funcionamiento y actores de la sincronización.

    9.- ¿Por que es tan importante SQL Server Compact en MSF?

    Los Metadata Services que MSF incorpora por defecto están basados en SQL Server Compact, sin embargo podemos hacer uso de servicios de metadata propios. Asimismo, en los Sync Services for ADO.NET, SQL Server Compact es uno de los proveedores de datos nativos.

    10.- ¿Hay algo similar para Windows Mobile?

    Sí. Sin embargo el único lanzamiento oficial actualmente es Sync Services for ADO.NET for Devices 1.0 SP1. Por otro lado, MSF 1.0 for Devices está en CTP y se prevé su lanzamiento durante este mismo año 2009. Importante: ¿Por qué Sync Services for ADO.NET está en la versión 2.0 y Microsfoft Sync Framework en la versión 1.0?

    11.- ¿Se puede desarrollar para código nativo?

    Efectivamente, se pueden desarrollar aplicaciones bajo código nativo con MSF.

    Sync Services for ADO.NET

    1.- ¿Por qué Sync Services for ADO.NET está en la versión 2.0 y Microsoft Sync Framework en la versión 1.0? / ¿Que diferencia hay entre Sync Services for ADO.NET 1.0 y 2.0?

    Con la presentación de las primeras betas de SQL Server Compact 3.5 (versión 2008) se presentó Sync Services for ADO.NET 1.0. Debido a que MSF estaba en fase de desarrollo Sync Services for ADO.NET 1.0 NO estaba construido sobre MSF. Con la aparición de MSF 1.0, se presentó Sync Services for ADO.NET 2.0 es cual SI estaba construido sobre MSF y aportaba una serie de nuevas características. Actualmente Sync Services for ADO.NET 1.0 está desfasado y únicamente se presentó como futura solución en entornos ocasionalmente conectados para clientes ligeros que utilizaran SQL Server Compact 3.5.

    2.- ¿Qué tipo de proveedores de datos puedo utilizar? / ¿Puedo utilizar Oracle - MySQL ó XML ?

    Se puede utilizar cualquier tipo de proveedor de datos que sea accesible mediante ADO.NET. Sync Services for ADO.NET es especialmente útil con proveedores SQL Server Compact 3.5, debido a que incorpora un LocalProvider específico, y con SQL Server 2008 gracias a que éste puede apoyarse en una de sus nuevas características, la llamada Change Tracking. Es importante comprender que a excepción de SQL Server Compact 3.5, todos los demás proveedores, sean locales o remotos, deben ser implementados como clases que derivan de Microsoft.Synchronization.Server.DbServerSyncProvider, como tal, cualquier origen accesible de ADO.NET debe proveer de un mecanismo que permita el seguimiento de las modificaciones como Change Tracking en SQL Server 2008.

    NOTA: Ver punto 8.- Y si no utilizo SQL Server 2008, y por lo tanto no hago uso de Change Tracking…

    Las siguientes dos clases muestran en modo de ejemplo como seria un proveedor de sincronización remoto para una base de datos SQL Server 2005, y por lo tanto sin un mecanismo propio de seguimiento de cambios, y un proveedor específico local para SQL Server Compact. Nótese la diferencia y complejidad.

    //Clase padre Microsoft.Synchronization.Server.DbServerSyncProvider
    //NOTA: Si el origen es SQL Server 2008 hacemos uso de Change Tracking
    //Sino, deben utilizarse técnicas invasivas sobre la base de datos como
    //se hace en la siguiente clase de ejemplo.
    //ATENCIÓN: La siguiente clase es únicamente de ejemplo de creación
    //de un proveedor remoto de SQL Server 2005. Sírvase únicamente como
    //referencia.
    public class ServidorSyncProvider : DbServerSyncProvider
    {
    public ServidorSyncProvider()
    {
    SqlConnection conexionServidor = new SqlConnection(strServerConnString);
    this.Connection = conexionServidor;

    //creamos comando ancla
    SqlCommand comandoMomento = new SqlCommand();
    string varMomento = "@" + SyncSession.SyncNewReceivedAnchor;
    comandoMomento.CommandText =
    "SELECT " + varMomento + " = @@DBTS";
    comandoMomento.Parameters.Add(varMomento, SqlDbType.Timestamp);
    comandoMomento.Parameters[varMomento].Direction = ParameterDirection.Output;
    comandoMomento.Connection = conexionServidor;
    this.SelectNewAnchorCommand = comandoMomento;

    //configuramos una tabla para sync bidireccional
    SyncAdapter piezaSyncAdapter = new SyncAdapter("Pieza");

    //
    //comandos de desacarga
    //

    //inserts nuevos del servidor
    SqlCommand piezaIncrInserts = new SqlCommand();
    piezaIncrInserts.CommandText =
    "SELECT [idPieza],[numSerie],[descripcion],[precio],[ultimoCoste],[stock],[stockMinimo]" +
    ",[ubicacionAlmacen],[unidadMedida] FROM [LightMaintenanceSynchSer].[dbo].[Pieza] " +
    "WHERE (InsertTimestamp > @sync_last_received_anchor " +
    "AND InsertTimestamp <= @sync_new_received_anchor " +
    "AND InsertId <> @sync_client_id)";
    piezaIncrInserts.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Timestamp);
    piezaIncrInserts.Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.Timestamp);
    piezaIncrInserts.Parameters.Add("@" + SyncSession.SyncClientId, SqlDbType.UniqueIdentifier);
    piezaIncrInserts.Connection = conexionServidor;
    piezaSyncAdapter.SelectIncrementalInsertsCommand = piezaIncrInserts;

    //updates nuevos del servidor
    SqlCommand piezaIncrUpdates = new SqlCommand();
    piezaIncrUpdates.CommandText =
    "SELECT [idPieza],[numSerie],[descripcion],[precio],[ultimoCoste],[stock],[stockMinimo]" +
    ",[ubicacionAlmacen],[unidadMedida] FROM [LightMaintenanceSynchSer].[dbo].[Pieza] " +
    "WHERE (UpdateTimestamp > @sync_last_received_anchor " +
    "AND UpdateTimestamp <= @sync_new_received_anchor " +
    "AND UpdateId <> @sync_client_id " +
    "AND NOT (InsertTimestamp > @sync_last_received_anchor " +
    "AND InsertId <> @sync_client_id))";
    piezaIncrUpdates.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Timestamp);
    piezaIncrUpdates.Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.Timestamp);
    piezaIncrUpdates.Parameters.Add("@" + SyncSession.SyncClientId, SqlDbType.UniqueIdentifier);
    piezaIncrUpdates.Connection = conexionServidor;
    piezaSyncAdapter.SelectIncrementalUpdatesCommand = piezaIncrUpdates;

    //deletes nuevos del servidor
    SqlCommand piezaIncrDeletes = new SqlCommand();
    piezaIncrDeletes.CommandText =
    "SELECT [idPieza],[numSerie],[descripcion],[precio],[ultimoCoste],[stock],[stockMinimo]" +
    ",[ubicacionAlmacen],[unidadMedida] " +
    " FROM [LightMaintenanceSynchSer].[dbo].[Pieza_Tombstone] " +
    "WHERE (@sync_initialized = 1 " +
    "AND DeleteTimestamp > @sync_last_received_anchor " +
    "AND DeleteTimestamp <= @sync_new_received_anchor " +
    "AND DeleteId <> @sync_client_id)";
    piezaIncrDeletes.Parameters.Add("@" + SyncSession.SyncInitialized, SqlDbType.Bit);
    piezaIncrDeletes.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Timestamp);
    piezaIncrDeletes.Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.Timestamp);
    piezaIncrDeletes.Parameters.Add("@" + SyncSession.SyncClientId, SqlDbType.UniqueIdentifier);
    piezaIncrDeletes.Connection = conexionServidor;
    piezaSyncAdapter.SelectIncrementalDeletesCommand = piezaIncrDeletes;

    //
    //comandos de carga/subida
    //

    //aplicamos inserts al servidor
    SqlCommand piezaInserts = new SqlCommand();
    piezaInserts.CommandText =
    "INSERT INTO [LightMaintenanceSynchSer].[dbo].[Pieza] ([idPieza],[numSerie],[descripcion]"+
    ",[precio],[ultimoCoste],[stock],[stockMinimo],[ubicacionAlmacen],[unidadMedida],[UpdateId],[InsertId]) "+
    "VALUES @idPieza,@numSerie,@descripcion,@precio,@ultimoCoste,@stock,@stockMinimo,@ubicacionAlmacen,"+
    "@unidadMedida, @sync_client_id, @sync_client_id) " +
    "SET @sync_row_count = @@rowcount";
    piezaInserts.Parameters.Add("@idPieza", SqlDbType.Int);
    piezaInserts.Parameters.Add("@numSerie", SqlDbType.NVarChar);
    piezaInserts.Parameters.Add("@descripcion", SqlDbType.NVarChar);
    piezaInserts.Parameters.Add("@precio", SqlDbType.Money);
    piezaInserts.Parameters.Add("@ultimoCoste", SqlDbType.Money);
    piezaInserts.Parameters.Add("@stock", SqlDbType.Int);
    piezaInserts.Parameters.Add("@stockMinimo", SqlDbType.Int);
    piezaInserts.Parameters.Add("@ubicacionAlmacen", SqlDbType.NChar);
    piezaInserts.Parameters.Add("@unidadMedida", SqlDbType.NChar);
    piezaInserts.Parameters.Add("@" + SyncSession.SyncClientId, SqlDbType.UniqueIdentifier);
    piezaInserts.Parameters.Add("@" + SyncSession.SyncRowCount, SqlDbType.Int);
    piezaInserts.Connection = conexionServidor;
    piezaSyncAdapter.InsertCommand = piezaInserts;


    //aplicamos updates al servidor
    SqlCommand piezaUpdates = new SqlCommand();
    piezaUpdates.CommandText =
    "UPDATE Pieza SET " +
    "idPieza = @idPieza " +
    ",numSerie = @numSerie " +
    ",descripcion = @descripcion " +
    ",precio = @precio " +
    ",ultimoCoste = @ultimoCoste " +
    ",stock = @stock " +
    ",stockMinimo = @stockMinimo " +
    ",ubicacionAlmacen = @ubicacionAlmacen " +
    ",unidadMedida = @unidadMedida " +
    ",UpdateId = @sync_client_id " +
    "WHERE (idPieza = @idPieza) " +
    "AND (@sync_force_write = 1 " +
    "OR (UpdateTimestamp <= @sync_last_received_anchor " +
    "OR UpdateId = @sync_client_id)) " +
    "SET @sync_row_count = @@rowcount";
    piezaInserts.Parameters.Add("@idPieza", SqlDbType.Int);
    piezaInserts.Parameters.Add("@numSerie", SqlDbType.NVarChar);
    piezaInserts.Parameters.Add("@descripcion", SqlDbType.NVarChar);
    piezaInserts.Parameters.Add("@precio", SqlDbType.Money);
    piezaInserts.Parameters.Add("@ultimoCoste", SqlDbType.Money);
    piezaInserts.Parameters.Add("@stock", SqlDbType.Int);
    piezaInserts.Parameters.Add("@stockMinimo", SqlDbType.Int);
    piezaInserts.Parameters.Add("@ubicacionAlmacen", SqlDbType.NChar);
    piezaInserts.Parameters.Add("@unidadMedida", SqlDbType.NChar);
    piezaUpdates.Parameters.Add("@" + SyncSession.SyncClientId, SqlDbType.UniqueIdentifier);
    piezaUpdates.Parameters.Add("@" + SyncSession.SyncForceWrite, SqlDbType.Bit);
    piezaUpdates.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Timestamp);
    piezaUpdates.Parameters.Add("@" + SyncSession.SyncRowCount, SqlDbType.Int);
    piezaUpdates.Connection = conexionServidor;
    piezaSyncAdapter.UpdateCommand = piezaUpdates;

    //aplicamos deletes al servidor
    SqlCommand piezaDeletes = new SqlCommand();
    piezaDeletes.CommandText =
    "DELETE FROM Pieza " +
    "WHERE (idPieza = @idPieza) " +
    "AND (@sync_force_write = 1 " +
    "OR (UpdateTimestamp <= @sync_last_received_anchor " +
    "OR UpdateId = @sync_client_id)) " +
    "SET @sync_row_count = @@rowcount " +
    "IF (@sync_row_count > 0) BEGIN " +
    "UPDATE Pieza_Tombstone " +
    "SET DeleteId = @sync_client_id " +
    "WHERE (idPieza = @idPieza) " +
    "END";
    piezaDeletes.Parameters.Add("@idPieza", SqlDbType.UniqueIdentifier);
    piezaDeletes.Parameters.Add("@" + SyncSession.SyncForceWrite, SqlDbType.Bit);
    piezaDeletes.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Timestamp);
    piezaDeletes.Parameters.Add("@" + SyncSession.SyncClientId, SqlDbType.UniqueIdentifier);
    piezaDeletes.Parameters.Add("@" + SyncSession.SyncRowCount, SqlDbType.Int);
    piezaDeletes.Connection = conexionServidor;
    piezaSyncAdapter.DeleteCommand = piezaDeletes;

    //añadimos el adaptador
    this.SyncAdapters.Add(piezaSyncAdapter);
    }
    }



    Ejemplo de implementación del proveedor local para SQL Server Compact




    //Proveedor local SQL Server Compact
    public class ClienteSyncProvider : SqlCeClientSyncProvider
    {

    public ClienteSyncProvider()
    {
    //Incializamos la cadena de conexión para la base de datos sql ce.
    this.ConnectionString = strClientConnString;

    //pese a que no es necesario podemos intervenir durante y despues de la creación del esquema de la base de datos local
    this.CreatingSchema += new EventHandler<CreatingSchemaEventArgs>(ClienteSyncProvider_CreatingSchema);
    this.SchemaCreated += new EventHandler<SchemaCreatedEventArgs>(ClienteSyncProvider_SchemaCreated);
    }

    private void ClienteSyncProvider_CreatingSchema(object sender, CreatingSchemaEventArgs e)
    {
    Console.Write("Creating schema for " + e.Table.TableName + " | ");
    //e.Schema.Tables["Pieza"].Columns["idPieza"].RowGuid = true;
    }

    private void ClienteSyncProvider_SchemaCreated(object sender, SchemaCreatedEventArgs e)
    {
    Console.WriteLine("Schema created for " + e.Table.TableName);
    }

    }



    3.- ¿Substituyen Sync Services for ADO.NET a RDA?



    NO. Sync Services for ADO.NET NO pretende sustituir a RDA. Sin embargo, Microsoft ha anunciado que no seguirá evolucionando RDA y mantendrá el soporte a ésta en tanto en cuanto existan clientes que basen sus soluciones de sincronización en base a esta tecnología.



    4.- Sync Services for ADO.NET funcionan únicamente para base de datos SQL Server Compact, ¿Cierto?



    Falso. Pese a que existe un proveedor específico para SQL Server Compact 3.5, Sync Services for ADO.NET 2.0 puede ser utilizado con cualquier proveedor de datos. Sin embargo, para aquellos proveedores distintos a SQL Server Compact, deberemos implementar la lógica de funcionamiento para cada uno de ellos. Actualmente existen algunos proveedores desarrollados como por ejemplo el de SQL Server Express.



    5.- ¿Qué tipos de escenarios de sincronización soporta Sync Services for ADO.NET 2.0?



    Básicamente se distinguen dos tipos de escenarios:




    • Escenarios off-line: este tipo de escenarios tienen la particularidad de que existe un único proveedor remoto y uno o varios proveedores locales los cuales pueden estar o no conectados en línea.


    • Escenarios de colaboración o peer-to-peer: sin embargo aquí no existe la figura de remoto o local. Sencillamente todos son proveedores se sincronizan entre sí ya que son proveedores de igual a igual.















    Escenarios off-line Escenarios de colaboración o punto a punto


    6.- ¿Existe algún proveedor para SQL Server Express?



    Sí. Pero es de código abierto y están bajo licencia Microsoft Public License, con lo que no hay garantías de uso, pese que a por mi experiencia funciona bastante bien (al menos en escenarios sencillos).



    7.- ¿En qué punto, exactamente se unen Sync Services for ADO.NET 2.0 y la nueva característica de SQL Server 2008 Change Tracking?



    Ante un escenarios de sincronización de dos proveedores como pueden ser SQL Server Compact y SQL Server 2008, Sync Services for ADO.NET 2.0 aporta un proveedor específico para SQL Server Compact en el cual apenas hay que hacer nada. Este proveedor se encarga de:




    • Insertar los cambios propagados desde el origen de datos remoto


    • Propagar sus propios cambios al origen de datos remoto


    • Manejar los conflictos, si los hay.



    Por otro lado, el proveedor remoto (SQL Server 2008) realizará:




    • Obtener todos los cambios acontecidos sobre las tablas o conjunto de datos susceptibles de sincronización y propagarlos al proveedor local. –> Aqui entra en escena Change Tracking!!!


    • Aplicar los cambios propagados desde el proveedor local a la base de datos SQL Server 2008.


    • Manejo y control de conflictos.



    Para el proveedor de SQL Server 2008, todo el control de cambios acontecidos en el mismo pueden ser controlados por Change Tracking. Change Tracking mantendrá durante el espacio de tiempo especificado todos los cambios realizados sobre las tablas habilitadas para tal fin.



    8.- Y si no utilizo SQL Server 2008, y por lo tanto no hago uso de Change Tracking…



    Deberás crear un mecanismo de seguimiento de filas mediante la inserción de columnas que determinen el cuándo y quién ha añadido o modificado cada una de las fila. Esto es una técnica invasiva con lo que conlleva una mayor sobrecarga de información. Para las filas que se eliminen se deben utilizar desencadenadores y “tablas lápida” para almacenar las filas eliminadas asi como la información de versión representada mediante RowGuid Columns.



    Por ejemplo,



    La tabla Customer de la base de datos Northwind tiene el siguiente esquema:




    CREATE TABLE SyncSamplesDb.Sales.Customer(
    CustomerId uniqueidentifier NOT NULL PRIMARY KEY DEFAULT NEWID(),
    CustomerName nvarchar(100) NOT NULL,
    SalesPerson nvarchar(100) NOT NULL,
    CustomerType nvarchar(100) NOT NULL)



    Siguiendo las instrucciones de la siguiente tabla de la librería del Microsoft Sync Framework Developer Center, vamos a configurar la tabla para que detecte las modificaciones bidireccionales de inserción, modificación y eliminación de datos y para ello primero añadimos las columnas que realizaran el seguimiento:




    ALTER TABLE SyncSamplesDb.Sales.Customer 
    ADD UpdateTimestamp timestamp
    ALTER TABLE SyncSamplesDb.Sales.Customer
    ADD InsertTimestamp binary(8) DEFAULT @@DBTS + 1
    ALTER TABLE SyncSamplesDb.Sales.Customer
    ADD UpdateId uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'
    ALTER TABLE SyncSamplesDb.Sales.Customer
    ADD InsertId uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'



    Y las indexamos con índices no clusterizados.




    CREATE NONCLUSTERED INDEX IX_Customer_UpdateTimestamp
    ON Sales.Customer(UpdateTimestamp)

    CREATE NONCLUSTERED INDEX IX_Customer_InsertTimestamp
    ON Sales.Customer(InsertTimestamp)

    CREATE NONCLUSTERED INDEX IX_Customer_UpdateId
    ON Sales.Customer(UpdateId)

    CREATE NONCLUSTERED INDEX IX_Customer_InsertId
    ON Sales.Customer(InsertId)



    Debido a que queremos realizar el seguimiento de eliminaciones, crearemos las tabla lápida.




    CREATE TABLE SyncSamplesDb.Sales.Customer_Tombstone(
    CustomerId uniqueidentifier NOT NULL PRIMARY KEY NONCLUSTERED,
    CustomerName nvarchar(100) NOT NULL,
    SalesPerson nvarchar(100) NOT NULL,
    CustomerType nvarchar(100) NOT NULL,
    DeleteId uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000',
    DeleteTimestamp timestamp)



    Y por lo tanto, debemos crear, además, el desencadenador.




    CREATE TRIGGER Customer_DeleteTrigger 
    ON SyncSamplesDb.Sales.Customer FOR DELETE
    AS
    BEGIN
    SET NOCOUNT ON
    DELETE FROM SyncSamplesDb.Sales.Customer_Tombstone
    WHERE CustomerId IN (SELECT CustomerId FROM deleted)
    INSERT INTO SyncSamplesDb.Sales.Customer_Tombstone (CustomerId, CustomerName, SalesPerson, CustomerType)
    SELECT CustomerId, CustomerName, SalesPerson, CustomerType FROM deleted
    SET NOCOUNT OFF
    END



    Finalmente, los índices de la tabla lápida.




    CREATE CLUSTERED INDEX IX_Customer_Tombstone_DeleteTimestamp
    ON Sales.Customer_Tombstone(DeleteTimestamp)

    CREATE NONCLUSTERED INDEX IX_Customer_Tombstone_DeleteId
    ON Sales.Customer_Tombstone(DeleteId)



    Más info: How to: Use a Custom Change Tracking System



    9.- También podría utilizar Data Change Capture en lugar de Change Tracking, ¿no?



    No. Debido a la naturaleza asíncrona de Data Change Capture, éste es idóneo para otro tipo de escenarios.



    10.- ¿Existe algún tipo de soporte en forma de diseñador o asistentes desde el Visual Studio 2008 .NET?



    Si. En nuestros proyectos podemos añadir un elemento llamado base de dato local caché (Local Database Cache),



    ElementoLocalDatabase



    cuyo diseñador nos permite la creación de una solución basada en Sync Services for ADO.NET.



    ConfigureDataSync



    11. ¿Cuál es la principal característica diferenciadora de Sync Services for ADO.NET con Merge Replication o RDA?



    Hay varias. Empezando por la independencia del protocolo o tipo de red. Sync Services for ADO.NET, además, está orientada a servicios con lo que podemos exponer el proveedor remoto mediante WCF. Además, el control y gestión de conflictos es único ya que nos permite aplicar lógica de negocio para la resolución de los mismos. Por último, podemos hacer un seguimiento de datos a nivel de columna.



    12.- ¿Que significa seguimiento a nivel de columna o de fila?



    Por defecto, el seguimiento de cambios se realiza a nivel de fila, igual que en Merge replication o RDA. Esto significa que ante la modificación de cualquier columna se evaluará la fila como modificada con lo que tras una sincronización se propagará dicha fila, como elemento básico, al proveedor de destino. El seguimiento a nivel de columna utiliza únicamente las columnas de las filas modificadas para la acción de sincronización, sin embargo, y pese a que la cantidad de información (cambios) es inferior repercute notablemente en el rendimiento de SQL Server y salvo casos excepcionales debería ser evitado. NOTA: Change Tracking de SQL Server 2008 permite el seguimiento a nivel de columna.



    13.- ¿Por qué la detección y manejo de conflictos es único?



    Porque permite, como dije antes, aplicar lógica de negocio o aplicar comportamientos específicos en base al origen del conflicto. Así pues, podemos predisponer el comportamiento de la sincronización ante un conflicto para que siempre “gane” el proveedor servidor o local, pudiendo incluso combinar los resultados si esto es posible. Si el conflicto es inesperado podemos registrarlo para poder evaluarlo más adelante.



    Más Info.



    14.- ¿Que se entiende por un conflicto?



    Un conflicto es la modificación simultánea durante una sesión de sincronización de unos elementos de datos (ItemData) por dos o más orígenes. Esto da lugar a que ante la acción de sincronización deba intervenir algún tipo lógica que determine cual va a ser el resultado final para todos los participantes. Además, un conflicto puede ser una violación de algún tipo de restricción del motor de la base de datos como por ejemplo una FOREIGN KEY.



    File Sync y Feed Sync



    1.- ¿Que tipo de archivos puedo sincronizar?



    Cualquier tipo de archivo o carpeta compatibles con Win-32 como FAT o NTFS.



    2.- ¿Existe alguna aplicación de ejemplo con código?



    La más famosa e idónea para comenzar en este tipo de aplicaciones es SyncToy





    3.- ¿Qué peculiaridad tiene RSS Sync Feed respecto a File Sync?



    Que la sincronización de orígenes SSE únicamente son unidireccionales mientras que en FileSync son bidireccionales.