segunda-feira, 13 de agosto de 2012

Salvando suas configurações no cartão microSD

Uma coisa muito importante que em qualquer aplicação hoje em dia é necessária é a configuração. Ninguém mais trabalha com soluções fixas sem salvar algum tipo de configuração. Seja um simples configuração de um IP ou mensagem personalizada, um arquivo de configuração sempre é necessário.

Inicialmente, quando eu comecei a programar utilizando microcontroladores PIC isso era uma tarefa nível "very hard" porque tratava-se de alocar uma parte da memória de código para salvar variáveis e como na época utilizava códigos de controle e variáveis tipo Double em processadores ponto fixo isso me custava horas de programação para desmontar com segurança um valor Double e tornar a montá-lo depois (O nome dessa técnica é EWR - EEPROM Read Write). Isso ainda é um bom método porque não necessita de uma memória externa como os tradicionais chips SPI  25LCxxxx onde xxxx é a quantidade de memória em míseros kb's (máximo 2Mb)!

Não estou dizendo que trata-se de uma solução ruim, não existe uma solução ruim. Se o interesse é tão somente gravar poucas variáveis essa é uma ideia excelente mesmo que o Chirs Walker não simpatize até porque as placas Netduino e Netduino Mini custam a metade do preço do Netduino Plus, ou seja sua aplicação cai pela metade do preço!. Esse tópico no fórum do Netduino mostra uma discussão a respeito do tema que um dia eu pretendo implementar para reduzir custo e tudo mais.

No caso do Netduino Plus, temos um cartão microSD de 4Gb de espaço para gravar tudo que se deseja e nada melhor que gravar um arquivo só para configurações no cartão, dessa forma o acesso a informação fica prático e além disso para modificar as entradas basta mudar o arquivo no cartão microSD sem necessitar recompilar o código.

O que os leitores devem estar de perguntando nesse momento é porque eu não terminei o atrigo sobre o NeonMika.Webserver e estou escrevendo sobre arquivos de configuração. A lógica está na etapa de salvar as informações da configuração da placa no cartão e para isso eu desenvolvi uma classe independente para tratar desse tipo de coisa.

using System;
using System.Text;
using System.Collections;
using System.Net.Sockets;
using System.Net;
using System.Diagnostics;
using System.IO;
using Microsoft.SPOT;

namespace SDCardClass
{
    /// <summary>
    /// Configuration File Class
    /// 
    /// Developed to: Netduino Plus REV.B Borad 
    /// .NETMF 4.2 RC5
    /// 
    /// By Victor M. Batista
    /// email: victor.mobatista@gmail.com
    /// 
    /// Date: 13-Ago-2012 
    /// </summary>
    class Configuration
    {
        //TODO: Insert List<Hashtable> to create a file with configuration sections
        private Hashtable _Configuration = new Hashtable();
        private string _FileName;

        public enum ConfigMode{Append,DoNothing};

        /// <summary>
        /// Open a configuration file if it exist and provide 
        /// information for request's.
        /// </summary>
        /// <param name="filename">Optinal file name</param>
        public Configuration(string filename = "ndpcfg.ini")
        {
            //Nome global
            _FileName = filename;

            //Clear configuration file;
            _Configuration.Clear();

            //Read configuration file
            if (File.Exists("\\SD\\" + _FileName))
            {
                FileStream CfgFileStream;
                StreamReader CfgStreamReader;

                try
                {

                    CfgFileStream = new FileStream("\\SD\\" + _FileName,
                                                               FileMode.Open,
                                                               FileAccess.ReadWrite,
                                                               FileShare.None);

                    CfgStreamReader = new StreamReader(CfgFileStream);

                    string val = CfgStreamReader.ReadLine();
                    if (val != null)
                    {
                        do
                        {
                            if (val[0] != ';') // If is not a comment line
                            {
                                string[] SplitVals = val.Split('=');
                                if (SplitVals.Length == 2)
                                {
                                    _Configuration.Add(SplitVals[0], SplitVals[1]);
                                }
                            }

                                val = CfgStreamReader.ReadLine();

                        } while (val != null);
                    }

                    CfgStreamReader.Close();
                    CfgFileStream.Close();


                }catch {}

            }

            
        }



        /// <summary>
        /// Apend new configuration in configuration file 
        /// </summary>
        /// <param name="key">Key as a configuration variable</param>
        /// <param name="value">Key value as string</param>
        /// <param name="SetIfExist">Set a this value to the key 
        /// if this current key already exist in context</param>
        public void AppendConfig(string key, string value, bool SetIfExist = false)
        {
            if (!_Configuration.Contains(key))
            {
                _Configuration.Add((object)key, (object)value);
            }
            else if(SetIfExist)
            {
                _Configuration[key] = value;
            }
        }

        /// <summary>
        /// Set new configuration in configuration file 
        /// </summary>
        /// <param name="key">Key as a configuration variable</param>
        /// <param name="value">Key value as string</param>
        public void SetConfig(string key, string value, bool CreateIfNotExist = false)
        {

            if (_Configuration.Contains(key))
            {
                _Configuration[key] = value;
                
            }
            else if (CreateIfNotExist)
            {
                _Configuration.Add((object)key, (object)value);
            }
        }

        /// <summary>
        /// Get a value of key. If current kay did not exist in the context
        /// you can put a default value to set or simples ignore and recive 
        /// a empty string as defult
        /// </summary>
        /// <param name="key">Key that you need the value</param>
        /// <param name="mode">Handling method if key don't exit in the file</param>
        /// <param name="val">Default value that to place in key if it don't exist.
        /// If *mode* is DoNothig this value was ignored</param>
        /// <returns></returns>
        public string GetConfigurationOf(string key, ConfigMode mode =
            ConfigMode.DoNothing, string val = "" ){

            //Verifay if this key exit
            if(_Configuration.Contains(key))
            {
                //If key exist return value.
                return _Configuration[key].ToString();
            }else{
                //else make the handling 
                if (mode != ConfigMode.DoNothing)
                {
                    //append new entry in configuration file.
                    AppendConfig(key, val);
                    return val;
                }
                else
                {
                    return "";
                }
            }
        }


        /// <summary>
        /// Function to force to write in the microSD card the current
        /// configuration.
        /// </summary>
        public void ForceToWrite()
        {
            try
            {
                FileStream CfgFileStream = new FileStream("\\SD\\" + _FileName,
                                                            FileMode.OpenOrCreate,
                                                            FileAccess.ReadWrite,
                                                            FileShare.ReadWrite);

                StreamWriter CfgStreamWriter = new StreamWriter(CfgFileStream);

                CfgStreamWriter.WriteLine(";Netduino Plus Configuration File");
                foreach (DictionaryEntry Entry in _Configuration)
                {
CfgStreamWriter.WriteLine(Entry.Key.ToString() + "=" +
                                                Entry.Value.ToString());
                }
                CfgStreamWriter.Close();
                CfgFileStream.Close();
            }
            catch { }
        }

        /// <summary>
        /// Desconstructor to write de file in card when the class is
        /// disposed
        /// </summary>
        ~Configuration()
        {
            ForceToWrite();
        }
    }
}


A classe é bem simples chama-se configuraition e serve para manipular eventos de escrita e leitura no cartão microSD isso facilita porque não é necessário acessar o arquivo diretamente, apenas criar a classe e pegar os valores de configuração desejados.


O formato escolhido para o arquivo é o INI - agradeço ao Cairo por ter me mostrado esse formato muito vantajoso de arquivo. Para quem não sabe o formato de arquivo INI é um padrão para elaboração de arquivos dedicados a carregar configurações de programas e dispositivos. Os arquivos INI são arquivos utilizam um texto simples com uma estrutura bem básica que muito difundidos na Microsoft e espalhados pelo Windows. Naturalmente como fica muito solto (um arquivo oculto) no Windows  foi substituído pelo registro do Windows mas o formato ficou.

Quando ao nome vem da extensão de nome de arquivo normalmente utilizada, ".INI", que vem de "inicialização". Como ".INI" é uma extensão do "Windows" muitos desenvolvedores começaram a bagunçar criando extensões diversas como como ".CFG", ".conf" ou ".TXT".


O formato INI encapsula a mensagem através de novas linhas onde uma  propriedade (ou parâmetro) é único e está numa linha qualquer do arquivo. A essa propriedade tem um nome e um valor, delimitado por um sinal de igual(=). O nome aparece à esquerda do sinal de igual.

propriedade = valor
propriedade2 = valor2
O ponto-e-vírgula (";") indica o início de uma linha que é comentário tratada como comentário Qualquer coisa entre o ponto e vírgula e o final da linha é ignorada.

 O código que manipula um arquivo escrito dessa forma no cartão microSD. permitindo a criação de comentários e a manipulação dos atributos através de uma tabela. Os princiapis métodos são o Construtor, o AppendConfig(), GetConfigurationOf() e o ForceToWrite().

O construtor da classe tem um valor padrão trata do nome padrão ndpcfg.ini que quer dizer Netduino Plus Configuration esse arquivo pode ser alterado de local ou nome criando a classe com um nome de arquivo diferente. Caso deseje colocar numa subpasta lembre-se da sintax:  "Pasta1\\Pasta2\\nomedoarquivo.extensão".

O método GetConfigurationOf( key ) trata ma requisição de uma propriedade listada no arquivo, o natural é que a propriedade me questão exista e o valor, como string, seja retornado caso contrário retorna um string sem caracteres (""). Mas é possível atribuir um valor padrão caso não existe que será gravado ao terminar a classe de manipulação.

O outro método é para atribuir uma chave nova pode utilizar as funções AppendConfig() ou SetConfig(), isso porque o arquivo INI tem uma sintaxe que não permite propriedades com o mesmo nome assim você pode tanto tentar criar e se já existir no contexto apenas mudar o valor ou então o vice-versa com o SetConfig()

Por fim, tem o método ForceToWrite() que serve para forçar a escrita de toda a configuração no cartão microSD mesmo antes da classe ser encerrada. Isso reduz a quantidade de vezes que o cartão é acessado, não é a melhor maneira de fazer isso mas essa classe ainda vai evoluir.

Para testar a classe baixe o projeto aqui.

Configuration config = new Configuration();
config.GetConfigurationOf("IP",
    Configuration.ConfigMode.Append, "192.200.6.254");
            
//Contando o numero de execuções do código
Int32 exec = Convert.ToInt32(
    config.GetConfigurationOf("ExecutionsCount", 
    Configuration.ConfigMode.Append, "1"));
exec++;
config.SetConfig("ExecutionsCount", exec.ToString());
            
//Gravando valores
config.AppendConfig("mask", "255.255.0.0");
config.SetConfig("gateway", "192.200.1.1");

//Forçando a escrita
config.ForceToWrite();

Para ilustrar o funcionamento eu criei uma varável no arquivo de configuração que é incrementada toda vez que o código é executado, um exemplo clássico de aplicação do arquivo de configuração.

Abra sua cabeça para novas ideias, no arquivo de configuração pode constar valores como mensagens de LCD para diferentes tipos de cultura, rotinas de tratamento de opções preferidas pelos usuários da sua solução e muito mais.

Nenhum comentário:

Postar um comentário