Commit 263c5e28 authored by Nelso Jost's avatar Nelso Jost
Browse files

FIX: finished refactoring and docs/DevelopOverview

parent 27cccac5
PYBIN := python3 PYBIN := python3
VENV := .venv VENVDIR := $(shell pwd)/.venv
VENVPY := ${VENV}/bin/python VENVPY := ${VENVDIR}/bin/python
INO_DIR := .ino INO_DIR := .ino
USE := ino USE := ino
ARDUINO :=#~/Downloads/arduino-1.6.5 ARDUINO :=#~/Downloads/arduino-1.6.5
.PHONY: help setup firmware
all: help all: help
help: help:
@ echo "USAGE: make <command>" @ echo "USAGE: make <target> where <target> can be:"
@ echo ""
@ echo "COMMANDS:"
@ echo ""
@ echo " install -- Install required tools, build and upload firmware, and"
@ echo " deploy logger daemon (background process)"
@ echo ""
@ echo " uninstall -- Remove daemon process (if present) and clean this folder"
@ echo ""
@ echo " tail-log -- Exibits and follow last modified execution log file"
@ echo " tail-data -- Exibits and follow last modified datalog file"
@ echo ""
@ echo " plot-data col=<C> -- Uses Gnuplot to plot last modified datalog file"
@ echo ""
@ echo " help -- Shows the full help"
help-full:
@ echo "USAGE: make <command>"
@ echo "" @ echo ""
@ echo "COMMANDS:" @ echo " setup Execute once to prepare the required Python virtual environment"
@ echo "" @ echo " firmware Compile and upload the firmware to the Arduino board via serial"
@ echo " install -- setup & bu & deploy-logger" @ echo " serial Starts a serial session with Python for board communication"
@ echo " uninstall -- undeploy-logger & clean-venv & clean-ino"
@ echo ""
@ echo " apt-install -- Uses Debian's apt to install required system tools"
@ echo " venv -- Creates a Python local virtual environment at ${VENV}"
@ echo " setup -- apt-install & venv"
@ echo "" @ echo ""
@ echo " serial -- Enter IPython session with serial opened" @ echo " run Execute the logger on the foreground. Hit Ctrl+C to stop it."
@ echo " bu -- build & upload firmware (with Arturo)" @ echo " deploy Install logger on the Supervisor daemon tool (exec background)"
@ echo " bus -- build & upload & serial" @ echo " undeploy Undo the 'deploy' command"
@ echo ""
@ echo " sync-rtc -- Synchronizes the board's clock with the system's"
@ echo ""
@ echo " run -- Starts the logger. Keep log at logger/logs"
@ echo " deploy -- Activate logger daemon. Keep log at logger/logs/"
@ echo " undeploy -- Deactivate logger daemon"
@ echo ""
@ echo " tail-log -- Exibits and follow last modified execution log file"
@ echo " tail-data -- Exibits and follow last modified datalog file"
@ echo ""
@ echo " plot-data col=<C> -- Uses Gnuplot to plot last modified datalog file"
@ echo " Replace <C> with the column number to plot as y axis"
@ echo " clean-venv -- Remove Python's virtual environment directory"
@ echo " clean-ino -- Remove Ino folder"
@ echo " clean-data -- !!! CAUTION !!! Remove all datalog files"
@ echo " clean-logs -- !!! CAUTION !!! Remove all execution log files"
@ echo " clean-all -- Performs all the above cleans"
@ echo "" @ echo ""
@ echo " tail-log Follow updates on the last execution log"
@ echo " tail-data Follow updates on the last data log"
@ echo " plot-data col=x Uses Gnuplot to plot last data log col number x"
apt-install: apt-install:
chmod +x scripts/apt-install.sh chmod +x scripts/apt-install.sh
...@@ -75,42 +41,42 @@ setup: clean-venv create-venv ...@@ -75,42 +41,42 @@ setup: clean-venv create-venv
create-venv: create-venv:
@ echo "-------------------------------------------------------" @ echo "-------------------------------------------------------"
virtualenv -v --python='${PYBIN}' ${VENV} virtualenv -v --python='${PYBIN}' ${VENVDIR} --no-site-packages
@ echo "Virtualenv with '${PYBIN}' interpreter was created at ${VENV}" ${VENVDIR}/bin/pip install --upgrade pip
${VENVDIR}/bin/pip install -r logger/requirements.pip
@ echo "-------------------------------------------------------" @ echo "-------------------------------------------------------"
${VENV}/bin/pip install --upgrade pip @ echo "Required Python virtual environment installed at "
@ echo "-------------------------------------------------------" @ du -sh ${VENVDIR}
${VENV}/bin/pip install -r logger/requirements.pip
@ echo "-------------------------------------------------------"
@ echo "Virtualenv is ready at ${VENV}!"
@ echo " "~/Downloads/arduino-1.0.1
@ echo "TOTAL SIZE: "
@ du -sh ${VENV}
clean-venv: clean-venv:
rm -rf ${VENV} rm -rf ${VENVDIR}
serial: check-venv:
${VENVPY} -i logger/init_serial.py @ command -v ${VENVPY} >/dev/null 2>&1 || \
{ printf "You need to prepare the required Python virtual environment";\
printf "\nfor running this software. Excecute, just once:";\
printf "\n\n $$ make setup\n\nor\n\n ";\
printf "$$ make setup PYBIN=<python_binary>\n\nfor specifying a ";\
printf "Python binary other than 'python3', like\n'python-3.x' ";\
printf "(where x is a number) for instance. \n\n"; exit 1; }
firmware: ${USE}-install firmware: ${USE}-install
chmod +x scripts/ino-build.sh chmod +x scripts/ino-build.sh
./scripts/ino-build.sh ${USE} ${INO_DIR} ${ARDUINO} ./scripts/ino-build.sh ${USE} ${INO_DIR} ${ARDUINO}
sync-rtc: serial:
${VENVPY} logger/run.py --syncrtc cd logger && ${VENVPY} -i init_serial.py
logger-run: run: check-venv
${VENVPY} logger/run.py --verbose ${VENVPY} logger/run.py
deploy: undeploy deploy:
mkdir -p logger/logs
sudo ${VENVPY} logger/deploy.py sudo ${VENVPY} logger/deploy.py
undeploy: undeploy:
sudo ${VENVPY} logger/deploy.py -u sudo ${VENVPY} logger/deploy.py -u
logger-tail: tail-log:
$(eval TMP := $(shell ls -t -I "pid*|stdout*" logger/logs | head -n 1)) $(eval TMP := $(shell ls -t -I "pid*|stdout*" logger/logs | head -n 1))
@ echo "Last log file updated: logger/logs/$(TMP)" @ echo "Last log file updated: logger/logs/$(TMP)"
@ echo "File size: `du -h logger/logs/$(TMP) | cut -f1`" @ echo "File size: `du -h logger/logs/$(TMP) | cut -f1`"
...@@ -118,7 +84,7 @@ logger-tail: ...@@ -118,7 +84,7 @@ logger-tail:
@ tail -F logger/logs/$(TMP) @ tail -F logger/logs/$(TMP)
tail-data: tail-data:
$(eval TMP := $(shell ls -t -I outgoing* data/ | head -n 1)) $(eval TMP := $(shell ls -t -I "outgoing.json" data/ | head -n 1))
@ echo "Last datalog file updated: data/$(TMP)" @ echo "Last datalog file updated: data/$(TMP)"
@ echo "Number of lines/points: `cat data/$(TMP) | wc -l`" @ echo "Number of lines/points: `cat data/$(TMP) | wc -l`"
@ echo "File size: `du -h data/$(TMP) | cut -f1`" @ echo "File size: `du -h data/$(TMP) | cut -f1`"
......
...@@ -4,6 +4,66 @@ Visão geral ...@@ -4,6 +4,66 @@ Visão geral
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:24 : INFO : Serial<id=0x7f1146fc5dd8, open=True>(port='/dev/ttyACM0', baudrate=9600, bytesize=8, parity='N', stopbits=1, timeout=1.5, xonxoff=True, rtscts=False, dsrdtr=False)
2015-09-03 16:12:26 : INFO : sent: 'read,DHT22_TEMP,DHT22_AH,BMP085_PRESSURE,LDR,RTC_DS1307' (55 bytes)
2015-09-03 16:12:29 : INFO : read: b'22.700001,66.199997,101224,40.762466,2015-9-3 16:12:26\r\n' (56 bytes)
2015-09-03 16:12:29 : INFO : JSON: {'datetime': {'format': '%Y-%m-%d-%H-%M-%S', 'source': 'RTC_DS1307', 'value': '2015-09-03-16-12-26'}, 'sensors': {'LDR': '40.762466', 'DHT22_AH': '66.199997', 'BMP085_PRESSURE': '101224', 'DHT22_TEMP': '22.700001'}}
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:10 : INFO : Serial<id=0x7f2c89ffb438, open=True>(port='/dev/ttyACM0', baudrate=9600, bytesize=8, parity='N', stopbits=1, timeout=1.5, xonxoff=True, rtscts=False, dsrdtr=False)
2015-09-03 16:17:12 : INFO : sent: 'read,DHT22_TEMP,DHT22_AH,BMP085_PRESSURE,LDR,RTC_DS1307' (55 bytes)
2015-09-03 16:17:15 : INFO : read: b'<NaN>,0.000000,<bmp085_not_found>,50.537636,2165-165-165 165:165:85\r\n' (69 bytes)
2015-09-03 16:17:15 : WARNING : SensorReadingError: [DHT22_TEMP]: '<NaN>'
2015-09-03 16:17:15 : WARNING : SensorReadingError: [BMP085_PRESSURE]: '<bmp085_not_found>'
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 : JSON: {'sensors': {'DHT22_AH': '0.000000', 'BMP085_PRESSURE': 'NaN', 'DHT22_TEMP': 'NaN', 'LDR': '50.537636'}, 'datetime': {'format': '%Y-%m-%d-%H-%M-%S', 'value': '2015-09-03-16-17-15', 'source': 'logger_system'}}
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``::
[program:{PROCESS_NAME}] [program:{PROCESS_NAME}]
command={BASE_DIR}/.venv/bin/python {BASE_DIR}/logger/run.py command={BASE_DIR}/.venv/bin/python {BASE_DIR}/logger/run.py
...@@ -355,17 +451,17 @@ O registro de um *daemon* no supervisor consiste na criação de um arquivo de c ...@@ -355,17 +451,17 @@ O registro de um *daemon* no supervisor consiste na criação de um arquivo de c
stdout_logfile={BASE_DIR}/logger/logs/stdout.log stdout_logfile={BASE_DIR}/logger/logs/stdout.log
logfile={BASE_DIR}/logger/logs/supervisor-{PROCESS_NAME}.log logfile={BASE_DIR}/logger/logs/supervisor-{PROCESS_NAME}.log
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``):
* ``validate_reading_sensors()``