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.

domingo, 11 de novembro de 2012

Getting Started with the Internet of Things - Parte 2

 O Cosm é um serviço bem legal para ver suas medidas na internet. Um bom passo é ler a documentação disponível no próprio site que informa como funciona o envio de dados para a plataforma online.

O primeiro passo é criar uma conta no https://cosm.com/. O Cosm é um serviço gratuito para nós pequenos desenvolvedores assim você pode ter uma plataforma online gratuita para enviar a informação dos seus medidores a acessar de qualquer lugar.

Depois de cirada a conta você deverá fazer algumas configurações para enviar os dados. A primeira delas é criar uma chave. A chave (Key) é uma primeira camada de controle de permissão, parece que no Pachube antigo isso não existia. Deste novo modelo com chave de acesso você pode criar permissões distintas de acesso a conta no Cosm. Por exemplo, você pode ter uma chave que permite a criação de novas chaves mas só pode ser feita por um endereço de IP específico, e você pode ter outras chaves com permissões restritas apenas para envio de dados. Da mesma forma você pode ter um dispositivo cliente que mostra dados acessando o Cosm por uma chave de leitura apenas.

Para criar uma chave nova vá em Keys, logo abaixo do seu nome de login no canto superior direito. Você deve informar um Label de identificação da chave, você deve informar atmbém como ele vai atuar com os alimentadores, os privilégios da chave e se quiser outras opções avançadas como filtro de IP, numero de acessos e até data de expiração da chave. Recomendo para o primeiro teste colocar uma chave com acesso a todos os serviços, muito embora só vou usar nesse posto para alimentar dados.

A chave será um código enorme como este: woxZM8LeIHOE2-2r0XTcthj_8XqSAKxzMmoxUS8zL0I4UT0g.

Agora já temos uma chave de acesso e devemos incluir um dispositivo que vai enviar dados para o serviço, na verdade não é bem um dispositivo mas é uma espécie de "concertador" de dados que pode enviar diversas informações para o Cosm. Para isso vá em "Console", no canto superior direto logo abaixo do seu nome de login do Cosm.

A página aberta estará vazia, clique no botão azul chamado "+ Device/Feed" e aparcerá quatro opções na tela: Arduino, Current Cost, Twitter Stats e Somithing Else; Selecione "Something Else".

São quatro passos para configurar:
  1. Método de envio de dados: Os dados de alimentação podem ser enviados do dispositivo para o Cosm ou o Cosm pode tentar acessar os dados através de uma requisição. A segunda opção é muito complicada porque geralmente o Netduino vai ficar dentro de uma rede doméstica que é protegida por um firewall do roteador e da provedora de internet assim o melhor mesmo é fazer o envio dos dados da placa do Netduino Plus para o Cosm. Por isso selecione a opção "No, I will push data to Cosm";
  2. Título do Dispositivo: apenas dê um nome para o seu dispositivo que vai se conectar ao Cosm, não faz diferença para a programação;
  3. Tags: Os tag's são os identificadores dos dados que serão enviados: por exemplo: Temperatura, Humidade, Energia, Vazão etc... Coloque os separados por virgulas.
  4. A ultima opção é só para confirmar.
Assim que você confirmar a operação aparecerá um dispositivo na lista com um gráfico para cada tag de medição que você criou, isso mostra que está tudo certo, como não teve medida nenhuma estará tudo limpo. Clique sobre sobre o título do dispositivo, os dados vaõ ocupar a página toda e a URL da página será do tipo https://cosm.com/feeds/83197. Esse código final é o Feed ID (83197), guarde esse número.

Ainda se você clicar sobre o as configurações no cantor superior direito do medidor, em "Edit" você poderá ainda colocar o local onde está o medidor, adicionar novas tag's e mudar aquela configuração inicial também. Agora a parte do Cosm está pronta, vamos a programação.

Para iniciar a programação tenha em mãos a Key e o Feed ID. O Meu código está disponível aqui. Vão notar que deixei minha chave e os meus tag's, fiz isso porque quando eu estva começando não tinha nem ideia de como era a chave e o Feed ID e demorei um dia tentando entender como funcionava o sistema. De qualquer forma pode utilizá-los, eu deixo. O código é copiado de um post do Forum do Netduino com algumas modificações.

Na primeira parte eu fiz a configuração do IP estático e do DNS estático também porque o Oi Velox é um lixo tão grande que dá problema de DNS. Essa parte é a configuração da camada de rede que uma vez configura o acesso aos serviços de internet do .NETMF tornam-se disponíveis. Tem gente que configura isso pelo MFDeploy mas eu particularmente gosto de ver configurado no código.

Lembre-se de conectar o cabo de rede antes da alimentação da placa, o Netduino Plus ainda está com esse problema.

Outra coisa que merce atenção é que o NetworkInterface não está como pacote default do Netduino assim você deve ir no projeto em "References", com o botão direito clicar em "Add Reference", e adicionar o System.Http, depois adicionar a linha ao topo do seu código "using Microsoft.SPOT.Net.NetworkInformation;".

NetworkInterface.GetAllNetworkInterfaces()[0].EnableStaticIP("10.20.19.122", "255.255.255.0", "10.20.19.1");
string[] NetworkDns = { "10.20.19.1", "200.222.122.133" };
NetworkInterface.GetAllNetworkInterfaces()[0].EnableStaticDns(NetworkDns);

Uma vez conectada a interface de rede você pode iniciar o envio de dados. Para enviar os dados você deve se conectar ao serviço do Cosm através da função conect(). Essa função cria um socket que é nada mais do que uma "tomada" que designa uma usada para ligar o dispositivo (Netduino Plus) a um ponto final específico da rede designado por um endereço de rede, nesse caso a URI do Cosm: api.cosm.com.
 
A função conect() também tem um atributo de timeout, quando o tempo máximo é atingido ele retorna um socket vazio, isso é feito porque o Cosm pode estar indisponível por algum problema nas camadas de rede  ou até mesmo o serviço fora do ar e desse modo o código pode processar outra atitude como gravar no cartão microSD, por exemplo o dado lido.

static Socket Connect(string host, int timeout)
{
    // look up host's domain name, to find IP address(es)
    IPHostEntry hostEntry = Dns.GetHostEntry(host);
    // extract a returned address
    IPAddress hostAddress = hostEntry.AddressList[0];
    IPEndPoint remoteEndPoint = new IPEndPoint(hostAddress, 80);

    // connect!
    Debug.Print("connect...");
    var connection = new Socket(AddressFamily.InterNetwork,
        SocketType.Stream, ProtocolType.Tcp);
    connection.Connect(remoteEndPoint);
    connection.SetSocketOption(SocketOptionLevel.Tcp,
        SocketOptionName.NoDelay, true);
    connection.SendTimeout = timeout;
    return connection;
}

Se tudo ocorrer bem na rede de internet será possível criar um socket e barir a comunicação com o serviço do Cosm e enviar dados. O Envio de dados é feito pela função SendRequest(). esta função recebe a chave (key), o Feed ID o socket criado e a informação que será enviada no formato CSV (Comma-Separated Format), ou formato separado por virgulas onde será enviado no corpo da mensagem a informação no estilo: "tag,valor".

Toda mensagem enviada por internet tem uma URI de requisição (requestLine), um cabeçalho (header)e um conteúdo (content). a reqisição é o endereço de onde será enviada a mensagem com o método de envio que neste caso é o método "PUT" "/v2/feeds/seu feedid.csv HTTP/1.1" que se soma a URL do site e acessa o serviço específico de dados do tipo CSV utilizando o protocolo HTTP 1.1. O Header, ou cabeçalho da mensagem contem as informações necessárias ao direcionamento da mensagem para sua conta mediante a chave de acesso, o host name e o tamanho do conteúdo da mensagem. e por fim a mensagem em formato de texto csv.

static void SendRequest(Socket s, string apiKey, string feedId,
    string content)
{
    byte[] contentBuffer = Encoding.UTF8.GetBytes(content);
    const string CRLF = "\r\n";
    var requestLine =
        "PUT /v2/feeds/" + feedId + ".csv HTTP/1.1" + CRLF;
    byte[] requestLineBuffer = Encoding.UTF8.GetBytes(requestLine);
    var headers =
        "Host: api.cosm.com" + CRLF +
        "X-ApiKey: " + apiKey + CRLF +
        "Content-Type: text/csv" + CRLF +
        "Content-Length: " + contentBuffer.Length + CRLF +
        CRLF;
    byte[] headersBuffer = Encoding.UTF8.GetBytes(headers);
    s.Send(requestLineBuffer);
    s.Send(headersBuffer);
    s.Send(contentBuffer);
}

O serviço do Cosm também permite outros formatos como JASON e XML mas você pode implementar depois pois muda apenas a sintaxe da mensagem.

Depois de ter tudo construído em texto basta enviá-los através do socket que foi aberto. E o código funciona assim em Loop Infinito e manda um dado a cada 20 segundos, aproximadamente. No meu código eu pego um valor aleatório e envio para o servidor do Cosm.

public static void Main()
{
    NetworkInterface.GetAllNetworkInterfaces()[0].EnableStaticIP("10.20.19.122", "255.255.255.0", "10.20.19.1");
    string[] NetworkDns = { "10.20.19.1", "200.222.122.133" };
    NetworkInterface.GetAllNetworkInterfaces()[0].EnableStaticDns(NetworkDns);

    const string apiKey = "woxZM8LeIHOE2-2r0XTctHb_8XqSAKxzMmoxUS8zL0I4UT0g";
    const string feedId = "83197";
    Socket connection = null;
    while (true)   // main loop
    {
        Debug.Print("time: " + DateTime.Now);
        Debug.Print("memory available: " + Debug.GC(true));

        if (connection == null)   // create connection
        {
            try
            {
                connection = Connect("api.cosm.com",1000 );
            }
            catch
            {
                Debug.Print("connection error");
            }
        }

        if (connection != null)
        {
            try
            {
                Random rnd = new Random();
                double value = rnd.NextDouble();
                string sample = "voltage," + value.ToString("f");
                Debug.Print("new sample: " + sample);
                SendRequest(connection, apiKey, feedId, sample);
                sample = "number," + (rnd.NextDouble()).ToString("f");
                SendRequest(connection, apiKey, feedId, sample);
            }
            catch (SocketException)
            {
                connection.Close();
                connection = null;
            }
        }
System.Threading.Thread.Sleep(20000);
    }
}
Espero que tenha sido útil todas essas informações para quem pretende acessar o serviço do Cosm e mandar dados pela internet. Aproveito para dizer que gostaria que vocês comentassem os post's para que eu continue melhorando e colocando o assunto que for mais pertinente.


Se você quiser ver meus gráficos pode acessá-los através do site do Cosm

Para baixar o arquivo pronto do projeto com as funções clique aqui.