Apostila de Programação com Arduino

4. Aprimorando a aplicação Alarme com C++ (classes)

Um modo mais tradicional de implementar o diagrama de componentes UML é através de classes. Na nossa implementação original, isso não era possível, pois C não possui suporte à programação orientada a objetos (POO). Já no caso da programação no Arduino, que é feita em C++, podemos utilizar classes e objetos, nos beneficiando das propriedades clássicas de POO como encapsulamento, herança e polimorfismo.

4.1 Diagrama de componentes do alarme

Antes de iniciar a conversão, vamos revisar o diagrama de componentes:

Diagrama de componentes

4.2 Criando a classe Timer

Vamos nos concentrar no componente Timer, que possui duas portas: iniciar e timeout. Estes são os nossos dois métodos públicos, que podem ser acessados em qualquer escopo. Assim, podemos declarar a classe Timer e seus métodos no arquivo timer.h

#ifndef TIMER_H_INCLUDED
#define TIMER_H_INCLUDED

class Timer {
  public:
  Timer();
  void iniciar(int controle);
  int timeout();

  private:
  int situacao;
  unsigned long horaInicio;
};

#endif // TIMER_H_INCLUDED

onde adicionamos dois membros privados,situação e horaInicio, e o construtor. Os dois membros estão encapsulados, uma vez que só podem ser acessados dentro dos métodos da instância.

Note que é recomendado utilizar o padrão de iniciar o nome da classe com letra maiúscula.

As definições podem ser adicionadas no arquivo timer.cpp

#include <Arduino.h>

#include "definicoes_sistema.h"
#include "timer.h"

#define TEMPO 10

Timer::Timer()
{
   situacao = false;
}

void Timer::iniciar(int controle)
{
   situacao = controle;
   if (controle)
   {
      horaInicio = millis();
   }
}

int Timer::timeout()
{
    if (situacao == false)
    {
        return false;
    }
    if(millis() - horaInicio > TEMPO*1000)
    {
        return true;
    }
    return false;
}

onde Arduino.h foi incluído para poder utilizar as função millis.

4.3 Lista de inicialização de membros (opcional)

Observe que a variável situacao foi inicializada dentro do corpo do construtor. Neste caso, a variável é primeiramente inicializada sem valor definido e depois o seu valor é modificado com o operador de atribuição. Ou seja, é algo similar a

int situacao;

int main() {
  situacao = false;
  ...

O que é ineficiente, pois estamos atribuindo valores duas vezes para a variável. O ideal seria escrever

int situacao = false;

Para membros de classes, um comportamento parecido com o código acima pode ser obtido utilizando a lista de inicialização de membros. No caso do componente Timer, isso significa que a definição do construtor deve ser mudada para:

Timer::Timer() : situacao(false)
{
}

Note que, para membros com modificador const ou para usar a sintaxe inicialização de array, é obrigatório o uso da lista de inicialização de membros.

4.4 Utilizando o componente Timer

Para utilizar a nova implementação do componente, vamos criar uma instância da classe no arquivo principal (p. ex. alarme.ino)

#include "definicoes_sistema.h"
#include "comunicacao.h"
#include "ihm.h"
#include "senhas.h"
#include "sirene.h"
#include "timer.h"

Timer tmr;
...

Depois, é necessário modificar a função executarAcao()

int executarAcao(int codigoAcao)
{
    int retval;

    retval = NENHUM_EVENTO;
    if (codigoAcao == NENHUMA_ACAO)
        return retval;

    switch(codigoAcao)
    {
    case A01:
        tmr.iniciar(true);
        break;
    case A02:
        sne_bip();
        com_notificar("Alarme em alerta");
        tmr.iniciar(false);
        break;
    case A03:
        com_notificar("Alarme desacionado");
        tmr.iniciar(false);
        break;
    case A04:
        com_notificar("Alarme desacionado");
        break;
    case A05:
        tmr.iniciar(true);
        break;
    case A06:
        sne_acionar(true);
        com_notificar("Invasao");
        tmr.iniciar(false);
        break;
    case A07:
        com_notificar("Alarme desacionado");
        tmr.iniciar(false);
        sne_acionar(false);
        break;
    } // switch

    return retval;
} // executarAcao

Para finalizar o aprimoramento do código, idealmente todos os componentes devem ser convertidos para classes. Isto fica como exercício.

No caso de dúvidas, o código final da aplicação pode ser consultado em

https://gitlab.uspdigital.usp.br/andre.kubagawa/alarme_arduino/-/tree/master/alarme_classes

4.5 Adaptando para o padrão de biblioteca do Arduino (opcional)

As bibliotecas do Arduino, como a Serial, são definidas utilizando classes. No entanto, para utilizá-las, às vezes é necessário instanciar objetos, principalmente se não existem argumentos no construtor. Isso porque, ao incluir o header, já é instanciado um único objeto, que é utilizado pelo usuário para acessar os métodos da biblioteca.

Vamos transformar o componente Timer em uma biblioteca do Arduino. Para isso, precisamos primeiro declarar a instância no escopo global no arquivo timer.h

#ifndef TIMER_H_INCLUDED
#define TIMER_H_INCLUDED

class Timer {
  public:
  Timer();
  void iniciar(int controle);
  int timeout();

  private:
  int situacao;
  unsigned long horaInicio;
};

extern Timer tmr;

#endif // TIMER_H_INCLUDED

Já no arquivo timer.cpp é necessário adicionar a linha

Timer tmr;

para definição da variável global tmr.

Por fim, no arquivo principal do projeto (alarme.ino), remova a linha

Timer tmr;

Note que, para simular uma biblioteca, o nome da instância deve iniciar com letra maiúscula. No nosso caso, o mais apropriado seria utilizar o nome Timer para a instância. Para tanto, deveríamos modificar o nome da classe para não coincidir o do objeto. Isto fica como exercício, assim como a adaptação dos outros componentes para bibliotecas Arduino.

Bibliografia para este capítulo