In summer our bedroom is getting pretty warm so we wanted to buy a nice big fan. In the end we found a ceiling fan at our local Hornbach for about 100 euro's (https://www.hornbach.nl/shop/MADEIRA-Pl ... tikel.html). It was big, silent and controllable via an infrared remote. I installed it and we were happy
But soon it became apparent that having one remote was not that good . This was always on the bed-stand, so when entering the room you had to find it in the dark, then find the right button and turn on the light. So I went online to check of I could get a separate remote, but too bad. You could only buy the full IR control kit with the fan control box for 30 euro's (https://www.hornbach.nl/shop/MADEIRA-Af ... tikel.html). Also you would still be using a remote control with small buttons and I wanted a light switch.
So I went on a search on how to send IR signals using an ESP. After some searching I found out that the Hornbach Madeira fan's use some kind of Westinghouse IR code. For this I could not find any codes with the standard ESPeasy/Tasmota solutions. I even tried the sniffer applications, but these did not give me any usable codes
So I had to go back to basic. I ordered a Wemos D1 mini (https://nl.aliexpress.com/item/32803725 ... 4c4dnpWEH4) and and IR shield at AliExpress (https://nl.aliexpress.com/item/32891173 ... 4c4diMNXUO). With a small logic analyzer (https://nl.aliexpress.com/item/40001793 ... 4c4dnpWEH4) I sniffed out the codes that the remote was sending and these were indeed somewhat matching Westinghouse codes, but not completely. After searching I found an example code on a German website (https://homematic-forum.de/forum/viewtopic.php?t=41163) by Christoph G. (fadingdream). I set the right pins in the code for the IR shield and programmes it into the Wemos D1 mini. The web page with the buttons turned up and I could send the codes.
Controlling the fan however was a bit hit and miss. Sometimes the light did turn on, but then it stopped responding for minutes. Also the speed control was not controlling the rights speeds and turning off/on without any logic to it. So I had to dig deeper.
After sniffing all the codes with the IR shield and the logic analyzer and comparing it to the codes generated by Christoph's program, I found some differences:
- the Hornbach remote was sending 12 instead of 11 bits
- the hornbach remote was always sending first one specific code (preamble) which was always the same and then the control code for the button pressed was repeated until the button was released
- Christoph's program only sent the control code and repeated it twice
So I adapted the program to send 12 bits codes, added the preamble and changed the control codes for Light-toggle, fan-off and fan speed low/medium/high. Also the codes repeats the control code several times. Then I Programmed the Wemos and back to the bedroom. Testing the new code immediately made things more stable. I think the preamble has changed the way the receiver processes the commands.
this is the code I created:
Code: Select all
/*
Hornback Madeira Ceiling fan control with IR on Wemos D1 Mini by John H.
Based on "Westinghouse Deckenventilator über Infrarot (Westinghouse IR 78095) by Frank M.
And IR Codes von Christoph G. (fadingdream) mikrocontroller.net
Header codes for reliable control of Madeira Ceiling fan added by John H.
*/
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
// Configure your pin where the IR led is connected
#define irLedPin 0 // <-- GPIO0 = D3
MDNSResponder mdns;
// Replace with your network credentials
const char* ssid = "your ssid";
const char* password = "your wifi password";
ESP8266WebServer server(80);
// replace with your values
char* socketcodes[] = {"1", "2", "3", "4"};
char* socketnames[] = {"Fan High", "Fan Medium", "Fan Low", "Light"};
int tmp = 0;
int numofsockets = sizeof(socketcodes)/4;
// you can write your own css and html code (head) here
String css = "body {background-color:#ffffff; color: #000000; font-family: 'Century Gothic', CenturyGothic, AppleGothic, sans-serif;}h1 {font-size: 2em;}";
String head1 = "<!DOCTYPE html> <html> <head> <title>Hornbach Madeira Ceiling Fan</title> <style>";
String head2 = "</style></head><body><center>";
String header = head1 + css + head2;
String body = "";
String website(String h, String b)
{
String complete = h+b;
return complete;
}
void setup(void){
pinMode(irLedPin, OUTPUT);
// if you want to modify body part of html start here
body = "<h1>Hornbach Madeira Ceiling Fan</h1>";
// socket names and buttons are created dynamical
for(int i = 0; i < numofsockets; i++){
String namesocket = socketnames[i];
body = body + "<p>" + namesocket + " <a href=\"socket" + String(i) + "On\"><button>ON</button></a> <a href=\"socket" + String(i) + "Off\"><button>OFF</button></a></p>";
}
body += "</center></body>";
Serial.begin(115200);
WiFi.begin(ssid, password);
Serial.println("");
// Wait for connection
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
// serial output of connection details
Serial.println("");
Serial.print("Connected to ");
Serial.println(ssid);
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
if (mdns.begin("esp8266", WiFi.localIP())) {
Serial.println("MDNS responder started");
}
// this page is loaded when accessing the root of esp8266�s IP
server.on("/", [](){
String webPage = website(header, body);
server.send(200, "text/html", webPage);
});
// pages for all your sockets are created dynamical
for(int i = 0; i < numofsockets; i++){
String pathOn = "/socket"+String(i)+"On";
const char* pathOnChar = pathOn.c_str();
String pathOff = "/socket"+String(i)+"Off";
const char* pathOffChar = pathOff.c_str();
server.on(pathOnChar, [i](){
String webPage = website(header, body);
server.send(200, "text/html", webPage);
tmp = atoi(socketcodes[i]);
switch (tmp) {
case 1:
sendCodeHead1();
sendCodeHead2();
sendCodeFan1();
break;
case 2:
sendCodeHead1();
sendCodeHead2();
sendCodeFan2();
break;
case 3:
sendCodeHead1();
sendCodeHead2();
sendCodeFan3();
break;
case 4:
sendCodeHead1();
sendCodeHead2();
sendCodeLight();
break;
}
delay(1500); // Waiting time before send
});
server.on(pathOffChar, [i](){
String webPage = website(header, body);
server.send(200, "text/html", webPage);
tmp = atoi(socketcodes[i]);
switch (tmp) {
case 1:
sendCodeHead1();
sendCodeHead2();
sendCodeFan0();
break;
case 2:
sendCodeHead1();
sendCodeHead2();
sendCodeFan0();
break;
case 3:
sendCodeHead1();
sendCodeHead2();
sendCodeFan0();
break;
case 4:
sendCodeHead1();
sendCodeHead2();
sendCodeLight();
break;
}
delay(1500); // Waiting time before send start
});
}
server.begin();
Serial.println("HTTP server started");
}
void loop(void){
server.handleClient();
}
// IR-Codes definieren
void sendCodeLight() {
byte irCode[] = {1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0};
sendCode(irCode);
}
void sendCodeLightDimmer(byte dimmingSteps) {
byte irCode[] = {1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0};
for (byte i = 0; i < dimmingSteps; i++) {
sendCode(irCode);
}
}
void sendCodeFan0() {
byte irCode[] = {1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0};
sendCode(irCode);
}
void sendCodeFan1() {
byte irCode[] = {1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1};
sendCode(irCode);
}
void sendCodeFan2() {
byte irCode[] = {1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0};
sendCode(irCode);
}
void sendCodeFan3() {
byte irCode[] = {1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1};
sendCode(irCode);
}
void sendCodeHead1() {
byte irCode[] = {1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
sendCode1(irCode);
}
void sendCodeHead2() {
byte irCode[] = {1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1};
sendCode1(irCode);
}
// IR-Code 1x senden
void sendCode1(byte irCode[]) {
for (byte r = 0; r < 1; r++) {
for (byte i = 0; i < 12; i++) {
sendCodeStep(irCode[i]);
}
delayMicroseconds(6620);
}
}
// IR-Code 6x senden
void sendCode(byte irCode[]) {
for (byte r = 0; r < 6; r++) {
for (byte i = 0; i < 12; i++) {
sendCodeStep(irCode[i]);
}
delayMicroseconds(6620);
}
}
// aktuelle Stelle vom IR-Code senden
void sendCodeStep(byte actualStep) {
if (actualStep == 0) {
sendPulse(460);
delayMicroseconds(1180);
} else {
sendPulse(1300);
delayMicroseconds(340);
}
}
// Puls mit ca. 38 kHz senden
void sendPulse(int pulseLength) {
cli();
while (pulseLength > 0) {
digitalWrite(irLedPin, HIGH);
delayMicroseconds(10);
digitalWrite(irLedPin, LOW);
delayMicroseconds(10);
pulseLength -= 26;
}
sei();
}
Within Domoticz I have created 2 manual switches:
1. toggle switch --> sending the light toggle command
2. selector switch --> sending the fan off, low, medium and high commands
The IR transmitters on the Wemos IR shield are not that strong an omni-directional. So I bought a 2 led IR transmitter mini module (https://nl.aliexpress.com/item/19729454 ... 4c4dUr8iE1) that now sends the IR codes. Everything is wired together and integrated in a small plastic box (https://nl.aliexpress.com/item/40001548 ... 4c4diMNXUO) with a WiFi antenna sticking out and the IR leds pointing to the ceiling. The whole thing is powered by a small USB adapter.
The light is now controlled via Domoticz by a Aqara single button Zigbee switch (https://nl.aliexpress.com/item/32944412 ... 4c4drHf74l) on the wall directly by the door. It is battery operated and I installed it over the wallbox so it's in the same place the normal switch was. Pressing it will toggle the light. Double tap will increase the fan speed (via a dzvents script) and press/hold will turn off the fan.
On the nightstand I have a small one-button Aqara switch (https://nl.aliexpress.com/item/32990494 ... 4c4drHf74l) which is much easier to use in the dark. Functions are the same as the wall switch. The small switch also has a shake function which now controls the light in the downstairs hallway.
This surely was not cheaper, but it is much more flexible and user friendly and it was a lot of fun .
I hope the code will help others control their fans from Domoticz.
Cheers,
John