domingo, 25 de novembro de 2012

Hora Certa no Netduino - NIST Internet Time Service IP


Assim que comecei a fazer medições a primeira coisa que precisa era atribuir data e hora de cada medição ao salvar no cartão microSD. A boa notícia é que o .NETMF tem suporte ao formato "DateTime"  que é muito usado no .NET normal para pegar a data e hora do windows e usar na aplicação, o problema é que o serviço de hora no Netduino é iniciado quando a placa liga ou chama o serviço de hora a primeira vez, ou seja, a primeira chamada é sempre zero hora.....

Para fazer um teste inicial você pode executar um código simples:

while (true)
{
 Debug.Print("Horário Atual: " + DateTime.Now.ToString());
    Thread.Sleep(50000);
}

A saída desse código é só para o Debug e é mais ou menos assim:


Note que o horário é meio louco, agora são 17:25 de 2012. Não sei que hora é essa mas vamos lá se queremos um medidor que envie dados para a internet é conveniente que ele também tenha um ajuste de hora automático. Já pensou, toda vez que a placa desligar ter que ir até ela e corrigir a hora na "mão grande"...

A solução para o Netduino Plus é utilizar um servidor de tempo. O Time Server ou servidor de tempo (NIST) é um servidor que lê uma informação de tempo de um relógio de tempo real e distribui a informação pela internet através de um protocolo próprio chamado de Network Time Protocol (NTP), embora outros protocolos de tempo menos populares (ultrapassados) ainda estejam em uso.

O NTP é um protocolo baseado no UDP que tem uma finalidade muito especial de sincronização do relógio de um conjunto de computadores em redes de dados com latência variável. O NTP permite manter o relógio de um computador com a hora sempre certa e com grande exatidão. Originalmente idealizado por David L. Mills da Universidade do Delaware e ainda hoje mantido por si e por uma equipa de voluntários, o NTP foi utilizado pela primeira vez antes de 1985, sendo ainda hoje muito popular e um dos mais antigos protocolos da internet (veja mais detalhes na wikipédia). 

Exitem na internet uma série de servidores de tempo (NIST) na internet para capturar a hora certa e utilizar no sei código como referência, o site que eu peguei os IP's foi este: http://tf.nist.gov/tf-cgi/servers.cgi. Que são exatamente todos servidores no EUA, mas exitem outros pelo mundo a fora.


O código que eu fiz é bem simples. Trata-se de uma classe que quando inicializada atualiza o valor do tempo para a hora atual obtida através de um servidor da lista de servidores NIST da classe. O único parametro que deve ser passo para classe são os minutos de defasagem entre o valor da hora de referência em Greenwich para sua hora local, que no meu caso, Rio de Janeiro é -120 minutos ( -2 horas).

Esta é a classe:

using System.Net.Sockets;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using System.IO;
using System.Text;
using System;
using System.Net;
using Microsoft.SPOT.Net.NetworkInformation;


namespace RealTimeTest
{
           
    /// <summary>
    /// Autor: Victor Manuel
    /// Data: 11/02/2012
    /// 
    /// Descrição: Classe De serviços de sincronização de relógio
    /// para o NetduinoPlus
    /// 
    /// 
    /// Exemplo:
    /// 
    /// //Seta o IP da porta
    /// NetworkInterface.GetAllNetworkInterfaces()[0].EnableStaticIP("10.20.19.35", "255.255.255.0", "10.20.19.1");
    /// string[] dns = { "10.20.19.1", "200.222.122.133" };
    /// NetworkInterface.GetAllNetworkInterfaces()[0].EnableStaticDns(dns);
    /// System.Threading.Thread.Sleep(10000);
    /// 
    /// InternetTimeService nits = new InternetTimeService(-120);
    /// </summary>
    class InternetTimeService
    {
        private int TimeServerIndex = 0;
        private int OffSetMinutes = 0;


        /// <summary>
        /// System Synchronize Internal DateTime
        /// </summary>
        /// <param name="minutes">Time Zone Difference</param>
        public InternetTimeService(int minutes)
        {
            Debug.Print("InternetTimeService routine is running ...");
            OffSetMinutes = minutes;
            Utility.SetLocalTime(GetNetworkTime());
            Debug.GC(true);
        }

        /// <summary>
        /// NIST Internet Time Servers
        /// For server list access: http://tf.nist.gov/tf-cgi/servers.cgi
        /// </summary>
        public string[] InternetTimeServers = {"time.nist.gov", //  global address for all servers  Multiple locations 
                                               "nist1-ny.ustiming.org", //New York City, NY 
                                               "nist1.aol-va.symmetricom.com", //Reston, Virginia 
                                               "nist1-atl.ustiming.org ", //Atlanta, Georgia 
                                               "nist1-la.ustiming.org",  //Los Angeles, California 
                                               "time-nw.nist.gov", //Microsoft, Redmond, Washington 
                                               "utcnist.colorado.edu" //University of Colorado, Boulder 
                                               };
        


        /// <summary>
        /// Force to system synchronize internal DateTime
        /// </summary>
        public void ForceToUpdate()
        {
            Utility.SetLocalTime(GetNetworkTime());
            Debug.GC(true);
        }


        /// <summary>
        /// Method to query a NTP server and set the device date to the returned value
        /// </summary>
        /// <returns>Recived Server Time</returns>
        public DateTime GetNetworkTime()
        {
            while (true)
            {
                try
                {
                    Debug.Print("Conecting to:" + InternetTimeServers[TimeServerIndex].ToString());
                    IPEndPoint ep = new IPEndPoint(Dns.GetHostEntry(InternetTimeServers[TimeServerIndex].ToString()).AddressList[0], 123);

                    Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
                    s.Connect(ep);

                    byte[] ntpData = new byte[48]; // RFC 2030
                    ntpData[0] = 0x1B;
                    for (int i = 1; i < 48; i++)
                        ntpData[i] = 0;

                    s.Send(ntpData);
                    s.ReceiveTimeout = 10000;
                    s.Receive(ntpData);

                    byte offsetTransmitTime = 40;
                    ulong intpart = 0;
                    ulong fractpart = 0;
                    for (int i = 0; i <= 3; i++)
                        intpart = 256 * intpart + ntpData[offsetTransmitTime + i];

                    for (int i = 4; i <= 7; i++)
                        fractpart = 256 * fractpart + ntpData[offsetTransmitTime + i];

                    ulong milliseconds = (intpart * 1000 + (fractpart * 1000) / 0x100000000L);

                    s.Close();

                    TimeSpan timeSpan = TimeSpan.FromTicks((long)milliseconds * TimeSpan.TicksPerMillisecond);

                    DateTime networkDateTime = (new DateTime(1900, 1, 1)) + timeSpan;
                    
                    Debug.Print("UTC: " + networkDateTime.ToString());

                    networkDateTime = (new DateTime(1900, 1, 1)) + timeSpan + TimeSpan.FromTicks((long)OffSetMinutes * TimeSpan.TicksPerMinute);

                    Debug.Print("LOCAL: " + networkDateTime.ToString());

                    return networkDateTime;
                }
                catch
                {
                    Debug.Print("Impossible to conect on " + InternetTimeServers[TimeServerIndex].ToString());
                    Debug.Print("try next time server..");
                    TimeServerIndex++;
                    if (TimeServerIndex == InternetTimeServers.Length)
                    {
                        Debug.Print("Impossible to access Internet Time Servers... ");
                        return DateTime.Now;
                    }

                }
            }
        }

    }
}
 
Este é o exemplo de utilização:

NetworkInterface.GetAllNetworkInterfaces()[0].EnableStaticIP("10.20.19.35", "255.255.255.0", "10.20.19.1");
string[] dns = { "10.20.19.1", "200.222.122.133" };
NetworkInterface.GetAllNetworkInterfaces()[0].EnableStaticDns(dns);
System.Threading.Thread.Sleep(10000);

InternetTimeService nits = new InternetTimeService(-120);
      
while (true)
{
    Debug.Print("Horário Atual: " + DateTime.Now.ToString());
    Thread.Sleep(50000);
}

Quando você mandar rodar aparecerá a seguinte mensagem na janela de depuração:

Espero que tenha ajudado mais com esse artigo sobre servidores NIST no Netduino.

Nenhum comentário:

Postar um comentário