Control a Heat & Glo fireplace via RF remote

Heat & Glo RF Transmitter

ESPHome has everything you need to emulate your Heat & Glo RF fireplace remote with a very simple hardware setup - 1 pin on an ESP to a $2 433Mhz transmitter.
The hard part was figuring out the codes and the timings - so here you are!

controls

What you need

I got 5 sets of xmit/receivers with antennas for $10 from Amazon.

Wiring

Wiring is dead simple


-’ to GND
+’ to 3.3v
DAT’ to a GPIO pin on your ESP; I used GPIO12 on an ESP32.
EN’ not connected
TUO’ is “OUT” printed backward; solder the short antenna here. This works 30’ from my fireplace.

You don’t need to use the receivers for this project at all, but if you want to hook one up to decode other RF remotes, it also just takes 1 GPIO pin. (I used GPIO13.)
Receiver on left, transmitter on right.

Code

Once you hook this up, all you need is the appropriate ESPHome configuration yaml. I have one significant lambda in a script that calls remote_transmitter.transmit_raw to actually send out the various signals.

remote_receiver:
  pin: GPIO13
  # Uncomment below to view signals in debug logs 
  #dump:
  #  - raw
  tolerance: 50%
  filter: 250us
  idle: 80ms
  buffer_size: 2kb

remote_transmitter:
  pin: GPIO12
  # RF uses a 100% carrier signal
  carrier_duty_percent: 100%

# Heat and Glo RF fireplace remote 2166-330 for RC300
# https://www.amazon.com/Universal-Fireplace-2166-330-Replacement-Transmitter/dp/B0DKTJ5KSK
# Thanks to initial hints from https://github.com/milksteakmatt/Ardufuego/blob/1d156b15717538e092160b09a3677fffbb59ce37/libraries/FireplaceRF/FireplaceRF.h
  
# 4-bit header pattern, then 6 bytes of data:
#	F4E133015EA1	on
#	F4E133025FA0	off
#	F4E133318E71	flame1
#	F4E133328F70	flame2
#	F4E13333906F	flame3
#	F4E13334916E	flame4
#	F4E13335926D	flame5
#	F4E133409D62	fan0
#	F4E133419E61	fan1
#	F4E133429F60	fan2
#	F4E13343A05F	fan3
#	F4E13350AD52	light 0
#	F4E13351AE51	light 1
#	F4E13352AF50	light 2
#	F4E13353B04F	light 3
#	F4E13354B14E	light 4
#	F4E13361BE41	aux off

number:
  - platform: template
    name: Fan Level
    icon: mdi:fan
    min_value: 0
    max_value: 3
    step: 1
    optimistic: True
    restore_value: True
    set_action:
      - script.execute:
          id: decode
          code: !lambda |-
            std::string codes[]={"409D62","419E61","429F60","43A05F"}; 
            return codes[(int)x];

  - platform: template
    name: Flame Level
    icon: mdi:fire
    min_value: 1
    max_value: 5
    step: 1
    optimistic: True
    restore_value: True
    set_action:
      - script.execute:
          id: decode
          code: !lambda |-
            std::string codes[]={"empty","318E71","328F70","33906F","34916E","35926D"};
            return codes[(int)x];

  - platform: template
    name: Light Level
    icon: mdi:candelabra-fire
    min_value: 0
    max_value: 3
    step: 1
    optimistic: True
    restore_value: True
    set_action:
      - script.execute:
          id: decode
          code: !lambda  |-
            std::string codes[]={"50AD52","51AE51","52AF50","53B04F"};
            return codes[(int)x];

switch:
  - platform: template
    name: Fireplace Switch
    id: fireplace
    icon: mdi:fireplace
    optimistic: True
    restore_mode: DISABLED
    device_class: switch
    turn_on_action: 
      then:
        - script.execute:
            id: decode
            code: "015EA1"
    turn_off_action: 
      then:
        - script.execute:
            id: decode
            code: "025FA0"

button:
  - platform: template
    name: Fireplace On
    icon: mdi:fireplace
    on_press:
      - switch.turn_on: fireplace
  - platform: template
    name: Fireplace Off
    icon: mdi:fireplace-off
    on_press:
      - switch.turn_off: fireplace

script:
  - id: decode
    mode: single
    parameters:
      code: string
    then:
      - remote_transmitter.transmit_raw:
          repeat:
            times: 11
            wait_time: 92ms
          code: !lambda |-
            // Add common prefix
            std::string fullcode = "F4E133" + code;
            std::vector<int> rawcodes;
            // 4-bit header with distinct off times
            std::vector<int> header = {900,-460,380,-460,900,-460,380,-1280};
            for (int num : header) {
              rawcodes.push_back(num);
            }
            // Decode hex to raw: bytes to bits to rawcode
            // 1=900, 0=380, end-of-bit=-680 EndOfByte=-1500
            for (size_t i = 0; i < fullcode.length(); i += 2) {
              std::string byteString = fullcode.substr(i, 2);
              char byteChar = (char)stoi(byteString, nullptr, 16);
              for (int j = 7; j >= 0; --j) {
                char bit = ((byteChar >> j) & 1);
                if (bit == 1) {
                  rawcodes.push_back(900);
                } else {
                  rawcodes.push_back(380);
                }
                if (j)
                  rawcodes.push_back(-680);
              }
              rawcodes.push_back(-1500);
            }
            ESP_LOGD("fireplace", "hexcode %s, opcode count %d", fullcode.c_str(), rawcodes.size());
            std::string csv;
            for (int num : rawcodes) {
              csv += std::to_string(num) + " ";    
            }
            ESP_LOGD("fireplace", "opcodes: %s", csv.c_str());
            return rawcodes;
2 Likes

Wow - this is perfect timing for a project that I’m working on. (adding a remote control to an older millivolt Heat and Glo fireplace)

Can this be used to repurpose the Heat and Glo remote (RC300) for other purposes?

For example, I’d like to use the remote control to trigger a Shelly relay to turn on/off my older fireplace.

I’d like to use the remote control to trigger

I’m using my above setup to send the signals to the fireplace (transmit) from HA, which is actually pretty simple. To do what you want, you’ll need HA to receive the signals. So you would need to use the RF receiver module, but then develop enough code in ESPHome to recognize and decode the signals. It can be done, but will take some effort. Here’s an example for my doorbell (not fireplace), could probably be adapted.

remote_receiver:
  pin: GPIO13
  dump:
    - raw
  tolerance: 50%
  filter: 50us
  # for heat & glo, I know my retransmit spacing is 92ms, so idle: 80ms
  # for doorbell, 20.6ms
  idle: 10ms
  buffer_size: 2kb # only for ESP8266
  on_raw:
    then:
      - lambda: |-
          std::vector<int> bell = {-2,1,-6,1,-4,1,-7,1,-6,1,-5,1,-3,1,-4,1,-7,1,-6,1,-3,1,-4,1,-5,1,-6,1,-5,1,-4,1,-5,1,-26};
          int cnt = x.size();
          if (x[cnt-1] != -20000)
            return;
          ESP_LOGD("doorbell", " %d codes, last %d", cnt, x[cnt-1]);
          if (cnt < bell.size())
            return;
          int skips = cnt - bell.size(); 
          std::vector<int> vunit;
          std::string csu;
          for (int num : x) {
            int uval = (int)((float)num/770.0 + ((num < 0) ? -0.5 : 0.5));
            vunit.push_back(uval);
            csu += std::to_string(uval) + ",";  
          }
          ESP_LOGD("doorbell", " %d ucodes: %s", x.size(), csu.c_str());
          for (int i = 0; i < bell.size(); i++) {
            if (bell[i] != vunit[i + skips]) {
              ESP_LOGD("doorbell", "mismatch at %d (%d): %d (%d)", i, i + skips, vunit[i + skips], bell[i]);
              return;
            }
          }
          ESP_LOGD("doorbell", "ring detected!");
          return;

Great, thanks a bunch. I’ve decoded another remote control I had using Universal Radio Hacker so at least I’ve made some progress. The issue with this other remote is that it’s 350 MHz (but my 433 MHz antennas seem to work OK… not sure about distance though)