Integrate *any* remote to Home Assistant

Here is a little project, easy to replicate to any use case.

Goal of the project
Being able to control any remote, using any protocol, from your home assistant UX or automations.

Pre requisite:

  • 1 soldering iron
  • 1 ESP8266 (Lolin Wemos D1 Mini are just perfect)
  • 1 remote you want to automate
  • A USB-mini power source (like it’s really consuming nothing)
  • A working MQTT server (HA has one integration, otherwise Mosquitto is easy to install)

Concept
You’ll want to emulate the press of a button with your finger by shorting the two poles/pads of the button of the remote using an ESP8266. From there, the ESP just has to read an MQTT channel and when a specific message is received, it “presses” the button. This is universal, alarm, parking, shutters, tv, whatever.

Implementation
Nothing fancy here, remotes are usually putting one of the poles to GND to “close” the circuit and register a button press. Some are wiring it to a voltage pin, but they are rare and can be dealt with in a very similar way.

Example

Powering the remote
Here on the picture, you can see the remote of an old curtain remote. (Old like in 20 y/o)
It was running on a 3V battery, so I just had to power it directly from the ESP 3.3V rail.
Some are working on 5V, same here, with a Wemos D1 mini, you can power them directly from the 5V rail of the ESP. Some tricky (and usually old) ones are running on 12V or 24V. In those cases, use a buck converter to convert a 5V source into a 12 or 24V one. You should power them from the same USB adapter (to keep a coherent GND value, warning otherwise you can burn components) and not directly from the ESP to avoid pulling to much current. Avoid powering it from a battery and piggybacking an ESP on it otherwise, your GND may not be aligned and could provide unexpected results.

Tip
You can also wire some press buttons on it, and 3D print a little case to retain the classical remote ability. This allows human-hand interaction should one day your HA be out of commission.

Wiring
This one was straightforward. Large solder pads & simple powering from the ESP, it was previously powered by a “3V button cell” like a CR2021. If you have doubts, the multimeter continuity function is your friend. Identify a ground pad and check which pole of the button is GND. The other is signal. Same if you can’t access a specific soldering point, check with the multimeter if you cannot hook it somewhere else on the PCB.

3 buttons to emulate: Up, Down, Stop.
I wanted to retain manual operation capacity on Up & Down, hence the extra pair of white & yellow wires.

Wire                Wemos      Remote
Red                 3V3          + pad 
Black               GND          - pad
Green               D2           UP
Yellow              D6           DOWN 
White               D1           STOP

So when I set D2 (Pin 4 of the GPIO) of the Wemos to “LOW”, it closes the loop and the button is pressed. Then I release the press by setting back the value of D2 to “HIGH”.

Code context
We want Wifi, MQTT communication, some storage of the last position (should the thing reboot) and some OTA (update over the air) not to move ourselves if anything needs to be updated. Compile & upload it using Arduino IDE for example.

Consider the following code to be opensource under MIT license. (you can do whatever you like with it)

Code

#include <PubSubClient.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>
#include <EEPROM.h>
#ifdef ESP32
    #include <WiFi.h>
#elif ESP8266
    #include <ESP8266WiFi.h>
#endif
#ifdef ESP32
    #include <ESPmDNS.h>
#elif ESP8266
    #include <ESP8266mDNS.h>
#endif

int                  OTAport = 8266;
const char*      OTApassword = "OTA PASSWORD";
uint8_t                 OPEN = 4;
uint8_t                 STOP = 5;
uint8_t                CLOSE = 12;
const char*        cmd_topic = "homeassistant/name_of_remote/cmd";
const char*      state_topic = "homeassistant/name_of_remote/state";
const char*     status_topic = "homeassistant/name_of_remote/status";
const char*      device_name = "NAME OF YOUR REMOTE";
const char*        wifi_ssid = "WIFI SSID";
const char*    wifi_password = "WIFI PASSWORD";
const unsigned int mqtt_port = 1883;
long                     tps = 0;
long                       i = 0;
const char*      mqtt_server = "MQTT IP SERVER IP ADDRESS";
const char*        mqtt_user = "MQTTUSER";
const char*    mqtt_password = "MQTTPASSWORD";
String         state_shutter;

void Wifi_init();
void OTA_init();
void Button_press(char* button);
void mqttconnect();
void receivedCallback(char* topic, byte* payload, unsigned int length);
void Epromwrite(char *msg);
void Epromread();
WiFiClient wifiClient;
PubSubClient client(wifiClient);

void Wifi_init()
{
  long logrssi = 0 ;
  if (WiFi.SSID() != wifi_ssid)
  {
    Serial.printf("[Connecting to %s                  ]\n", wifi_ssid);
    WiFi.mode(WIFI_STA);
    WiFi.begin(wifi_ssid, wifi_password);
    Serial.printf("[");
    WiFi.persistent(true);
    WiFi.setAutoConnect(true);
    WiFi.setAutoReconnect(true);
  }
  while (WiFi.status() != WL_CONNECTED)
  {
    Serial.print(".");
    delay(1000);
  }
  Serial.println("]");
  for (size_t i = 0; i < 10; i++)
  {
    long rssi = WiFi.RSSI();
    logrssi = logrssi + rssi;
    delay(200);
  }
  #ifdef ESP32
    WiFi.setHostname(device_name);
  #elif ESP8266
    WiFi.hostname(device_name);
  #endif
  Serial.printf("[MAC: %s IP: %s]\n", WiFi.macAddress().c_str(), WiFi.localIP().toString().c_str());
  Serial.printf("[Mean rssi = %0.1d dBm                    ]\n", logrssi / 10);
  delay(1000);
}

void OTA_init()
{
  ArduinoOTA.setPort(OTAport);
  ArduinoOTA.setHostname(device_name);
  ArduinoOTA.setPassword((const char *)OTApassword);
  ArduinoOTA.onStart([]() { Serial.println("Starting"); });
  ArduinoOTA.onEnd([]() { Serial.println("\nEnd"); });
  ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { Serial.printf("Progress: %u%%\r", (progress / (total / 100))); });
  ArduinoOTA.onError([](ota_error_t error) {
    Serial.printf("Error[%u]: ", error);
    if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
    else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
    else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
    else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
    else if (error == OTA_END_ERROR) Serial.println("End Failed");
  });
  ArduinoOTA.begin();
}

void Epromwrite(const String &msg) {
  byte len = msg.length();
  for (int i = 0; i < len; i++) { EEPROM.write(i,msg[i]); }
  EEPROM.commit();
  Serial.printf("- wrote to EEprom: ");
  Serial.println(msg);
}

void Epromread() {
  char tmp[]="";
  EEPROM.begin(3);
  for (int i = 0; i < 3; i++) { tmp[i] = char(EEPROM.read(i)); }
  if (tmp[1] == 'o'){ state_shutter = "open"; }
  else if ((tmp[1] == 'c')) { state_shutter = "close"; }
  else { state_shutter = "unknown"; }
}

void mqttconnect() {
    while ( !client.connected() ) {
        Serial.print("[Connecting to MQTT server: ");
        if (client.connect(device_name, mqtt_user, mqtt_password, status_topic, 1, 1, "offline", 1)) {
            Serial.println("connected   ]");
            client.subscribe(cmd_topic, 1);
            Serial.println("[Subscribed to cmd topics               ]");
            client.publish(status_topic, "online", false);
        }
        else {
            Serial.print("*** MQTT broker connexion failed, status code=");
            Serial.print(client.state());
            Serial.print(" ***");
            Serial.println("[Retrying in 5 seconds]");
            delay(5000);
        }
    }
}

void receivedCallback(char* topic, byte* payload, unsigned int length) {
  payload[length]='\0';
  if(strcmp((char *)payload, "OPEN") == 0) {
    Serial.println("- Shutter up cmd received");
    Button_press(OPEN);
    state_shutter = "open";
    Epromwrite(state_shutter);
  }
  if(strcmp((char *)payload, "STOP") == 0) {
    Serial.println("- Shutter stop cmd received");
    Button_press(STOP);
    state_shutter = "unknown";
    Epromwrite(state_shutter);
  }
  if(strcmp((char *)payload, "CLOSE") == 0) {
    Serial.print("- Shutter down cmd received");
    Button_press(CLOSE);
    state_shutter = "closed";
    Epromwrite(state_shutter);
  }
  client.publish(state_topic, state_shutter.c_str(), false);
}

void Button_press(uint8_t button) {
  Serial.print("-- Pressing button: ");
  Serial.println(button);
  digitalWrite(button,LOW);
  delay(38000);
  Serial.println("-- Releasing button");
  digitalWrite(button, HIGH);
}

void setup()
{
  String previous_state;
  Serial.begin(115200);
  Serial.printf("[<<<<<<<<<Booting up %s>>>>>>>>>>]\n", device_name);
  Wifi_init();
  Serial.println("[Setting up I/O                         ]");
  pinMode(OPEN,OUTPUT);
  pinMode(STOP,OUTPUT);
  pinMode(CLOSE,OUTPUT);
  digitalWrite(OPEN,HIGH);       // Set IN and OUT HIGH so the remote doesn't activate on start IN
  digitalWrite(STOP,HIGH);
  digitalWrite(CLOSE,HIGH);
  Serial.println("[Setting up OTA                         ]");
  OTA_init();
  Serial.println("[Setting up MQTT                        ]");
  client.setKeepAlive(60);
  client.setServer(mqtt_server, mqtt_port);
  client.setCallback(receivedCallback);
  Serial.println("[Getting state pre-reboot from EEPROM   ]");
  Epromread();
  Serial.println("[Connecting to MQTT                     ]");
  mqttconnect();
  delay(2000);
  if ( client.connected() ) {
    Serial.println("[Updating MQTT with pre-reboot state    ]");
    client.publish(state_topic, previous_state.c_str(), false);
  }
  Serial.println("[<<<<<<Init completed successfully>>>>>>]");
}

void loop()
{
  ArduinoOTA.handle();
  if (WiFi.status() != WL_CONNECTED) {
    Serial.println("*** Wifi disconnected: reconnecting ***");
    Wifi_init();
  }
  if ( !client.connected() ) {
    Serial.println("*** MQTT disconnected: reconnecting ***");
    mqttconnect();
  }
  client.loop();
  delay(200);
  if (millis()-tps>55000){
    tps=millis();
    client.publish(status_topic, "online", false);
  }
}

HA configuration

The relevant part of my configuration.yaml:

 mqtt:
  cover: !include_dir_merge_list mqtt/cover

Content of the mqtt/cover/frontdoor.yaml file:

- name: "Frontdoor"
  command_topic: "homeassistant/ESP-Frontdoor/cmd"
  state_topic: "homeassistant/ESP-Frontdoor/state"
  availability_topic: "homeassistant/ESP-Frontdoor/status"
  qos: 0
  retain: false
  payload_open: "OPEN"
  payload_close: "CLOSE"
  state_open: "open"
  state_closed: "closed"
  payload_available: "online"
  payload_not_available: "offline"

Declaration in HA dashboard:

 type: entities
 state_color: true
 show_header_toggle: false
 entities:
   - entity: cover.frontdoor
     name: Frontdoor
     state_color: true
     show_name: true
     show_icon: true

Disclaimer
Own risk, not responsible, don’t put a soldering iron in your mouth, whatever.

(My other posts: Integrate any remote to HA, Run HA boot+data on SSD with a PI, False positive proof security system, Advanced solar panel monitoring)

3 Likes

I got this error please help me

Try without a hyphen in the devicename?

ESP-Frontdoor:21:62: error: unable to find string literal operator ‘operator""ESP_Frontdoor1’ with ‘const char [19]’, ‘unsigned int’ arguments

21 | const char* cmd_topic = “homeassistant/“ESP_Frontdoor1”/cmd”;

  |                                                              ^~~~~~

ESP-Frontdoor:22:60: error: unable to find string literal operator ‘operator""ESPFrontdoor’ with ‘const char [21]’, ‘unsigned int’ arguments

22 | const char* state_topic = “homeassistant/“ESPFrontdoor”/state”;

  |                                                            ^~~~~~~~

ESP-Frontdoor:23:60: error: unable to find string literal operator ‘operator""ESPFrontdoor’ with ‘const char [22]’, ‘unsigned int’ arguments

23 | const char* status_topic = “homeassistant/“ESPFrontdoor”/status”;

  |                                                            ^~~~~~~~~

Multiple libraries were found for “WiFiUdp.h”

Used: C:\Users\Administrator\Documents\ArduinoData\packages\esp8266\hardware\esp8266\3.1.2\libraries\ESP8266WiFi

Not used: C:\Users\Administrator\Documents\Arduino\libraries\WiFi101_Generic

Not used: C:\Users\Administrator\Documents\Arduino\libraries\WiFiEspAT

Not used: C:\Program Files\WindowsApps\ArduinoLLC.ArduinoIDE_1.8.57.0_x86__mdqgnx93n4wtt\libraries\WiFi

Not used: C:\Users\Administrator\Documents\Arduino\libraries\WiFi101

Multiple libraries were found for “ArduinoOTA.h”

Used: C:\Users\Administrator\Documents\ArduinoData\packages\esp8266\hardware\esp8266\3.1.2\libraries\ArduinoOTA

Not used: C:\Users\Administrator\Documents\Arduino\libraries\ArduinoOTA

exit status 1

unable to find string literal operator ‘operator""ESP_Frontdoor1’ with ‘const char [19]’, ‘unsigned int’ arguments

This report would have more information with
“Show verbose output during compilation”
option enabled in File → Preferences.

this is the error i get

const char* cmd_topic = “homeassistant/“ESP_Frontdoor1”/cmd”;
const char* state_topic = “homeassistant/“ESPFrontdoor”/state”;
const char* status_topic = “homeassistant/“ESPFrontdoor”/status”;
const char* device_name = “ESPFrontdoor”;

If I have understood well, you are powering the esp8266 with the same battery as the remote? Does it works even if the esp is powered by usb?

Thanks!!

Hi @dapuzz, actually, I power the Lolin Wemos D1 mini through its USB-mini port & I hooked the remote + / - pads on the 3V3 (3V) and GND of the Wemos.

In my experience, if your remote is powered by a 3 or 5V battery, you can directly hook it on the Wemos GPIO.

yeah sorry @Cao_Hoa, I update the proposed code to remove the hyphens.

nothing I will wait for you to update the new code again

it’s done, in the original post

Why not use esphome for that usecase? It makes integration, updating aso. a lot easier.

I really like the idea to modify an old remote!

1 Like

Well spotted. I did this project before ESPhome became a significant component of HA. I love to know what’s in the code and have tight control over it and I don’t find it much more complicated to kick off my Arduino IDE than using ESPhome to be honest.

Since my code is working fine and is very simple, I saw no reason to port it to ESPhome.
You know what they say, if it ain’t broken…

But it sounds doable indeed.
If some of you try this route, please don’t hesitate to share the outcome!

ok thanks I will try again and let you know

Hmmm. This just gave me an idea… I wonder if I can find a remote laying around that uses I2C or some other type of easily reverse-engineerable protocol between its button array and its microprocessor. That would make for a nice and easy integration point to hijack the bus and capture all of the button presses without needing to wire into each button. I imagine most remotes just use pins on a SoC in a grid array (x and y pins) but if I can find one that has a separate button array decoder I bet it could lend itself well to becoming part of a little franken-remote!