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

FIX: finished refactoring and docs/DevelopOverview

parent 27cccac5
PYBIN := python3
VENV := .venv
VENVPY := ${VENV}/bin/python
VENVDIR := $(shell pwd)/.venv
VENVPY := ${VENVDIR}/bin/python
INO_DIR := .ino
USE := ino
ARDUINO :=#~/Downloads/arduino-1.6.5
.PHONY: help setup firmware
all: help
help:
@ echo "USAGE: make <command>"
@ 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 "USAGE: make <target> where <target> can be:"
@ echo ""
@ echo "COMMANDS:"
@ echo ""
@ echo " install -- setup & bu & deploy-logger"
@ 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 " setup Execute once to prepare the required Python virtual environment"
@ echo " firmware Compile and upload the firmware to the Arduino board via serial"
@ echo " serial Starts a serial session with Python for board communication"
@ echo ""
@ echo " serial -- Enter IPython session with serial opened"
@ echo " bu -- build & upload firmware (with Arturo)"
@ echo " bus -- build & upload & serial"
@ 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 " run Execute the logger on the foreground. Hit Ctrl+C to stop it."
@ echo " deploy Install logger on the Supervisor daemon tool (exec background)"
@ echo " undeploy Undo the 'deploy' command"
@ 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:
chmod +x scripts/apt-install.sh
......@@ -75,42 +41,42 @@ setup: clean-venv create-venv
create-venv:
@ echo "-------------------------------------------------------"
virtualenv -v --python='${PYBIN}' ${VENV}
@ echo "Virtualenv with '${PYBIN}' interpreter was created at ${VENV}"
virtualenv -v --python='${PYBIN}' ${VENVDIR} --no-site-packages
${VENVDIR}/bin/pip install --upgrade pip
${VENVDIR}/bin/pip install -r logger/requirements.pip
@ echo "-------------------------------------------------------"
${VENV}/bin/pip install --upgrade pip
@ echo "-------------------------------------------------------"
${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}
@ echo "Required Python virtual environment installed at "
@ du -sh ${VENVDIR}
clean-venv:
rm -rf ${VENV}
rm -rf ${VENVDIR}
serial:
${VENVPY} -i logger/init_serial.py
check-venv:
@ 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
chmod +x scripts/ino-build.sh
./scripts/ino-build.sh ${USE} ${INO_DIR} ${ARDUINO}
sync-rtc:
${VENVPY} logger/run.py --syncrtc
serial:
cd logger && ${VENVPY} -i init_serial.py
logger-run:
${VENVPY} logger/run.py --verbose
run: check-venv
${VENVPY} logger/run.py
deploy: undeploy
mkdir -p logger/logs
deploy:
sudo ${VENVPY} logger/deploy.py
undeploy:
sudo ${VENVPY} logger/deploy.py -u
logger-tail:
tail-log:
$(eval TMP := $(shell ls -t -I "pid*|stdout*" logger/logs | head -n 1))
@ echo "Last log file updated: logger/logs/$(TMP)"
@ echo "File size: `du -h logger/logs/$(TMP) | cut -f1`"
......@@ -118,7 +84,7 @@ logger-tail:
@ tail -F logger/logs/$(TMP)
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 "Number of lines/points: `cat data/$(TMP) | wc -l`"
@ echo "File size: `du -h data/$(TMP) | cut -f1`"
......
......@@ -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.
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
*********************
......@@ -13,39 +73,79 @@ Segue uma breve descrição dos arquivos/diretórios presentes na pasta raiz do
arduino-meteorolog/
├── 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
├── meteorolog/ # projeto ".ino" do firmware (compilável pela Arduino Toolchain)
├── scripts/ # scripts utilizados pelo Makefile
├── 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
$ 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
......@@ -97,6 +197,12 @@ Essa string contendo o comando e seus argumentos é enviada para ``execute_board
<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
===========
......@@ -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_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>`_.
......@@ -151,7 +257,7 @@ Esse módulo contém:
* ``__SENSOR_COUNT``: total de sensores;
* ``__SENSOR_NAMES``: vetor de strings de nomes 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.
......@@ -189,7 +295,7 @@ onde ``NOVO_NOME`` será o nome do novo sensor.
* Incremente ``__SENSOR_COUNT``;
* 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 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**
......@@ -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``.
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
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
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::
......@@ -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+).
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:
.. 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:
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:
* **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;
* Pacote Debian: ``python3-pip``
* Instalação no Debian: ``$ sudo apt-get install python3-pip``
* **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
* **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
.. 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
******
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
$ 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
==========
* ``--verbose``
Se presente, resulta em ``Meteorologger.verbose = True``, o que coloca o log de execução em nível debug.
* ``--background``
* ``--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
*********
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
$ 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}]
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
stdout_logfile={BASE_DIR}/logger/logs/stdout.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``.
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.
* ``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
......@@ -374,38 +470,69 @@ Por fim, o mesmo script `deploy.py <https://git.cta.if.ufrgs.br/meteorolog/ardui
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()``
* ``ConfigMissingSectionError``
Utiliza os valores da seção ``[reading]`` para determinar quais sensores terão a leitura solicitada pelo logger e também se deverá ser lido o relógio da placa (visto como um sensor de nome ``RTC_DS1307``). A ordem dos sensores na chave ``SENSORS`` determinará as colunas do arquivo *datalog.csv* (guarda dados localmente).
Exemplo de menssagem::
Também introduz a nova chave ``reading/command`` contendo a linha de comando a ser enviada para a porta serial. Essa linha vai conter todos os sensores da chave ``SENSORS``, e também o ``RTC_DS1307`` caso a chave ``RTC_DS1304`` seja ``true``.
[reading]
^
Missing section!
* ``validate_reading_interval()``
* ``ConfigMissingKeyError``
Valida a chave ``INTERVAL`` da seção ``[reading]``, transformando a string ``h:m:s`` em um dicionário ``{'H': hh, 'M': mm, 'S': ss}`` para fácil acesso posterior a esses valores.
Também introduz a nova chave ``reading/interval_seconds`` contendo o valor do intervalo de leitura já convertido para segundos.
Exemplo de menssagem::
[reading]
; time between logger cycles, in minutes
SLEEP_TIME =
^
Missing key!
* ``ConfigValueError``
Exemplo de menssagem::
[reading]
; time between logger cycles, in minutes
SLEEP_TIME = 5-
^
TypeError: Number expected!
Baseando-se na máxima pythônica de que "nenhum erro deve passar despercebido", ``ConfigMissingSectionError`` e ``ConfigMissingKeyError`` poderão acontecer no método ``assert_config_keys()`` responsável por assegurar a existência de seções e chaves em ``settings.ini`` tomando ``DEFAULT_INI`` como referência. Já ``ConfigValueError`` poderá acontecer ao longo dos métodos ``validate_section_()``, descritos na próxima seção.
Validações
==========
* ``validate_section_server()``
* ``validate_datalog_csv_sep()``
Utiliza valores da seção ``[server]`` para compor a URL utilizada na 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, juntamente com ``USER_HASH``, ao realizar autenticação do usuário da placa.
Valida o caracetere utilizado como separador CSV do arquivo *datalog.csv*, configurado na chave ``CSV_SEP`` da seção ``[datalog]``. Além de eliminar opções inválidas, decodifica o caractere para uso ASCII correto posteriormente.
* ``validate_section_reading()``
* ``validate_arduino_serial_port()``
Utiliza os valores da seção ``[reading]`` para determinar quais sensores terão a leitura solicitada pelo logger e também se deverá ser lido o relógio da placa (visto como um sensor de nome ``RTC_DS1307``). A ordem dos sensores na chave ``SENSORS`` determinará as colunas do arquivo *datalog.csv* (armazenamento local de dados).
Introduz a nova chave ``reading/command`` contendo a linha de comando a ser enviada para a porta serial. Essa linha vai conter todos os sensores da chave ``SENSORS``, e também o ``RTC_DS1307`` caso a chave ``RTC_DS1304`` seja ``true``.
* ``validate_section_datalog()``
Valida o caracetere utilizado como separador CSV do arquivo *datalog.csv*, configurado na chave ``CSV_SEP`` da seção ``[datalog]``. Além de eliminar opções inválidas, decodifi)ncia)ncica o caractere para uso ASCII correto posteriormente.
* ``validate_section_arduino()``
Valida a chave ``SERIAL_PORT`` da seção ``[arduino]``. O usuário pode especificar uma ou mais portas separadas por vírgula para que o logger tente conexão caso uma delas falhe. Adicionalmente, essa chave pode ser deixada em branco, caso em que será gerada a seguinte lista de portas:
......@@ -416,5 +543,101 @@ Validações
app/main.py
***********
Este módulo contém toda a funcionalidade do logger em si implementada na classe ``Meteorologger``. Uma leitura do método ``Meteorologger.run()`` (ponto de entrada) dá uma idéia clara de cada etapa necessária ao fluxo de execução.
Meteorologger.__init__()
========================
A instanciação dessa classe inicializa o atributo ``background`` (flag utilizada pelo método ``setup_logging``) e também atributo ``config`` com uma instancia da classe ``Config``. Conforme discutido na seção anterior sobre ``app/config.py``, é nesse momento que ocorre a validação do arquivo de configuração.
Meteorologger.setup_session_files()
===================================
Os seguintes arquivos serão criados a cada nova execução do logger (seja em *foreground* ou em *background*)::
logger/logs/exec-%Y-%m-%d-%H-%M-%S.log
data/datalog-%Y-%m-%d-%H-%M-%S.csv
onde ``%Y-%m-%d-%H-%M-%S`` consiste no ``datetime`` do início da execução. Estabelecer o nome desses arquivos é o objetivo primário de ``setup_session_files()``. O primeiro arquivo é o log de execução e o segundo é o log de dados no formato **CSV** (*comma-separated values*), cuja primeira linha contendo o nome das colunas é escrita já na execução deste método para garantir existência e permissões de arquivo.
Meteorologger.setup_logging()
=============================
Considerando que o logger foi pensado para execução em *background*, o uso de ``print()`` para menssagens de log não consiste na melhor abordagem -- por exemplo, deseja-se que a mesma menssagem apareça tanto em arquivo como na saída padrão. A excelente biblioteca padrão ``logging`` traz diversas soluções para esses e outros problemas relativos à criação de logs.
O log em arquivo é criado conforme especificações de ``logging.basicConfig()``, seja a execução feita em *background* ou *foreground*. Neste último caso, desejamos imprimir também na tela as mesmas menssagens de log. Isto é alcançado adicionando-se o objeto ``logging.StreamHandler()`` ao logger principal ``root``.
Meteorologger.get_serial()
==========================
Esse método varre a lista de portas seriais ``self.config['arduino']['serial_port']`` em busca de uma conexão válida. Quando uma tentativa falha, registra-se um ``logging.error()`` prossegue-se com o próximo item da lista, retornando ao início quando o último item também falha.
.. note:: Vale lembrar que uma lista de portas é gerada automaticamente quando a chave ``arduino/SERIAL_PORT`` de ``settings.ini`` é deixada em branco. Nesse caso, deve-se assegurar de que a única placa Arduino presente na máquina é aquela na qual deseja-se conectar.
Meteorologger.serial_read()
===========================
A leitura dos dados consiste no envio de uma string para a porta serial e a consequente leitura da string de resposta. Logo, a primeira coisa a ser feita é obter uma conexão serial pelo método ``get_serial()``. Em seguida, entra-se em um loop que encerra apenas quando a resposta obtida é uma string ASCII válida.
A comunicação serial ocorre através do objeto ``serial.Serial()`` (biblioteca `pyserial <http://pyserial.sourceforge.net/>`_) retornado pelo método ``get_serial()``. Tendo a conexão estabelecida, envia-se a string contendo o comando de leitura configurado em ``self.config['reading']['command']`` -- detalhe para o fato que strings em Python 3 são *unicode* por padrão e portanto devem ser convertidas para ``bytes()``.
Uma boa prática consiste em dormir por um intervalo de tempo (``BOARD_RESPONSE_DELAY``, 3 segundos, por exemplo) para aguardar enquanto a placa é reiniciada pelo fato da conexão serial ter sido estabelecida via ``pyserial``.
A leitura da string de resposta retorna bytes que devem ser convertidos para string. No entanto, pode acontecer de bytes retornados não serem caracteres ASCII válidos (por exemplo, contém códigos de controle de envios interrompidos anteriormente). O método ``_decode_bytes()`` assegura essa validação.
Meteorologger.create_json()
===========================
Esse método recebe uma string de valores CSV, por exemplo::
<NaN>,80.0,101201,45.5,2015-09-01 18:30:12
correspondendo aos sensores cuja leitura foi solicitada conforme ``self.config['reading']['sensors']``, por exemplo::
DHT22TEMP,DHT22AH,BMP085_PRESSURE,LDR,RTC_DS1307
e então retorna um dicionário *JSON* válido, por exemplo::
{
"datetime":
{
"format": "%Y-%m-%d-%H-%M-%S",
"source": "RTC_DS1307",
"value": "2015-09-01-18-30-12"
},
"sensors":
{
"DHT22_TEMP": "NaN",
"DHT22_AH": 80.0,
"BMP085_PRESSURE": 101201,
"LDR": 45.5
}
}
O formato de serialização *JSON* é bastante usado na web, inclusive pela API do site `<http://dados.cta.if.ufrgs.br>`_. O dicionário acima contém tudo que o servidor precisa para armazenar os valores corretamente no banco de dados.
No exemplo acima, a leitura de ``DHT22_TEMP`` retornou a string ``<NaN>``. É uma convenção deste projeto que todos os erros retornados pelo firmware apareçam entre ``<>`` para facilitar a identificação. O sensor ``BMP085_PRESSURE``, por exemplo, poderia ter retornado ``<BMP085_not_found>``. Independente do erro acusado pelo firmware, ``"NaN"`` será gravado como leitura tanto no datalog local como no servidor pois é um valor tratável pelas bibliotecas de plotagem.
Meteorologger.write_datalog()
=============================
Não há segredo neste método: simplesmente adiciona uma nova linha CSV no arquivo de log de dados local com base no *JSON* recebido. Naturalmente o arquivo de dados não deve incluir notas de erro, de modo que apenas o valor ``NaN`` apacerá nas colunas onde algum erro de leitura tenha ocorrido.
Caso deseje se informar sobre o erro o usuário pode fazer uma simples busca textual pelo *timestamp* no log de execução.
Meteorologger.send_to_server()
==============================
Utiliza a excelente biblioteca ``requests`` para enviar os dados ao servidor, processo elaborado em diversas etapas para garantir o tratamento de possíveis erros:
1. Adiciona-se o *JSON* resultante da leitura atual no arquivo ``data/outgoing.json`` (será criado caso não exista). Cada linha desse arquivo conterá um *JSON* válido para o servidor.
2. Abre-se o arquivo ``data/outgoing.json`` para leitura e converte-se as linhas para uma lista de dicionários *JSON* válidos ao servidor. Essa lista é armazenada no atributo ``"data"`` do *JSON* principal.
3. Adiciona-se o atributo ``"user_hash"`` contendo a chave de autenticação do usuário da placa ao *JSON* principal.
4. É feita uma tentativa de envio do *JSON* principal. Caso bem suscedida, apaga-se o arquivo ``data/outgoing.json``. Caso falhe, seja por servidor fora do ar ou seja por uma resposta negativa do mesmo (resposta da API ser algo como ``{"error": ...}``), nada se faz ao arquivo ``data/outgoing.json``.
Naturalmente, enquanto a comunicação do servidor falhar, novas linhas são adicionadas ao arquivo ``data/outgoing.json`` e se acumularão com o tempo até que um envio único seja bem sucedido. Repare que tudo isso acontece de maneira independente ao log local.
.. [1] Requer que o programa ``make`` esteja instalado no sistema Linux. Felizmente ele vem por padrão nas principais distribuições.
# Makefile for Sphinx documentation
#
PYBIN := python3
VENVDIR := $(shell pwd)/.venv
VENVPY := ${VENVDIR}/bin/python
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
SPHINXBUILD = ${VENVDIR}/bin/sphinx-build
PAPER =
BUILDDIR = _build
# User-friendly check for sphinx-build
# ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
# $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
# endif
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
......@@ -20,7 +19,7 @@ I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext setup
help:
help: check-venv
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
......@@ -50,38 +49,38 @@ help:
clean:
rm -rf $(BUILDDIR)/*
html:
html: check-venv
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
dirhtml:
dirhtml: check-venv
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
singlehtml:
singlehtml: check-venv
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
pickle:
pickle: check-venv
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json:
json: check-venv
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp:
htmlhelp: check-venv
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp:
qthelp: check-venv
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
......@@ -90,7 +89,7 @@ qthelp:
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/arduino-meteorolog.qhc"
applehelp:
applehelp: check-venv
$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
@echo
@echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
......@@ -98,7 +97,7 @@ applehelp:
"~/Library/Documentation/Help or install it in your application" \
"bundle."