Manual

do

Maker

.

com

Arquivo ini e parâmetros por linha de comando (dicas 2)

Arquivo ini e parâmetros por linha de comando (dicas 2)

Dando continuidade à série de dicas para programar em C++ no Raspberry, vamos ver mais alguns recursos  importantes, como a utilização de arquivo ini e parâmetros por linha de comando.

Se não leu o artigo anterior relacionado, veja em Como programar em C++ no Raspberry.

Onde comprar Raspberry

Como citado anteriormente, os artigos relacionados ao Raspberry são uma parceria com a CurtoCircuito. Visite o site e compre Raspberry com nosso parceiro para reforçar essa parceria.

Arquivo ini para carregar predefinições

Vamos criar uma situação hipotética (porque apesar de ter usado em um projeto real, não posso dar informações a respeito). Temos no Raspberry um acionamento de relé no pino 22, geramos log em /var/log/DobitAoByte.log. Nossa base de dados de produção está em /home/pi/DobitAoByte.sqlite3. O caminho de logs é um padrão, mas o da base de dados provavelmente deveria ser /opt/database. Só que ainda assim pareceria meio distribuído, certo? E se formos dar manutenção depois de um tempo e não lembrarmos os caminhos dos arquivos? E se alguém desconectar o pino do acionamento do relé sem querer?

Para não ter que recorrer à documentação do projeto para coisas tão simples e para não ter que recompilar o programa para definir novos parâmetros, podemos utilizar um arquivo ini.

Para carregarmos parâmetros a partir de arquivo, temos a opção da lib boost no Raspberry. Então, criamos um arquivo ini em /home/pi com os seguintes parâmetros:

[pins]
relay1=22
[logs]
log=/var/logs/DobitAoByte.log
[database]
dbase=/home/pi/DobitAoByte.sqlite3
table=history

Aqui definimos o pino 22 para o relay1, o log como citado anteriormente, a base de dados e o nome de tabela a ser criada. Instalemos agora a libboost:

apt-get install libboost1.62-dev

E criamos o código para ler nosso arquivo DobitAoByte.ini:

#include <iostream>
#include <cstring>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/ini_parser.hpp>

#define INIFILE "DobitAoByte.ini"

using namespace std;

int main(){
    //tenta abrir o arquivo primeiro. Se existir...
    if (FILE *f = fopen(INIFILE,"r")){
        fclose(f);
        boost::property_tree::ptree pt;
        boost::property_tree::ini_parser::read_ini(INIFILE, pt);
        
        uint8_t relay = pt.get<uint8_t>("pins.relay1", 22); //22 é o padrão nesse caso, se a sessão pins ou o item relay1 não existirem
        string logs   = pt.get<string>("logs.log","/var/log/DobitAoByte.log");
        string db     = pt.get<string>("database.dbase","/opt/database/DobitAoByte.sqlite3");
        string table  = pt.get<string>("database.table","outro");
        
        cout << "O pino do relé é " << to_string(relay) << endl;
        cout << "o arquivo de log é " << logs << endl;
        cout << "A base de dados está em " << db << endl;
        cout << "A tabela a ser criada é " << table << endl;
        return 0;
    }
}

Perceba que o próprio método pt.get faz um casting inflow, usando comoDa compilação à execução:

inifile.webp

Agora, vamos mudar os parametros no arquivo ini e rodar novamente:

inifileChanged.webp

E pra finalizar, vamos remover o parâmetro "table" da sessão database do arquivo ini:

inifileVoidField.webp

Parâmetros por linha de comando em C++

O arquivo ini é excelente para rodar programas que serão serviços de sistema. Mas em alguns casos, pode ser necessário passar parâmetros por linha de comando para fazer testes previamente à criação do arquivo ini, ou para sobrescrever um parâmetro padrão sem ter que mexer no arquivo. Para tal, existem diversas opções, das quais acho mais simples utilizar essa abaixo.

#include <iostream>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

using namespace std;

int c;
int aflag;
int bflag;
int cflag;

int main(int argc, char *argv[]){
    char *fq = NULL;
    while ((c = getopt(argc,argv,"abc")) != -1)

        switch(c){
        case 'a':
            aflag = 1;
            cout << "voce escolheu parametro A" << endl;
            break;

            case 'b':
                bflag         = 1;
                cout << "voce escolheu o parametro B" << endl;
                break;
            case 'c':
                char *valor;
                valor = argv[optind];
                cout << "Voce escolheu C com o valor " << valor << endl;
                break;

            case '?':
                if (optopt == 'c'){
                    fprintf (stderr, "Option -%c requires an argument.\n", optopt);
                }
                else if (isprint(optopt)){
                    fprintf (stderr, "Unknown option `-%c'.\n", optopt);
                }
                else if (optopt == '\x0'){
                    uint8_t x = 0; //só pra evitar esse bug até descobrir a razão
                }
                else{
                    fprintf (stderr,"Unknown option character `\\x%x'.\n",optopt);
                }
            default:
                abort();
    }
}

Para compilar:

g++ -o params parameters.cpp

E o teste:

parameters.webp

Se esse recurso for utilizado após o carregamento do arquivo ini, os parâmetros podem ser sobrescritos, de modo que os parâmetros de linha de comando prevaleçam sobre predefinições em variáveis internas, assim como em variáveis alimentadas pelo arquivo ini.

Se eu me lembrar, no próximo artigo relacionado mostrarei como criar uma função de debug e uma função de log.

Inscreva-se no nosso canal Manual do Maker no YouTube.

Também estamos no Instagram.

Nome do Autor

Djames Suhanko

Autor do blog "Do bit Ao Byte / Manual do Maker".

Viciado em embarcados desde 2006.
LinuxUser 158.760, desde 1997.