Hampton Bay Fan RF/MQTT bridge

I just came accross this searching! I just had to take apart my Hampton Bay fan remote to clean the buttons because some soda got spilled on it and they were sticky. But saw on the circuit board, its so simple, just the UC, a PIN selector and buttons with the RF antenea built into the PCB… Started to think about how to add an ESP32 to this…

While doing some research on the RF 303.9Mhz frequency that the fan uses, I came accross yoiur post and GIThub with all the info already done! LOL. Thank you for this!!! The GITHUB you posted shows the 303.9, but then the RF module in the requirements is a 315-900Mhz and seems like it mainly operates at 433Mhz. your code is for ESP8266, but I’ll just update it to port over for an ESP32… OR I just use up some of my old 8266 boards… But if this works, I’ll design my own PCB’s to plug and play all my fans (6) of them in each room. Since they are wifi, I just need to 3D print a box and run power to them. I may add a MMwave sensor if possible and temp sensor to it too, so I can automate to turn fan on to xx speed if room gets hotter then xx degrees and a the room is occupied. Awesome work on the feedback to be able to verify the fan setting actually set!

Did that board end up working for the RF? 10’ to the next room, was that the max you got, or was only able to test? I would like to build a single device for my house and put the sensor in a single location and then control each room via the PIN selection on the control. I have a 3800sqft house with 6 of the same Hampton Bay 303.9Mhz ceiling fans that are remote.

Is there any plans to update this to the new MQTT format in HA?

It looks like some of the properties are no longer available.

1 Like

Hello,

The Hampton Bay fans in my home use a remote with model number “HD3”. Looking up the FCCID: “2AAZPHD3” on this website:https://fccid.io/, it shows the frequency is 304.25MHz, which matched what I was seeing with my RTL-SDR using universal radio hacker.

I have been able to decipher the following protocol, and have it working on esphome using dbuezas’ cc1101 template: GitHub - dbuezas/esphome-cc1101

Below is the protocol for the Hampton Bay fans using the HD3 remote using esphome. I send 2 input_select variables to esphome from home assistant, device and command.

api:
  encryption:
    key: "key"
  services:
    - service: transmit
      variables: 
        command: string
        device: string
      then:
        - logger.log: "running script"
        - script.execute:
            id: transmit
            device: !lambda return device;
            command: !lambda return command;

script:
  - id: transmit
    mode: queued
    parameters:
      command: string
      device: string
    then:
      - if:
          condition:
            lambda: 'return ( device.substr(device.size() - 3) == "fan" );'            
          then:
            - lambda: get_cc1101(transceiver).setFreq(304.25);
            - lambda: get_cc1101(transceiver).beginTransmission();
            - remote_transmitter.transmit_rc_switch_raw:
                repeat:
                  times: 5
                  wait_time: 8.0ms

                code: !lambda |-
                        std::map<std::string, std::string> devices, commands;
                        devices["room1_fan"] = "0110";
                        devices["room2_fan"] = "1100";
                        devices["room3_fan"] = "0111";
                        devices["room4_fan"] = "0000";
                        devices["room5_fan"] = "1111";
                        commands["fan_high"] = "11111110";
                        commands["fan_medium"] = "11111101";
                        commands["fan_low"] = "11111100";
                        commands["fan_off"] = "11111111";
                        commands["off"] = "01100100";
                        commands["on"] =  "01100101";
                        commands["light_toggle"] = "11100100";
                        return ( "1000000000000" + id(devices[device]) + id(commands[command]) );

                protocol: 
                  pulse_length: 375
                  sync: [0,0]
                  zero: [2,1]
                  one: [1,2]
                  inverted: True
            - lambda: get_cc1101(transceiver).endTransmission();

#  Code breakdown:
#    - 13-bit preamble + 4-bit dip settings + 8-bit command
#      -  preamble:  1000000000000  (1x "1"-bit + 12x "0"-bits)
#      - dip settings - as seen in the remote - NOT inverted NOT reversed 
#      - 8-bit commands: 
#         - power on (light on, fan high): 01100101
#         - power off (light off, fan off): 01100100; 
#         - toggle light: 11100100
#         - fan high: 11111110
#         - fan medium 11111101
#         - fan low 11111100
#         - fan off 11111111; 

Again, a reminder if you have an HD3 remote, the frequency is 304.25MHz.

Also, I can control all 5 fans in my house with a single transmitter in the master bedroom - so it’s reaching about 40 ft with no issues - granted my home is only constructed with single wall wood.

I also have the same transmitter controlling my 433.83MHz string lights outside - with the
- lambda: get_cc1101(transceiver).setFreq(xxx.xx);
command prior to transmitting any signal. The same antenna handles both frequencies pretty well.

Hey,

I have the same remote, HD3, 2AAZPDH3 and 304.25MHz. I am trying to go through the process of going through capturing it with URH. Never done this before so I might just be missing something but I am not seeing the same patterns.

What I have captured for pressing the power button is:

11000100010001000100010001000100010001000100010001000101100010110001011011000100010110001011 [Pause: 8082 samples]
11000100010001000100010001000100010001000100010001000101100010110001011011000100010110001011 [Pause: 8083 samples]
1100010001000100010001000100010001000100010001000100010110001011000101101100010001011001011 [Pause: 8081 samples]
1100010001000100010001000100100010001000100010001000101100010110001011011000100010110001011 [Pause: 8078 samples]
110001000100010010001000100010001000100010001000100010110010110001011011000100010110001011 [Pause: 8081 samples]
11000100010001000100010001000100010001000100010001000101100010110001011011000100010110001011 [Pause: 8080 samples]
11000100010001000100010001000100010001000100010001000101100010110001011011000100010110001011 [Pause: 8078 samples]
11000100010001000100010001000100010001000100010001000101100010110001011011000100010110001011 [Pause: 8080 samples]
11000100010001000100010001000100010001000100010001000101100010110001011011000100010110001011 [Pause: 8079 samples]
11000100010001000100010001000100010001000100010001000101100010110001011011000100010110001011 [Pause: 845449 samples]

I’m don’t think I am seeing the 13 bits of preamble, I can find my dip setting of 0101, but looking before and after it don’t line up.

Any advice on decoding this?

the data is there.
if you define the ‘one bit’ as ‘011’ and the ‘zero bit’ as ‘0001’ and realize that there is actually a 0 before the first 11 that doesn’t show up, then this signal;

11000100010001000100010001000100010001000100010001000101100010110001011011000100010110001011

Translates to the following data bits:
1000000000000010101100101

So preamble is: 100000000000
Then the dip pin is 0101
And the command is 01100101, which is power on.

The only difference between your result and mine is your zero bit has 4 characters where mine was three “001”
Try leaving sample rate as default in URH.

2 Likes

I was wanting to get this to work, but I dont understand how the MQTT FAN changes work and so the original script will not work when setup of the config.yaml file from it. Have you got this working in HA??

Can you share the whole esp code to see how you control the buttons?
I have a similar fan but the light is dimmeable and there is a code for each light status, but i cant find the pattern yet

Received RCSwitch Raw: protocol=6 data=‘000000000000110001100’ only light at 96%
Received RCSwitch Raw: protocol=6 data=‘000000000000110101101’ with fan high
Received RCSwitch Raw: protocol=6 data=‘000000000000111001110’ with fan med
Received RCSwitch Raw: protocol=6 data=‘000000000000111101111’ with fan low

Received RCSwitch Raw: protocol=6 data=‘000000000110010001010’ only light at 50%
Received RCSwitch Raw: protocol=6 data=‘000000000110010101011’ with fan high
Received RCSwitch Raw: protocol=6 data=‘000000000110011001100’ with fan med
Received RCSwitch Raw: protocol=6 data=‘000000000110011101101’ with fan low

my esp code:

esphome:
  name: d1-rf
  friendly_name: D1 RF

  includes:
    - cc1101.h
  libraries:
    - SPI
    - "SmartRC-CC1101-Driver-Lib"

esp8266:
  board: d1_mini

# Enable logging

logger:

# Enable Home Assistant API

api:

ota:

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  fast_connect: true
  power_save_mode: HIGH

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:

    ssid: "D1-Rf Fallback Hotspot"
    password: "0dsO0HAYrNTw"

captive_portal:

sensor:
  - platform: custom
    lambda: |-
      auto my_sensor = new CC1101(
        D5, // SCK
        D6, // MISO
        D7, // MOSI
        D3, // CSN
        D1, // GDO0
        96.96, // bandwidth_in_khz
        303.90 // freq_in_mhz
      );
      App.register_component(my_sensor);
      return {my_sensor};
    sensors:
      id: transciver_1
      name: "RSSI"
      unit_of_measurement: dBm
      entity_category: diagnostic
  # you can have multiple transcivers in the same board
  # with more than 2, an esp8266 will get out of RAM. You'll need to remove the logger and reduce the recevier buffer size to 200
number:
  - platform: template
    max_value: 812
    min_value: 450
    step: 1
    mode: slider
    optimistic: true
    unit_of_measurement: "kHz"
    name: BW
    on_value:
      then:
        - lambda: get_cc1101(transciver_1).setBW(x);
  - platform: template
    min_value: 304.2
    max_value: 348
    #min_value: 378
    #max_value: 464
    # min_value: 799
    # max_value: 928
    step: .001
    mode: box
    optimistic: true
    unit_of_measurement: "MHz"
    name: FREQ
    on_value:
      then:
        - lambda: get_cc1101(transciver_1).setFreq(x);
switch:
  - platform: template
    name: "RSSI"
    entity_category: diagnostic
    lambda: return get_cc1101(transciver_1).rssi_on;
    turn_on_action:
      lambda: get_cc1101(transciver_1).rssi_on = true;
    turn_off_action:
      lambda: get_cc1101(transciver_1).rssi_on = false;
button:
  - platform: template
    name: Apaga Todo
    on_press:
      - lambda: get_cc1101(transciver_1).beginTransmission();
      - remote_transmitter.transmit_rc_switch_raw:
          code: '000000000000000000000'
          protocol: 6
          repeat:
            times: 3
            wait_time: 0s
      - lambda: get_cc1101(transciver_1).endTransmission();
  - platform: template
    name: Fan Off
    on_press:
      - lambda: get_cc1101(transciver_1).beginTransmission();
      - remote_transmitter.transmit_rc_switch_raw:
          code: '000000000110000000110'
          protocol: 6
          repeat:
            times: 3
            wait_time: 0s
      - lambda: get_cc1101(transciver_1).endTransmission();
  - platform: template
    name: Fan Low
    on_press:
      - lambda: get_cc1101(transciver_1).beginTransmission();
      - remote_transmitter.transmit_rc_switch_raw:
          code: '000000000110001101001'
          protocol: 6
          repeat:
            times: 3
            wait_time: 0s
      - lambda: get_cc1101(transciver_1).endTransmission();
  - platform: template
    name: Fan Med
    on_press:
      - lambda: get_cc1101(transciver_1).beginTransmission();
      - remote_transmitter.transmit_rc_switch_raw:
          code: '000000000110001001000'
          protocol: 6
          repeat:
            times: 3
            wait_time: 0s
      - lambda: get_cc1101(transciver_1).endTransmission();
  - platform: template
    name: Fan High
    on_press:
      - lambda: get_cc1101(transciver_1).beginTransmission();
      - remote_transmitter.transmit_rc_switch_raw:
          code: '000000000110000100111'
          protocol: 6
          repeat:
            times: 3
            wait_time: 0s
      - lambda: get_cc1101(transciver_1).endTransmission();
remote_transmitter:
  - pin: D1 # This is GDO0
    carrier_duty_percent: 100%
remote_receiver:
  - pin: D1 # This is GDO0
      # on the esp8266 use any of D1,D2,D5,D6,D7,Rx
      # Don't use D3,D4,D8,TX, boot often fails.
      # Can't be D0 or GPIO17 b/c no interrupts
    dump:
      - rc_switch
    # Settings to optimize recognition of RF devices
    tolerance: 50%
    filter: 250us
    idle: 4ms
    buffer_size: 2kb

@jbrande
Not sure whose yaml you wanted, but I added a bit more to my post so you can see how I tackled the issue.
I didn’t use buttons. I defined a service in esphome which is callable from inside HA with the parameters of ‘device’ and ‘command’. I then just call that service either in automations or with the UI.

@jbrande
Another note - with your receiver tolerance set at 50%, there is a very good chance your remote is not rc_switch protocol 6.
I recommend buying an rtl-sdr to analyze the signal before trying to duplicate it with a cc1101. It’ll save a lot of guesswork.

Just wanted to say thanks, this post pointed me in the right direction, and I finally got it working. SDR + URH + ESPHOME + dbuezas’ cc1101 template is the way. URH is really cool.

I made a more analog version of this. By means of slaving the remote to an ESP32 and adding a light sensor for the light sync check.



substitutions:
  device_name: 'mbr-fan-remote'
  friendly_name: mbr-fan-remote
  device_comment: "located in the bedroom"
  entity_prefix: "MBR"

packages:
  core: !include _config/core.yaml

esphome:
  name: mbr-fan-remote
  friendly_name: mbr-fan-remote
  on_boot:
    priority: 800
    then:
      - script.execute: check_light_sync

      
esp32:
  board: esp32dev
  framework:
    type: arduino

api:
  encryption:
    key: "kBt+Rva8i/RSsJNYNx+wnM5MhdGnfX8oEIcK0OcJe7o="

logger:
  level: DEBUG

i2c:
  sda: GPIO16
  scl: GPIO17
  scan: True

sensor:
  - platform: bh1750
    id: light
    device_class: illuminance
    state_class: measurement
    internal: True
    address: 0x23
    update_interval: 500ms
    accuracy_decimals: 1

output:
  - platform: template
    id: fanoutput
    type: float
    write_action:
      - lambda: ""
  - platform: gpio
    pin: 
      number: GPIO25
      inverted: true
      mode: OUTPUT_OPEN_DRAIN
    id: light_out
  - platform: gpio
    pin: 
      number: GPIO14
      inverted: true
      mode: OUTPUT_OPEN_DRAIN
    id: reverse_out
  - platform: gpio
    pin: 
      number: GPIO23
      inverted: true
      mode: OUTPUT_OPEN_DRAIN
    id: fan1_out
  - platform: gpio
    pin: 
      number: GPIO18
      inverted: true
      mode: OUTPUT_OPEN_DRAIN
    id: fan2_out
  - platform: gpio
    pin: 
      number: GPIO19
      inverted: true
      mode: OUTPUT_OPEN_DRAIN
    id: fan3_out
  - platform: gpio
    pin: 
      number: GPIO21
      inverted: true
      mode: OUTPUT_OPEN_DRAIN
    id: fan4_out
  - platform: gpio
    pin: 
      number: GPIO27
      inverted: true
      mode: OUTPUT_OPEN_DRAIN
    id: fan5_out
  - platform: gpio
    pin: 
      number: GPIO26
      inverted: true
      mode: OUTPUT_OPEN_DRAIN
    id: fan6_out
  - platform: gpio
    pin: 
      number: GPIO22
      inverted: true
      mode: OUTPUT_OPEN_DRAIN
    id: fan0_out

switch:
  - platform: template
    id: light_toggle
    name: Fan Light
    optimistic: True
    turn_on_action:
      - button.press: btn_light
      - delay: 1s
      - script.execute: check_light_sync
    turn_off_action:
      - button.press: btn_light
      - delay: 1s
      - script.execute: check_light_sync      

fan:
  - platform: speed
    output: fanoutput
    id: mbr_fan
    name: "Master Bedroom Ceiling Fan"
    speed_count: 6
    on_turn_off:
      - lambda: |-
          id(btn_fan0).press();
    on_speed_set:
      - lambda: |-
          if      (id(mbr_fan).state == 0) { /* Fan is off, do nothing */ }
          else if (id(mbr_fan).speed == 1) { id(btn_fan1).press(); }
          else if (id(mbr_fan).speed == 2) { id(btn_fan2).press(); }
          else if (id(mbr_fan).speed == 3) { id(btn_fan3).press(); }
          else if (id(mbr_fan).speed == 4) { id(btn_fan4).press(); }
          else if (id(mbr_fan).speed == 5) { id(btn_fan5).press(); }
          else if (id(mbr_fan).speed == 6) { id(btn_fan6).press(); }

button:
  - platform: output
    id: btn_light
    internal: False
    output: light_out
    duration: 250ms
  - platform: output
    id: btn_reverse
    name: Switch Direction
    internal: False
    output: reverse_out
    duration: 250ms  
  - platform: output
    id: btn_fan1
    internal: True
    output: fan1_out
    duration: 250ms  
  - platform: output
    id: btn_fan2
    internal: True
    output: fan2_out
    duration: 250ms  
  - platform: output
    id: btn_fan3
    internal: True
    output: fan3_out
    duration: 250ms     
  - platform: output
    id: btn_fan4
    internal: True
    output: fan4_out
    duration: 250ms  
  - platform: output
    id: btn_fan5
    internal: True
    output: fan5_out
    duration: 250ms  
  - platform: output
    id: btn_fan6
    internal: True
    output: fan6_out
    duration: 250ms 
  - platform: output
    id: btn_fan0
    internal: True
    output: fan0_out
    duration: 250ms 

script:
  - id: check_light_sync
    mode: single
    then:
      - if:
          condition:
            switch.is_on: light_toggle
          then:
            if:  
              condition:
                - lambda: 'return id(light).state < 5;'
              then:
                - button.press: btn_light
                - logger.log: "The light was off, turning ON"
                - delay: 2s   
              else:
                - logger.log: "The light was already ON"
                - delay: 2s    
          else:
            if:  
              condition:
                - lambda: 'return id(light).state > 5;'
              then:
                - button.press: btn_light
                - logger.log: "The light was on, turning OFF"
                - delay: 2s   
              else:
                - logger.log: "The light was already OFF"
                - delay: 2s

Hi Ryan, do you have any guide on how to use URH to find the signal modulation? I think I have the same remote type or similar you do by the way. If I hold down the light button it allows me to dim the light on the fan.

Hello @implicit_none , im a complete newbie.
I have 3 hampton bay fans, can you share your esphome with me plesse?
i only see a partial code on this thread.
thanks
Miguel

I tried the below and the fan goes on and off but the speeds are not working. I have posted directly to @owenb321 on Github. Will update this thread – If I get the answer.

  - fan:
      name: "Family Room Fan"
      state_topic: "home/hamptonbay/1101/on/state"
      command_topic: "home/hamptonbay/1101/on/set"
      percentage_state_topic: "home/hamptonbay/1101/percent"
      percentage_command_topic: "home/hamptonbay/1101/percent"
      preset_mode_state_topic: "home/hamptonbay/1101/speed"
      preset_mode_command_topic: "home/hamptonbay/1101/speed"
      preset_modes:
        - low
        - medium
        - high
mqtt:
  - fan:
      name: "Kim Fan"
      state_topic: "home/hamptonbay/1010/on/state"
      command_topic: "home/hamptonbay/1010/on/set"
      preset_mode_command_topic: "home/hamptonbay/1010/speed/state"
      preset_mode_command_template: "home/hamptonbay/1010/speed/set"
      preset_modes:
        - low
        - medium
        - high

@implicit_none is your esp code complete? Thanks for any help you can give.

I get the error

  • remote_transmitter.transmit_rc_switch_raw:
    Couldn’t find any component that can be used for ‘remote_base::RemoteTransmitterBase’. Are you missing a hub declaration?.
# https://github.com/dbuezas/esphome-cc1101
esphome:
  name: esp-kim-fan
  friendly_name: esp-kim-fan
  includes:
    - cc1101.h
  libraries:
    - SPI
    - "SmartRC-CC1101-Driver-Lib"

esp8266:
  board: d1_mini

#esp32:
#  board: esp32dev

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  fast_connect: true
  power_save_mode: HIGH

logger:
  level: VERBOSE
api:
  encryption:
    key: "gp2Xhidden"
  services:
    - service: transmit
      variables: 
        command: string
        device: string
      then:
        - logger.log: "running script"
        - script.execute:
            id: transmit
            device: !lambda return device;
            command: !lambda return command;

script:
  - id: transmit
    mode: queued
    parameters:
      command: string
      device: string
    then:
      - if:
          condition:
            lambda: 'return ( device.substr(device.size() - 3) == "fan" );'            
          then:
            - lambda: get_cc1101(transceiver).setFreq(304.25);
            - lambda: get_cc1101(transceiver).beginTransmission();
            - remote_transmitter.transmit_rc_switch_raw:
                repeat:
                  times: 8
                  wait_time: 0ms

                code: !lambda |-
                        std::map<std::string, std::string> devices, commands;
                        devices["room1_fan"] = "0110";
                        devices["room2_fan"] = "1100";
                        devices["room3_fan"] = "0111";
                        devices["room4_fan"] = "0000";
                        devices["room5_fan"] = "1111";
                        commands["fan_high"] = "11111110";
                        commands["fan_medium"] = "11111101";
                        commands["fan_low"] = "11111100";
                        commands["fan_off"] = "11111111";
                        commands["off"] = "01100100";
                        commands["on"] =  "01100101";
                        commands["light_toggle"] = "11100100";
                        return ( "1000000000000" + id(devices[device]) + id(commands[command]) );

                protocol: 
                  pulse_length: 375
                  sync: [0,0]
                  zero: [2,1]
                  one: [1,2]
                  inverted: True
            - lambda: get_cc1101(transceiver).endTransmission();

#  Code breakdown:
#    - 13-bit preamble + 4-bit dip settings + 8-bit command
#      -  preamble:  1000000000000  (1x "1"-bit + 12x "0"-bits)
#      - dip settings - as seen in the remote - NOT inverted NOT reversed 
#      - 8-bit commands: 
#         - power on (light on, fan high): 01100101
#         - power off (light off, fan off): 01100100; 
#         - toggle light: 11100100
#         - fan high: 11111110
#         - fan medium 11111101
#         - fan low 11111100
#         - fan off 11111111;

My journey with this project.

I was intrigued by this project that @owenb321 started. I purchased the transmitters from Ali Express in May of 2023 and just got to starting it off this month. This topic has since evolved and I was estatic to see that I could put this into Esp Home. However the requirements were more and needed a tuner. So I set out and bought a tuner. Then there was the journey on how to learn what was being transmitted. I managed to get rtl_433 going in Virtual Box and I managed to decode high to being the below and same. However I did not know what the protocol was and how many repeats. But I thought I would keep going

[00] {22} 8a 00 98  : 10001010 00000000 100110

Then I started to use @implicit_none code and changed the frequency etc however that did not work for me and I got an error.

At this point having invested days into this project I tried @owenb321 project and figured out how to use Arduino. There was instant sucess! :tada:I could turn the fan on and the light on, control two FAN-9T’s however the speed wasn’t working and the MQTT was giving me an error. Long story short @owenb321 was amazing and he fixed the code. So what works? If you are newbie and have FAN-9T remotes then here are the simple steps.

  1. Purchased this https://a.aliexpress.com/_msdGfSk
  2. Wire up an ESP8266 using this link https://github.com/LSatan/SmartRC-CC1101-Driver-Lib
  3. Open Arduino IDE – File → New
  4. Copy the Ino file into the editor
  5. Change to your WiFi & MQTT settings
  6. In Arduino Sketch → Verify/Compile
  7. After step 6 click on the upload button it is second from the check mark button
  8. Use the below in configuration.yaml
# Example using percentage based speeds with preset modes configuration.yaml
mqtt:
  - fan:
      name: "Family Room Fan"
      state_topic: "home/hamptonbay/1101/on/state"
      command_topic: "home/hamptonbay/1101/on/set"
      percentage_state_topic: "home/hamptonbay/1101/speed/state"
      percentage_value_template: >-
        {% if value == 'off' -%}
        0
        {% elif value == 'low' -%}
        1
        {%- elif value == 'medium' -%}
        2
        {%- else -%}
        3
        {%- endif %}
      percentage_command_topic: "home/hamptonbay/1101/speed/set"
      percentage_command_template: >-
        {% if value | int(default=0) == 0 -%}
        off
        {%- elif value | int(default=0) == 1 -%}
        low
        {%- elif value | int(default=0) == 2 -%}
        medium
        {%- else -%}
        high
        {%- endif %}
      speed_range_max: 3      
      
  - fan:
      name: "Kim Room Fan"
      state_topic: "home/hamptonbay/1010/on/state"
      command_topic: "home/hamptonbay/1010/on/set"
      percentage_state_topic: "home/hamptonbay/1010/speed/state"
      percentage_value_template: >-
        {% if value == 'off' -%}
        0
        {% elif value == 'low' -%}
        1
        {%- elif value == 'medium' -%}
        2
        {%- else -%}
        3
        {%- endif %}
      percentage_command_topic: "home/hamptonbay/1010/speed/set"
      percentage_command_template: >-
        {% if value | int(default=0) == 0 -%}
        off
        {%- elif value | int(default=0) == 1 -%}
        low
        {%- elif value | int(default=0) == 2 -%}
        medium
        {%- else -%}
        high
        {%- endif %}
      speed_range_max: 3         
      
  - light:
      name: "Kim Fan Light"
      command_topic: "home/hamptonbay/1010/light/state"
      state_topic: "home/hamptonbay/1010/light/set"

You should be good to go. If this post gets updated with a usable configuration in esp home then I will update. Hope this helps someone.