6. Actuador/Sensor -Emisor/Receptor Infrarojo ESP07

El objetivo es disponer de un dispositivo que permita controlar el encendido o apagado de un acondicionador de aire.

Las premisas para el desarrollo son:

  • independencia del equipo acondicionador de aire, no intervenga físicamente en los circuitos del equipo,
  • integre el reconocimiento del código de encendido/apagado desde el control remoto
  • Para acceso remoto, fuera del sitio, permita monitorear el estado del medio ambiente antes de encender/apagar.
  • Se conecte al broker mediante MQTT
  • Incorpore la configuración de red mediante SmartConfig.
  • Las consideraciones de los dispositivos anteriores, open software/hardware, etc.

Se desarrolla a partir de los ejemplos:

ESP07 IR detector/demodulador

ESP07 IR Emisor

4. Sensor Temperatura/Humedad ESP-01

Referencias 

El pin del emisor infrarojo se lo toma de la hoja de datos para el ESP8266 donde se indica usar el pin 5 para emisor y el 14 para receptor. Sección 4.4.Interfaces, página 12.

https://www.mikrocontroller.net/attachment/338570/Ai-thinker_ESP-07_WIFI_Module-EN.pdf

Observaciones

Por la variedad de receptores infrarojos, se recomienda verificar la configuración de pines antes de conectar.

El encendido del LED infrarojo se realiza por medio de un transistor para manejar más corriente que la salida del ESP8266

Por la necesidad de usar más entradas/salidas de control se usó el ESP07


Esquema propuesto

Se integran el diseño presentado en los ejemplos de Receptor, Emisor infrarojo y sensor de temperatura/humedad, lo que lo convierte en un ejercicio sumativo de experiencias previas.

Protoboard

Como referencia a las conexiones en también se adjunta la conexión usando un protoboard.

Instrucciones

Las instrucciones enfocadas en cada sensor o actividad se resumen en funciones, tratando de mantener la simplicidad del lazo principal.

Las instrucciones incorporan las siguientes características:

  • El control se realiza mediante las instrucciones MQTT
  • Usa el LED del ESP8266 como indicador de estado del acondicionador de aire o equipo activado por control remoto
  • Se emite la señal de control mediante la variable senal_ONOFF
  • Se controla el estado de lectura del sensor/decodificador infrarojo mediante MQTT, y la respuesta se emite a la conexión serial.
  • La temperatura y humedad se actualizan con el parametro «intervalo» cuyo primer número es minutos
/* Control Remoto con sensor de temperatura/Humedad
 *  version 1.0
Para usar, se debe actualizar las secciones de:
 - WIFI datos para conexión a Router
 - MQTT: Servidor MQTT 
 - MQTT: identificador de dispositivo y topics
Se está usando ESP8266-07, pero para programar,
usar Placa:ESP8285
*/
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <dht.h>

#include <IRremoteESP8266.h>
#include <IRsend.h>
#include <IRrecv.h>

#ifndef UNIT_TEST
#include <Arduino.h>
#endif
#include <IRutils.h>
// The following are only needed for extended decoding of A/C Messages
#include <ir_Coolix.h>
#include <ir_Daikin.h>
#include <ir_Fujitsu.h>
#include <ir_Gree.h>
#include <ir_Haier.h>
#include <ir_Hitachi.h>
#include <ir_Kelvinator.h>
#include <ir_Midea.h>
#include <ir_Mitsubishi.h>
#include <ir_MitsubishiHeavy.h>
#include <ir_Panasonic.h>
#include <ir_Samsung.h>
#include <ir_Tcl.h>
#include <ir_Teco.h>
#include <ir_Toshiba.h>
#include <ir_Vestel.h>
#include <ir_Whirlpool.h>

// WIFI datos para conexión a Router
const PROGMEM char* ssid = "xxxx";
const PROGMEM char* password = "xxxx";

// MQTT: Servidor MQTT 
const PROGMEM char* MQTT_SERVER_IP = "xx.xx.xx.xx";
const PROGMEM uint16_t MQTT_SERVER_PORT = 1883;
const PROGMEM char* MQTT_USER = "username";
const PROGMEM char* MQTT_PASSWORD = "password";

// MQTT: identificador de dispositivo y topics
const PROGMEM char* MQTT_CLIENT_ID = "Control Remoto 01";
const PROGMEM char* MQTT_SENSOR_TOPIC_T = "oficina/CR01/Temperatura";
const PROGMEM char* MQTT_SENSOR_TOPIC_H = "oficina/CR01/Humedad";

// MQTT: Control remoto enciende
const PROGMEM char* MQTT_SENSOR_TOPIC_10 = "oficina/CR01/enciende/status";
const PROGMEM char* MQTT_SENSOR_STATE_TOPIC_10 = "oficina/CR01/enciende/switch";
const PROGMEM char* MQTT_SENSOR_COMMAND_TOPIC_10 = "oficina/CR01/enciende/set";


// MQTT: lectura sensor IR
const PROGMEM char* MQTT_SENSOR_TOPIC_IR = "oficina/CR01/lectura/status";
const PROGMEM char* MQTT_SENSOR_STATE_TOPIC_IR = "oficina/CR01/lectura/switch";
const PROGMEM char* MQTT_SENSOR_COMMAND_TOPIC_IR = "oficina/CR01/lectura/set";
const PROGMEM char* MQTT_SENSOR_AVAILABILITY_TOPIC_IR = "oficina/CR01/lectura/available";


char MQTT_SENSOR_STATE_T[10] = "0"; // inicializa
char MQTT_SENSOR_STATE_H[10] = "0"; // inicializa
char MQTT_SENSOR_STATE[10] = "OFF"; // inicializa LED interno
char MQTT_SENSOR_STATE_IR[10] = "OFF"; // inicializa lectura IR

const PROGMEM char* SENSOR_ON  = "ON";
const PROGMEM char* SENSOR_OFF = "OFF";

volatile boolean SENSOR_estado = false;
volatile boolean SENSOR_IR = false;

// LED monitor interno
//ESP01-pin=1, ESP07-pin=2
const PROGMEM uint8_t LED_pin = 2; 

// Sensor de Temperatura&Humedad
dht DHT;
#define DHT11_PIN 13

// Control de tiempos
unsigned long antes = 0; 
unsigned long ahora = millis();
const long intervalo = 5*60*1000;//minutos*60s*1000ms

// Control Remoto Emisor LED
const PROGMEM uint8_t IR_LED = 5;
uint32_t senal_ONOFF = 0xFF5AA5; // código de control

IRsend irsend(IR_LED);  // configura emisor

// Control Remoto Receptor -------
const uint16_t kRecvPin = 4;
const uint32_t kBaudRate = 115200;
const uint16_t kCaptureBufferSize = 1024;
#if DECODE_AC
// Some A/C units have gaps in their protocols of ~40ms. e.g. Kelvinator
// A value this large may swallow repeats of some protocols
const uint8_t kTimeout = 50;
#else   // DECODE_AC
// Suits most messages, while not swallowing many repeats.
const uint8_t kTimeout = 15;
#endif  // DECODE_AC
const uint16_t kMinUnknownSize = 12; //12
IRrecv irrecv(kRecvPin, kCaptureBufferSize, kTimeout, true);
decode_results results;  // Somewhere to store the results

WiFiClient wifiClient;
PubSubClient client(wifiClient);

void setup() {
    // Conexion serial
    Serial.begin(115200);//74880);
    Serial.setTimeout(2000);
    // espera inicializar serial
    while(!Serial) { }

    // SENSOR Temperatura&Humedad
    pinMode(DHT11_PIN, INPUT);

    // Control Remoto Emisor LED
    irsend.begin();

    // LED monitor
    pinMode(LED_pin, OUTPUT);

    // Inicia redes
    if(WiFi.status() != WL_CONNECTED){
        inicia_wifi();
        }
    if (!client.connected()){
        inicia_mqtt();
        }

    // Inicia Receptor IR
    #if DECODE_HASH
    // Ignore messages with less than minimum on or off pulses.
    irrecv.setUnknownThreshold(kMinUnknownSize);
    #endif                  // DECODE_HASH
    irrecv.enableIRIn();  
}

void loop() {
    unsigned long ahora = millis();
    
    if(WiFi.status() != WL_CONNECTED){
        inicia_wifi();
        }
    if (!client.connected()){
        inicia_mqtt();
        }
    delay(10);   
    client.loop();

    if (((ahora - antes)> intervalo)) {
        antes = ahora;
    
        // lectura de sensores
        SensorTempHum();
        Serial.print("ESTADO DISPOSITIVO: \nLED ");
        Serial.print(SENSOR_estado);
        Serial.print(" , IR ");
        Serial.println(SENSOR_IR);
    }
    
    if (SENSOR_IR == true){
        sensarcodigos();
    }
    delay(10);
}

// establece estado LED monitor en ESP8266
void CR01activaestado(){
        irsend.sendNEC(senal_ONOFF,32);
}

// llega mensaje MQTT
void callback(char* p_topic, byte* p_payload, unsigned int p_length) {
    // convierte a cadena
    String payload;
    for (uint8_t i = 0; i < p_length; i++) {
        payload.concat((char)p_payload[i]);
        }
    Serial.print(" Llegó un mensaje: ");
    Serial.println(p_topic);
    Serial.println(payload);
    // analiza mensaje por topico
    if (String(MQTT_SENSOR_COMMAND_TOPIC_10).equals(p_topic)) {
        if (payload.equals(String(SENSOR_ON))) {
            if (SENSOR_estado != true) {
                SENSOR_estado = true;
                LEDactivaestado();
                CR01activaestado();
                SENSORpublicaestado();
                }
        } else if (payload.equals(String(SENSOR_OFF))) {
            if (SENSOR_estado != false) {
                SENSOR_estado = false;
                LEDactivaestado();
                CR01activaestado();
                SENSORpublicaestado();
                }
        }
     }
     if (String(MQTT_SENSOR_COMMAND_TOPIC_IR).equals(p_topic)) {
        Serial.print("+++ cambio de estado detectado para IR: ");
            if (payload.equals(String(SENSOR_ON))) {
                Serial.println("Lectura ON");
                if (SENSOR_IR != true) {
                SENSOR_IR = true;
                SENSOR_IRpublicaestado();
                }
            } else if (payload.equals(String(SENSOR_OFF))) {
                Serial.println("Lectura OFF");
                if (SENSOR_IR != false) {
                SENSOR_IR = false;
                SENSOR_IRpublicaestado();
                }
            }
     }
}

// establece estado LED monitor en ESP8266
void LEDactivaestado(){
    if (SENSOR_estado){
        digitalWrite(LED_pin, LOW);
    }else{
        digitalWrite(LED_pin, HIGH);
    }
}

void SensorTempHum(){
    int chk = DHT.read11(DHT11_PIN);
    float temperatura = DHT.temperature;
    float humedad = DHT.humidity;
    Serial.println("Temperatura,Humedad: ");
    Serial.print(temperatura);
    Serial.print(",");
    Serial.println(humedad);
    client.publish(MQTT_SENSOR_TOPIC_T, 
                    String(temperatura).c_str(), 
                    true);
    client.publish(MQTT_SENSOR_TOPIC_H, 
                    String(humedad).c_str(), 
                    true);
    delay(1000);
}

void inicia_wifi() {
    // conexion WiFi
    Serial.print("\n Conectando a ");
    Serial.println(ssid);
    WiFi.begin(ssid, password);
    int cuenta = 0;
    while (WiFi.status() != WL_CONNECTED){
        Serial.print(".");
        cuenta = cuenta+1;
        if (cuenta>=40){
            Serial.println();
            cuenta = 0;}
        // LED interno enciende en LOW
        digitalWrite(LED_pin, LOW);
        delay(250);
        digitalWrite(LED_pin, HIGH);
        delay(250);
        }
    Serial.print("\n WiFi conectado \n Dirección IP: ");
    Serial.println(WiFi.localIP());
    delay(10);
}

void inicia_mqtt(){
    client.setServer(MQTT_SERVER_IP, MQTT_SERVER_PORT);
    client.setCallback(callback);
    while (!client.connected()) {
        Serial.println("\n Conectando a MQTT ");
        if (client.connect(MQTT_CLIENT_ID, MQTT_USER, MQTT_PASSWORD)) {
            Serial.println(" MQTT conectado");  
        } else {
            Serial.print("Falló, estado: ");
            Serial.print(client.state()); 
            Serial.print(" , reintento en 5 segundos");
            // LED interno enciende en LOW 
            for (int i=0;i&lt;=5;i=i+1){
                digitalWrite(LED_pin, LOW);
                delay(600);
                digitalWrite(LED_pin, HIGH);
                delay(400);
            }
        }
    }
    client.publish(MQTT_SENSOR_TOPIC_10, MQTT_SENSOR_STATE_TOPIC_10);
    client.subscribe(MQTT_SENSOR_COMMAND_TOPIC_10);

    client.publish(MQTT_SENSOR_TOPIC_IR, MQTT_SENSOR_STATE_TOPIC_IR);
    client.subscribe(MQTT_SENSOR_COMMAND_TOPIC_IR);
}

// Publicar el estado del dispositivo
void SENSORpublicaestado() {
    if (SENSOR_estado) {
        client.publish(MQTT_SENSOR_TOPIC_10, SENSOR_ON, true);
    }else{
        client.publish(MQTT_SENSOR_TOPIC_10, SENSOR_OFF, true);
    }
    client.subscribe(MQTT_SENSOR_COMMAND_TOPIC_10);
}
// Publicar el estado del dispositivo IR
void SENSOR_IRpublicaestado() {
    if (SENSOR_IR) {
        client.publish(MQTT_SENSOR_TOPIC_IR, SENSOR_ON, true);
    }else{
        client.publish(MQTT_SENSOR_TOPIC_IR, SENSOR_OFF, true);
    }
    client.subscribe(MQTT_SENSOR_COMMAND_TOPIC_IR);
}

// Sensor IR para recibir códigos
void sensarcodigos(){
  // Check if the IR code has been received.
  if (irrecv.decode(&results)) {
    // Display a crude timestamp.
    uint32_t now = millis();
    Serial.printf("Timestamp : %06u.%03u\n", now / 1000, now % 1000);
    if (results.overflow)
      Serial.printf(
          "WARNING: IR code is too big for buffer (>= %d). "
          "This result shouldn't be trusted until this is resolved. "
          "Edit & increase kCaptureBufferSize.\n",
          kCaptureBufferSize);
    // Display the basic output of what we found.
    Serial.print(resultToHumanReadableBasic(&results));
    dumpACInfo(&results);  // Display any extra A/C info if we have it.
    yield();  // Feed the WDT as the text output can take a while to print.

    // Display the library version the message was captured with.
    Serial.print("Library   : v");
    Serial.println(_IRREMOTEESP8266_VERSION_);
    Serial.println();

    // Output RAW timing info of the result.
    Serial.println(resultToTimingInfo(&results));
    yield();  // Feed the WDT (again)

    // Output the results as source code
    Serial.println(resultToSourceCode(&results));
    Serial.println("");  // Blank line between entries
    yield();             // Feed the WDT (again)
  }
}

// Display the human readable state of an A/C message if we can.
void dumpACInfo(decode_results *results) {
  String description = "";
  #if DECODE_DAIKIN
  if (results->decode_type == DAIKIN) {
    IRDaikinESP ac(0);
    ac.setRaw(results->state);
    description = ac.toString();
  }
  #endif  // DECODE_DAIKIN
  #if DECODE_DAIKIN2
  if (results->decode_type == DAIKIN2) {
    IRDaikin2 ac(0);
    ac.setRaw(results->state);
    description = ac.toString();
  }
  #endif  // DECODE_DAIKIN2
  #if DECODE_DAIKIN216
  if (results->decode_type == DAIKIN216) {
    IRDaikin216 ac(0);
    ac.setRaw(results->state);
    description = ac.toString();
  }
  #endif  // DECODE_DAIKIN216
  #if DECODE_FUJITSU_AC
  if (results->decode_type == FUJITSU_AC) {
    IRFujitsuAC ac(0);
    ac.setRaw(results->state, results-&gt>bits / 8);
    description = ac.toString();
  }
  #endif  // DECODE_FUJITSU_AC
  #if DECODE_KELVINATOR
  if (results->decode_type == KELVINATOR) {
    IRKelvinatorAC ac(0);
    ac.setRaw(results->state);
    description = ac.toString();
  }
  #endif  // DECODE_KELVINATOR
  #if DECODE_MITSUBISHI_AC
  if (results->decode_type == MITSUBISHI_AC) {
    IRMitsubishiAC ac(0);
    ac.setRaw(results->state);
    description = ac.toString();
  }
  #endif  // DECODE_MITSUBISHI_AC
  #if DECODE_MITSUBISHIHEAVY
  if (results->decode_type == MITSUBISHI_HEAVY_88) {
    IRMitsubishiHeavy88Ac ac(0);
    ac.setRaw(results->state);
    description = ac.toString();
  }
  if (results->decode_type == MITSUBISHI_HEAVY_152) {
    IRMitsubishiHeavy152Ac ac(0);
    ac.setRaw(results->state);
    description = ac.toString();
  }
  #endif  // DECODE_MITSUBISHIHEAVY
  #if DECODE_TOSHIBA_AC
  if (results->decode_type == TOSHIBA_AC) {
    IRToshibaAC ac(0);
    ac.setRaw(results->state);
    description = ac.toString();
  }
  #endif  // DECODE_TOSHIBA_AC
  #if DECODE_GREE
  if (results->decode_type == GREE) {
    IRGreeAC ac(0);
    ac.setRaw(results->state);
    description = ac.toString();
  }
  #endif  // DECODE_GREE
  #if DECODE_MIDEA
  if (results->decode_type == MIDEA) {
    IRMideaAC ac(0);
    ac.setRaw(results->value);  // Midea uses value instead of state.
    description = ac.toString();
  }
  #endif  // DECODE_MIDEA
  #if DECODE_HAIER_AC
  if (results->decode_type == HAIER_AC) {
    IRHaierAC ac(0);
    ac.setRaw(results->state);
    description = ac.toString();
  }
  #endif  // DECODE_HAIER_AC
  #if DECODE_HAIER_AC_YRW02
  if (results->decode_type == HAIER_AC_YRW02) {
    IRHaierACYRW02 ac(0);
    ac.setRaw(results->state);
    description = ac.toString();
  }
  #endif  // DECODE_HAIER_AC_YRW02
  #if DECODE_SAMSUNG_AC
  if (results->decode_type == SAMSUNG_AC) {
    IRSamsungAc ac(0);
    ac.setRaw(results->state, results->bits / 8);
    description = ac.toString();
  }
  #endif  // DECODE_SAMSUNG_AC
  #if DECODE_COOLIX
  if (results->decode_type == COOLIX) {
    IRCoolixAC ac(0);
    ac.setRaw(results->value);  // Coolix uses value instead of state.
    description = ac.toString();
  }
  #endif  // DECODE_COOLIX
  #if DECODE_PANASONIC_AC
  if (results->decode_type == PANASONIC_AC &&
      results->bits > kPanasonicAcShortBits) {
    IRPanasonicAc ac(0);
    ac.setRaw(results->state);
    description = ac.toString();
  }
  #endif  // DECODE_PANASONIC_AC
  #if DECODE_HITACHI_AC
  if (results->decode_type == HITACHI_AC) {
    IRHitachiAc ac(0);
    ac.setRaw(results->state);
    description = ac.toString();
  }
  #endif  // DECODE_HITACHI_AC
  #if DECODE_WHIRLPOOL_AC
  if (results->decode_type == WHIRLPOOL_AC) {
    IRWhirlpoolAc ac(0);
    ac.setRaw(results->state);
    description = ac.toString();
  }
  #endif  // DECODE_WHIRLPOOL_AC
  #if DECODE_VESTEL_AC
  if (results->decode_type == VESTEL_AC) {
    IRVestelAc ac(0);
    ac.setRaw(results->value);  // Like Coolix, use value instead of state.
    description = ac.toString();
  }
  #endif  // DECODE_VESTEL_AC
  #if DECODE_TECO
  if (results->decode_type == TECO) {
    IRTecoAc ac(0);
    ac.setRaw(results->value);  // Like Coolix, use value instead of state.
    description = ac.toString();
  }
  #endif  // DECODE_TECO
  #if DECODE_TCL112AC
  if (results->decode_type == TCL112AC) {
    IRTcl112Ac ac(0);
    ac.setRaw(results->state);
    description = ac.toString();
  }
  #endif  // DECODE_TCL112AC
  // If we got a human-readable description of the message, display it.
  if (description != "") Serial.println("Mesg Desc.: " + description);
}

La configuración en el broker para activar la señal LED y Receptor infrarojo se establece como un switch. Revisar la sección correspondiente en el broker.

Se realizaron pruebas con un acondicionador de aire marca genérica (ECOX), también con un TV Sony; ambas para encendido y apagado.

La siguiente actividad será aumentar la cantidad de botones/actividades que se puedan manejar.