Smart Remote Controller (smartphone from outdoors, AI speaker cooperation)

In the Video

Japanese【日本語】

English【英語】

1. Overview

We are making smart remote controls. This is the final of the 7 sessions, and we will be able to operate home appliances from the outside using a smartphone and link with an AI speaker.
Any home appliance that can be controlled by a normal remote control, such as lighting, TVs, and air conditioners, can be used.
For development, we will use Arduino and ESP32 to realize it.

2. Overall flow of Smart Remote Controller Production

We will eventually create a smart remote control, and we will distribute it in a total of 7 posts.
This is the 6th “Home appliance remote control with a smartphone”.

NoItemContentHardSoftNote
1Overall flow, system configuration, items used,
reasons for selection, development environment, etc.
Another Post
2Green LEDLearn the basics for beginners. We will make “L blinking” that lights up and blinks the LED.
3Infrared receiving sensorDescription of infrared receiving sensor Schematic to Wiring, Software
4Infrared transmission LEDInfrared transmission LED description
Schematic to Wiring, Software
5LED operation with smartphone(at home)We will create software to operate the LED with smartphone. (Web server function, SPIFFS operation)
6Remote control with smartphone(at home)We will create software that to operate the remote control with smartphone indoors. (Button name, signal save/read)
7Operate from outside And AI speaker cooperationWe will create software to operate the remote control with smartphone from the outdoors, and AI speaker cooperation.This Post

3. The development environment

Arduino was developed in Italy under the philosophy of “making things easier and easier to understand”.
Currently, it is widely used for learning all over the world, and the library is also substantial.
So, if you want to start electronic work, I think this is the only development environment.
Therefore, I use Arduino at this time.

4. Software

All program files are put together in this zip file. Please download as needed.

Listed below are the 4 files of the Arduino program in this zip file.
(HTML and Javascript are in the zip file.)

//*************************************************************************
//  SmartRemocon Ver2023.2.2
//  Arduino board : ESP32(Arduino core for the ESP32) by Espressif Systems ver 2.0.6
//  Written by IT-Taro
//***********************************************************************

// Load library and Config file
#include <EEPROM.h>
#include <SPIFFS.h>
#include <ESPAsyncWebServer.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>      // Version[5.13.5]
#include "config.h"

// for WebServer
AsyncWebServer webServer ( 80 );
// Declare the type (structure) used in EEPROM (the type that saves the button name)
struct st_remocon {
  char remo_name[65];
};
// For saving remote control data (1500 unsigned short type arrays)
unsigned short irData[1500];
// Valuable
bool ledFlag = true;       // LED control flag

// MQTT connection ON/OFF (false when OFF)
bool MQTT_CONNECT = true;
// for MQTT client
WiFiClientSecure wifiClient;            // for MQTT
PubSubClient mqttClient(host, 8883, wifiClient);

// First thing to do when booting
void setup(void) {
  // Serial setting
  Serial.begin(115200);
  Serial.println ( );
  // SPIFFS start
  SPIFFS.begin();
  // Start EEPROM (Size specification: 10 button names [65*10])
  EEPROM.begin(650);
  // pin mode setting
  pinMode ( LED_PIN,  OUTPUT );
  pinMode ( IR_R_PIN, INPUT );
  pinMode ( IR_S_PIN, OUTPUT );
  // Wi-Fi connection processing
  setup_wifi();
  // WebServer setting process
  setup_webserver();
  Serial.println("##### setup completed! #####");
}

// Wireless Wi-Fi settings
void setup_wifi() {
  // Wireless Wi-Fi information setting
  Serial.println ( "Wi-Fi SetUp" );
  WiFi.config( ip, gateway, subnet, DNS );  // MQTT connection is not possible without DNS
  WiFi.begin ( ssid, password );
  // Wi-Fi connection processing (infinite loop until connected)
  while ( WiFi.status() != WL_CONNECTED ) {
    // Blink the LED every 1 second
    ledFlag = !ledFlag;
    digitalWrite(LED_PIN, ledFlag);
    // Wait for 1 second
    delay ( 1000 );
    Serial.print ( "." );
  }
  // Since the Wi-Fi connection was established, the IP address is displayed on the serial monitor
  Serial.print ( "Wi-Fi Connected! IP address: " );
  Serial.println ( WiFi.localIP() );
  // LED lighting (Wi-Fi connection status)
  digitalWrite ( LED_PIN, true );
  // ------- Set CA certificate to client only when MQTT_CONNECT is ON -------
  if ( MQTT_CONNECT ) {
    wifiClient.setCACert(beebottle_ca_cert);
  }
}

// After setup is complete, repeat processing until power is turned off
void loop(void){
  // ------- only if MQTT_CONNECT is ON -------
  if ( MQTT_CONNECT ) {
    // Check the MQTT status and process the MQTT connection if it is not connected
    if ( !mqttClient.connected() ) {
      reconnect();
    }
    // MQTT client processing
    mqttClient.loop();
  }
}

// MQTT connection process
void reconnect() {
  // loop until MQTT connection state
  while (!mqttClient.connected()) {
    Serial.println("Attempting MQTT connection...");
    // MQTT setting information definition
    String username = "token:";
    username += channelToken;
    // MQTT connection process
    mqttClient.connect(clientID, username.c_str(), NULL);
    delay(2000);
  }
  Serial.println("MQTT connected");
  // Process setting when receiving MQTT message
  mqttClient.setCallback(callback);
  // Configure TOPIC to receive MQTT messages
  mqttClient.subscribe(topic);
}

// Processing when MQTT message is received
void callback(char* topic, byte* payload, unsigned int length) {
  // save MQTT received message in variable
  char recvData[MQTT_MAX_PACKET_SIZE];
  snprintf(recvData, sizeof(recvData), "%s", payload);
  // Display MQTT received message on serial monitor
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.println("] ");
  Serial.println(recvData);
  // Parse the JSON format of the received data and save it to a variable
  StaticJsonBuffer<MQTT_MAX_PACKET_SIZE> jsonBuffer;
  JsonObject& jsonBuf = jsonBuffer.parseObject(recvData);
  // If JSON format parsing is not successful, display an error and exit
  if (!jsonBuf.success()) {
    Serial.println("parseObject() failed");
    return;
  }
  // Acquire and save the received data (data)
  const char* parsedPayload = jsonBuf["data"];
  // Determine if received data (data) exists
  if (parsedPayload != NULL) {
    Serial.print("payload: ");
    Serial.println(parsedPayload);
    // Transmit remote control number in receive data (data)
    if (contRemocon( parsedPayload )) {
      Serial.println("MQTT send OK");
    } else {
      Serial.println("MQTT send NG");
    }
  }
}
// pin arrangement etc.
const byte LED_PIN  = 22;  // green LED
const byte IR_R_PIN = 23;  // Infrared reception
const byte IR_S_PIN = 32;  // Infrared transmission(IO 34,35 does not work)

// Wi-Fi settings
const char *ssid      = "##### SSID #####";
const char *password  = "### PASSWORD ###";
IPAddress ip(192, 168, 1, 123);     // IP address (IP used by this machine)
IPAddress gateway(192, 168, 1, 1);  // default gateway
IPAddress subnet(255, 255, 255, 0); // sub-net mask
IPAddress DNS(192, 168, 1, 1);      // DNS server

// Beebotte settting
const char *host = "mqtt.beebotte.com";
const char *clientID = "esp32_001";
const char *channelToken = "##### TOKEN #####";
const char *topic = "##### CHANNEL #####/resource1";
// The certificate is changed periodically, so it needs to be updated from time to time
// Use this for download ca 
https://beebotte.com/certs/mqtt.beebotte.com.pem
const char *beebottle_ca_cert = \ "-----BEGIN CERTIFICATE-----\n" \ "MIIGQzCCBSugAwIBAgIQS+4z5c2Vxpa/089CrjLxRDANBgkqhkiG9w0BAQsFADCB\n" \ "jzELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G\n" \ "A1UEBxMHU2FsZm9yZDEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTcwNQYDVQQD\n" \ "Ey5TZWN0aWdvIFJTQSBEb21haW4gVmFsaWRhdGlvbiBTZWN1cmUgU2VydmVyIENB\n" \ "MB4XDTIyMDgwNTAwMDAwMFoXDTIzMDgwNTIzNTk1OVowHDEaMBgGA1UEAxMRbXF0\n" \ "dC5iZWVib3R0ZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC2\n" \ "HOgaiUNPgq4weQWnEARZZ15YSmJGos+6OfaJ+rfU7xN2TNIseLMDFyor6ehKKBfF\n" \ "weB/EBQJOlq4p48KbIyY9pqZLa6QoUr+2l06hkyEro4nbj/w4VWee+RccgvV1w5A\n" \ "nGm6BRzQTd2zZVBkCnKLdlBYWfqdWJAzODh+AGXCNJ06oE3W9rurh0Xorh8jmZcI\n" \ "ihfkfW14EC9nx0gNMxOTKYW6w8k+SsOcp6jOd2tNFmm/RZlpUkM+CwGXrn8WbrmF\n" \ "/KAwRboVw/DCtPmvg4ItrylQNjAZ2eY1UqI9SuaDzRPUhghYWWZthJoeuXsb7ps+\n" \ "wp8S9jHUw9ry/uyO0V6NAgMBAAGjggMLMIIDBzAfBgNVHSMEGDAWgBSNjF7EVK2K\n" \ "4Xfpm/mbBeG4AY1h4TAdBgNVHQ4EFgQU3Ysg/3l33FxDdSjtpWo9sTdAI7EwDgYD\n" \ "VR0PAQH/BAQDAgWgMAwGA1UdEwEB/wQCMAAwHQYDVR0lBBYwFAYIKwYBBQUHAwEG\n" \ "CCsGAQUFBwMCMEkGA1UdIARCMEAwNAYLKwYBBAGyMQECAgcwJTAjBggrBgEFBQcC\n" \ "ARYXaHR0cHM6Ly9zZWN0aWdvLmNvbS9DUFMwCAYGZ4EMAQIBMIGEBggrBgEFBQcB\n" \ "AQR4MHYwTwYIKwYBBQUHMAKGQ2h0dHA6Ly9jcnQuc2VjdGlnby5jb20vU2VjdGln\n" \ "b1JTQURvbWFpblZhbGlkYXRpb25TZWN1cmVTZXJ2ZXJDQS5jcnQwIwYIKwYBBQUH\n" \ "MAGGF2h0dHA6Ly9vY3NwLnNlY3RpZ28uY29tMDMGA1UdEQQsMCqCEW1xdHQuYmVl\n" \ "Ym90dGUuY29tghV3d3cubXF0dC5iZWVib3R0ZS5jb20wggF/BgorBgEEAdZ5AgQC\n" \ "BIIBbwSCAWsBaQB3AK33vvp8/xDIi509nB4+GGq0Zyldz7EMJMqFhjTr3IKKAAAB\n" \ "gm1H8UwAAAQDAEgwRgIhAPSkokAAmuuFPQcLwnzmklGV2LG18nxYMszVwcshdNLd\n" \ "AiEAnXonMoYJ3woym5ZklDq3llrzShulgbijU3hWTJMiZwQAdgB6MoxU2LcttiDq\n" \ "OOBSHumEFnAyE4VNO9IrwTpXo1LrUgAAAYJtR/EYAAAEAwBHMEUCIQC0VI4khyqj\n" \ "laTjeNNGs61TOX7n1S6qE1FuIXFsGkuCAwIgaxzXXL/HPqBUGUb7EDoD+yOy0l8p\n" \ "UZTgY5hS5bnj/dAAdgDoPtDaPvUGNTLnVyi8iWvJA9PL0RFr7Otp4Xd9bQa9bgAA\n" \ "AYJtR/DlAAAEAwBHMEUCIQDKDk5MX6omMZXNdxrLUhkKUwm2yWaC2Rzqh9yuGb9k\n" \ "2wIgeRqzIClbgHNA0oK9lL3RYU2CFih434PamgZsTGupkuMwDQYJKoZIhvcNAQEL\n" \ "BQADggEBALrM4n2WfCJpuF74zZPFqfVmYgsgnlcFzUjL7Dom3dxXb1sGkQ8vukjW\n" \ "NnLQ64FlZEvz5OJotZeuikzBwyjfFNyfKSFElBr/zqdW7Mp8RcYoa6zOG2+T/GA2\n" \ "C3Xesg4WBFvQ8DjZFobZiICuI5gZ8m/MuMMX6JXobMDa/9p0ECOxsCfeOesnLNJu\n" \ "h05h3PLkLxRgHROSIp3hoUtN1vGnW2cJBLr29uu7utTjgRX514IRqefFwEovOb3Z\n" \ "DcWcxkpcMM7Z26UoNO2V2d7kyuQgXq3RwePm3a8v5Pp7Fy8goGIrnfoLPhX8QCX3\n" \ "Pz3SiPLrMq9IpjzlAOP0wwu6QLSwUXQ=\n" \ "-----END CERTIFICATE-----\n" \ "-----BEGIN CERTIFICATE-----\n" \ "MIIGEzCCA/ugAwIBAgIQfVtRJrR2uhHbdBYLvFMNpzANBgkqhkiG9w0BAQwFADCB\n" \ "iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl\n" \ "cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV\n" \ "BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTgx\n" \ "MTAyMDAwMDAwWhcNMzAxMjMxMjM1OTU5WjCBjzELMAkGA1UEBhMCR0IxGzAZBgNV\n" \ "BAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEYMBYGA1UE\n" \ "ChMPU2VjdGlnbyBMaW1pdGVkMTcwNQYDVQQDEy5TZWN0aWdvIFJTQSBEb21haW4g\n" \ "VmFsaWRhdGlvbiBTZWN1cmUgU2VydmVyIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC\n" \ "AQ8AMIIBCgKCAQEA1nMz1tc8INAA0hdFuNY+B6I/x0HuMjDJsGz99J/LEpgPLT+N\n" \ "TQEMgg8Xf2Iu6bhIefsWg06t1zIlk7cHv7lQP6lMw0Aq6Tn/2YHKHxYyQdqAJrkj\n" \ "eocgHuP/IJo8lURvh3UGkEC0MpMWCRAIIz7S3YcPb11RFGoKacVPAXJpz9OTTG0E\n" \ "oKMbgn6xmrntxZ7FN3ifmgg0+1YuWMQJDgZkW7w33PGfKGioVrCSo1yfu4iYCBsk\n" \ "Haswha6vsC6eep3BwEIc4gLw6uBK0u+QDrTBQBbwb4VCSmT3pDCg/r8uoydajotY\n" \ "uK3DGReEY+1vVv2Dy2A0xHS+5p3b4eTlygxfFQIDAQABo4IBbjCCAWowHwYDVR0j\n" \ "BBgwFoAUU3m/WqorSs9UgOHYm8Cd8rIDZsswHQYDVR0OBBYEFI2MXsRUrYrhd+mb\n" \ "+ZsF4bgBjWHhMA4GA1UdDwEB/wQEAwIBhjASBgNVHRMBAf8ECDAGAQH/AgEAMB0G\n" \ "A1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAbBgNVHSAEFDASMAYGBFUdIAAw\n" \ "CAYGZ4EMAQIBMFAGA1UdHwRJMEcwRaBDoEGGP2h0dHA6Ly9jcmwudXNlcnRydXN0\n" \ "LmNvbS9VU0VSVHJ1c3RSU0FDZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDB2Bggr\n" \ "BgEFBQcBAQRqMGgwPwYIKwYBBQUHMAKGM2h0dHA6Ly9jcnQudXNlcnRydXN0LmNv\n" \ "bS9VU0VSVHJ1c3RSU0FBZGRUcnVzdENBLmNydDAlBggrBgEFBQcwAYYZaHR0cDov\n" \ "L29jc3AudXNlcnRydXN0LmNvbTANBgkqhkiG9w0BAQwFAAOCAgEAMr9hvQ5Iw0/H\n" \ "ukdN+Jx4GQHcEx2Ab/zDcLRSmjEzmldS+zGea6TvVKqJjUAXaPgREHzSyrHxVYbH\n" \ "7rM2kYb2OVG/Rr8PoLq0935JxCo2F57kaDl6r5ROVm+yezu/Coa9zcV3HAO4OLGi\n" \ "H19+24rcRki2aArPsrW04jTkZ6k4Zgle0rj8nSg6F0AnwnJOKf0hPHzPE/uWLMUx\n" \ "RP0T7dWbqWlod3zu4f+k+TY4CFM5ooQ0nBnzvg6s1SQ36yOoeNDT5++SR2RiOSLv\n" \ "xvcRviKFxmZEJCaOEDKNyJOuB56DPi/Z+fVGjmO+wea03KbNIaiGCpXZLoUmGv38\n" \ "sbZXQm2V0TP2ORQGgkE49Y9Y3IBbpNV9lXj9p5v//cWoaasm56ekBYdbqbe4oyAL\n" \ "l6lFhd2zi+WJN44pDfwGF/Y4QA5C5BIG+3vzxhFoYt/jmPQT2BVPi7Fp2RBgvGQq\n" \ "6jG35LWjOhSbJuMLe/0CjraZwTiXWTb2qHSihrZe68Zk6s+go/lunrotEbaGmAhY\n" \ "LcmsJWTyXnW0OMGuf1pGg+pRyrbxmRE1a6Vqe8YAsOf4vmSyrcjC8azjUeqkk+B5\n" \ "yOGBQMkKW+ESPMFgKuOXwIlCypTPRpgSabuY0MLTDXJLR27lk8QyKGOHQ+SwMj4K\n" \ "00u/I5sUKUErmgQfky3xxzlIPK1aEn8=\n" \ "-----END CERTIFICATE-----\n" \ "-----BEGIN CERTIFICATE-----\n" \ "MIIFgTCCBGmgAwIBAgIQOXJEOvkit1HX02wQ3TE1lTANBgkqhkiG9w0BAQwFADB7\n" \ "MQswCQYDVQQGEwJHQjEbMBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYD\n" \ "VQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UE\n" \ "AwwYQUFBIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTE5MDMxMjAwMDAwMFoXDTI4\n" \ "MTIzMTIzNTk1OVowgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpOZXcgSmVyc2V5\n" \ "MRQwEgYDVQQHEwtKZXJzZXkgQ2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBO\n" \ "ZXR3b3JrMS4wLAYDVQQDEyVVU0VSVHJ1c3QgUlNBIENlcnRpZmljYXRpb24gQXV0\n" \ "aG9yaXR5MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAgBJlFzYOw9sI\n" \ "s9CsVw127c0n00ytUINh4qogTQktZAnczomfzD2p7PbPwdzx07HWezcoEStH2jnG\n" \ "vDoZtF+mvX2do2NCtnbyqTsrkfjib9DsFiCQCT7i6HTJGLSR1GJk23+jBvGIGGqQ\n" \ "Ijy8/hPwhxR79uQfjtTkUcYRZ0YIUcuGFFQ/vDP+fmyc/xadGL1RjjWmp2bIcmfb\n" \ "IWax1Jt4A8BQOujM8Ny8nkz+rwWWNR9XWrf/zvk9tyy29lTdyOcSOk2uTIq3XJq0\n" \ "tyA9yn8iNK5+O2hmAUTnAU5GU5szYPeUvlM3kHND8zLDU+/bqv50TmnHa4xgk97E\n" \ "xwzf4TKuzJM7UXiVZ4vuPVb+DNBpDxsP8yUmazNt925H+nND5X4OpWaxKXwyhGNV\n" \ "icQNwZNUMBkTrNN9N6frXTpsNVzbQdcS2qlJC9/YgIoJk2KOtWbPJYjNhLixP6Q5\n" \ "D9kCnusSTJV882sFqV4Wg8y4Z+LoE53MW4LTTLPtW//e5XOsIzstAL81VXQJSdhJ\n" \ "WBp/kjbmUZIO8yZ9HE0XvMnsQybQv0FfQKlERPSZ51eHnlAfV1SoPv10Yy+xUGUJ\n" \ "5lhCLkMaTLTwJUdZ+gQek9QmRkpQgbLevni3/GcV4clXhB4PY9bpYrrWX1Uu6lzG\n" \ "KAgEJTm4Diup8kyXHAc/DVL17e8vgg8CAwEAAaOB8jCB7zAfBgNVHSMEGDAWgBSg\n" \ "EQojPpbxB+zirynvgqV/0DCktDAdBgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rID\n" \ "ZsswDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wEQYDVR0gBAowCDAG\n" \ "BgRVHSAAMEMGA1UdHwQ8MDowOKA2oDSGMmh0dHA6Ly9jcmwuY29tb2RvY2EuY29t\n" \ "L0FBQUNlcnRpZmljYXRlU2VydmljZXMuY3JsMDQGCCsGAQUFBwEBBCgwJjAkBggr\n" \ "BgEFBQcwAYYYaHR0cDovL29jc3AuY29tb2RvY2EuY29tMA0GCSqGSIb3DQEBDAUA\n" \ "A4IBAQAYh1HcdCE9nIrgJ7cz0C7M7PDmy14R3iJvm3WOnnL+5Nb+qh+cli3vA0p+\n" \ "rvSNb3I8QzvAP+u431yqqcau8vzY7qN7Q/aGNnwU4M309z/+3ri0ivCRlv79Q2R+\n" \ "/czSAaF9ffgZGclCKxO/WIu6pKJmBHaIkU4MiRTOok3JMrO66BQavHHxW/BBC5gA\n" \ "CiIDEOUMsfnNkjcZ7Tvx5Dq2+UUTJnWvu6rvP3t3O9LEApE9GQDTF1w52z97GA1F\n" \ "zZOFli9d31kWTz9RvdVFGD/tSo7oBmF0Ixa1DVBzJ0RHfxBdiSprhTEUxOipakyA\n" \ "vGp4z7h/jnZymQyd/teRCBaho1+V\n" \ "-----END CERTIFICATE-----\n" \ "-----BEGIN CERTIFICATE-----\n" \ "MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEb\n" \ "MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow\n" \ "GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmlj\n" \ "YXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVowezEL\n" \ "MAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE\n" \ "BwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNVBAMM\n" \ "GEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP\n" \ "ADCCAQoCggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQua\n" \ "BtDFcCLNSS1UY8y2bmhGC1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe\n" \ "3M/vg4aijJRPn2jymJBGhCfHdr/jzDUsi14HZGWCwEiwqJH5YZ92IFCokcdmtet4\n" \ "YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszWY19zjNoFmag4qMsXeDZR\n" \ "rOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjHYpy+g8cm\n" \ "ez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQU\n" \ "oBEKIz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF\n" \ "MAMBAf8wewYDVR0fBHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20v\n" \ "QUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29t\n" \ "b2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDANBgkqhkiG9w0BAQUF\n" \ "AAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm7l3sAg9g1o1Q\n" \ "GE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz\n" \ "Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2\n" \ "G9w84FoVxp7Z8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsi\n" \ "l2D4kF501KKaU73yqWjgom7C12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3\n" \ "smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg==\n" \ "-----END CERTIFICATE-----\n" \ ;
// Infrared reception (signal reception or processing for 30 seconds)
void setRemocon (AsyncWebServerRequest *request) {
  // Declare variables to use
  unsigned short irCount = 0;   // number of HIGH and LOW signals
  uint32_t lastt = 0;           // keep previous elapsed time
  uint32_t deltt = 0;           // keep previous elapsed time
  uint32_t sMilli;              // start time of this process
  uint32_t wMilli;              // Infrared wait start time
  uint32_t sMicro;              // Processing start time
  uint32_t cMicro;              // current time
  bool rState = 1;              // Infrared receiver module status 0: LOW, 1: HIGH
  // Get current time in milliseconds
  sMilli = millis();
  // Infinite loop until specific condition (signal received or 30 seconds elapsed)
  while(1) {
    // Get start time to wait for infrared reception
    wMilli = millis();
    // Waiting to receive an inverted signal
    while (digitalRead(IR_R_PIN) == rState) {
      // After waiting for 0.5 seconds or more
      if (millis() - wMilli > 500) {
        // When the count exceeds 10 times
        if ( irCount > 10 ) {
          Serial.println("");
          Serial.print(irCount);
          Serial.println(":recvOK!");
          delay(1);
          // Save button name to EEPROM and remote control data to file
          //xTaskCreatePinnedToCore(saveIr,"saveIr", 4096, ((irCount - 1), request), 1, NULL, 1); //Task1実行
          if (saveIr( (irCount - 1), request)) {
            // Returned OK from the web server because it ended normally
            request->send(200, "text", "OK");
            Serial.println("setRemocon OK");
          } else {
            // Reply NG due to failure to save signal
            request->send(200, "text", "NG");
            Serial.println("setRemocon NG");
          }
          return;    // completed successfully
        }
        // If there are not more than 10 0,1 signals, receive again from zero due to noise
        irCount = 0;
      }
      // Timeout after 15 seconds of processing
      if ( millis() - sMilli > 15000 ) {
        request->send(200, "text", "NG");// Send NG due to timeout
        Serial.println("setRemocon T.O.");
        return;                          // Processing ends after 15 seconds (no reception)
      }
    }
    // Get the current time or elapsed time at the start of signal reception
    if ( irCount == 0 ) {
      sMicro = micros();
      lastt = 0;
      irCount++;
      Serial.print("ir:");
    // Processing after starting signal reception processing (irCount is 1 or more)
    } else {
      //Calculate the elapsed time from the time when the state change of the infrared receiver last changed
      cMicro = micros();
      deltt = ( (cMicro - sMicro)/ 10 ) - lastt;
      irData[(irCount - 1 )] = deltt;
      // Save last changed elapsed time for next elapsed time calculation
      lastt = lastt + deltt;
      irCount++;
      Serial.print( deltt );
      Serial.print(",");
    }
    // Change the value that detects the state change in the next While
    rState = !rState;
  }
}

// Save button name to EEPROM and remote control data to file
bool saveIr(unsigned short irLength, AsyncWebServerRequest *request){
  String setirname = "";
  String setNumStr = "";
  // Get and check button number (HTTP GET request parameter)
  if (request->hasParam("n")) {
    setNumStr = request->getParam("n")->value();
  } else {
    return false;
  }
  // Get and check button name (parameter of HTTP GET request)
  if (request->hasParam("a")) {
    setirname = request->getParam("a")->value();
  } else {
    return false;
  }
  // Convert the button number from String type to int type
  int setNum = setNumStr.toInt();
  // Append the identification character "O:" to the beginning of the button name
  setirname = "O:" + setirname;
  // Define a variable with matching type for storage in EEPROM
  st_remocon remRom;
  // Convert from String to char type (Length +1 to add end character)
  setirname.toCharArray(remRom.remo_name, setirname.length()+1);
  // Calculate memory location and write to EEPROM
  int memPos = (65 * setNum);
  EEPROM.put<st_remocon>(memPos, remRom);
  EEPROM.commit();
  Serial.println("setIr:" + String(setNum) + ":" + setirname);
  // Create a file name to save the remote control signal (the file name is the button number)
  String t_file = "/" + setNumStr;
  Serial.println( "recvFile:" + t_file);
  // open file in write mode
  File fw = SPIFFS.open(t_file.c_str(), "w");
  // Write remote control signal length first (first line)
  fw.println( String( irLength, HEX ) );
  // Write the time length of 0 and 1 of the remote control signal (from the second line)
  for (int i = 0; i < irLength; i++) {
    fw.println( String( irData[i], HEX ) );
  }
  // Close the file when writing is complete
  fw.close();
  // Returns true because processing was completed normally
  return true;
}

// Infrared transmission process
bool  contRemocon (String setNumStr) {  // save transmission number
  // Variable declaration
  unsigned short irCount = 0; // Number of HIGH and LOW signals
  unsigned long l_now = 0;    // number of HIGH and LOW signals
  unsigned long sndt = 0;     // Elapsed time since transmission started
  // Get remote control signal from file (file name is button number)
  String t_file = "/" + setNumStr;
  Serial.println( "sendFile:" + t_file);
  // open file
  File fr = SPIFFS.open(t_file.c_str(), "r");
  // File open failure
  if(!fr || fr.isDirectory()){
    Serial.println("FileOpen NG");
    return false;
  }
  // Get readout signal length (number of 0 and 1) only for the first row
  String snum = fr.readStringUntil('\n');
  // Convert the string in the first line to numeric type
  irCount = strtol(snum.c_str(), NULL, 16);
  Serial.println( "sendData:" + String(irCount));
  // Once read from the file to the variable irData and saved
  for (int i = 0; i < irCount; i++) {
    snum = fr.readStringUntil('\n');
    irData[i] = strtol(snum.c_str(), NULL, 16);
    //Serial.print("," + String(irData[i]));
  }
  fr.close();
  // get transmission start time
  l_now = micros();
  // Loop for the number of signals 0 and 1 with a For statement
  for (int i = 0; i < irCount; i++) {
    // Calculate signal end time from transmission start
    sndt += irData[i];
    do {
      // If i is an even number, the infrared is ON, if it is an odd number, it remains OFF
      // Transmit with ON time at carrier frequency 38kHz (approximately half of 26μSec period)
      digitalWrite(IR_S_PIN, !(i&1));
      microWait(13);
      // Transmits at carrier frequency 38kHz (half of 26μSec cycle) during OFF time
      digitalWrite(IR_S_PIN, 0);
      microWait(13);
    // Loop from transmission start until signal end time exceeds
    } while (long(l_now + (sndt * 10) - micros()) > 0);
  }
  // Returned true because it ended normally
  Serial.println(":End");
  return true;
}

// wait in microseconds
void microWait(signed long waitTime) {
  unsigned long waitStartMicros = micros();
  while (micros() - waitStartMicros < waitTime) {};
}
// WebServer setting processing
void setup_webserver() {
  // WebServer processing settings (send control screen)
  webServer.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
      Serial.println ( "Top" );
      request->send(SPIFFS, "/top.html", "text/html");
  });
  // For favicon display (optional)
  webServer.on("/favicon.ico", HTTP_GET, [](AsyncWebServerRequest *request){
      Serial.println ( "favicon" );
      request->send(SPIFFS, "/favicon.ico", "image/x-icon");
  });
  // WebServerLED control (LED control)
  webServer.on("/led", HTTP_GET, [](AsyncWebServerRequest *request){
      String setLedVal = request->getParam("o")->value();
      if (setLedVal == "ON") {
        digitalWrite ( LED_PIN, true );
        Serial.println ( "led on" );
      } else {
        digitalWrite ( LED_PIN, false );
        Serial.println ( "led off" );
      }
      request->send(200, "text/plain", "OK");
  });
  // WebServer processing settings (sending Javascript for control screen)
  webServer.on("/rem.js", HTTP_GET, [](AsyncWebServerRequest *request){
      Serial.println ( "js" );
      request->send(SPIFFS, "/rem.js", "text/html");
  });
  // WebServer processing settings (send setting screen)
  webServer.on("/set", HTTP_GET, [](AsyncWebServerRequest *request){
      Serial.println ( "set" );
      request->send(SPIFFS, "/set.html", "text/html");
  });
  // WebServer processing settings (button information transmission)
  webServer.on("/getrem", HTTP_GET, [](AsyncWebServerRequest *request){
      Serial.println ( "getrem" );
      getRemocon(request);
  });
  // WebServer processing settings (remote control reception settings)
  webServer.on("/setrem", HTTP_GET, [](AsyncWebServerRequest *request){
      Serial.println ( "setrem" );
      setRemocon(request);
  });
  // WebServer processing settings (remote control transmission)
  webServer.on("/cntrem", HTTP_GET, [](AsyncWebServerRequest *request){
      Serial.println ( "cntrem" );
      // Get and check button number (HTTP GET request parameter) and send process
      if (request->hasParam("n") && contRemocon( request->getParam("n")->value() )) {
        request->send(200, "text", "OK");
        Serial.println("Web send OK");
      } else {
        request->send(200, "text", "NG");
        Serial.println("Web send NG");
      }
  });
  // WebServer start processing
  DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*");
  webServer.begin();
  Serial.println ( "Web server started" );
}

// Button information transmission function
void getRemocon(AsyncWebServerRequest *request) {
  // Create transmission data (JSON format)
  String senddata = "{";
  // Declare a variable to store EEPROM data
  st_remocon remRom;
  // Read 10 pieces of button information and reply
  for (byte i = 0; i < 10; i++) {
    // Calculate EEPROM memory location
    int memPos = (65 * i);
    // Erase so that the previous value 'O:' does not remain
    remRom.remo_name[0] = 'n';
    // Get data from EEPROM
    EEPROM.get<st_remocon>(memPos, remRom);
    // Check if data is saved
    if (remRom.remo_name[0] == 'O' && remRom.remo_name[1] == ':' ) {
      // If the response string length exceeds 1, add "," (delimiter from the second and subsequent characters)
      if (senddata.length() > 1) {
        senddata += ",";
      }
      // Replace the returned value with String type once (to remove "O:")
      String getirname = String(remRom.remo_name);
      // Create reply string (from 2 to the end to remove "O:")
      senddata += "\"" + (String)i + "\":\"" + getirname.substring(2,getirname.length()) + "\"";
    }
  }
  // Add "}" at the end to close the JSON data
  senddata += "}";
  // Send the created response (JSON) data from the web server
  request->send(200, "text", senddata);
  Serial.println( "getRemocon:" + senddata);
}

The HTML and Javascript files are in the Zip file, but only “outdoor.html” is displayed here.

<!doctype html>
<!-- ◆◆◆HTML Tag◆◆◆ -->
<html>
  <!-- ◆◆◆head Tag◆◆◆ -->
  <head>
    <meta charset='UTF-8'/>
    <meta name='viewport' content='width=device-width'/>
    <!-- ##### StyleSheet ##### -->
    <style type='text/css'><!--
      #contents { width: 100%; max-width: 320px; }
      #menu{ color: #fff; background: #222; }
      .underTheEarthKai {
         background-image: radial-gradient(50% 150%, #CCCCCC 5%, #777777 100%);
      }
      button { width:155px; height:35px }
      #dispStatus{ color: #f00; }
      footer { text-align: right; }
    --></style>
    <!-- ##### Javascript ##### -->
    <script type='text/javascript'>
// ############## Beebotte setting ############
var beToken   = "#### TOKEN ####";   // Beebotte Token
var beChannel = "### CHANNEL ###";   // Beebotte Channel
// ############################################

// ● Remote control signal processing
var irFlg = false;
function snd(setNum) {
  // ● Judgment during processing
  if (irFlg) {
    // ●If processing is in progress, display processing and exit.
    document.getElementById('dispStatus').innerHTML = "<b>Processing</b>";
    return;
  }
  // ● Set the action flag as being processed, and perform display processing during reception
  irFlg=true;
  document.getElementById('dispStatus').innerHTML = "<b>Sending remote control</b>";
  var xhr = new XMLHttpRequest();
  // ● Make send data
  var sdata = "{ \"data\":" + setNum +  "}";
  // ● Create an access URL
  var url = "https://api.beebotte.com/v1/data/publish/" + beChannel + "/resource1?token=" + beToken;
  xhr.timeout = 5000;
  xhr.ontimeout = function(e){
    irFlg=false;
    // ● Show failure in status
    document.getElementById('dispStatus').innerHTML = "<b>Access Timeout Failure!</b>";
  };
  xhr.open("POST", url);
  xhr.setRequestHeader( 'Content-Type', 'application/json' );
  xhr.send(sdata);
  xhr.addEventListener("load",function(ev){
    var resStr = xhr.responseText;
      // ●When OK is received, the status is displayed in the if statement. Otherwise, display the state inside else
    if ( resStr.indexOf("true") != -1 ) {
      document.getElementById('dispStatus').innerHTML = "<b>Transmission Completed!</b>";
    } else {
      document.getElementById('dispStatus').innerHTML = "<b>Transmission Failure!</b>";
    }
    // ● Return the processing flag
    irFlg=false;
  });
}
    </script>
  </head>
  <!-- ◆◆◆Body Tag◆◆◆ -->
  <body class='underTheEarthKai'><center><div id='contents'>
    <header><h3>Smart Remote controller [OutDoor]</h3></header>
    <div id='menu'>Controller Screen</div>
    <br/>
    <!-- ##### Button Tag ##### -->
    <table>
      <tr>
        <td><button id='btn0' class='cntbtn' onClick="snd(0)">
          <font size=+1><span id='spn0'>1</span></font></button></td>
        <td><button id='btn1' class='cntbtn' onClick="snd(1)">
          <font size=+1><span id='spn1'>2</span></font></button></td>
      </tr>
      <tr>
        <td><button id='btn2' class='cntbtn' onClick="snd(2)">
          <font size=+1><span id='spn2'>3</span></font></button></td>
        <td><button id='btn3' class='cntbtn' onClick="snd(3)">
          <font size=+1><span id='spn3'>4</span></font></button></td>
      </tr>
      <tr>
        <td><button id='btn4' class='cntbtn' onClick="snd(4)">
          <font size=+1><span id='spn4'>5</span></font></button></td>
        <td><button id='btn5' class='cntbtn' onClick="snd(5)">
          <font size=+1><span id='spn5'>6</span></font></button></td>
      </tr>
      <tr>
        <td><button id='btn6' class='cntbtn' onClick="snd(6)">
          <font size=+1><span id='spn6'>7</span></font></button></td>
        <td><button id='btn7' class='cntbtn' onClick="snd(7)">
          <font size=+1><span id='spn7'>8</span></font></button></td>
      </tr>
      <tr>
        <td><button id='btn8' class='cntbtn' onClick="snd(8)">
          <font size=+1><span id='spn8'>9</span></font></button></td>
        <td><button id='btn9' class='cntbtn' onClick="snd(9)">
          <font size=+1><span id='spn9'>10</span></font></button></td>
      </tr>
    </table>
    <!-- ##### DivTag(Display Status) ##### -->
    <div id='dispStatus'><br></div>
    <!-- ##### Footer Tag ##### -->
    <footer><font size=-1>©Hobby-IT</font></footer>
  </div></center></body>
</html>

Document [at youtube]

Comments

Copied title and URL