Commit 8074a13f authored by Nelso Jost's avatar Nelso Jost

FIX: refactoring and documenting

parent 0f97f4dc
###########
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.
Estrutura de arquivos
*********************
Segue uma breve descrição dos arquivos/diretórios presentes na pasta raiz do projeto:
.. code-block:: shell
arduino-meteorolog/
├── data/ # contém dados gerados pelo logger
├── docs/ # arquivos de 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
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:
.. code-block:: shell
$ make <comando>
para realizar alguma tarefa. Experimente ``make help`` para listar todos os comandos possíveis.
Funcionamento
*************
Essa estrutura existe para comportar os dois seguintes softwares:
* **Firmware**: Executado no processador do Arduino, é responsável por ler os sensores conectados de acordo com solicitações enviadas à porta serial.
* **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)).
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.1,24.5
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
########
Escrito na linguagem C++ suportada pela Arduino Toolchain, pode ser compilado utilizando a IDE do Arduino ou pelo terminal através de ``$ make firmware``.
Estrutura de arquivos
*********************
.. code-block:: shell
meteorolog/
├── libs/ # bibliotecas de terceiros
│   ├── Adafruit_BMP085.cpp
│   ├── Adafruit_BMP085.h # sensor BMP085 (licença BSD)
│   ├── DHT.cpp
│   ├── DHT.h # sensor DHT11 e 22 (licença MIT)
│   ├── RTClib.cpp
│   └── RTClib.h # relógio RTC DS1307 (domínio público)
├── meteorolog.ino # setup() e loop() da Arduino Toolchain
├── mysensors.cpp
├── mysensors.h # leitura dos sensores disponíveis
├── boardcommands.cpp
├── boardcommands.h # execução de comandos para a placa
├── utils.cpp
└── utils.h # utilidades suplentes da Arduino Toolchain
.. note:: Os arquivos ``.h`` (cabeçalhos) contém os protótipos juntamente com a documentação do código implementado nos ``.cpp``.
O ponto de entrada é o arquivo ``meteorolog.ino``, pois ele define as duas seguintes funções padrões de um Sketch Arduino:
* ``setup()``
Executada uma vez quando a placa é ligada, inicializa a comunicação serial e chama ``mysensors_setup()``, que fará inicialização dos sensores.
* ``loop()``
Executada enquanto a placa estiver ligada, verifica constantemente se há caracteres disponíveis na porta serial. Caso afirmativo, lê a string ali presente e encaminha ela para ``execute_board_command()``. Essa função, por sua vez, interpreta o comando presente na string recebida e retorna uma string como resposta, que é então devolvida para a porta serial e o loop recomeça.
boardcommands.h
***************
Os comandos esperados pelo firmware constituem strings no seguinte formato CSV::
nomeDoComando,arg1,arg2,...,argN
Essa string contendo o comando e seus argumentos é enviada para ``execute_board_command()``, que interpretará a parte inicial ``nomeDoComando`` para delegar uma ação apropriada. O retorno de ``execute_board_command()`` é uma string contendo a resposta do comando ou, em caso de erros (comando inexistente, argumentos insuficientes, etc)::
<invalid_commmand:nomeDoComando,...>
readSensors
===========
A leitura dos sensores é feita pelo seguinte comando da placa::
readSensors,nome1,nome2,...,nomeN
onde os argumentos ``nome1,nome2,...,nomeN`` são transmitidos para ``read_sensors()``, que fará as solicitações de leitura. Essa função itera sobre cada nome/apelido, passando o mesmo para ``call_read_sensor()`` de modo que a função correta de leitura seja invocada.
Por exemplo, sejam os dois seguintes sensores passados como argumento::
LDR,p
O primeiro deve levar à execução da função ``read_LDR()`` e o segundo, à execução de ``read_BMP085_PRESSURE()`` (pois ``"p"`` é um apelido para ``BMP085_PRESSURE``). Ambas funções não recebem nenhum argumento e retornam uma string contendo, presumivelmente, o número medido ou um indicador de erro conforme programado em `my_sensors.h <https://git.cta.if.ufrgs.br/meteorolog/arduino-meteorolog/blob/master/meteorolog/mysensors.h>`_.
A operação de ``call_read_sensor()`` depende então de mapear-se uma string como ``"LDR"`` para um ponteiro da função ``read_LDR()``. Isso é alcançado em `boardcommands.cpp <https://git.cta.if.ufrgs.br/meteorolog/arduino-meteorolog/blob/master/meteorolog/boardcommands.cpp>`_ através dos três seguintes vetores globais:
* ``_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()``.
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.
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>`_.
setRTC
======
A configuração do relógio (se presente) na placa é feita com o comando::
setRTC,ano,mes,dia,hora,minuto,segundo
onde os argumentos ``ano,mes,dia,hora,minuto,segundo`` são repassados para ``set_time_from_csv()`` (`my_sensors.h <https://git.cta.if.ufrgs.br/meteorolog/arduino-meteorolog/blob/master/meteorolog/mysensors.h>`_), cujo funcionamento depende do RTC em questão (ver seção sobre o ``RTC_DS1307`` e suas funções).
Exemplo::
setRTC,2015,8,17,14,43,10
caso bem sucedido deverá retornar a string::
done: 2015-08-17 14:43:10
mysensors.h
***********
Esse módulo contém:
* Funções ``read_X()`` onde ``X`` é o nome de um sensor disponível;
* Função ``mysensors_setup()`` para inicialização programada de todos os sensores ao ligar a placa;
* Constantes a serem usadas por `boardcommands.cpp <https://git.cta.if.ufrgs.br/meteorolog/arduino-meteorolog/blob/master/meteorolog/boardcommands.cpp>`_ nos vetores de lookup das funções ``read_X()`` :
* ``__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()``.
.. note:: Entenderemos 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 cadastrado em http://dados.cta.if.ufrgs.br/emm.
Em particular, relógio RTC DS1307 também é visto como um sensor ainda que não seja cadastrado no servidor.
De um modo geral as funções ``read_X()`` são bem simples, pois apenas invocam funções externas para obteção da medição numérica que é então convertida para string -- tipo de retorno esperado.
Por exemplo, sensores que atuam diretamente em pinos analógicos podem fazer uso de ``analogRead()`` (biblioteca do Arduino) e alguma matemática para calibração, como o ``LDR``. Já sensores que possuem controladoras Wire ou I2C, acabam fazendo uso de bibliotecas separadas para melhor organização do código. Ainda que sejam bibliotecas de terceiros, optamos por mantê-las aqui dentro do subdiretório ``libs/``.
Inserindo novos sensores
========================
O software do repositório contém o código básico para os sensores suportados oficialmente, mas nada impede que novos sensores sejam adicionados. Para isso, siga os seguintes passos:
**1. (opcional) Disponibilize uma biblioteca dentro de libs/**
Caso o código de leitura seja complexo demais, considere criar uma nova biblioteca::
libs/novo_sensor.h
libs/novo_sensor.cpp
Dica: utilize orientação a objetos para melhor organização.
**2. Registre o protótipo da nova função read_X() em mysensors.h**
.. code-block:: cpp
#include "libs/novo_sensor.h"
String read_NOVO_NOME();
onde ``NOVO_NOME`` será o nome do novo sensor.
**3. Registre novo nome e apelido**
* 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.
**4. Implemente o código em mysensors.cpp**
Exemplo :
.. code-block:: cpp
// === NOVO_NOME SETUP =======================================
#define NOVO_NOME_PIN 8 // digital
String read_NOVO_NOME()
{
return FloatToString(...);
}
// ===========================================================
######
Logger
######
Escrito na linguagem Python, é o software responsável por solicitar leitura dos sensores conectados na placa, guardar os dados localmente e também enviá-los ao servidor.
.. [1] Requer que o programa ``make`` esteja instalado no sistema Linux. Felizmente ele vem por padrão nas principais distribuições.
......@@ -109,6 +109,16 @@ todo_include_todos = False
# -- Options for HTML output ----------------------------------------------
# on_rtd is whether we are on readthedocs.org
on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
if not on_rtd: # only import and set the theme if we're building docs locally
import sphinx_rtd_theme
html_theme = 'sphinx_rtd_theme'
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
# otherwise, readthedocs.org uses their theme by default, so no need to specify it
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#html_theme = 'alabaster'
......
......@@ -11,8 +11,20 @@ Contents:
.. toctree::
:maxdepth: 2
DevelopOverview.rst
Let me tell you: this is a test.
Since Pythagoras, we know that :math:`a^2 + b^2 = c^2`.
But now that how it goes:
.. math::
(a + b)^2 = a^2 + 2ab + b^2
(a - b)^2 = a^2 - 2ab + b^2
Indices and tables
==================
......
......@@ -3,12 +3,12 @@
#include "mysensors.h"
#include "utils.h"
String _sensor_names[__SENSOR_COUNT] = __LIST_SENSOR_NAMES;
String _sensor_nicknames[__SENSOR_COUNT] = __LIST_SENSOR_NICKNAMES;
ReadSensorFP _fp_array_read_sensor[__SENSOR_COUNT] = __FP_ARRAY_SENSOR_READ;
String _sensor_names[__SENSOR_COUNT] = __SENSOR_NAMES;
String _sensor_nicknames[__SENSOR_COUNT] = __SENSOR_NICKNAMES;
ReadSensorFP _fp_array_read_sensor[__SENSOR_COUNT] = __FP_ARRAY_READ_SENSOR;
String execute_command(String csv_line)
String execute_board_command(String csv_line)
{
String command = csv_line.substring(0, csv_line.indexOf(CSV_SEP));
String csv_args = csv_line.substring(csv_line.indexOf(CSV_SEP) + 1);
......
......@@ -20,7 +20,7 @@
* Returns a new string with the command output or "<invalid_command:...>"
* if the command name or its arguments are invalid.
*/
String execute_command(String csv_line);
String execute_board_command(String csv_line);
/* Given a CSV string line with sensor names or nicknames, returns a new CSV
......
......@@ -23,6 +23,6 @@ void loop()
if (Serial.available())
{
stream_line = Serial.readString();
Serial.println(execute_command(stream_line));
Serial.println(execute_board_command(stream_line));
}
}
......@@ -22,15 +22,15 @@ void mysensors_setup();
#define __SENSOR_COUNT 5
#define __LIST_SENSOR_NICKNAMES {"l", "p", "t", "ah", "dt"}
#define __SENSOR_NICKNAMES {"l", "p", "t", "ah", "dt"}
#define __LIST_SENSOR_NAMES {"LDR", \
#define __SENSOR_NAMES {"LDR", \
"BMP085_PRESSURE", \
"DHT22_TEMP", \
"DHT22_AH", \
"RTC_DS1307"}
#define __FP_ARRAY_SENSOR_READ {&read_LDR, \
#define __FP_ARRAY_READ_SENSOR {&read_LDR, \
&read_BMP085_PRESSURE, \
&read_DHT22_TEMP, \
&read_DHT22_AH, \
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment