DevelopOverview.rst 11.3 KB
Newer Older
Nelso Jost's avatar
Nelso Jost committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
###########
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.