Este documento procura oferecer uma visão geral sobre o funcionamento do código deste projeto, útil para aqueles que desejam modificá-lo ou simplesmente endendê-lo.
Este documento procura oferecer uma visão geral sobre o funcionamento do código deste projeto, útil para aqueles que desejam modificá-lo ou simplesmente endendê-lo.
Funcionamento
*************
Este projeto compreende as duas seguintes ferramentas:
* **Firmware**
Executado no processador do Arduino, é responsável por ler os sensores conectados de acordo com solicitações enviadas à porta serial. Utiliza bibliotecas de terceiros para leitura de sensores complexos.
* **Logger**
Executado em uma máquina Linux (PC, Raspberry, etc), é responsável por coletar dados da placa através de uma leitura serial, fazer armazenamento local e também remoto através do envio de dados para o nosso servidor em `dados.cta.if.ufrgs.br/emm <http://dados.cta.if.ufrgs.br/emm>`_ ou algum outro especificado pelo usuário.
Ambos encontram-se no mesmo repositório pois o logger está preparado para enviar comandos pela serial cujo formato o firmware está preparado para receber. Por exemplo, considere a seguinte string enviada pelo logger à porta serial onde está a placa Arduino::
readSensors,LDR,DHT22_TEMP
Ao receber esses caracteres, o firmware determinará que trata-se de um comando para leitura de sensores e que os sensores a serem lidos são, nessa ordem: o ``LDR`` e o ``DHT22_TEMP`` (luminosidade e temperatura, respectivamente). O firmware retorna pela serial uma resposta com números separados por vírgula, algo como::
84,24.5
indicando 84 % de luminosidade e 24,5 ºC de temperatura. O logger estará então preparado para receber dois valores, guardá-los em um arquivo de log local (juntamente com a hora do sistema) e também fazer uma tentativa de envio ao servidor. Caso o envio falhe, a leitura será adicionada ao arquivo ``outgoing.json`` para futuras tentativas de comunicação com o servidor.
Opcionalmente, poderá ser utilizada a hora de um relógio ``RTC_DS1307`` da placa. Caso este não esteja presente ou não retorne valores consistentes, a hora do sistema é utilizada por padrão.
Exemplos
========
Segue abaixo o exemplo de um log de execução para uma estação 100% funcional, possuindo os 4 sensores oficialmente suportados (``DHT22_TEMP``, ``DHT22_AH``, ``BMP085_PRESSURE`` e ``LDR``) juntamente com o relógio ``RTC_DS1307``::
2015-09-03 16:12:24 : INFO : ========================================
2015-09-03 16:12:29 : INFO : Updated datalog file at '/home/nelso/lief/arduino-meteorolog/data/datalog-2015-09-03-15-37-00.csv'
2015-09-03 16:12:29 : INFO : Starting new HTTP connection (1): localhost
2015-09-03 16:12:29 : INFO : Server response: {'success': '1 new points were saved on the board.'}
2015-09-03 16:12:29 : INFO : Going to sleep now for 0.2 minutes
A exemplo de como os erros são reportados, segue abaixo o log de execução para uma placa Arduino sem nenhum sensor, com um servidor fora do ar, mas com a mesma configuração ``settings.ini`` do exemplo anterior::
2015-09-03 16:17:10 : INFO : ========================================
2015-09-03 16:17:15 : WARNING : DateTimeError: [RTC_DS1307]: Expected format '%Y-%m-%d %H:%M:%S' but was given '2165-165-165 165:165:85' (Exception: time data '2165-165-165 165:165:85' does not match format '%Y-%m-%d %H:%M:%S')
2015-09-03 16:17:15 : INFO : Updated datalog file at '/home/nelso/lief/arduino-meteorolog/data/datalog-2015-09-03-16-17-10.csv'
2015-09-03 16:17:15 : INFO : Starting new HTTP connection (1): localhost
2015-09-03 16:17:15 : ERROR : Request: None. Unable to reach the server at 'http://localhost:5000/api/post/rawsensordata/2'. Exception: ('Connection aborted.', ConnectionRefusedError(111, 'Connection refused'))
2015-09-03 16:17:15 : INFO : Updated local file '/home/nelso/lief/arduino-meteorolog/data/outgoing.json'.
2015-09-03 16:17:15 : INFO : Going to sleep now for 0.2 minutes
Apenas sensores que utilizam o protocolo I2C podem ter sua presença detectada de antemão, como é o caso do ``BMP085`` e do ``RTC_DS1307``, retornando um erro como ``<bmp085_not_found>``. Repare que embora o **DHT22** não esteja presente na placa, o valor retornado pela leitura de umidade do ar foi ``0.000000``, claramente sem significado físico. O mesmo acontece com o **LDR**.
O log dispara **WARNINGS** para as falhas de leitura detectadas. No caso do relógio, o erro indica data inválida e portanto, a hora do sistema será utilizada. Por fim, o log também disparou um **ERROR** na tentativa de conexão com o servidor. A consequência é a criação do arquivo ``data/outgoing.json`` contendo dados a serem enviados em tentativas posteriores.
Estrutura de arquivos
Estrutura de arquivos
*********************
*********************
...
@@ -13,39 +73,79 @@ Segue uma breve descrição dos arquivos/diretórios presentes na pasta raiz do
...
@@ -13,39 +73,79 @@ Segue uma breve descrição dos arquivos/diretórios presentes na pasta raiz do
arduino-meteorolog/
arduino-meteorolog/
├── data/ # contém dados gerados pelo logger
├── data/ # contém dados gerados pelo logger
├── docs/ # arquivos de documentação
├── docs/ # contém essa documentação
├── logger/ # software que faz coleta de dados e envio para o servidor
├── logger/ # software que faz coleta de dados e envio para o servidor
├── meteorolog/ # projeto ".ino" do firmware (compilável pela Arduino Toolchain)
├── meteorolog/ # projeto ".ino" do firmware (compilável pela Arduino Toolchain)
├── scripts/ # scripts utilizados pelo Makefile
├── scripts/ # scripts utilizados pelo Makefile
├── settings.ini # configurações do logger
├── settings.ini # configurações do logger
└── Makefile # proporciona diversos comandos para fácil utilização
└── Makefile # proporciona diversos comandos para facilitar a manutenção
Makefile
********
O arquivo ``Makefile`` contém diversos comandos [1]_ curtos para facilitar a realização de diversas operações de instalação e manutenção de todos os softwares do projeto. Assim, basta estar presente nesta pasta raiz e executar:
Esse arquivo contém diversos comandos simples a serem passados para a ferramenta ``make`` [1]_ de modo a facilitar o uso e manutenção dos softwares desse projeto. Basta estar na pasta onde se encontra o ``Makefile`` e executar:
.. code-block:: shell
.. code-block:: shell
$ make <comando>
$ make <target>
para realizar alguma tarefa. Experimente ``make help`` para listar todos os comandos possíveis.
para realizar alguma tarefa. Os *targets* possíveis são listados com ``make`` ou ``make help``::
Funcionamento
setup Execute once to prepare the required Python virtual environment
*************
firmware Compile and upload the firmware to the Arduino board via serial
serial Starts a serial session with Python for board communication
sync-rtc Synchronizes the board RTC_DS1307 with this system's time
Essa estrutura existe para comportar os dois seguintes softwares:
run Execute the logger on the foreground. Hit Ctrl+C to stop it.
deploy Install logger on the Supervisor daemon tool (exec background)
undeploy Undo the 'deploy' command
* **Firmware**: Executado no processador do Arduino, é responsável por ler os sensores conectados de acordo com solicitações enviadas à porta serial.
tail-log Follow updates on the last execution log
tail-data Follow updates on the last data log
plot-data col=x Uses Gnuplot to plot last data log col number x
* **Logger**: Executado em uma máquina Linux (PC, Raspberry, etc), é responsável por coletar dados da placa através de uma leitura serial, fazer armazenamento local e também remoto (envio de dados para [dados.cta.if.ufrgs.br/emm](dados.cta.if.ufrgs.br/emm)).
Na prática o usuário deverá fazer, ao obter uma cópia do repositório:
Ambos encontram-se no mesmo repositório pois o logger está preparado para enviar comandos pela serial cujo formato o firmware está preparado para receber. Por exemplo, considere a seguinte string enviada pelo logger à porta serial onde está a placa Arduino::
1. ``make setup`` para instalar as dependências do Logger em um ambiente virtual de Python
2. ``make firmware`` para compilar e gravar o firmware na placa Arduino. Alternativamente, isso pode ser feito pela IDE do Arduino.
3. ``make serial`` para testar a leitura dos sensores com ``>>> send('read,...')`` e também sincronizar o relógio da placa com o do sistema com ``>>> sync_rtc()``, caso possível.
4. ``make run`` para testar a execução do logger com a configuração atual de ``settings.ini``.
5. ``make deploy`` para instalar o logger no Supervisor (gerenciador de processos em background).
6. ``make tail-log`` para acompanhar o log da execução em background e certificar-se de que tudo ocorre como esperado.
Variáveis
=========
readSensors,LDR,DHT22_TEMP
Na parte superior encontram-se definidas variáveis a serem utilizadas pela macro ``${VARIABLE_NAME}``.
Ao receber esses caracteres, o firmware determinará que trata-se de um comando para leitura de sensores e que os sensores a serem lidos são, nessa ordem: o ``LDR`` e o ``DHT22_TEMP`` (luminosidade e temperatura, respectivamente). O firmware retorna pela serial uma resposta com números separados por vírgula, algo como::
* ``PYBIN``
Nome do executável de Python 3 a ser utilizado pelo comando ``make setup``. Padrão: ``python3``. Alguns sistemas utilizam outros nomes, como ``python-3.x`` (onde x é um número). Nesse caso, o usuário deverá passar o nome correto como em::
$ make setup PYBIN=python-3.x
* ``VENVDIR``
Nome do diretório onde será instalado o ambiente virtual de Python pelo comando ``make setup``. Padrão: pasta ``.venv`` ao lado do ``Makefile``.
* ``VENVPY``
Caminho do interpretador Python dentro do ambiente virtual. Mesmo que a versão instalada de Python seja 3.x, a ferramenta ``virtualenv`` disponibiliza o link simbólico ``python`` para acessar o interpretador, seja qual versão for.
Sintaxe
=======
Cada *target* do ``Makefile`` contém uma série de comandos para o shell cuja funcionalidade é auto-explicativa. Vale apenas notar o detalhe de que um *target* pode ser executado por outro e, em caso de falha, nenhum outro comando ou *target* será executado.
A exemplo, considere a *target* ``run``::
run: check-venv
${VENVPY} logger/run.py --verbose
84.1,24.5
Antes de executar seus comandos (no caso, apenas uma linha conforme identação), será executada a *target* ``check-venv``, que verifica a existência do ambiente virtual de Python e imprime uma menssagem de ajuda caso negativo.
.. note:: A sintaxe do ``Makefile`` impõe o uso de tabulação para comandos de um *target*. Editores configurados para expandir tabs em espaços (o que é recomendado para programação Python, por exemplo) deverão ser configurados para tratar arquivos ``Makefile`` de maneira separada, i.e., sem expandir tabs em espaços. Isto acontece por padrão no editor ``Vim``.
indicando 84,1 % de luminosidade e 24,5 ºC de temperatura. De acordo com o comando enviado, o logger estará preparado para receber uma resposta com dois valores **CSV** (*comma-separated-values*), que serão guardados juntamente com a hora do sistema em um arquivo de log local. Adicionamente, o logger também fará uma tentativa de envio ao servidor remoto. Caso falhe, será guardado em um arquivo ``outgoing.json`` para tentativas futuras.
########
########
Firmware
Firmware
...
@@ -97,6 +197,12 @@ Essa string contendo o comando e seus argumentos é enviada para ``execute_board
...
@@ -97,6 +197,12 @@ Essa string contendo o comando e seus argumentos é enviada para ``execute_board
<invalid_commmand:nomeDoComando,...>
<invalid_commmand:nomeDoComando,...>
.. note:: Esses comandos devem ser enviados através de um monitor serial, como por exemplo o presente na IDE do Arduino. Alternativamente, esse projeto disponibiliza o *target* ``$ make serial`` para inicializar uma seção Python com uma comunicação aberta conforme configurado em ``settings.ini``. Nesse caso, os comandos da placa devem ser enviados como segue::
>>> send('nomeDoComando,arg1,arg2,...,argN')
onde ``send()`` é uma função definida no script ``init_serial.py`` que recebe uma string a ser enviada à porta serial e retorna uma string contendo a resposta lida pela porta.
readSensors
readSensors
===========
===========
...
@@ -116,9 +222,9 @@ A operação de ``call_read_sensor()`` depende então de mapear-se uma string co
...
@@ -116,9 +222,9 @@ A operação de ``call_read_sensor()`` depende então de mapear-se uma string co
* ``_sensor_names[]``: Contém o nome de todos os sensores disponíveis.
* ``_sensor_names[]``: Contém o nome de todos os sensores disponíveis.
* ``_sensor_nicknames[]``: Contém todos os respectivos apelidos.
* ``_sensor_nicknames[]``: Contém todos os respectivos apelidos.
* ``_fp_array_read_sensor[]``: Contém os ponteiros de função das ``read_X()``, onde ``X`` é o nome de um sensor -- por exemplo, ``read_LDR()``.
* ``_fp_read_sensor[]``: Contém os ponteiros de função das ``read_X()``, onde ``X`` é o nome de um sensor -- por exemplo, ``&read_LDR`` é o ponteiro de ``read_LDR()``.
Percorrendo-se os dois primeiros, ``call_read_sensor()`` busca por um nome/apelido válido. Caso encontre, o índice é utilizado para acessar ``_fp_array_read_sensor[]``, obter o ponteiro da função e finalmente executá-la.
Percorrendo-se os dois primeiros, ``call_read_sensor()`` busca por um nome/apelido válido. Caso encontre, o índice é utilizado para acessar ``_fp_read_sensor[]``, obter o ponteiro da função e finalmente executá-la.
Os vetores são incializados com as respectivas constantes declaradas em `my_sensors.h <https://git.cta.if.ufrgs.br/meteorolog/arduino-meteorolog/blob/master/meteorolog/mysensors.h>`_.
Os vetores são incializados com as respectivas constantes declaradas em `my_sensors.h <https://git.cta.if.ufrgs.br/meteorolog/arduino-meteorolog/blob/master/meteorolog/mysensors.h>`_.
...
@@ -151,7 +257,7 @@ Esse módulo contém:
...
@@ -151,7 +257,7 @@ Esse módulo contém:
* ``__SENSOR_COUNT``: total de sensores;
* ``__SENSOR_COUNT``: total de sensores;
* ``__SENSOR_NAMES``: vetor de strings de nomes de todos os sensores;
* ``__SENSOR_NAMES``: vetor de strings de nomes de todos os sensores;
* ``__SENSOR_NICKNAMES``: vetor de strings de apelidos de todos os sensores;
* ``__SENSOR_NICKNAMES``: vetor de strings de apelidos de todos os sensores;
* ``__FP_ARRAY_READ_SENSOR``: vetor de ponteiros de função das ``read_X()``.
* ``__FP_READ_SENSOR``: vetor de ponteiros de função das ``read_X()``.
.. note:: Entende-se aqui **sensor** por um elemento de software capaz de proporcionar um valor medido. Ou seja, ainda que um único componente eletrônico possa oferecer diversas medições (como temperatura e umidade do ar pelo DHT22), em termos do software cada medição é devida a um sensor, conforme cadastrado em http://dados.cta.if.ufrgs.br/emm.
.. note:: Entende-se aqui **sensor** por um elemento de software capaz de proporcionar um valor medido. Ou seja, ainda que um único componente eletrônico possa oferecer diversas medições (como temperatura e umidade do ar pelo DHT22), em termos do software cada medição é devida a um sensor, conforme cadastrado em http://dados.cta.if.ufrgs.br/emm.
...
@@ -189,7 +295,7 @@ onde ``NOVO_NOME`` será o nome do novo sensor.
...
@@ -189,7 +295,7 @@ onde ``NOVO_NOME`` será o nome do novo sensor.
* Incremente ``__SENSOR_COUNT``;
* Incremente ``__SENSOR_COUNT``;
* Inclua ``NOVO_NOME`` no vetor ``__SENSOR_NAMES``;
* Inclua ``NOVO_NOME`` no vetor ``__SENSOR_NAMES``;
* Inclua um apelido curto qualquer no vetor ``__SENSOR_NICKNAMES``, na mesma posição utilizada por ``NOVO_NOME`` anteriormente;
* Inclua um apelido curto qualquer no vetor ``__SENSOR_NICKNAMES``, na mesma posição utilizada por ``NOVO_NOME`` anteriormente;
* Inclua o ponteiro de função ``&read_NOVO_NOME`` no vetor ``__FP_ARRAY_READ_SENSOR``, na mesma posição utilizada por ``NOVO_NOME`` anteriormente.
* Inclua o ponteiro de função ``&read_NOVO_NOME`` no vetor ``__FP_READ_SENSOR``, na mesma posição utilizada por ``NOVO_NOME`` anteriormente.
**4. Implemente o código em mysensors.cpp**
**4. Implemente o código em mysensors.cpp**
...
@@ -244,15 +350,15 @@ Este software está escrito na linguagem Python 3 e apresenta a seguinte estrutu
...
@@ -244,15 +350,15 @@ Este software está escrito na linguagem Python 3 e apresenta a seguinte estrutu
.. note:: Normalmente em projetos Python, o arquivo de configuração fica presente no nível superior da pasta package ao lado de ``run.py``. No caso deste projeto, optamos por mantê-lo na raíz do repositório, na posição de destaque ao lado do ``Makefile``.
.. note:: Normalmente em projetos Python, o arquivo de configuração fica presente no nível superior da pasta package ao lado de ``run.py``. No caso deste projeto, optamos por mantê-lo na raíz do repositório, na posição de destaque ao lado do ``Makefile``.
Projetos Python multi-arquivos fazem uso do conceito de **package**: pasta que contém um arquivo `__init__.py <https://git.cta.if.ufrgs.br/meteorolog/arduino-meteorolog/blob/master/logger/app/__init__.py>`_ tornando-se acessível exteriormente como um módulo. Assim, tanto um interpretador em `logger/ <https://git.cta.if.ufrgs.br/meteorolog/arduino-meteorolog/blob/master/logger>`_ como o arquivo `run.py <https://git.cta.if.ufrgs.br/meteorolog/arduino-meteorolog/blob/master/logger/run.py>`_ fora do package podem fazer::
Projetos Python multi-arquivos fazem uso do conceito de **package**: pasta que contém um arquivo ``__init__.py`` para tornar-se acessível exteriormente como um módulo. Assim, o arquivo ``run.py`` que está fora do package pode fazer::
from app.main import Meteorologger
from app.main import Meteorologger
Módulos internos do package podem acessar uns aos outros por importação relativa, como acontece em `app/main.py <https://git.cta.if.ufrgs.br/meteorolog/arduino-meteorolog/blob/master/logger/app/main.py>`_::
Módulos internos do package podem acessar uns aos outros por importação relativa, como acontece em ``app/main.py``::
from .config import Config
from .config import Config
onde o operador ``.`` refere-se ao nível atual (`main.py <https://git.cta.if.ufrgs.br/meteorolog/arduino-meteorolog/blob/master/logger/app/main.py>`_ e `config.py <https://git.cta.if.ufrgs.br/meteorolog/arduino-meteorolog/blob/master/logger/app/config.py>`_ estão na mesma pasta), ``..`` indica nível superior e assim por diante.
onde o operador ``.`` refere-se ao nível atual (``main.py`` e `]]]ncia]ncia]n`config.py`` estão na mesma pasta), ``..`` indica nível superior e assim por diante.
Em resumo, o código do aplicativo logger está todo na pasta ``app/``, onde ``Meteorologger`` é a classe principal, e sua execução se dá pelo arquivo ``run.py`` com o seguinte ponto de entrada::
Em resumo, o código do aplicativo logger está todo na pasta ``app/``, onde ``Meteorologger`` é a classe principal, e sua execução se dá pelo arquivo ``run.py`` com o seguinte ponto de entrada::
...
@@ -275,21 +381,15 @@ Além da linguagem Python 3 o logger depende das seguintes bibliotecas de tercei
...
@@ -275,21 +381,15 @@ Além da linguagem Python 3 o logger depende das seguintes bibliotecas de tercei
.. note:: As bibliotecas e suas versões estão listadas no arquivo `requirements.pip <https://git.cta.if.ufrgs.br/meteorolog/arduino-meteorolog/blob/master/logger/requirements.pip>`_ para instalação automatizada através do gerenciador de pacotes **pip3** (vem por padrão com Python 3.4+).
.. note:: As bibliotecas e suas versões estão listadas no arquivo `requirements.pip <https://git.cta.if.ufrgs.br/meteorolog/arduino-meteorolog/blob/master/logger/requirements.pip>`_ para instalação automatizada através do gerenciador de pacotes **pip3** (vem por padrão com Python 3.4+).
Afim de evitar comprometer a instalação global do Python do usuário, optamos aqui pelo uso da ferramenta `virtualenv <https://virtualenv.pypa.io/en/latest/>`_ para a criação de um ambiente virtual contendo uma cópia isolada do interpretador Python. Todo o processo é automatizado pelo ``Makefile`` principal do projeto através do comando:
Afim de evitar comprometer a instalação global do Python do usuário, optamos aqui pelo uso da ferramenta `virtualenv <https://virtualenv.pypa.io/en/latest/>`_. Todo o processo é automatizado pelo comando ``$ make setup``, cujo resultado é a criação de uma pasta ``.venv`` contendo uma instalação isolada de Python 3 e as bibliotecas mencionadas acima. A execução correta desse comando depende dos seguintes programas no sistema:
.. code-block:: shell
$ make setup
O resultado é a criação de uma pasta ``.venv`` contendo uma instalação isolada de Python 3 e as bibliotecas mencionadas acima. A execução correta desse comando depende dos seguintes programas no sistema:
* **python3** : interpretador da linguagem Python 3.x (recomenda-se versão 3.4);
* **python3** : interpretador da linguagem Python 3.x (recomenda-se versão 3.4);
* Pacote Debian: ``python3``
* Instalação no Debian: ``$ sudo apt-get install python3``
* **pip3** : gerenciador de pacotes do Python 3;
* **pip3** : gerenciador de pacotes do Python 3;
* Pacote Debian: ``python3-pip``
* Instalação no Debian: ``$ sudo apt-get install python3-pip``
* **virtualenv** : criação de ambientes virtuais de Python;
* **virtualenv** : criação de ambientes virtuais de Python;
...
@@ -299,51 +399,47 @@ Adicionamente, para que o logger possa ser executado em background (ver seção
...
@@ -299,51 +399,47 @@ Adicionamente, para que o logger possa ser executado em background (ver seção
* **supervisor** : gerenciador de daemons (processos background);
* **supervisor** : gerenciador de daemons (processos background);
* Pacote Debian: ``supervisor``
* Instalação no Debian: ``supervisor``
.. note:: Algumas distribuições podem possuir as versões 3.x do interpretador Python registradas em comandos diferentes de ``python3``, como por exemplo, ``python-3.x``, o mesmo valendo para o **pip** (ex: ``pip-3.x``). Nesse caso, você precisa fornecer o nome correto através da variável ``PYBIN``::
.. note:: Algumas distribuições podem possuir o executável de Python 3.x registrado em nomes diferentes de ``python3`` (assumido por ``$ make setup``). Nesse caso, forneça o nome correto fazendo, por exemplo::
$ make setup PYBIN=python-3.x
$ make setup PYBIN=python-3.x
.. note:: Os pacotes Debian podem ser instalados com ``$ sudo apt-get install <pacote>``. Usuários de outras distribuições deverão procurar os equivalentes para o seu gerenciador de pacotes.
onde x é um número. O mesmo vale para o **pip3**::
$ sudo pip-3.x install virtualenv
run.py
run.py
******
******
Este arquivo consiste no ponto de entrada da aplicação, permitindo a execução do logger por um interpretador Python: ``$ python3 run.py [options]``. Entretanto, conforme descrito na seção anterior sobre dependências, deve ser utilizado o interpretador do ambiente virtual. Isso é alcançado pelo seguinte comando do ``Makefile``:
Este arquivo consiste no ponto de entrada da aplicação, permitindo a execução do logger por um interpretador Python: ``$ python3 run.py [options]``. Entretanto, conforme descrito na seção anterior sobre dependências, deve ser utilizado o interpretador do ambiente virtual através de:
.. code-block:: shell
.. code-block:: shell
$ make logger-run
$ make run
que deve ser executado após a criação do ambiente virtual com ``make setup``.
.. note:: Deve ser executado após a criação do ambiente virtual com ``$ make setup``.
Parâmetros
Parâmetros
==========
==========
* ``--verbose``
* ``--background``
Se presente, resulta em ``Meteorologger.verbose = True``, o que coloca o log de execução em nível debug.
* ``--sync-rtc``
Desabilita impressão de menssagens de log na saída padrão.
Se presente, ao invés de executar o logger, simplesmente executa o método ``Meterologger().sync_rtc()`` para realizar sincronização do relógio da placa (``RTC_DS1307``) com o da máquina. Essa operação está disponibilizada no seguinte comando do ``Makefile``::
$ make sync-rtc
deploy.py
deploy.py
*********
*********
Conforme mencionado na introdução, o logger foi pensado como um programa para ser executado em background. Por exemplo, as menssagens do log de execução são, por padrão, escritas em um arquivo dentro de ``logger/logs`` através da biblioteca padrão ``logging`` de Python. O script `deploy.py <https://git.cta.if.ufrgs.br/meteorolog/arduino-meteorolog/blob/master/logger/deploy.py>`_ é responsável por registrar um novo processo **daemon** no `supervisor <https://supervisor.readthedocs.org/en/latest/>`_ atendendo pelo nome ``meteorologger``.
Conforme mencionado na introdução, o logger foi pensado como um programa para ser executado em background. Por exemplo, as menssagens do log de execução são escritas em um arquivo dentro de ``logger/logs`` através da biblioteca padrão `logging <https://docs.python.org/3/library/logging.html>`_. O script ``deploy.py`` é responsável por registrar um novo processo *daemon* no `Supervisor <https://supervisor.readthedocs.org/en/latest/>`_ para colocar o logger em execução no background, persistindo mesmo após a máquina ser reiniciada.
A execução se dá pelo seguinte comando do ``Makefile`` (permissões de administrador serão solicitadas):
A operação é feita pelo seguinte comando, que requer permissões de root:
.. code-block:: shell
.. code-block:: shell
$ make deploy
$ make deploy
O registro de um *daemon* no supervisor consiste na criação de um arquivo de configuração em ``/etc/supervisor/conf.d/`` e a subsquente execução de ``supervisorctl update``. É exatamente isso que faz a função ``deploy_supervisor()`` do script `deploy.py <https://git.cta.if.ufrgs.br/meteorolog/arduino-meteorolog/blob/master/logger/deploy.py>`_. O arquivo de configuração utiliza o seguinte template (string ``TEMPLATE_SUPERVISOR_CONF``)::
O registro de um *daemon* no supervisor consiste na criação de um arquivo de configuração em ``/etc/supervisor/conf.d/`` e a subsquente execução de ``supervisorctl update``. É exatamente isso que faz a função ``deploy_supervisor()``. O arquivo de configuração utiliza o seguinte modelo presente na string ``TEMPLATE_SUPERVISOR_CONF``::
Os valores substituídos nesse template estão declarados nas constantes globais:
Os valores substituídos nesse template estão declarados nas constantes globais, também utilizadas em outros lugares:
* ``PROCESS_NAME``: apelido para o *daemon* dentro do supervisor. No caso, ``meteorologger``.
* ``PROCESS_NAME``: apelido para o *daemon* dentro do supervisor. Valor: ``meteorologger``.
* ``BASE_DIR``: diretório raiz do projeto, que contém o ``Makefile``. Obtido pelo cálculo relativo da posição do arquivo ``deploy.py``.
* ``BASE_DIR``: diretório raiz do projeto, que contém o ``Makefile``. Obtido pelo cálculo relativo da posição do arquivo ``deploy.py``.
Sobre as configurações, vale destacar:
Sobre as configurações do **Supervisor**, vale destacar:
* ``redirect_stderr``: menssagens de erro serão escritas na saída padrão.
* ``redirect_stderr``: menssagens de erro serão escritas na saída padrão.
* ``stdout_logfile``: além das menssagens da saída padrão, o traceback aparecerá nesse arquivo caso o programa falhe.
* ``stdout_logfile``: além das menssagens da saída padrão, o traceback aparecerá nesse arquivo caso o programa falhe.
Por fim, o mesmo script `deploy.py <https://git.cta.if.ufrgs.br/meteorolog/arduino-meteorolog/blob/master/logger/deploy.py>`_ é utilizado também para *undeployment*, isto é, remoção do *daemon* no supervisor. Isso é feito passando-se o argumento ``-u`` para o script, operação disponível pelo seguinte comando do ``Makefile``:
Por fim, o mesmo script ``deploy.py`` é utilizado também para *undeployment*, isto é, remoção do *daemon* no Supervisor. Isso é feito passando-se o argumento ``-u`` para o script, operação disponibilizada pelo comando:
.. code-block:: shell
.. code-block:: shell
...
@@ -374,38 +470,69 @@ Por fim, o mesmo script `deploy.py <https://git.cta.if.ufrgs.br/meteorolog/ardui
...
@@ -374,38 +470,69 @@ Por fim, o mesmo script `deploy.py <https://git.cta.if.ufrgs.br/meteorolog/ardui
app/config.py
app/config.py
*************
*************
O arquivo de configuração utilizado pelo logger chama-se `settings.ini <https://git.cta.if.ufrgs.br/meteorolog/arduino-meteorolog/blob/master/settings.ini>`_ e encontra-se na pasta raiz do projeto, ao lado do `Makefile <https://git.cta.if.ufrgs.br/meteorolog/arduino-meteorolog/blob/master/Makefile>`_ por ser uma posição de destaque. Foi concebido para ser configurado por um usuário leigo.
O arquivo de configuração utilizado pelo logger, `settings.ini <https://git.cta.if.ufrgs.br/meteorolog/arduino-meteorolog/blob/master/settings.ini>`_, encontra-se na pasta raiz do projeto ao lado do ``Makefile`` por ser uma posição de destaque. Foi concebido para ser configurado por um usuário leigo em computação.
Existem várias opções de sintaxe para arquivos de configuração no universo Python: *XML*, *JSON*, *YAML*, *INI*, etc. Apesar de que sintaticamente o *YAML* seja mais interessante para projetos Python por levar em conta a identação, esse mesmo motivo dificultaria a configuração por usuários leigos em computação. Assim, a flexibilidade do formato *INI* tratado pela biblioteca padrão ``configparser`` determinou sua escolha para esse projeto.
Existem várias opções de sintaxe para arquivos de configuração no universo Python: *XML*, *JSON*, *YAML*, *INI*, etc. Apesar de que sintaticamente o *YAML* seja mais interessante para projetos Python por levar em conta a identação, esse mesmo motivo dificultaria a configuração por usuários leigos. A flexibilidade do formato *INI*, tratado pela biblioteca padrão ``configparser``, determinou sua escolha para esse projeto.
O módulo `app/config.py <https://git.cta.if.ufrgs.br/meteorolog/arduino-meteorolog/blob/master/app/config.py>`_ é responsável pela leitura e validação do arquivo de configuração através da classe ``Config``, que deverá se comportar como um dicionário para obteção das seções e chaves. Como todos valores lidos e armazenados pelo objeto ``configparser.ConfigParser`` são strings, optamos aqui por utilizar e manipular uma cópia em dicionário das configurações através do atributo ``_sections`` deste objeto. Assim, quando uma seção de configuração é acessada dentro de ``Config`` com o operador ``[]``, o método mágico ``__getitem__()`` retorna um dicionário dentro de ``_sections`` podendo conter qualquer tipo de dados como chaves e valores.
O módulo `app/config.py <https://git.cta.if.ufrgs.br/meteorolog/arduino-meteorolog/blob/master/app/config.py>`_ é responsável pela leitura e validação do arquivo de configuração através da classe ``Config``, que deverá se comportar como um dicionário para obteção das seções e chaves::
Além disso, a classe ``Config`` implementa diversos métodos iniciando por ``validate_``, dedicados a validar seções e chaves específicas do arquivo de configuração, por vezes introduzindo novas chaves úteis à classe ``Meteorologger``. A organização foi feita assim para evitar um único método com mais de 20 linhas de código. Segue um resumo das validações realizadas:
config = Config()
config['reading']['sleep_time'] # acessa a chave 'sleep_time' da seção 'reading'
Validações
Como todos valores lidos e armazenados pelo objeto ``configparser.ConfigParser`` são strings, optamos aqui por utilizar e manipular uma cópia em dicionário das configurações através do atributo ``_sections`` deste objeto. Assim, quando uma seção de configuração é acessada dentro de ``Config()`` (instancia) com o operador ``[]``, o método mágico ``__getitem__()`` retorna um dicionário dentro de ``_sections`` podendo conter qualquer tipo de dados como chaves e valores.
==========
* ``validate_server_url()``
.. note:: As chaves do dicionário ``_sections`` são todas em *lowercase*, independente do original em ``settings.ini``! Esse fato é levado em conta na implementação da classe ``main.Meteorologger``.
Utiliza valores da seção ``[server]`` para compor a URL utilizada para postagem de dados. O valor ``URL`` consiste na base do endereço do servidor -- opção disponibilizada para o caso de o usuário desejar utilizar outro servidor que não o nosso (por exemplo, um servidor local como ``http://localhost``). O valor ``BOARD_ID`` é utilizado pela URL e também pela API do site ao validar o usuário no momento da postagem.
No que diz respeito à validação dos dados, a classe ``Config`` implementa as três seguintes **exceptions** (classes que herdam de ``Exception``):