Hampton Bay Fan RF/MQTT bridge

I have a Hampton Bay ceiling fan that I wanted to automate, so I wrote a sketch for an ESP8266 to control it over MQTT.

Repository is here: https://github.com/owenb321/hampton-bay-fan-mqtt

The fan remote operates on 303 MHz, which doesn’t seem to be a common frequency. I’m using a CC1101 transceiver for the RF control, since it can be programmed to operate at any frequency between 300-348 MHz. This connects via SPI to the ESP8266 which does all the MQTT work.

This will not only send RF commands to the fan, but also listens for commands sent to the fan. This allows the fan state in MQTT to stay in sync even if the fan is operated with the original Hampton Bay remote. This should also be able to control multiple fans at once, but I can’t test that as I only have one.

More details for setup are on the GitHub repo. Stay cool!

17 Likes

Cant believe no one has commented on how cool this is. I have some sonoffs now and hate how they respond, even had to go to sonoff remotes and they really are bad. Cant wait to put these controllers back on. Kudos for developing this.

1 Like

Really awesome project. Unfortunately, I’m struggling to get it working, and I don’t really know enough about RF to know where to start looking. Any help or pointers to good resource would be really appreciated.

I was able to use my SDR to determine that my fan remote transmits on 313Mhz, and I’ve changed the freq in the code to match. I can now hear both the remote and the CC1101 transmitting on that freq, but the fan doesn’t respond to the CC1101 transmission and the CC1101 doesn’t seem to be picking receiving the remote transmission (no prot - value - bits are displayed in the serial terminal).

I’ve noticed that changing the fan_id in the mqtt command transmits on the same freq, while changing the dip switches on my remote makes the remote transmit on a slightly different freq (ie, 0000 transmits 313Mhz, 0001 transmits on 312.4Mhz). It is a Hampton Bay fan and remote.

I am looking to try this… I do not see which Hampton Bay controller that you are using, can you provide a link or part number?

Thanks Matt

I too have started this project with the Hampton Bay remote model TX028-RS, and could use some help. I am at the point where I can see this is feasible but I think the project sketch is either outdated or not compatible with my remote. For starters this remote has a fifth dipswitch to enable dimming. It seems clear to me that the issue of this not working is coming from the dipswitch section of the code and I am at a loss. Any help would be greatly appreciated. Below is where I am at currently.

I am using a Wemos D1 Mini with a CC1101 Wireless Transceiver 433MHZ + SMA Antenna Wireless Module.

I identified the remote frequency by the FCC ID to be 304.0.

Using the ReceiveDemo_Advanced_cc1101 I was able to determine my protocol was 11 not 6 like in the project sketch, and I was at 24 bits instead of 21. From there I was able to see the RF commands coming through in the serial monitor when pressing remote buttons.

I then took these RF codes and ran them through the SendDemo_cc1101 and was able to control the fan with this demo sketch. Clearly I can see it’s possible to receive and send with the module I have. Now if I can translate those codes from MQTT to RF like this project is able to I would be all set. Like I said in the beginning something just is not messing in the project sketch even when I change it to the correct frequency, protocol, and bits.

2 Likes

Did you try OpenMQTTGateway, it has configuration with cc1101 and rcswitch that looks very close to what you are doing.

Funny you mention this as I stumbled across it shortly before your post. I’ve been looking into it and the web installation didn’t work for me. I am trying the arduino installation now but I am a noob at that as well. If that doesn’t work I will try platformio. Will report back with any progress and information for others that I discover. Thank you.

I would advise to go to platformio directly.
The setup with CC1101 is not the easiest one, and having all the parameters already set thanks to PIO will help.

I’ve been able to get ZgatewayRF up and running but I wanted to try ZgatewayRF2. There is a dimmer function on this remote and I want to see if ZgatewayRF2 may capture it since ZgatewayRF doesn’t seem to be picking it up. When I go to compile ZgatewayRF2 in Arduino I keep getting this error and I am by no means a programmer and I am hoping someone can help me out. Thanks.

Line 359 of ZgatewayRF2

NewRemoteReceiver::init(RF_RECEIVER_GPIO, 2, rf2Callback);

no matching function for call to ‘NewRemoteReceiver::init(int, int, void (&)(unsigned int, long unsigned int, long unsigned int, long unsigned int, long unsigned int))’

#include "User_config.h"

#ifdef ZgatewayRF2

#  ifdef ZradioCC1101
#    include <ELECHOUSE_CC1101_SRC_DRV.h>
#  endif

#  include <NewRemoteReceiver.h>
#  include <NewRemoteTransmitter.h>

struct RF2rxd {
  unsigned int period;
  unsigned long address;
  unsigned long groupBit;
  unsigned long unit;
  unsigned long switchType;
  bool hasNewData;
};

RF2rxd rf2rd;

void setupRF2() {
#  ifdef ZradioCC1101 //receiving with CC1101
  ELECHOUSE_cc1101.Init();
  ELECHOUSE_cc1101.setMHZ(receiveMhz);
  ELECHOUSE_cc1101.SetRx(receiveMhz);
#  endif
  NewRemoteReceiver::init(RF_RECEIVER_GPIO, 2, rf2Callback);
  Log.notice(F("RF_EMITTER_GPIO: %d " CR), RF_EMITTER_GPIO);
  Log.notice(F("RF_RECEIVER_GPIO: %d " CR), RF_RECEIVER_GPIO);
  Log.trace(F("ZgatewayRF2 command topic: %s%s%s" CR), mqtt_topic, gateway_name, subjectMQTTtoRF2);
  Log.trace(F("ZgatewayRF2 setup done " CR));
  pinMode(RF_EMITTER_GPIO, OUTPUT);
  digitalWrite(RF_EMITTER_GPIO, LOW);
}

#  ifdef ZmqttDiscovery
//Register for autodiscover in Home Assistant
void RF2toMQTTdiscovery(JsonObject& data) {
  Log.trace(F("switchRF2Discovery" CR));
  String payloadonstr;
  String payloadoffstr;

  int org_switchtype = data["switchType"]; // Store original switchvalue
  data["switchType"] = 1; // switchtype = 1 turns switch on.
  serializeJson(data, payloadonstr);
  data["switchType"] = 0; // switchtype = 0 turns switch off.
  serializeJson(data, payloadoffstr);
  data["switchType"] = org_switchtype; // Restore original switchvalue

  String switchname;
  switchname = "RF2_" + String((int)data["unit"]) + "_" +
               String((int)data["groupbit"]) + "_" +
               String((unsigned long)data["address"]);

  char* switchRF[8] = {"switch",
                       (char*)switchname.c_str(),
                       "",
                       "",
                       "",
                       (char*)payloadonstr.c_str(),
                       (char*)payloadoffstr.c_str(),
                       ""};
  // component type,name,availability topic,device class,value template,payload
  // on, payload off, unit of measurement

  Log.trace(F("CreateDiscoverySwitch: %s" CR), switchRF[1]);

  // As RF2 433Mhz switches do not render their state, no state topic should be
  // provided in the discovery. This will cause the switch to be in optimistic
  // mode in HA with separate on and off icons.
  // The two separate on/off icons allow for subsequent on commands to support
  // the dimming feature of KAKU switches like ACM-300.
  createDiscovery(switchRF[0], "", switchRF[1],
                  (char*)getUniqueId(switchRF[1], "").c_str(), will_Topic,
                  switchRF[3], switchRF[4], switchRF[5], switchRF[6],
                  switchRF[7], 0, "", "", true, subjectMQTTtoRF2,
                  "", "", "", "", false,
                  stateClassNone);
}
#  endif

void RF2toMQTT() {
  if (rf2rd.hasNewData) {
    Log.trace(F("Creating RF2 buffer" CR));
    StaticJsonDocument<JSON_MSG_BUFFER> jsonBuffer;
    JsonObject RF2data = jsonBuffer.to<JsonObject>();

    rf2rd.hasNewData = false;

    Log.trace(F("Rcv. RF2" CR));
    RF2data["unit"] = (int)rf2rd.unit;
    RF2data["groupBit"] = (int)rf2rd.groupBit;
    RF2data["period"] = (int)rf2rd.period;
    RF2data["address"] = (unsigned long)rf2rd.address;
    RF2data["switchType"] = (int)rf2rd.switchType;
#  ifdef ZmqttDiscovery //component creation for HA
    if (disc)
      RF2toMQTTdiscovery(RF2data);
#  endif

    pub(subjectRF2toMQTT, RF2data);
  }
}

void rf2Callback(unsigned int period, unsigned long address, unsigned long groupBit, unsigned long unit, unsigned long switchType) {
  rf2rd.period = period;
  rf2rd.address = address;
  rf2rd.groupBit = groupBit;
  rf2rd.unit = unit;
  rf2rd.switchType = switchType;
  rf2rd.hasNewData = true;
}

#  ifdef simpleReceiving
void MQTTtoRF2(char* topicOri, char* datacallback) {
#    ifdef ZradioCC1101
  NewRemoteReceiver::disable();
  ELECHOUSE_cc1101.SetTx(CC1101_FREQUENCY); // set Transmit on
#    endif

  // RF DATA ANALYSIS
  //We look into the subject to see if a special RF protocol is defined
  String topic = topicOri;
  bool boolSWITCHTYPE;
  boolSWITCHTYPE = to_bool(datacallback);
  bool isDimCommand = false;

  long valueCODE = 0;
  int valueUNIT = -1;
  int valuePERIOD = 0;
  int valueGROUP = 0;
  int valueDIM = -1;

  int pos = topic.lastIndexOf(RF2codeKey);
  if (pos != -1) {
    pos = pos + +strlen(RF2codeKey);
    valueCODE = (topic.substring(pos, pos + 8)).toInt();
    Log.notice(F("RF2 code: %l" CR), valueCODE);
  }
  int pos2 = topic.lastIndexOf(RF2periodKey);
  if (pos2 != -1) {
    pos2 = pos2 + strlen(RF2periodKey);
    valuePERIOD = (topic.substring(pos2, pos2 + 3)).toInt();
    Log.notice(F("RF2 Period: %d" CR), valuePERIOD);
  }
  int pos3 = topic.lastIndexOf(RF2unitKey);
  if (pos3 != -1) {
    pos3 = pos3 + strlen(RF2unitKey);
    valueUNIT = (topic.substring(pos3, topic.indexOf("/", pos3))).toInt();
    Log.notice(F("Unit: %d" CR), valueUNIT);
  }
  int pos4 = topic.lastIndexOf(RF2groupKey);
  if (pos4 != -1) {
    pos4 = pos4 + strlen(RF2groupKey);
    valueGROUP = (topic.substring(pos4, pos4 + 1)).toInt();
    Log.notice(F("RF2 Group: %d" CR), valueGROUP);
  }
  int pos5 = topic.lastIndexOf(RF2dimKey);
  if (pos5 != -1) {
    isDimCommand = true;
    valueDIM = atoi(datacallback);
    Log.notice(F("RF2 Dim: %d" CR), valueDIM);
  }

  if ((topic == subjectMQTTtoRF2) || (valueCODE != 0) || (valueUNIT != -1) || (valuePERIOD != 0)) {
    Log.trace(F("MQTTtoRF2" CR));
    if (valueCODE == 0)
      valueCODE = 8233378;
    if (valueUNIT == -1)
      valueUNIT = 0;
    if (valuePERIOD == 0)
      valuePERIOD = 272;
    NewRemoteReceiver::disable();
    Log.trace(F("Creating transmitter" CR));
    NewRemoteTransmitter transmitter(valueCODE, RF_EMITTER_GPIO, valuePERIOD, RF2_EMITTER_REPEAT);
    Log.trace(F("Sending data" CR));
    if (valueGROUP) {
      if (isDimCommand) {
        transmitter.sendGroupDim(valueDIM);
      } else {
        transmitter.sendGroup(boolSWITCHTYPE);
      }
    } else {
      if (isDimCommand) {
        transmitter.sendDim(valueUNIT, valueDIM);
      } else {
        transmitter.sendUnit(valueUNIT, boolSWITCHTYPE);
      }
    }
    Log.trace(F("Data sent" CR));
    NewRemoteReceiver::enable();

    // Publish state change back to MQTT
    String MQTTAddress;
    String MQTTperiod;
    String MQTTunit;
    String MQTTgroupBit;
    String MQTTswitchType;
    String MQTTdimLevel;

    MQTTAddress = String(valueCODE);
    MQTTperiod = String(valuePERIOD);
    MQTTunit = String(valueUNIT);
    MQTTgroupBit = String(rf2rd.groupBit);
    MQTTswitchType = String(boolSWITCHTYPE);
    MQTTdimLevel = String(valueDIM);
    String MQTTRF2string;
    Log.trace(F("Adv data MQTTtoRF2 push state via RF2toMQTT" CR));
    if (isDimCommand) {
      MQTTRF2string = subjectRF2toMQTT + String("/") + RF2codeKey + MQTTAddress + String("/") + RF2unitKey + MQTTunit + String("/") + RF2groupKey + MQTTgroupBit + String("/") + RF2dimKey + String("/") + RF2periodKey + MQTTperiod;
      pub((char*)MQTTRF2string.c_str(), (char*)MQTTdimLevel.c_str());
    } else {
      MQTTRF2string = subjectRF2toMQTT + String("/") + RF2codeKey + MQTTAddress + String("/") + RF2unitKey + MQTTunit + String("/") + RF2groupKey + MQTTgroupBit + String("/") + RF2periodKey + MQTTperiod;
      pub((char*)MQTTRF2string.c_str(), (char*)MQTTswitchType.c_str());
    }
  }
#    ifdef ZradioCC1101
  ELECHOUSE_cc1101.SetRx(receiveMhz); // set Receive on
  NewRemoteReceiver::enable();
#    endif
}
#  endif

#  ifdef jsonReceiving
void MQTTtoRF2(char* topicOri, JsonObject& RF2data) { // json object decoding

  if (cmpToMainTopic(topicOri, subjectMQTTtoRF2)) {
    Log.trace(F("MQTTtoRF2 json" CR));
    int boolSWITCHTYPE = RF2data["switchType"] | 99;
    bool success = false;
    if (boolSWITCHTYPE != 99) {
#    ifdef ZradioCC1101
      NewRemoteReceiver::disable();
      ELECHOUSE_cc1101.SetTx(CC1101_FREQUENCY); // set Transmit on
#    endif
      Log.trace(F("MQTTtoRF2 switch type ok" CR));
      bool isDimCommand = boolSWITCHTYPE == 2;
      unsigned long valueCODE = RF2data["address"];
      int valueUNIT = RF2data["unit"] | -1;
      int valuePERIOD = RF2data["period"];
      int valueGROUP = RF2data["group"];
      int valueDIM = RF2data["dim"] | -1;
      if ((valueCODE != 0) || (valueUNIT != -1) || (valuePERIOD != 0)) {
        Log.trace(F("MQTTtoRF2" CR));
        if (valueCODE == 0)
          valueCODE = 8233378;
        if (valueUNIT == -1)
          valueUNIT = 0;
        if (valuePERIOD == 0)
          valuePERIOD = 272;
        NewRemoteReceiver::disable();
        NewRemoteTransmitter transmitter(valueCODE, RF_EMITTER_GPIO, valuePERIOD, RF2_EMITTER_REPEAT);
        Log.trace(F("Sending" CR));
        if (valueGROUP) {
          if (isDimCommand) {
            transmitter.sendGroupDim(valueDIM);
          } else {
            transmitter.sendGroup(boolSWITCHTYPE);
          }
        } else {
          if (isDimCommand) {
            transmitter.sendDim(valueUNIT, valueDIM);
          } else {
            transmitter.sendUnit(valueUNIT, boolSWITCHTYPE);
          }
        }
        Log.notice(F("MQTTtoRF2 OK" CR));
        NewRemoteReceiver::enable();

        success = true;
      }
    }
    if (RF2data.containsKey("active")) {
      Log.trace(F("RF2 active:" CR));
      activeReceiver = ACTIVE_RF2;
      success = true;
    }
#    ifdef ZradioCC1101 // set Receive on and Transmitt off
    float tempMhz = RF2data["mhz"];
    if (RF2data.containsKey("mhz") && validFrequency(tempMhz)) {
      receiveMhz = tempMhz;
      Log.notice(F("Receive mhz: %F" CR), receiveMhz);
      success = true;
    }
#    endif
    if (success) {
      pub(subjectGTWRF2toMQTT, RF2data); // we acknowledge the sending by publishing the value to an acknowledgement topic, for the moment even if it is a signal repetition we acknowledge also
    } else {
#    ifndef ARDUINO_AVR_UNO // Space issues with the UNO
      pub(subjectGTWRF2toMQTT, "{\"Status\": \"Error\"}"); // Fail feedback
#    endif
      Log.error(F("MQTTtoRF2 failed json read" CR));
    }
    enableActiveReceiver(false);
  }
}
#  endif

void disableRF2Receive() {
  Log.trace(F("disableRF2Receive" CR));
  NewRemoteReceiver::deinit();
  NewRemoteReceiver::init(-1, 2, rf2Callback); // mark _interupt with -1
  NewRemoteReceiver::deinit();
}

void enableRF2Receive() {
#  ifdef ZradioCC1101
  Log.notice(F("Switching to RF2 Receiver: %F" CR), receiveMhz);
#  else
  Log.notice(F("Switching to RF2 Receiver" CR));
#  endif
#  ifdef ZgatewayPilight
  disablePilightReceive();
#  endif
#  ifdef ZgatewayRTL_433
  disableRTLreceive();
#  endif
#  ifdef ZgatewayRF
  disableRFReceive();
#  endif

  NewRemoteReceiver::init(RF_RECEIVER_GPIO, 2, rf2Callback);
#  ifdef ZradioCC1101
  ELECHOUSE_cc1101.SetRx(receiveMhz); // set Receive on
#  endif
}

#endif

I would advise taking the library links/version from the platformio.ini, for newremoteswitch:

So an update on where I am in this RF Fan process. I do have the basics setup and functioning, being able to transmit and receive. All my entities are working in HA (i.e. fan speed, light, power on/off). I’ve started building out my automations and in doing so realized I never tested the RF range.

My setup is a D-Sun CC1101 with an antenna connected to a Wemos D1 Mini. But the receiving range I am getting is maybe 5 or 6 feet line of sight. I’ve tried holding it up high, different receive pins on the Wemos, even a second D-Sun CC1101 just to make sure it wasn’t the board, I even tried an ESP32 Dev Board. Nothing improved my reception and I am hoping somebody could five me some suggestions. Note the transmission range does appear to be greater at about 10 plus feet through the wall into the next room where the fan resides.

1 Like

So to answer my own question, the antenna provided with the unit doesn’t seem to support 304mhz too well. I attached a traditional collapsible antenna to the SMA connector. Now I can walk all around the house picking up and transmitting signals to the fan.

Looks like I jumped the gun on claiming victory. It now is exhibiting the same behavior as before with the new antenna. The only thing I did was unplug the module for a few days until I had time to come back to it and tinker.

Did you ever find a fix for the range issue?

I have not solved the reception range issue. Even with a 1/4 wavelength antenna the reception is, at best, 5 feet from the remote. Transmission does not seem to be an issue. I’ve even experimented with one of those Bond Bridges but found out they only transmit (they do make a pro version now which looks like it is two way but costs $350) but they can learn remotes and transmit throughout a whole house. My next attempt will probably be a combination of an RTL-SDR and either the Bond or the cc1101 as just a transmitter.

Just to follow up on this real quick. I ordered 2x cc1101 boards. The first one was having issues with range; anything more than a few inches wouldn’t work.

In the process of doing some troubleshooting, i got the other one out and noticed much greater range. I’ve got this one working from 15 feet away. Haven’t tried to max out the range, but it’s def more than a few inches.

1 Like

I’ve tried two different boards but I can’t remember where I got them from. May I ask where you ordered yours from?

I got them off of amazon, this was going to be nothing more of a “hmm, wonder if this will work”, but now i’m off to bringing this fan into the 21st century.

https://www.amazon.com/dp/B01DS1WUEQ?psc=1&ref=ppx_yo2ov_dt_b_product_details

So a bit of an update. I started suspecting I may have fried something in the circuitry with the boards I had. Running them on 5V instead of 3V. I know dumb. Even though they transmitted fine the reception has never been good. So I bought new boards from the link below and a 9 inch quarter wave antenna meant for 315Mhz. So far the results are better from the tests I have done. The remotes are getting picked up from multiple rooms in the house with the unit in a central location. I am still working on the coding as it is quite difficult to ensure the states remain in sync when you are talking about using an RF remote, the HA iOS app, and a wall switch hooked to a Shelly 2.5.

Module: https://www.aliexpress.us/item/2251832619329110.html?spm=a2g0o.order_list.order_list_main.11.604f1802qON9eJ&gatewayAdapt=glo2usa&_randl_shipto=US

Antenna: https://www.aliexpress.us/item/3256802985171233.html?spm=a2g0o.order_list.order_list_main.5.604f1802qON9eJ&gatewayAdapt=glo2usa&_randl_shipto=US

I’m trying to get this up and running and seem to have the same model, if not a very similar model, remote that you have. How did you end up getting this to work? With the pin-out diagram from SmartRC the serial monitor stops working and uploads do as well so I’m flying blind as to what’s going on with the ESP8266 module. I can’t get the receive or send demos working and I’m completely at a loss. The only modicum of success I’ve had so far is when I boot up the module with it wired up to the CC1101 I get an “online” message from home/hamptonbay/status when listening for all MQTT messages in Home Assistant…I would love to hear how you were able to get this up and running as you may be my only hope lol

I’m working with a Wemoss D1 Mini V4.0, and my remote’s model number is TR220A.