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!
What you need
- Fireplace controlled with Heat N Glo RC300 (Heat N Glo, Heatilator and Quadra-Fire Gas Units)
- ESP with 1 GPIO pin. An ESP-01 should do.
- 433Mhz transmitter.
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%
non_blocking: true
# 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
# Data format:
# 3-byte header pattern, 1 byte command, 1 byte command+offset, 1 byte cmd+off inverted
# bytes 1-3 header, first two bytes and offset (see below) vary by remote. Some examples:
# A: F4E133 offset 5D
# B: 513E33 offset 17
# C: C4B133 offset FD
# byte 4: command
# on: 0x01
# off: 0x02
# flame: 0x31-0x35
# fan: 0x40-0x43
# light: 0x50-0x54
# aux on: 0x60
# aux off: 0x61
# byte 5: (command + offset) % 256
# The same command as byte 4, plus an offset.
# The offset varies by remote, see below.
# bytes 6: bitwise inverse of byte 5
# See https://github.com/wQQhce2g93Ei/Ardufuego for details on figuring out your values for the header. (Note offset there is off by one from my definition.)
# Capturing the header is sufficient:
# byte1 = seed1
# byte2 = seed2
# byte3 = 0x33
# offset = (seed1 + seed2 + 0x88) % 0xFF
substitutions:
cmd_header: 0xF4E133
cmd_offset: 0x5D
debug: 0
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: sendcommand
command: 0x01
turn_off_action:
then:
- script.execute:
id: sendcommand
command: 0x02
number:
- 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: sendcommand
command: !lambda return 0x30 + (int)x;
- 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: sendcommand
command: !lambda return 0x40 + (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: sendcommand
command: !lambda return 0x50 + (int)x;
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: sendcommand
mode: single
parameters:
command: int
then:
- remote_transmitter.transmit_raw:
repeat:
times: 11
wait_time: 92ms
code: !lambda |-
// Compose full command sequence
long int cmd_header = ${cmd_header};
long int cmd_offset = ${cmd_offset};
std::vector<long int> fullcmd;
fullcmd.push_back((cmd_header & 0xFF0000) >> 16);
fullcmd.push_back((cmd_header & 0xFF00) >> 8);
fullcmd.push_back((cmd_header & 0xFF));
fullcmd.push_back(command);
int cmd2 = (command + cmd_offset) & 0xFF;
fullcmd.push_back(cmd2);
fullcmd.push_back(~cmd2 & 0xFF);
// Debug print
if (${debug}) {
ESP_LOGD("fireplace", "header %x cmd %02x %02x %02x", cmd_header, command, cmd2, ~cmd2 & 0xFF);
std::string hexcode;
for (auto byte : fullcmd) {
char hex[3];
sprintf(hex, "%02X", byte);
hexcode += hex;
}
ESP_LOGD("fireplace", "command %s", hexcode.c_str());
}
// Convert to radio output pulse sequence
std::vector<long int> rawcodes;
// 4-bit header with distinct off times
std::vector<long 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 (auto byteChar : fullcmd) {
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);
}
// Debug print
if (${debug}) {
std::string csv;
for (int num : rawcodes) {
csv += std::to_string(num) + ",";
}
ESP_LOGD("fireplace", "rawcodes [%d]: %s", rawcodes.size(), csv.c_str());
}
return rawcodes;
Edit 2025-12-23
Some commentors in this thread noted that there are some differences between remote codes. Thanks to @Crss for his code; I have now added an easy way to change the values if you need to in the substitutions section of the yaml. Finding your particular settings unfortunately requires sampling the raw signals from your remote. Youâll need a 433Mhz receiver setup for that. Some good info can be found here and here, and in comments in this thread.



