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)