quinta-feira, 23 de agosto de 2012

Explorando o NeonMika.Webserver - Parte 3

   Nesta ultima parte sobre o NeonMika.Webserver mostraremos uma opção simples de configuração da placa reunindo tudo que já foi falado antes com relação ao Webserver e as opções de leitura e escrita no cartão microSD. Também será avaliado as opções de requisição XML que existem no servidor através de um pequeno programa simples que eu fiz.

Requisição de Login

        Tive fazer algumas alterações na parte de requisição de login. Se você teve a oportunidade de testar o código que disponibilizei na segunda etapa desses artigos sobre o NeonMika.Webserver certamente notou que uma vez o cliente conectado na placa a conexão nunca mais seria fechada; e mais ainda, todo e qualquer cliente poderia acessá-la sem necessitar de uma nova requisição de login.

        Naturalmente isso está errado, os clientes não podem se conectar indiscriminadamente e alguma espécie de Timeout tem que existir na requisição do cliente para que automaticamente seja removido por inatividade.
        Para compreender melhor esse processo fiz um pequeno esquema de como funciona a nova requisição de login:


        Uma vez requisitada uma página de HTML o código verifica se o cliente (o IP de onde partiu a requisição) já esta na lista interna de clientes, caso não esteja envia a página de login e armazena a página requisitada na memória para enviá-la depois de um login bem sucedido.

        Na página de login o usuário deve informar o nome de usuário e senha através de um formulário que será enviado pelo método de POST contendo ao menos estes dois objetos: "username" e "password". Uma vez logado corretamente esse cliente é incluído na lista de clientes com um tempo de vida da sua conexão e em seguida a página requisitada será aberta, se está página é nula ou vazia é carregada a página "index.html".

        O usuário pode navegar em todas as página disponíveis no servidor e pode também ter acesso aos outros tipos de resposta do servidor como XMLResponse e JSONResponse. Tova vez que um acesso desse tipo é feito o tempo de vida da conexão do cliente é regenerado ao valor pré-definido nas configurações.

        O tempo de vida da conexão é automaticamente decrementado numa função periódica independente. Ao atingir o valor zero o cliente é removido da lista e uma nova conexão é necessária. Dessa forma o servidor web já tem um login mais seguro.

Configurações da conexão via HTML

        A configuração do IP foi uma união da segunda parte dessa sequência de artigos sobre o NeonMika.Webserver com o artigo sobre escrita de configuração no cartão microSD. Fui necessária uma nova função de construção do servidor onde os dados seriam obtidos com base num arquivo gravado no cartão microSD.
        Infelizmente a universalidade do código se foi, ou seja, foi necessário uma página de html vinculada a uma função de código. Também utilizando o método POST a requisição de login foi adicionada como uma requisição XML (embora a resposta não seja bem XML). Dessa forma as requirições da página "http://ipnetduino/config" são enviadas para a a função "SetNewConfig" em Server.cs.

public Server(OutputPort ledPort, int portNumber = 80)
{

    Configuration config = new Configuration(Settings.CONFIG_FILE);
                       

    var interf = NetworkInterface.GetAllNetworkInterfaces()[0];
    if (config.GetConfigurationOf("dhcp", Configuration.ConfigMode.Append, "false") == "true")
    {
        interf.EnableDhcp();
        interf.RenewDhcpLease();
    }
    else
    {
        //New to fix 
        string ipAddress = config.GetConfigurationOf("ip", Configuration.ConfigMode.Append, "10.20.19.200");
        string subnetMask = config.GetConfigurationOf("mask", Configuration.ConfigMode.Append, "255.255.0.0");
        string gatewayAddress = config.GetConfigurationOf("gateway", Configuration.ConfigMode.Append, "10.20.19.1");
            
        interf.EnableStaticIP(ipAddress, subnetMask, gatewayAddress);
    }

            
    //New to set login webpage
    string loginName = config.GetConfigurationOf("loginName", Configuration.ConfigMode.Append, "admin");
    string loginPassword = config.GetConfigurationOf("loginPassword", Configuration.ConfigMode.Append, "admin");
    if (!((loginName == null) && (loginPassword == null)))
    {
        _UserName = loginName;
        _Password = loginPassword;
    }

    ClientsLogedTimeOut = new Timer(new TimerCallback(ClientsLogedEvent), null, 60000, 60000);

           
    this._PortNumber = portNumber;
    _OnboardLed = ledPort;
    ResponseListInitialize();

    _ListeningSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    _ListeningSocket.Bind(new IPEndPoint(IPAddress.Any, portNumber));
    _ListeningSocket.Listen(4);

    var webserverThread = new Thread(WaitingForRequest);
    webserverThread.Start();

    //To force file write in the card
    config.ForceToWrite();

    //limpa a memória
    Debug.GC(true);
}

         A função "SetNewConfig"é uma função que pode ser chamada através de dois métodos: POST e o GET. Quando solicitado o método GET trata-se de uma requisição da página do HTML enviando a pagina de HTML existente no cartão SD: "http://ipnetduino/config.html". Está página "config.html" pode ser configurada através da definição REQUEST_CONFIG_URL em Settings.cs. Uma vez carregada a página é possível enviar o formulário através de um POST com as informações da nova configuração. Quando o cliente envia o POST a requisição trata cada informação enviada manipulando o arquivo de configuração e ajustando as definições.

>private void SetNewConfig(RequestReceivedEventArgs e, Hashtable results)
{
    if (e.Request.Method == "POST")
    {
        //Esta enviando as informações de configuração
        Configuration config = new Configuration(Settings.CONFIG_FILE);

        foreach( DictionaryEntry ent in e.Request.PostArguments)
        {
            config.SetConfig(ent.Key.ToString(), ent.Value.ToString(),true);
        }
        //Envia a página de configuração
        e.Request.URL = Settings.REQUEST_CONFIG_OK_URL;
        Response response = null;
        response = (Response)_Responses["FileResponse"];
        if (response != null)
        {
            if (response.ConditionsCheckAndDataFill(e))
            {
                response.SendResponse(e);
            }
        }   

        //Force to Write
        config.ForceToWrite();
        //Apaga tudo!
        Debug.GC(true);
        //Desliga a placa
        PowerState.RebootDevice(false);
    }
    else
    {
        //envia a página de configuração
        e.Request.URL = Settings.REQUEST_CONFIG_URL;
        Response response = null;
        response = (Response)_Responses["FileResponse"];
        if (response != null)
        {        
            if (response.ConditionsCheckAndDataFill(e))
            {
                if (!response.SendResponse(e))
                {
                }
                ////Debug("Sending response failed");

                Thread ledThread = new Thread(new ThreadStart(delegate()
                {
                    for (int i = 0; i < 3; i++)
                    {
                        _OnboardLed.Write(true); Thread.Sleep(5);
                        _OnboardLed.Write(false); Thread.Sleep(20);
                    }
                }));
                        
                ledThread.Start();
            }
        }   
    }
}

         A final, uma página informado que as configurações foram aceitas é enviada de volta e a placa é reiniciada pelo código para que as configurações da conexão entrem em funcionamento.

//Force to Write
config.ForceToWrite();

//Apaga tudo!
Debug.GC(true);

//Desliga a placa
PowerState.RebootDevice(false);

         Observe que a página quando enviada não é pré-ajustada com os valores de configuração atual isso não fiz a implementação mas é necessário para tornar a solução bem robusta, além disso quando o método POST é recebido os campos do formulário são escritos no arquivo sem nenhum tipo de validação se está correta a informação enviada ou se o atributo faz sentido para a configuração. Uma validação nessa etapa é necessária.

         Outra simplificação feita foi que nem todos os atributos podem ser ajustados, o username e password não estão por definição no meu formulário, mas você pode simplesmente aumentar no formulário e incluir dois campos com label: username e password e tudo estará resolvido. Lembre-se de que como não existe validação deixar esses espaços em branco permite que o usuário seja "" (string vazio) e a senha a mesma coisa "" (vazio).

          Com isso é possível mostrar o quão longe pode-se ir com o Netduino Plus. Naturalmente ainda exite muito a se fazer para tornar essa solução realmente comercial, mas o primeiro passo já foi dado a fim de criar o Netduino Plus como um porto seguro em termos de hardware para webserver
           Esses dias procurando no forum do Netduino encontrei um post argumentando sobre um artigo de uma revista criticando o Netduino e a iniciativa como algo obsoleto e que vai cair em desuso, li diversosr comentários tanto do Chirs Walker como de outros membros da comunidade para saber a opinião de cada um a respeito da continuidade do projeto e posso dizer que a possibilidade que temos em função do Netduino é imensa. Destaco não o Netduino porque acho uma plataforma extremamente cara mais sim a solução do .NET Micro Framework que permite que sem um sistema operacional embarcado seja possível programar em alto nível sem um custo de memória externa. Tenho certeza montar um servidor web com u PIC24 ou um MSP430 não é uma tarefa tão fácil e universal como foi feito aqui.


Requisições XML

           Uma das funcionalidades implementadas pelo Marcus VV. foram as requisições por XML. Trata-se de enviar um GET para escrever/ler algo em hardware/software no Netduino; o que é muito útil se você pretende mandar através do servidor algum comando de automação como abrir e fechar um relé ou verificar o estado de iluminação (por exemplo).
           As respostas XML são na verdade a delegação de um tratamento de um GET (a princípio porque já provei nas configurações que é possível enviar um POST) enviado por um cliente. Como no meu código modificado requisita um login e senha, este login também é necessário para acesso dos métodos XML.
           Em Server.cs tem uma função chama de ResponseListInitialize(), esta função inicia todas as delegações de requisição e os métodos de cada delegação são funções dedicadas para a dada finalidade. Vide o exemplo do "switchDigitalPin", quando enviada a requisição: "http://ipnetduino/switchDigitalPin" essa requisição será interpretada pela função SwitchDigitalPin que necessita do atributo "pin" e "state" que são recebidos pelo método GET, assim a requisição é feita da seguinte maneira:

>http://10.20.19.41/setDigitalPin?pin=10&amp;state=false

           O retorno dessa função é um arquivo XML como pode ser visto nesta requisição:

     
            Além da reuisição de ajuste de pino ainda está disponível as seguintes funções de acesso ao hardware:
  • echo
    > [ipnetduino]/echo?value=[a-Z]
  • switchDigitalPin
    > [ipnetduino]/switchDigitalPin?pin=[0-13]
     
  • setDigitalPin
    > [ipnetduino]/setDigitalPin?pin=[0-13]&state=[true|false]
     
  • pwm
    > [ipnetduino]/pwm?pin=[5|6|9|10]&period=[int]&duration=[int]
     
  • xmlResponselist
    > [ipnetduino]/xmlResponselist
     
  • getAnalogPinValue
    > [ipnetduino]/getAnalogPinValue?pin=[0-5]
     
  • getDigitalPinState
    > [ipnetduino]/getDigitalPinState?pin=[0-13]
     
  • multipleXML
    >[ipnetduino]/multixml
            O que vem a cabeça nesse momento é necessidade expressiva da utilização do navegador para realizar o acesso a essas "funções" disponíveis para acesso a placa. Para provar que isso não é necessário eu fiz um pequeno programa animado para realizar o login no servidor do Netduino Plus e realizar os ajustes dos pinos e ler as entradas analógicas de maneira prática.

            A ideia do software é tão somente mostrar a praticidade do NeoMika.Webserver e o suporte ao XML. Iniciando o programa você pode ajustar o IP para o valor do IP que a sua placa do Netduino Plus está e realizar um teste de ping. Ping's recebidos retornam um true no log de eventos. Antes de acessar a placa você necessita realizar login, eu tentei de diversas formas realizar o envio do POST de login mas foi em vão, por isso, quando você clicar no botão de Login  você será enviado para a página de login que uma vez realizado corretamente dá acesso ao programa a placa.

            Do lado aparece a imagem do Netduino e ao redor dela os campos de acesso. Ao clicar em atualizar os valores dos pinos analógicos são atualizados e ao mudar o valor de percentagem dos PWM's estes mudam na placa bem como os botões manipulam os pinos IO. Tudo isso é realizado através de métodos de XML.
Function getAnvalue(pin As Integer) As String
        Dim webClient As New System.Net.WebClient
        Dim result As String = webClient.DownloadString("http://" + boardIP.Text.ToString() + "/getAnalogPinValue?pin=" + pin.ToString())
        EventsLog.Text += result.ToString() + vbNewLine
        Dim AdStrVal As String() = result.Split(">")

        Dim AdStrVal2 As String() = AdStrVal(3).Split("<")

        Dim advalue As Integer = CType(AdStrVal2(0), Integer)

        Return (3.3 * advalue / 1024).ToString()
    End Function

Considerações Finais

            Para encerrar essa serie de artigos sobre o NeoMika.Webserver e o trabalho do Marcus VV. gostaria de destacar a praticidade com que tudo isso foi implementado e que as novas aplicações podem ser encerradas no meio do código sem muitas alterações. Isso é importante porque mostra o quão rápido pode se desenvolver algo novo sendo ideal para pequenas aplicações profissionais.

            Muito embora ainda existam problemas e a solução não seja completamente lapidada ainda existem muitos pontos que podem ser aprimorados e meu desenvolvimento do servidor vai continuar. Espero que tenham gostado do que foi realizado, se tiverem dúvidas postem comentários que assim que possível responderei.


A versão atualizada do servidor web pode ser baixada aqui.
O software de manipulação do XML pode ser baixado aqui.

Um comentário:

  1. Parabéns Victor por esta série de posts explicando esta aplicação.

    ResponderExcluir