Manual

do

Maker

.

com

ESP8266 - Acionando relé com Sming (usando MQTT)

ESP8266 - Acionando relé com  Sming (usando MQTT)

No post anterior mostrei como montar um SDK para o framework Sming, que é uma maneira de programar o ESP8266 em tão alto nível quanto Arduino, porém precisamente desenvolvido para o ESP8266, de modo a ser totalmente otimizado. Como citado também no post anterior, existe certa compatibilidade com as bibliotecas do Arduino mas o modo de programar muda um pouquinho. E veio bem a calhar conhecer esse framework nesse momento, pois estou dando continuidade à implementação de domótica em meu apartamento, como você pôde acompanhar desse post para trás.

Configuração da conexão WiFi

Estamos tratando de ESP8266 aqui. Claro que o primeiro passo será a configuração do WiFi. Por ser o Sming um framework nativo para ESP8266, ele vai além do que oferece a IDE do Arduino, tornando a codificação ainda mais simples.

Os dispositivos em minha casa estão conectados todos a uma rede comum, portanto, todos são estações. Os testes iniciais seriam feitos com o Wemos D1, mas como citei no post anterior, usarei um ESP-01 para controlar um modulo com 2 relés e assim controlarei as luzes da frente e do fundo da sala utilizando os dois pinos de GPIO disponíveis nessa board. Não foi por acaso ue pulei o teste com o Wemos; é que não deu mesmo, não sei se foi por causa dos endereços do firmware, mas não vou me prolongar com essa questão.

Vamos ao WiFi:

Serial.begin(9600);
Serial.println("Kibe do exemplo");

WifiStation.enable(true);
WifiStation.config("dobitaobyte", "senhaSecreta");

Estupidamente simples, não? Eu sei que essa implementação não tem nenhum tratamento, mas você pode implementar diversos tratamentos. Aqui segue um singelo exemplo:

void WiFiSetup(){
    WifiStation.enable(true);
    WiFiAccessPoint.enable(false);
    WifiStation.config("dobitaobyte", "senhaSecreta");
    //Nada de prender no loop!
    WifiStation.waitConnection(connectOk, 20, connectFail); //20 seg.
}

Fora toda a configuração WiFi, tem também métodos validadores que retornam booleanos, é muito recomendado que você dê uma olhada na configuração de WiFi aqui.

MQTT client

Estou utilizando MQTT em minha rede (se precisar iniciar uma configuração própria de broker, veja esse outro post, e se deseja mais informações sobre o client, ele continua aqui). Mas o suporte ao MQTT no Sming ainda não está consolidado e conforme informa o desenvolvedor do recurso, uma versão estável deve ser lançada em Abril. Se der bug, o máximo que pode acontecer é minha sala virar um pisca-pisca de natal, então correrei o risco, minimizando-o ao reaproveitar o código de exemplo, adaptado-o às minhas necessidades:

#include <user_config.h>
#include <SmingCore/SmingCore.h>

#define MQTT_USERNAME "dobitaobyte"
#define MQTT_PWD "senhaSecretaDoMQTT"

#define MQTT_HOST "ns1.dobitaobyte.lan"
#define MQTT_PORT 1883

#define WIFI_SSID "MinhaRedeWiFi"
#define WIFI_PASS "senhaSecreta"

#define ON  0
#define OFF 1

int lightStatus = 0; //tudo apagado

void startMqttClient();
void onMessageReceived(String topic, String message);

Timer procTimer;

MqttClient mqtt(MQTT_HOST, MQTT_PORT, onMessageReceived);


void checkMQTTDisconnect(TcpClient& client, bool flag){
    if (flag == true){
        Serial.println("MQTT Broker Disconnected!!");
    }
    else {
        Serial.println("MQTT Broker Unreachable!!");
    }

    procTimer.initializeMs(2000, startMqttClient).start();
}

void publishMessage(){
    if (mqtt.getConnectionState() != eTCS_Connected)
        startMqttClient(); // Auto reconnect

    Serial.println("Publicando...");
    mqtt.publish("casa/sala/status/luzes", String(lightStatus));
}

// Callback
void onMessageReceived(String topic, String message){
    if (topic.compareTo("casa/sala/luzes") != 0 || message.length() > 2){
        return;
    }
    lightStatus = message.charAt(0)-48;
    if (lightStatus > 2){
        //APAGAR TODAS
        digitalWrite(0,OFF);
        digitalWrite(2,OFF);
    }
    else if (lightStatus == 1){
        //ACENDE FRENTE
        digitalWrite(0,ON);
        digitalWrite(2,OFF);
    }
    else if (lightStatus == 2){
        //ACENDE FUNDO
        digitalWrite(0,OFF);
        digitalWrite(2,ON);
    }
    else {
        //ACENDE TODAS COM QUALQUER VALOR NA MENSAGEM
        digitalWrite(0,ON);
        digitalWrite(2,ON);
    }
    Serial.print(topic);
    Serial.print(":\n");
    Serial.println(message);
}

//Inicializar o MQTT
void startMqttClient(){
    procTimer.stop();
    if(!mqtt.setWill("last/will","The connection from this device is lost:(", 1, true)){
        debugf("Unable to set the last will and testament. Most probably there is not enough memory on the device.");
    }
    mqtt.connect("Sming", MQTT_USERNAME, MQTT_PWD);
    mqtt.setCompleteDelegate(checkMQTTDisconnect);
    mqtt.subscribe("casa/sala/#");
}

// Chamada quando conectado ao WiFi
void connectOk(){
    Serial.println("I'm CONNECTED");
    startMqttClient();

    //Loop de publicacao (sem while, for, loop()...)
    procTimer.initializeMs(2000, publishMessage).start();
}

//Chamada na falha da conexao WiFi
void connectFail()
{
    Serial.println("Offline");
}

void init(){
    //configuracao do GPIO no init sem problemas pois nao eh loop
    pinMode(0,OUTPUT);
    pinMode(2,OUTPUT);
    Serial.begin(115200);
    Serial.systemDebugOutput(true); // Debug na serial

    WifiStation.config(WIFI_SSID, WIFI_PASS);
    WifiStation.enable(true);
    WifiAccessPoint.enable(false);

    //aguarda status da conexao
    WifiStation.waitConnection(connectOk, 20, connectFail);
}


void publishMessage()

O código é bastante simples e direto. Basicamente o publicador manda o status das lâmpadas conforme o valor guardado em lightStatus.

void onMessageReceived(String topic, String message)

A mensagem recebida traz duas strings, o que já é característico de C++. As strings são o tópico de resposta e a mensagem correspondente ao valor. Esse valor será numérico de 0 a 3, indicando 4 estados possíveis; luz da frente, do fundo, ambas ligadas ou ambas desligadas. Para facilitar, já coloquei a lógica de forma a apagar automaticamente a luz do fundo se a luz da frente for solicitada. Se quer ambas acesas, então o código deverá ser 0. Se ambas devem estar apagadas, então o código deve ser 3. Claro que para o usuário isso será transparente na interface onde ele simplesmente deve clicar em um ícone (chegaremos nesse ponto em algum post tão logo seja possível e adequado).

 void startMqttClient()

Isso será quase um padrão quando eu estiver utilizando Sming com MQTT. Basicamente testa-se o estado prévio da conexão, daí faz-se uma nova conexão ou uma reconexão.

Logo em seguida há um validador próprio que coloca a conexã MQTT em tão alto nível que o usuário nem sabe quantas validações são feitas nesse momento - e o quão reduzido está o código graças a essa interface oferecida pelo próprio Sming.

Essa função faz a última chamada subescrevendo-se ao tópico "casa/sala/#", onde ele poderá ler tudo relacionado à sala, mas destes só lhe interessará o status de lâmpadas e o comando de acendimento delas. Parece que na versão atual do MQTT para o Sming não é suportado a subescrição em multiplos tópicos, por isso não limitei esse ESP-01 exclusivamente às lâmpadas, mas no futuro será implementado.

void connectOk()

Se a conexão for iniciada corretamente, inicia-se então o client MQTT.

void init()

Como visto no post anterior, a estrutura do programa feito para ESP8266 com Sming é diferente da estrutura do Arduino. Invés de utilizar setup() e loop(), apenas a função init() é utilizada. O Sming não trabalha com loop, mas reage a eventos, por isso funções de repetição são disparadas a partir de timers.

Como você pode notar, o que estaria em setup() está bem no início da função init, onde também foi iniciada a comunicação serial, debuging e WiFi. Acaba ficando ainda mais simples que programar na IDE do Arduino.

Qual é melhor? Arduino IDE ou Sming?

Não sou nenhum expert no assunto, mas vou participar a minha opinião discorrendo a respeito de pontos importantes. O principal deles é que atuar reagindo a eventos ajuda previnir o bloqueio de tarefas prioritárias como as relacionadas à conexão WiFi. Se você travar a execução em um loop for(), while() ou em uma função recursiva, certamente afetará a execução de tarefas de rede. Usar interrupções mantém o processador totalmente livre para essas tarefas e reduz processamento. De resto, você pode deixar sua visão a respeito nos comentários.

Documentação

Se precisar da documentação offline, dentro do diretório Sming (se você não leu o post anterior, dê uma olhada) tem um diretório docs. Entre nele (pelo shell) e rode o comando doxygen. Caso não o tenha instalado:

sudo apt-get install doxygen-latex doxygen-doc doxygen-gui graphviz

Em outro artigo devo falar um pouco sobre geração de documentação de código, será divertido e útil.

Enfim, após instalado e executado, você terá a árvore ~/Sming/docs/gh-pages/api. Execute o index.html pela linha de comando mesmo:

cd ~/Sming/docs/gh-pages/api && firefox index.html

O objeto String é igual ao do Arduino

Se você reparar direitinho, vai ver que as strings são tratadas fora do padrão da strings do C++. Invés de .at(), .compare, é .charAt() e .compareTo() respectivamente, exatamente como no Arduino. Infelizmente não me dei conta disso na hora e me coloquei a editar o código java pra identificar os métodos da classe String do SDK do ESP8266. Caso necessite, os arquivos são:

~/Sming/esp-open-sdk/crosstool-NG/.build/src/gcc-4.8.2/libjava/java/lang/String.java
~/Sming/esp-open-sdk/crosstool-NG/.build/src/gcc-4.8.2/libjava/java/lang/String.h

Mas é muito mais prático referenciar-se nesse link já que sabemos de quê estamos falando.

Como compilar seu próprio programa

Me perdoem se esse não for o modo correto de fazê-lo. Eu não pesquisei a respeito, fui seguindo minha própria lógica enquanto tomado pela ansiedade de vê-lo em execução, por isso fiz meu programa copiando recursivamente o diretório Sming/samples/Basic_Blink para teste.

Os dois primeiros includes são fundamentais, sendo que não modifiquei nada aí. A estrutura completa é a seguinte:

program_structure.webp

Diretórios

No primeiro nível temos os diretório app include e out. O código principal fica em 'app/application.cpp' (se não quiser mudar nada no Makefile, claro). Os includes obviamente ficam no diretório 'includes/*.h'. Eu acho que assim fica mais claro para você criar seus próprios includes que, em Arduino, não os vejo sendo criados.

O diretório 'out' contém mais 2 níveis, sendo o buid e o firmware. Em 'build' se constrói o firmware que é disponibilizado em 'firmware'. Não que você precise se preocupar com isso, uma vez que executando 'make flash' o firmware será enviado para o dispositivo. de forma transparente. Para limpar o diretório após 'uppar' seu firmware, simplesmente digite 'make clean'.

Makefile

As primeiras linhas estão explicitando que nada deve ser modificado nesse arquivo. Se deseja modificar algo, isso deve ser feito em outro lugar.

Makefile-user.mk

Todos os parâmetros desse arquivo estão comentados, mas pode ser auxiliador em casos onde o arquivo descritor do dispositivo não seja /dev/ttyUSB0 por exemplo.

Essas são todas as recomendações que você precisa para chegar a esse ponto que estamos. O video mostra o upload do firmware e o acendimento de 2 LEDs para comprovar o acionamento. No próximo post mostrarei como embutir o ESP-01 na caixa dos interruptores, junto à sua alimentação e ao módulo de 2 relés.

GPIO

Os pinos utilizados do ESP-01 são o GPIO0 e GPIO2. Como o teste inicial seria feito com o Wemos D1, fiz a correspondência  dos pinos, que já não servirão para nada nesse post.

PinFunctionESP-8266 Pin
D0RXGPIO3
D1TXGPIO1
D2IOGPIO16
D3 (D15)IO, SCLGPIO5
D4 (D14)IO, SDAGPIO4
D5 (D13)IO, SCKGPIO14
D6 (D12)IO, MISOGPIO12
D7 (D11)IO, MOSIGPIO13
D8IO, Pull-upGPIO0
D9IO, Pull-up, BUILTIN_LEDGPIO2
D10IO, Pull-down,SSGPIO15
A0Analog InputA0

Logo D8 e D9 para GPIO0 e GPIO2, respectivamente.

Se tiver problema com permissão para acessar a ttyUSB0, adicione seu usuário ao grupo dialout e garanta as permissões a todos, inclusive 'others'. A última linha dá suid ao dispositivo, acho que depois disso tudo você não deverá ter mais problemas:

sudo usermod -a -G dialout seuUsuario
chmod 666 /dev/ttyUSB0
chmod +s /dev/ttyUSB0

A outra opção seria colocar os exports no .bashrc do root (foi o que fiz) e então digitar 'sudo su' e 'make flash'. O video mostra o processo e o teste.

Inscreva-se no nosso canal Manual do Maker Brasil no YouTube.

Próximo post a caminho!

Nome do Autor

Djames Suhanko

Autor do blog "Do bit Ao Byte / Manual do Maker".

Viciado em embarcados desde 2006.
LinuxUser 158.760, desde 1997.