QuietCool smart control ESP32

I have a QuietCool fan in my attic (DIY as a whole house fan) that is controlled with the QuietCool Smart Control (model: IT-AF-SMT). It uses Bluetooth and connects to their app (QuietCool Smart Attic Fan Control). The app can control the 2 speed fan, shows the humidity & temperature, and has a few other features. Took the plastic box apart, it uses a Esressif ESP32-WROOM-32. I would like to control it with Home Assistant, but would like to still be able to control it with the original app, in case I sell the house. I am a newbie, but have come up with the following possible options, not too sure how to do all of them, and even if they will work. Any suggestions will be helpful. Thank you.

  1. Use a D1 mini to a relay and wire to the high fan speed and put an external temp/humidity sensor in the attic. Then I could just turn the fans on/off or set up automation for temp.
  2. Somehow have the ESP32 read and controlled through ESPhome, but I think that would involve flashing the chip. Not sure if possible.
  3. Soldering another flashed esp32 to the corresponding spots on the board and then controlling that esp32 with ESPhome. Would have to figure out how to do that.
    Sch2
    Case
    Sch3
2 Likes

I just installed one of these fans in my attic yesterday. Came here hoping someone already found a solution. I’m thinking along the same lines are you as far as being to resell the house and still be able to use the original app. I’m thinking it might not be too difficult to replicate the control circuit on a bread board with a new ESP32 and wire it to the speed control wires instead. That way the original product remains untouched. If I didn’t have more pressing projects to complete I think it might be fun, and a good learning experience.

You don’t have the WiFi module? There are a few ways to control it with WiFi discussed in the forums.

Nevermind. I see what’s going on. The WiFi controller was discontinued.

FYI you don’t need the fan controller to control these. The way they work is there are different wires powered for each speed. I don’t recall exactly but I think my two speed fan has 3 wires. A common and two more for speed. One being low, the other high. You can only power two of the 3 wires at once or you’ll probably blow it up. When I installed mine I debated making my own with relays but I went with the WiFi module.

Any more ideas on how to control via Home Assistant using the standard QuietCool Bluetooth controller?

Would it be possible to use an ESP32 to emulate the iOS app BLE commands and make a “bridge” between Wifi and the QuietCool Bluetooth?

I can see it should be relatively straightforward to replace the QuietCool BLE controller with an ESP32 and relays,… but I worry about safety/reliability as it it will be operating in a fairly hot environment. I trust the QC controller way more.

Any ideas appreciated.

Thanks.

I’m looking for the same thing and just stumbled across this:

https://www.hagensieker.com/wordpress/2021/10/09/flashing-quietcool-afg-smt-pro-2-0-attic-fan-with-tasmota/

I am going to try to make it work with esphome but I don’t have a fan yet. Wanna try this and let me know if I should buy one?

Got it working with esphome!

Here is a little writeup with my code, let me know if you have any questions…

This is specific to the Quiet Cool AFG SMT ES-3.0 with the Smart Controller, board IT-AF-SMT REV:D 2019.11.01 as printed on the board. Other fans will work with this board but I have no way of knowing if other board revisions will work with this config, please double check.

All credit for the interface and pinout goes to these links:
https://www.hagensieker.com/wordpress/2021/10/09/flashing-quietcool-afg-smt-pro-2-0-attic-fan-with-tasmota/

Here is the pinout (from the article linked above):

Plug your FTDI programmer in and pull a backup just in case (this is again from the link above, please click it and give them some ad revenue):

Run this command to find the port number

ls /dev/*

Run this command replacing the XXXXXXX with your port number to pull the backup

esptool.py -b 115200 --port /dev/cu.usbserial-XXXXXXX read_flash 0x000000 0x400000 flash_4M.bin

Once the backup is done head over to ESPHome in your interface and click the new device button at the bottom right. Let it do its thing and flash the base ESPHome config.

If it completes and boots move on. For some reason mine did not boot with esphome, after much troubleshooting I ended up soldering another 10k SMD resistor in parallel on the existing 10k SMD labeled R11 on the board. This effectively reduces it to 5k and pulls the EN pin high faster. This has led to 100% boot reliability so far, here is a picture:

And the moment you have been waiting for, here is my code. A few notes first: All LED’s, buttons, sensors and speeds work properly. I set up an interlock on the motor speeds to ensure only one relay is active at a time. The speed light blinks the appropriate number of times for the current speed, this is handled in the device. There is also a hard coded fire shutoff at 182F matching the stock configuration. I am planning on using NodeRed to make the fan smart, I want to use the outside temp to make it even smarter, you could write some automations in the controller to make it operate without HA connected if you want.

Don’t forget to replace the ota and ap passwords with something custom to you.

esphome:
  name: attic-fan
  on_boot: #Display cascading LED's to show full boot
    priority: 800 #run after full boot
    then:
      - delay: 1s
      - light.turn_on: left_led_light
      - light.turn_on: middle_led_light
      - light.turn_on: right_led_light
      - delay: 1s
      - light.turn_off: right_led_light
      - delay: 0.5s
      - light.turn_off: middle_led_light
      - delay: 0.5s
      - light.turn_off: left_led_light

esp32:
  board: esp32dev
  framework:
    type: arduino

# Enable logging
logger:

# Enable Home Assistant API
api:

web_server:

ota:
  password: "XXXXXX"

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Attic-Fan Fallback Hotspot"
    password: "XXXXX"

captive_portal:

#Default Stock Temp and Humidity Conditions - Summer Profile
#Humidity - Summer - high off 90%
#Humidity - Summer - low on 70%
#Temp - Summer - Low on 80F
#Temp - Summer - Med on 90F
#Temp - Summer - High on 100F
#Fire Protection - Off above 182F

    
#Front Buttons
binary_sensor:
  - platform: gpio
    name: "Button 2 - Pair"
    pin:
      number: 26
      inverted: true
      mode:
        input: true
        pullup: true
  - platform: gpio
    name: "Button 1 - Test"
    pin:
      number: 27
      inverted: true
      mode:
        input: true
        pullup: true
    on_press: #cascading if statements to enable switching though modes with the test button.
      then:
        - if:
            condition:
              and:
                - switch.is_off: Speed1
                - switch.is_off: Speed2
                - switch.is_off: Speed3
            then:
              - switch.turn_on: Speed1
            else:
              - if:
                  condition:
                    - switch.is_on: Speed1
                  then:
                    - switch.turn_on: Speed2
                  else:
                    - if:
                        condition:
                          - switch.is_on: Speed2
                        then:
                          - switch.turn_on: Speed3
                        else:
                          - if:
                              condition:
                                - switch.is_on: Speed3
                              then:
                                - switch.turn_off: Speed3
                

light:
#Note: There are 4 LEDs the far left Red LED is tied directly to power and not controllable.
  - platform: binary
    name: "Right LED - Bluetooth"
    output: right_led
    id: right_led_light
    restore_mode: ALWAYS_OFF
    
  - platform: binary
    name: "Middle LED - Speed"
    output: middle_led
    id: middle_led_light
    restore_mode: ALWAYS_OFF
    effects: #Setup strobe blinking for speed led to indicate current speed
      - strobe:
          name: Speed 1
          colors:
            - state: true
              brightness: 100%
              duration: 150ms
            - state: false
              duration: 1000ms
      - strobe:
          name: Speed 2
          colors:
            - state: true
              brightness: 100%
              duration: 150ms
            - state: false
              duration: 150ms
            - state: true
              brightness: 100%
              duration: 150ms
            - state: false
              duration: 1000ms
      - strobe:
          name: Speed 3
          colors:
            - state: true
              brightness: 100%
              duration: 150ms
            - state: false
              duration: 150ms
            - state: true
              brightness: 100%
              duration: 150ms
            - state: false
              duration: 150ms
            - state: true
              brightness: 100%
              duration: 150ms
            - state: false
              duration: 1000ms

    
  - platform: binary
    name: "Left LED - Temp & Hum Timer"
    output: left_led
    id: left_led_light
    restore_mode: ALWAYS_OFF

output:
  - id: right_led
    platform: gpio
    pin: GPIO32
    inverted: true
    
  - id: middle_led
    platform: gpio
    pin: GPIO33
    inverted: true
    
  - id: left_led
    platform: gpio
    pin: GPIO25
    inverted: true
    
#2022 Green Gable Fan AFG SMT ES-3.0 has 3-speeds so all three relays below are used. Must comment out unused speeds for other fan models
switch:
  - platform: gpio
    pin: 5
    name: "Fan Speed 1"
    id: Speed1
    restore_mode: ALWAYS_OFF
    interlock: &interlock_group [Speed1, Speed2, Speed3] #Setup interlock to prevent multiple speed relays to be active at the same time
    interlock_wait_time: 2s #2 seconds of all relays off before enabling a new speed
    on_turn_on:
      - light.turn_on: 
          id: middle_led_light
          effect: "Speed 1"
    on_turn_off:
      - light.turn_off: 
          id: middle_led_light
    
  - platform: gpio
    pin: 22
    name: "Fan Speed 2"
    id: Speed2
    restore_mode: ALWAYS_OFF
    interlock: *interlock_group
    interlock_wait_time: 2s
    on_turn_on:
      - light.turn_on: 
          id: middle_led_light
          effect: "Speed 2"
    on_turn_off:
      - light.turn_off: 
          id: middle_led_light

    
  - platform: gpio
    pin: 23
    name: "Fan Speed 3"
    id: Speed3
    restore_mode: ALWAYS_OFF
    interlock: *interlock_group
    interlock_wait_time: 2s
    on_turn_on:
      - light.turn_on: 
          id: middle_led_light
          effect: "Speed 3"
    on_turn_off:
      - light.turn_off: 
          id: middle_led_light
          
    
i2c: #setup i2c for temp/humidity sensor
  sda: 18
  scl: 19
  scan: true
  id: bus_a

  

    
sensor:
  - platform: sht3xd
    temperature:
      name: "Attic Fan Temperature"
      filters:
        - lambda: return x * (9.0/5.0) + 32.0; #Change to Freedom Units
      unit_of_measurement: "°F"
      
      #Fire Protection Automation
      on_value_range:
        above: 182
        then:
          - switch.turn_off: Speed1
          - switch.turn_off: Speed2
          - switch.turn_off: Speed3
          
    humidity:
      name: "Attic Fan Humidity"
    address: 0x44
    update_interval: 60s
    

4 Likes

I followed the steps posted by axwillnj and everything is working great (though I didn’t need to solder a resistor).

I’ve customized the yaml for my own ends so I’ll post it here in case anyone is interested. It’s based on azwillnj’s yaml but changed so that there is one on-off switch for power, a speed select box, and a timer box.

I also added a fan component that’s only really used to expose to google assistant. however, you can use it as the primary control if you want.

I wrote it up in a day-ish so it’s probably not the most elegant solution. If anyone sees any immediate improvement that can be made, let me know.

#based off the yaml by azwillnj at https://community.home-assistant.io/t/quietcool-smart-control-esp32/284565/7
esphome:
  name: whole-house-fan
  on_boot: #Display cascading LED's to show full boot
    priority: 800 #run after full boot
    then:
      - delay: 1s
      - light.turn_on: temp_hum_led_light
      - light.turn_on: speed_led_light
      - light.turn_on: bluetooth_led_light
      - delay: 1s
      - light.turn_off: bluetooth_led_light
      - delay: 0.5s
      - light.turn_off: speed_led_light
      - delay: 0.5s
      - light.turn_off: temp_hum_led_light
  on_loop:
    then:
      - lambda: |-
          //force fan component to sync with speed select component
          if (id(on_off_switch).state && id(house_fan).speed != id(speed_mode)){
            auto call = id(house_fan).turn_on();
            call.set_speed(id(speed_mode));
            call.perform();
          }

esp32:
  board: esp32dev
  framework:
    type: arduino

# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: !secret API-Password3

ota:
  password: OTA-Password4

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Whole-House-Fan"
    password: !secret ap-password

captive_portal:

globals:
  - id: speed_mode
    type: int
    initial_value: '1'
  - id: timer_offset
    type: long
    initial_value: '0'
  - id: timer_stop
    type: long
    initial_value: '0'
  - id: timer_en
    type: bool
    initial_value: 'false'
  - id: select_lock
    type: bool
    initial_value: 'false'

time:
  - platform: homeassistant
    id: homeassistant_time   
    

select:
  - platform: template
    name: "Speed Select"
    id: speed_select
    update_interval: 6s
    options:
      - "Low"
      - "Medium"
      - "High"
    set_action:
      - lambda: |-
          int prev_mode = id(speed_mode);
          if (x == "Low") {
            id(speed_mode) = 1;
          } else if (x == "Medium") {
            id(speed_mode) = 2;
          } else if (x == "High") {
            id(speed_mode) = 3;
          }
          
          if (id(speed_mode) != prev_mode && id(on_off_switch).state){
            if (id(speed_mode) == 1){
              id(Speed_Low).turn_on();
            } else if (id(speed_mode) == 2) {
              id(Speed_Medium).turn_on();
            } else if (id(speed_mode) == 3) {
              id(Speed_High).turn_on();
            }
          }
          
          //if speed is changed in the middle of operation change fan component output to match
          //select_lock will prevent excessive commands sent to fan compnent in the turn on process
          if (id(on_off_switch).state && (id(house_fan).speed != id(speed_mode) || id(speed_mode) != prev_mode) && !id(select_lock)){
            auto call = id(house_fan).turn_on();
            call.set_speed(id(speed_mode));
            call.perform();
          } else if (id(select_lock)){
            //clear select_lock if it sets to true
            id(select_lock) = false;
          }
          
    lambda: |-
      if (id(Speed_Low).state && id(speed_select).state != "Low") {
        return {"Low"};
      } else if (id(Speed_Medium).state && id(speed_select).state != "Medium") {
        return {"Medium"};
      } else if (id(Speed_High).state && id(speed_select).state != "High") {
        return {"High"};
      } 
      if(id(speed_select).state == "") {
        return {"Low"};
      }
      
      return{};
      
  - platform: template
    name: "Timer"
    id: timer
    update_interval: 20s
    options:
      - "off"
      - "1 hour"
      - "2 hours"
      - "4 hours"
      - "8 hours"
      - "12 hours"
    set_action:
      - lambda: |-
        
          if (x == "1 hour"){
            id(timer_offset) = 3600;
            id(timer_en) = true;
          } else if (x == "2 hours"){
            id(timer_offset) = 7200;
            id(timer_en) = true;
          } else if (x == "4 hours"){
            id(timer_offset) = 14400;
            id(timer_en) = true;
          } else if (x == "8 hours"){
            id(timer_offset) = 28800;
            id(timer_en) = true;
          } else if (x == "12 hours"){
            id(timer_offset) = 43200;
            id(timer_en) = true;
          } else if (x == "off") {
            id(timer_offset) = 0;
            id(timer_en) = false;
          }
          
          if (id(timer_en) && id(on_off_switch).state){
            id(timer_stop) = id(homeassistant_time).now().timestamp + id(timer_offset); // when the fan should turn off
          }
          
    lambda: |-
    
      if (id(timer_en) && id(on_off_switch).state && id(timer_stop) != 0) {
        auto time_remaining = id(timer_stop) - id(homeassistant_time).now().timestamp;
        
        if (time_remaining <= 0) {
          id(on_off_switch).turn_off();
          id(timer_en) = false;
          id(time_left).publish_state(0);
          return {"off"};
        } else if (time_remaining <= 3600 && id(timer).state != "1 hour"){
          return {"1 hour"};
        } else if (time_remaining > 3600 && time_remaining <= 7200 && id(timer).state != "2 hours"){
          return {"2 hours"};
        } else if (time_remaining > 7200 && time_remaining <= 14400 && id(timer).state != "4 hours"){
          return {"4 hours"};
        } else if (time_remaining > 14400 && time_remaining <= 28800 && id(timer).state != "8 hours"){
          return {"8 hours"};
        } else if (time_remaining > 28800 && time_remaining <= 43200 && id(timer).state != "12 hours"){
          return {"12 hours"};
        }
      } 
      if (id(timer).state == ""){
        return {"off"};
      }    
      
      return {};

#Front Buttons
binary_sensor:
  - platform: gpio
    name: "Button 2 - Pair"
    pin:
      number: 26
      inverted: true
      mode:
        input: true
        pullup: true
    internal: true
    filters:
      - delayed_on_off: 100ms
  - platform: gpio
    name: "Button 1 - Test"
    pin:
      number: 27
      inverted: true
      mode:
        input: true
        pullup: true
    internal: true
    filters:
      - delayed_on_off: 100ms
    on_press: #if statements to enable switching though modes with the test button.
      then:
        - lambda: |-
           if (id(Speed_Low).state){
             id(Speed_Medium).turn_on();
           } else if (id(Speed_Medium).state) {
             id(Speed_High).turn_on();
           } else if (id(Speed_High).state) {
             id(Speed_High).turn_off();
           } else {
             id(Speed_Low).turn_on();
           }

light:
#Note: There are 4 LEDs the far left Red LED is tied directly to power and not controllable.
  - platform: binary
    name: "Right LED - Bluetooth"
    output: bluetooth_led
    id: bluetooth_led_light
    restore_mode: ALWAYS_OFF
    internal: true
    
  - platform: binary
    name: "Middle LED - Speed"
    output: speed_led
    id: speed_led_light
    restore_mode: ALWAYS_OFF
    internal: true
    effects: #Setup strobe blinking for speed led to indicate current speed
      - strobe:
          name: Speed Low
          colors:
            - state: true
              brightness: 100%
              duration: 150ms
            - state: false
              duration: 1000ms
      - strobe:
          name: Speed Medium
          colors:
            - state: true
              brightness: 100%
              duration: 150ms
            - state: false
              duration: 150ms
            - state: true
              brightness: 100%
              duration: 150ms
            - state: false
              duration: 1000ms
      - strobe:
          name: Speed High
          colors:
            - state: true
              brightness: 100%
              duration: 150ms
            - state: false
              duration: 150ms
            - state: true
              brightness: 100%
              duration: 150ms
            - state: false
              duration: 150ms
            - state: true
              brightness: 100%
              duration: 150ms
            - state: false
              duration: 1000ms

    
  - platform: binary
    name: "Left LED - Temp & Hum Timer"
    output: temp_hum_led
    id: temp_hum_led_light
    internal: true
    restore_mode: ALWAYS_OFF

output:
  - id: bluetooth_led
    platform: gpio
    pin: GPIO32
    inverted: true
    
  - id: speed_led
    platform: gpio
    pin: GPIO33
    inverted: true
    
  - id: temp_hum_led
    platform: gpio
    pin: GPIO25
    inverted: true
    
  - platform: template
    id: fan_output_control
    type: float
    write_action:
      - lambda: |-
          auto call = id(speed_select).make_call();
          std::string option = "";
          
          if (state > 0.67 && state <= 1 && id(speed_select).state != "High") {
            option = "High";
          } else if (state > 0.34 && state <= 0.67 && id(speed_select).state != "Medium") {
            option = "Medium";
          } else if (state > 0.1 && state <= 0.34 && id(speed_select).state != "Low") {
            option = "Low";
          }
          
          if (option != "") {
            call.set_option(option);
            call.perform();
          }
          
fan:
  - platform: speed
    output: fan_output_control
    name: "Whole House Fan"
    id: house_fan
    speed_count: 3
    restore_mode: ALWAYS_OFF
    on_turn_on:
      - switch.turn_on: on_off_switch
    on_turn_off:
      - switch.turn_off: on_off_switch

switch:
  - platform: gpio
    pin: 5
    name: "Fan Speed Low"
    id: Speed_Low
    restore_mode: ALWAYS_OFF
    interlock: &interlock_group [Speed_Low, Speed_Medium, Speed_High] #Interlock prevents multiple on relays 
    interlock_wait_time: 2s #2 seconds of all relays off before enabling a new speed
    internal: true
    on_turn_on:
      - light.turn_on: 
          id: speed_led_light
          effect: "Speed Low"
    on_turn_off:
      - light.turn_off: 
          id: speed_led_light
    
  - platform: gpio
    pin: 22
    name: "Fan Speed Medium"
    id: Speed_Medium
    restore_mode: ALWAYS_OFF
    interlock: *interlock_group
    interlock_wait_time: 2s
    internal: true
    on_turn_on:
      - light.turn_on: 
          id: speed_led_light
          effect: "Speed Medium"
    on_turn_off:
      - light.turn_off: 
          id: speed_led_light

  - platform: gpio
    pin: 23
    name: "Fan Speed High"
    id: Speed_High
    restore_mode: ALWAYS_OFF
    interlock: *interlock_group
    interlock_wait_time: 2s
    internal: true
    on_turn_on:
      - light.turn_on: 
          id: speed_led_light
          effect: "Speed High"
    on_turn_off:
      - light.turn_off: 
          id: speed_led_light
   
  - platform: template
    name: "Fan On/Off"
    id: on_off_switch
    turn_on_action: 
      lambda: |-
        if (id(speed_mode) == 1) {
          id(Speed_Low).turn_on();
        } else if (id(speed_mode) == 2) {
          id(Speed_Medium).turn_on();
        } else if (id(speed_mode) == 3) {
          id(Speed_High).turn_on();
        }
        if (id(timer_en)){ //start the timer if its set
          id(timer_stop) = id(homeassistant_time).now().timestamp + id(timer_offset);
          id(time_left).publish_state((double)id(timer_offset) / 60.0);
        }
        
        //turn on fan component if it's not already on and set it to the current speed mode
        if (!id(house_fan).state && !id(select_lock)) {
          id(select_lock) = true;
          auto call = id(house_fan).turn_on();
          call.set_speed(id(speed_mode));
          call.perform();
          
        }
        
    turn_off_action:
      lambda: |-
        id(Speed_Low).turn_off();
        id(Speed_Medium).turn_off();
        id(Speed_High).turn_off();
        id(timer_stop) = 0;
        id(time_left).publish_state(0);
        
        //turn off fan component if it's not already off
        if(id(house_fan).state) {
          id(timer).publish_state("off");
          auto call = id(house_fan).turn_off();
          call.perform();
        }
    lambda: |-
      return (id(Speed_Low).state | id(Speed_Medium).state | id(Speed_High).state);

i2c: #setup i2c for temp/humidity sensor
  sda: 18
  scl: 19
  scan: true
  id: bus_a

sensor:
  - platform: wifi_signal
    name: "WiFi Strength"
    update_interval: 60s
  - platform: uptime
    name: "Uptime"
  - platform: template
    name: "Time Left"
    id: time_left
    update_interval: 20s
    unit_of_measurement: "minutes"
    lambda: |-
      if (id(timer_en)) {
        auto time_remaining = id(timer_stop) - id(homeassistant_time).now().timestamp;
        
        return (time_remaining >=0) ? ((float)time_remaining / 60.0):0;
      } else if (id(time_left).state != 0) {
        return 0;
      }
      return {};
      
  - platform: sht3xd
    temperature:
      name: "Attic Temperature"
      filters:
        - lambda: return x * (9.0/5.0) + 32.0; #Change to Freedom Units
      unit_of_measurement: "°F"
      
      #Fire Protection Automation
      on_value_range:
        above: 182
        then:
          - switch.turn_off: on_off_switch
          
    humidity:
      name: "Attic Humidity"
    address: 0x44
    update_interval: 60s

text_sensor:
  - platform: wifi_info
    ip_address:
      name: "IP Address"

I just installed this today after following these instructions, my first time flashing a commercial off-the-shelf-product with ESPHOME and worked great!

A few things I found. On my model, the order of the pins went 3.3, Tx, Rx, Gnd not 3.3, Rx, Tx, Gnd (as pictured). Before I reversed Rx and Tx, none of my devices (I tried three different ones) could communicate over the COM port. Once I switched them, firmware flashed perfectly.

Second thing that happened, I connected it to a circuit, to just check to make sure it all worked and using the YAML that @TCecil posted, I got the friendly sequential LED indicator that the device booted up. I added it to Home Assistant via the ESPHome Integration and everything seemed fine. I wanted to test it out and make sure it worked before climbing into my attic, installing it to the fan just to realizing something was wrong. It all worked, but then when I connected it to the fan with confidence, it didn’t. I had to pull it down, reflash it with the USB to TTL device again, then it worked. Not really sure what happened there, but always a good idea to test it out before installing.

Excited because in the spring, I’m going to install a second gable fan and use the same procedure to set it up. Great work everyone!

Bah, I just got one of these this afternoon and I’ve been struggling with it for a few hours. No dice.

Did you guys need 110V power to this thing to get it to program? I’m just using 3.3V and it is not happening. I have tried:

  • Using the USB-to-serial plug directly
    • and swapping Tx/Rx
  • Powering with a separate 3.3V supply
    • and swapping Tx/Rx
  • Flashing from Windows esphome-flasher
    • and swapping Tx/Rx
  • Flashing from Linux esptool.py
    • and swapping Tx/Rx

I can tell when I’m in boot loader mode because the red LED is the only one on. If I don’t short the boot pins, then all LEDs flash green once and then one of the LEDs keeps flashing and the others stop.

I even tried flashing it while apparently not in boot loader mode and the response is the same. :expressionless:

I’m not sure why I can’t get mine to respond. I’ve tried dumping the image with read_flash and I’ve tried just writing the flash and neither things seem to work. :confused:

Linux flash always shows this:

$ esptool.py -p /dev/ttyUSB0 read_mac
esptool.py v4.6.2
Serial port /dev/ttyUSB0
Connecting......................................

A fatal error occurred: Failed to connect to Espressif device: No serial data received.
For troubleshooting steps visit: https://docs.espressif.com/projects/esptool/en/latest/troubleshooting.html
$

Windows flash always shows this:

Using 'COM4' as serial port.
Connecting......................................
Unexpected error: ESP Chip Auto-Detection failed: Failed to connect to Espressif device: No serial data received.
For troubleshooting steps visit: https://github.com/espressif/esptool#troubleshooting

I soldered a 4-pin header and I’m using wires with crimped ends to eliminate continuity problems.

Any ideas on how to get this thing to flash?? Maybe I need to wall-power it with 110V, though I don’t really want to have to do that.

EDIT

Adding more detail. This is the REV:D 2019.11.01 version. Here’s a quick photo:image

1 Like

Ok, I returned that previous unit and got a different unit from a different supplier. I’ve played with the new one all night last night and got exactly the same response as the first unit. I tried pulling the stock firmware image likely over 20-30 times and couldn’t get anything to respond. Same attempts and results as above with and without external 3.3V power supply.

This morning I even tried powering it with 120V wall voltage and pulling the firmware and still could not. I tried all four ways with and without shorting the boot pins and both directions of Rx/Tx – the response is always the same from the terminal. I noticed that the FTDI programmer flashes differently depending on which direction the Rx/Tx pins are oriented – likely because in one direction it’s backward.

The strangest thing is that I can see the lights on the FTDI flickering quite a bit when I allow it to power up without shorting the boot pins – so I’d like to see that it’s spewing stuff out to a terminal (even random junk I’d be happy with) but NOTHING comes out. I tried changing the baud rate using screen and catting the /dev/ttyUSB0 but still nothing.

I know that the screen works because I can just take the Rx/Tx pins and manually short them together and see when I press a key in the screen, it spits out on the terminal – with the pins separated the key press goes “into the ether” and never gets printed to the screen. Also, I suspected that my FTDI programmer was to blame or bad but I, just yesterday, also got Sonoff S31s and I used the same programmer to put ESPHome on one of those, it worked great!

I’m at a complete loss here… I have like 25 ESPHome devices in my network, I feel like I’m decent at hacking stuff to make it work but this thing is stumping me. :-1:

That’s like 1nm infront of the goal :goal_net:

:scream: :zap:

You want your RX from the FTDI going to the TX of the ESP32, then obviously TX from FTDI goes to RX of the ESP32. :twisted_rightwards_arrows:

In the past I also had problems flashing pre-build boards and most of the time my 3.3V power supply just wasn’t beefy enough because it automatically powers all peripherals (not just the esp) connected to the power rail. :zap:

Using the 3.3V from the FTDI often isn’t enough juice! :pinching_hand:

This is exactly why I had already tried powering it with line voltage and also with a separate 3.3V supply, neither of which yielded positive results.

What were the ratings of this (power/current?) and did you check all connections if they introduce a high resistance?

It is a 1A power supply but I’m not sure that’s even relevant since when I power it with 120V AC it does exactly the same thing. With wall power it should have enough current to operate.

What does you mean by this? Can you describe it in more detail? I tried a few different wire leads for power, ground, Rx and Tx. I haven’t soldered the connections completely but since the signature is always the same, I’m tempted to think that the problem (limitation) is in their circuit somewhere.

Ah ha! I finally got it working! The key was to use a better USB FTDI dongle. Apparently the cheapo one I was using wasn’t cutting the mustard… Here’s the comparison:

The one that didn’t work on the left and the one that did work on the right. It’s similar to the one found in the original article. I bought it from Amazon: https://www.amazon.com/gp/aw/d/B07BBPX8B8

Happy times, now we’re in business!

1 Like

In spirit of the original post, it should be possible to keep the original firmware by building a Bluetooth bridge. I believe the communication between phone and original firmware is just a serial interface over BT. I captured the BT traffic and it appears to be straight forward JSON.

I will not pursue this, as I am just flashing with EspHome. But let me know if anyone wants more details on this or instruction on how to get these logs.

== Fan at 47.317355000 ==


== Phone at 47.365926000 ==
{"Api":"Login","PhoneID":"**********"}

== Fan at 47.745508000 ==
{
	"Api":	"Login",
	"Result":	"Success",
	"PairState":	"No"
}

== Phone at 47.785590000 ==
{"Api":"GetParameter"}

== Fan at 48.166593000 ==
{
	"Api":	"GetParameter",
	"Mode":	"TH",
	"FanType":	"TWO",
	"GetTemp_H":	100,
	"GetTemp_L":	80,
	"GetHum_H":	90,
	"GetHum_L":	70,
	"GetHum_Range":	"LOW",
	"GetHour":	3,
	"GetMinute":	0,
	"GetTime_Range":	"HIGH"
}

== Phone at 48.205302000 ==
{"Api":"GetVersion"}

== Fan at 48.587887000 ==
{
	"Api":	"GetVersion",
	"Version":	"IT-BLT-ATTICFAN_V2.2",
	"ProtectTemp":	182
}

== Phone at 48.655509000 ==
{"Api":"GetPresets","FanType":"THREE"}

== Fan at 49.008573000 ==
{
	"Api":	"GetPresets",
	"Presets":	[["Summer", 100, 90, 80, 90, 70, "LOW"], ["Winter", 40, 35, 30, 90, 70, "LOW"]]
}

== Phone at 49.046186000 ==
{"Api":"GetFanInfo"}

== Fan at 49.430119000 ==
{
	"Api":	"GetFanInfo",
	"Name":	"AFG SMT PRO-2.0",
	"Model":	"1",
	"SerialNum":	"GSP20xxxxx"
}

== Phone at 49.466026000 ==
{"Api":"GetWorkState"}

== Fan at 56.829998000 ==
{
	"Api":	"GetWorkState",
	"Mode":	"TH",
	"Range":	"HIGH",
	"SensorState":	"OK",
	"Temp_Sample":	1071,
	"Humidity_Sample":	29
}

== Phone at 56.878379000 ==
{"Api":"SetMode","Mode":"Idle"}

== Fan at 57.751383000 ==
{
	"Api":	"SetMode",
	"WorkMode":	"Idle",
	"Flag":	"TRUE"
}

== Phone at 57.805993000 ==
{"Api":"GetWorkState"}

== Fan at 60.172574000 ==
{
	"Api":	"GetWorkState",
	"Mode":	"Idle",
	"Range":	"CLOSE",
	"SensorState":	"OK",
	"Temp_Sample":	1071,
	"Humidity_Sample":	29
}

Could you tell me how you captured this data? I am looking into using the BLE client function of ESP Home to potentially bridge or maybe finding another method. Hoping I can pull my own phone ID and login info so I can attempt the bridge.

Thanks!

@Tanabata42 @YagiBear Did either of you make any progress on this? I’m about to install an AFR SMT ES 2.0 attic fan and would prefer to use a Bluetooth proxy to get it into HA so the fan’s controls would still usable be future owners.