Skip to content

Conversation

@kentonr
Copy link

@kentonr kentonr commented Oct 25, 2025

I recently had quietcool attic fans installed and upon trying to connect them, I found I wasn't getting the sensor data.

Looking at the logs from the ESP device, I suspected an API change had occurred. With some research, I was able to confirm this as well as figure out how it was changed. On the whole, the API is somewhat made obfuscated, but purely by remapping the JSON keys to something else.

I think some of the older commands somewhat work with the old API, as I didn't need to re-pair my ESPHome device, but I chose to update all the API calls regardless.

Ultimately, this pull request should just be left alone, as I think your original code is valid for folks on older firmware while this code is valid for newer firmware.

I tested the sensor values, however I did not test the new pairing process (my fans are relatively-to-largely inaccessible, so I can't risk resetting them and needing to physically reach them). I also did not test turning on the fans either continuously or for a set time as I did not want to mess with the "smart" mode they're on for now.

…emp_L and GetTemp_H and avoid dividing it to help make it obvious to the user should they choose to view those sensors. Fixed a mistake done by copilot on GetFanInfo key "G".
@kentonr kentonr marked this pull request as ready for review October 25, 2025 06:40
@btalachian-cfp
Copy link

Hi,
Thank you for the script!

I tried to connect (pair) my new fan, but I'm getting the following error. Firmware version on the Quiet fan is 4.1
Do you have any ideas how I can fix it?

[18:12:27.640][I][ble_text_sensor:041]: [BLE Data] Connected successfully!
[18:12:29.040][I][esp32_ble_client:425]: [0] [F8:B3:B7:7D:6F:46] Service discovery complete
[18:12:31.098][I][quietcool:119]: Sending BLE message: {"Api":"Login","PhoneID":"a1b2c1d2a2b1c2d1"}
[18:12:31.314][E][json:059]: Parse error: InvalidInput
[18:12:31.317][W][quietcool:397]: Failed to parse JSON: QQ{
[18:12:31.317][W][quietcool:397]: "A": 13,
[18:12:31.317][W][quietcool:397]: "R": "Success",
[18:12:31.317][W][quietcool:397]: "P": "No"
[18:12:31.317]}
[18:12:32.226][I][quietcool:119]: Sending BLE message: {"Api":"GetWorkState"}
[18:12:32.337][E][json:059]: Parse error: InvalidInput
[18:12:32.337][W][quietcool:397]: Failed to parse JSON: QQ{
[18:12:32.337]}
[18:12:36.639][I][quietcool:119]: Sending BLE message: {"Api":"Pair","PhoneID":"a1b2c1d2a2b1c2d1"}
[18:12:36.822][E][json:059]: Parse error: InvalidInput
[18:12:36.822][W][quietcool:397]: Failed to parse JSON: QQ{
[18:12:36.822]}
[18:12:38.358][I][quietcool:119]: Sending BLE message: {"Api":"GetFanInfo"}
[18:12:38.460][E][json:059]: Parse error: InvalidInput
[18:12:38.460][W][quietcool:397]: Failed to parse JSON: QQ{
[18:12:38.460]}
[18:12:40.109][I][quietcool:119]: Sending BLE message: {"Api":"GetParameter"}
[18:12:40.212][E][json:059]: Parse error: InvalidInput
[18:12:40.212][W][quietcool:397]: Failed to parse JSON: QQ{
[18:12:40.212]}
[18:12:46.259][I][quietcool:119]: Sending BLE message: {"Api":"GetWorkState"}
[18:12:46.359][E][json:059]: Parse error: InvalidInput
[18:12:46.359][W][quietcool:397]: Failed to parse JSON: QQ{
[18:12:46.359]}
[18:12:52.329][I][quietcool:119]: Sending BLE message: {"Api":"GetFanInfo"}
[18:12:52.501][E][json:059]: Parse error: InvalidInput
[18:12:52.501][W][quietcool:397]: Failed to parse JSON: QQ{

@kentonr
Copy link
Author

kentonr commented Dec 11, 2025

Hi there, sorry github does not seem to notify me of comments. I happened to swing by and check this. (edit: I think I fixed the notifications)

I think this looks like the original code, as my pull request would strip the "QQ" which is why you get the "failed to parse JSON" error.

I recommend copying the file (without diff markers) by going to the "files changed" tab and hitting the "..." menu to see "view file". From there, theres a little icon for "download raw file"

This link may take you directly there.

One tell tale that you're sending the new commands is that instead of seeing BLE messages like "Api: "GetFanInfo" you'd see "A":13 (or whatever).

I'm also not 100% sure you managed to pair the esphome device or not. You need to hit the "pair BLE" button in homeassistant once you get the device up. From there you may need to restart the device so it can properly login. (Edit) I’d try to get the new code working first though, as the old pair code still works on these newer fan version. Let me know how it goes!

@kentonr
Copy link
Author

kentonr commented Dec 11, 2025

Oh, I forgot to add, instead of using the config as described in the readme, you should copy this file in the pull request to your local machine and use esphome run <the yaml file you copied in> You would follow directions for loading a "local package" in this case since you're not using anything from git at that point.

You'll need to edit the mac_address at the top to the fan's mac.

(Edit: do this if you haven’t tried it yet out of the new code isn’t fully working) Note, the esphome device holds onto the fan's bluetooth connection, so my suggestion is to get the device in a working state, power it down, use the quietcool phone app to put the fan into pair mode, then power on the esphome device and hit "pair BLE" in HA.

@xsorifc28
Copy link

xsorifc28 commented Dec 24, 2025

Fan Model: AFG SMT PRO-2.0 (built in bluetooth, firmware 4.1)

I installed your changes via remote package on my xiao-esp32c3:

substitutions:
  mac_address: "<redacted>" 

packages:
  remote_package: github://kentonr/quietcool-esphome/quietcool-smart-attic-fan-control.yaml@main

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

# THIS SECTION OVERRIDES THE PACKAGE HARDWARE
esphome:
  name: attic_fan
  # The XIAO C3 needs 'dio' flash mode to boot correctly
  platformio_options:
    board_build.flash_mode: dio

esp32:
  board: seeed_xiao_esp32c3
  variant: esp32c3
  framework:
    type: esp-idf

api:
ota:

The installation looks fine, and I think I'm paired, or at least connected, as I see this in the logs:

[00:46:57.849][I][quietcool:109]: Sending BLE message: {"A":17}
[00:47:00.694][I][quietcool:109]: Sending BLE message: {"A":2}
[00:47:06.808][I][quietcool:109]: Sending BLE message: {"A":1}
[00:47:12.932][I][quietcool:109]: Sending BLE message: {"A":17}
[00:47:15.694][I][quietcool:109]: Sending BLE message: {"A":2}
[00:47:21.772][I][quietcool:109]: Sending BLE message: {"A":1}
[00:47:27.880][I][quietcool:109]: Sending BLE message: {"A":17}
[00:47:30.697][I][quietcool:109]: Sending BLE message: {"A":2}
[00:47:36.770][I][quietcool:109]: Sending BLE message: {"A":1}
[00:47:42.888][I][quietcool:109]: Sending BLE message: {"A":17}
[00:47:45.697][I][quietcool:109]: Sending BLE message: {"A":2}
[00:47:51.843][I][quietcool:109]: Sending BLE message: {"A":1}
[00:47:57.935][I][quietcool:109]: Sending BLE message: {"A":17}
[00:48:00.698][I][quietcool:109]: Sending BLE message: {"A":2}
[00:48:06.811][I][quietcool:109]: Sending BLE message: {"A":1}
[00:48:12.883][I][quietcool:109]: Sending BLE message: {"A":17}
[00:48:15.702][I][quietcool:109]: Sending BLE message: {"A":2}
[00:48:21.848][I][quietcool:109]: Sending BLE message: {"A":1}
[00:48:27.924][I][quietcool:109]: Sending BLE message: {"A":17}
[00:48:30.701][I][quietcool:109]: Sending BLE message: {"A":2}

But, I don't see anything received. Is there a step I am missing? Am I not paired correctly?

As expected, when the xiao-esp32c3 is running and connected, the "smart control" app shows no devices in range.

Fan Model: AFG SMT PRO-2.0 (built in bluetooth, firmware 4.1)

EDIT: Adding some DEBUG logs:

[01:15:55.243][D][api.connection:1398]: Home Assistant 2025.12.4 (10.0.0.8) connected
[01:15:55.259][D][time:068]: Synchronized time: 2025-12-24 01:15:55
[01:15:56.923][I][quietcool:109]: Sending BLE message: {"A":1}
[01:15:56.992][D][esp32_ble_client:197]: [0] {MAC_REDACTED}] ESP_GATTC_NOTIFY_EVT
[01:15:56.992][D][text_sensor:097]: 'BLE Data': Sending state 'QQ{
[01:15:56.992]}'
[01:15:56.993][D][text_sensor:097]: 'BLE Data': Sending state 'QQ{
[01:15:56.993]}'
[01:15:56.993][D][quietcool:340]: Received JSON data: QQ{
[01:15:56.993]}
[01:15:56.994][D][esp32_ble_client:197]: [0] {MAC_REDACTED}] ESP_GATTC_WRITE_CHAR_EVT
[01:16:02.999][I][quietcool:109]: Sending BLE message: {"A":17}
[01:16:03.071][D][esp32_ble_client:197]: [0] {MAC_REDACTED}] ESP_GATTC_NOTIFY_EVT
...above logs loop...

@kentonr
Copy link
Author

kentonr commented Dec 24, 2025

Hi,

This is looking promising, note that response messages are LOGD (debug level) vs LOGI (info). By default you may not see the response.

Do you see data in HA? If so then you're good. If not, try the init steps in the readme, which basically have you use your phone to put the fan in pair mode, then you power up the esp32 and hit the "pair ble" button in HA to trigger the pair call.

From there, it may take a minute or maybe one last power cycle for things to settle down and data to arrive. Admittedly the ESPHome code isn't a very robust implementation. It's worth noting that changing some of the parameters (like duration of timer mode) is a little flaky, I would recommend changing it then cycling the off and on (if it was on to begin with).

For my use case, I cared more about the temp/humidity from the fan vs a weather station and used that to trigger fan automations, so I rarely change the timer setting.

If you continue to not see data in HA, Can you set the log to debug mode and share with you see?

logger:
  level: DEBUG

@xsorifc28
Copy link

Here are some debug logs:

[01:15:55.243][D][api.connection:1398]: Home Assistant 2025.12.4 (10.0.0.8) connected
[01:15:55.259][D][time:068]: Synchronized time: 2025-12-24 01:15:55
[01:15:56.923][I][quietcool:109]: Sending BLE message: {"A":1}
[01:15:56.992][D][esp32_ble_client:197]: [0] {MAC_REDACTED}] ESP_GATTC_NOTIFY_EVT
[01:15:56.992][D][text_sensor:097]: 'BLE Data': Sending state 'QQ{
[01:15:56.992]}'
[01:15:56.993][D][text_sensor:097]: 'BLE Data': Sending state 'QQ{
[01:15:56.993]}'
[01:15:56.993][D][quietcool:340]: Received JSON data: QQ{
[01:15:56.993]}
[01:15:56.994][D][esp32_ble_client:197]: [0] {MAC_REDACTED}] ESP_GATTC_WRITE_CHAR_EVT
[01:16:02.999][I][quietcool:109]: Sending BLE message: {"A":17}
[01:16:03.071][D][esp32_ble_client:197]: [0] {MAC_REDACTED}] ESP_GATTC_NOTIFY_EVT
...above logs loop...

I'm not able to see any temp/humidity data. Not sure why I am getting QQ{}.

@xsorifc28
Copy link

Seems like something is failing during the pairing process?

[01:38:34.564][I][quietcool:109]: Sending BLE message: {"A":13,"P":"redacted"}
[01:38:34.677][D][esp32_ble_client:197]: [0] [redacted] ESP_GATTC_WRITE_CHAR_EVT
[01:38:34.758][D][esp32_ble_client:197]: [0] [redacted] ESP_GATTC_NOTIFY_EVT
[01:38:34.759][D][text_sensor:097]: 'BLE Data': Sending state 'QQ{
[01:38:34.759][D][text_sensor:097]: 	"A":	13,
[01:38:34.759][D][text_sensor:097]: 	"R":	'
[01:38:34.760][D][esp32_ble_client:197]: [0] [redacted] ESP_GATTC_NOTIFY_EVT
[01:38:34.760][D][text_sensor:097]: 'BLE Data': Sending state '"Fail",
[01:38:34.761][D][text_sensor:097]: 	"P":	"Yes"
[01:38:34.761]'
[01:38:34.761][D][esp32_ble_client:197]: [0] redacted] ESP_GATTC_NOTIFY_EVT
[01:38:34.762][D][text_sensor:097]: 'BLE Data': Sending state '}'
[01:38:34.763][D][text_sensor:097]: 'BLE Data': Sending state 'QQ{
[01:38:34.763][D][text_sensor:097]: 	"A":	13,
[01:38:34.763][D][text_sensor:097]: 	"R":	"Fail",
[01:38:34.763][D][text_sensor:097]: 	"P":	"Yes"
[01:38:34.763]}'
[01:38:34.763][D][quietcool:340]: Received JSON data: QQ{
[01:38:34.764][D][quietcool:340]: 	"A":	13,
[01:38:34.764][D][quietcool:340]: 	"R":	"Fail",
[01:38:34.764][D][quietcool:340]: 	"P":	"Yes"
[01:38:34.764]}
[01:38:34.765][D][quietcool:464]: No mapping for API 13 key: R
[01:38:34.766][D][quietcool:464]: No mapping for API 13 key: P

@kentonr
Copy link
Author

kentonr commented Dec 24, 2025

(I just saw your latest reply, I edited this comment to the most relevant part. tl;dr: Flash the original code and do the pair steps, then flash to my code and hopefully you're good to go).

QQ is another change they made to the API, my version of the code removes the first two chars of the response for this reason, otherwise JSON parsing fails (like you see in the logs of the other comment).

In my case, the device was paired by the time I realized the API changed, and I didn't want to test the new pair API as it's been a bit of an ordeal getting the . The fan seems to support the old API at least as far as pairing goes. It's possible that my change to use the new API for pairing does not work. If so, then you should run the original code on your esp32 to pair the device, then switch to my code (no need to pair) to get the data and control the fan.

@xsorifc28
Copy link

original code flashed, paired, parsing failing, as expected:

[01:51:33.072][I][quietcool:109]: Sending BLE message: {"Api":"Pair","PhoneID":"redacted"}
[01:51:33.195][D][esp32_ble_client:197]: [0] [redacted] ESP_GATTC_WRITE_CHAR_EVT
[01:51:33.396][D][esp32_ble_client:197]: [0] [redacted] ESP_GATTC_NOTIFY_EVT
[01:51:33.396][D][text_sensor:097]: 'BLE Data': Sending state '{
[01:51:33.396][D][text_sensor:097]: 	"Api":	"Pair",
[01:51:33.396][D][text_sensor:097]: 	"'
[01:51:33.398][D][esp32_ble_client:197]: [0] redacted] ESP_GATTC_NOTIFY_EVT
[01:51:33.399][D][text_sensor:097]: 'BLE Data': Sending state 'Result":	"Success"
[01:51:33.400]}'
[01:51:33.400][D][text_sensor:097]: 'BLE Data': Sending state '{
[01:51:33.400][D][text_sensor:097]: 	"Api":	"Pair",
[01:51:33.401][D][text_sensor:097]: 	"Result":	"Success"
[01:51:33.401]}'
[01:51:33.401][D][quietcool:340]: Received JSON data: {
[01:51:33.401][D][quietcool:340]: 	"Api":	"Pair",
[01:51:33.401][D][quietcool:340]: 	"Result":	"Success"
[01:51:33.401]}
[01:51:33.403][D][quietcool:381]: No sensor found for key: api
[01:51:33.403][D][quietcool:381]: No sensor found for key: result
[01:51:33.944][D][script:128]: Script 'send_ble_msg' queueing new instance (mode: queued)
[01:51:34.204][I][quietcool:109]: Sending BLE message: {"Api":"GetParameter"}
[01:51:34.276][D][esp32_ble_client:197]: [0] redacted] ESP_GATTC_NOTIFY_EVT
[01:51:34.277][D][text_sensor:097]: 'BLE Data': Sending state 'QQ{
[01:51:34.277]}'
[01:51:34.277][D][text_sensor:097]: 'BLE Data': Sending state 'QQ{
[01:51:34.277]}'
[01:51:34.278][D][quietcool:340]: Received JSON data: QQ{
[01:51:34.278]}
[01:51:34.279][E][json:059]: Parse error: InvalidInput
[01:51:34.279][W][quietcool:387]: Failed to parse JSON: QQ{
[01:51:34.279]}

I then reflashed your branch, and saw in the logs the esp was able to get gatt chracteristics. i tried toggling a fan "on" in the HA UI, which worked according to the logs, but the fan did not turn on. Still seeing empty "QQ{}" and no temp/humidity data.
If I power off the esp, I'm able to connect via the app and see data.

Logs:

...
[01:59:39.974][I][quietcool:109]: Sending BLE message: {"A":1}
[01:59:40.048][D][esp32_ble_client:197]: [0] [redacted] ESP_GATTC_NOTIFY_EVT
[01:59:40.049][D][text_sensor:097]: 'BLE Data': Sending state 'QQ{
[01:59:40.050]}'
[01:59:40.050][D][text_sensor:097]: 'BLE Data': Sending state 'QQ{
[01:59:40.050]}'
[01:59:40.051][D][quietcool:340]: Received JSON data: QQ{
[01:59:40.051]}
[01:59:40.051][D][esp32_ble_client:197]: [0] [redacted] ESP_GATTC_WRITE_CHAR_EVT
[01:59:40.495][D][fan:044]: 'QuietCool Fan' - Setting:
[01:59:40.495][D][fan:047]:   State: OFF
[01:59:40.496][D][fan:170]: 'QuietCool Fan' - Sending state:
[01:59:40.496][D][fan:171]:   State: OFF
[01:59:40.496][D][fan:173]:   Speed: 1
[01:59:40.496][D][main:068]: Turning off
[01:59:40.498][D][script:128]: Script 'send_ble_msg' queueing new instance (mode: queued)
[01:59:41.055][I][quietcool:109]: Sending BLE message: {"A":9,"M":"Idle"}
[01:59:41.128][D][esp32_ble_client:197]: [0] [redacted] ESP_GATTC_NOTIFY_EVT
[01:59:41.129][D][text_sensor:097]: 'BLE Data': Sending state 'QQ{
[01:59:41.129]}'
[01:59:41.129][D][text_sensor:097]: 'BLE Data': Sending state 'QQ{
[01:59:41.129]}'
[01:59:41.130][D][quietcool:340]: Received JSON data: QQ{
[01:59:41.131]}
[01:59:41.132][D][esp32_ble_client:197]: [0] [redacted] ESP_GATTC_WRITE_CHAR_EVT
[01:59:42.134][D][event:025]: 'Fan Off Event' Triggered event 'fan_off'
[01:59:47.139][I][quietcool:109]: Sending BLE message: {"A":17}
[01:59:47.208][D][esp32_ble_client:197]: [0] [redacted] ESP_GATTC_NOTIFY_EVT
[01:59:47.209][D][text_sensor:097]: 'BLE Data': Sending state 'QQ{
[01:59:47.209]}'
[01:59:47.210][D][text_sensor:097]: 'BLE Data': Sending state 'QQ{
[01:59:47.211]}'
[01:59:47.211][D][quietcool:340]: Received JSON data: QQ{
[01:59:47.212]}
[01:59:47.213][D][esp32_ble_client:197]: [0] [redacted] ESP_GATTC_WRITE_CHAR_EVT
[01:59:48.879][I][quietcool:109]: Sending BLE message: {"A":2}
[01:59:48.968][D][esp32_ble_client:197]: [0] [redacted] ESP_GATTC_NOTIFY_EVT
[01:59:48.969][D][text_sensor:097]: 'BLE Data': Sending state 'QQ{
[01:59:48.969]}'
[01:59:48.971][D][text_sensor:097]: 'BLE Data': Sending state 'QQ{
[01:59:48.971]}'
[01:59:48.972][D][quietcool:340]: Received JSON data: QQ{
[01:59:48.973]}
[01:59:48.974][D][esp32_ble_client:197]: [0] [redacted] ESP_GATTC_WRITE_CHAR_EVT
...

@kentonr
Copy link
Author

kentonr commented Dec 24, 2025

(edit: I replied before I saw your latest, let me write something in a new reply but I keep this one for posterity)

oh, I saw that the api is "13" which ties to the login, the "R" param is the result of login (yes it failed) and "P" is indicating whether the device is in pairing mode (based on the emerose python library). I thought it was the pair call (which is API 14 and otherwise is the same).

I'm probably late to make this suggestion, but rather than switching your firmware around, you could probably hit "pair ble" for the esphome device in HA and see how that goes.

Note that "pair" is not BLE pairing, the device lets anything pair to it to communicate, "pair" in my context is a proprietary API call which you invoke by hitting "pair BLE" in HA once your esphome device is responsive.

@kentonr
Copy link
Author

kentonr commented Dec 24, 2025

I think what you should do is power on your esp and give turning on the fan a try. I have noticed that the fan / esp do not communicate well sometimes shortly after restart (or a BLE reconnect). You may want to even wait a minute (while tailing the logs to see whats happening). [edit: once you stop seeing empty JSON, you could expect to be able to turn on the fan. It's not unusual for the fan /esp to take a minute to stabilize, but once connected and running it's been reliable long term]

The logs show empty JSON responses, which makes me think it's not recognized the pair. I suspect that could be because the fan expects a "login" to follow before giving data. That's triggered automatically when the esp is powered on. Hopefully this is the last step.

I wouldn't necessarily trust the button / selectable mode states in HA to know if the fan is actually on/off, the "mode" property is most reliable as it's gotten from the fan itself.

@xsorifc28
Copy link

Thanks for the help - will give this a try in a couple hours and report back!

@xsorifc28
Copy link

No luck. I've tried turning the fan off, then on. Logs for this sequence:

...
09:53:48.340][D][text_sensor:097]: 'BLE Data': Sending state '"Fail",
[09:53:48.340][D][text_sensor:097]: 	"P":	"No"
[09:53:48.340]}'
[09:53:48.340][D][text_sensor:097]: 'BLE Data': Sending state 'QQ{
[09:53:48.340][D][text_sensor:097]: 	"A":	13,
[09:53:48.340][D][text_sensor:097]: 	"R":	"Fail",
[09:53:48.340][D][text_sensor:097]: 	"P":	"No"
[09:53:48.340]}'
[09:53:48.341][D][quietcool:340]: Received JSON data: QQ{
[09:53:48.341][D][quietcool:340]: 	"A":	13,
[09:53:48.341][D][quietcool:340]: 	"R":	"Fail",
[09:53:48.341][D][quietcool:340]: 	"P":	"No"
[09:53:48.341]}
[09:53:48.341][D][quietcool:464]: No mapping for API 13 key: R
[09:53:48.341][D][quietcool:464]: No mapping for API 13 key: P
[09:53:49.057][D][script:128]: Script 'send_ble_msg' queueing new instance (mode: queued)
[09:53:49.298][I][quietcool:109]: Sending BLE message: {"A":1}

Is A: 13, R: Fail, P: No a good sign here?

I waited 10-15 mins for the fan and esp to stabilize, but still see this stream of logs:

Sending BLE message: {"A":1}
...
Sending BLE message: {"A":2}
...
Sending BLE message: {"A":17}
...
(loop)

I tried to turn it "on" via HA and the request is sent (no errors in logs), but nothing happens even after waiting 10-15 mins.

Out of curiosity, what is the fan model and firmware that you have got this working on?

@kentonr
Copy link
Author

kentonr commented Dec 24, 2025

Regarding my fan, I have the AFG SMT PRO-2.0 (same as you) but firmware version 3.9(!)

Here's some sample logs, though for full disclosure, I have a modified esphome config as I am controlling two fans (in logs as east and west) with one device (but the code is effectively equivalent):

[10:31:58.760][I][ble_text_sensor:041]: [BLE Data East] Connected successfully!
[10:32:00.182][I][esp32_ble_client:425]: [0] [redact] Service discovery complete
[10:32:02.185][I][quietcool:125]: [east] Sending: {"A":13,"P":"redact"}
[10:32:02.484][I][quietcool:664]: [east] Received JSON data: QQ{
[10:32:02.484][I][quietcool:664]:       "A":    13,
[10:32:02.484][I][quietcool:664]:       "R":    "Success",
[10:32:02.484][I][quietcool:664]:       "P":    "No"
[10:32:02.484]}
[10:32:03.273][I][quietcool:125]: [east] Sending: {"A":17}
[10:32:03.469][I][quietcool:664]: [east] Received JSON data: QQ{
[10:32:03.469][I][quietcool:664]:       "A":    17,
[10:32:03.469][I][quietcool:664]:       "N":    "East Fan ",
[10:32:03.469][I][quietcool:664]:       "M":    "1",
[10:32:03.469][I][quietcool:664]:       "S":    "",
[10:32:03.469][I][quietcool:664]:       "G":    "No"
[10:32:03.469]}
[10:32:04.562][I][quietcool:140]: [west] Sending: {"A":1}
[10:32:04.628][I][quietcool:754]: [west] Received JSON data: QQ{
[10:32:04.628]}
[10:32:04.630][I][quietcool:125]: [east] Sending: {"A":13,"P":"redacted"}
[10:32:04.882][I][quietcool:664]: [east] Received JSON data: QQ{
[10:32:04.882][I][quietcool:664]:       "A":    13,
[10:32:04.882][I][quietcool:664]:       "R":    "Success",
[10:32:04.882][I][quietcool:664]:       "P":    "No"
[10:32:04.882]}
[10:32:07.192][I][quietcool:125]: [east] Sending: {"A":2}
[10:32:07.436][I][quietcool:664]: [east] Received JSON data: QQ{
[10:32:07.436][I][quietcool:664]:       "A":    2,
[10:32:07.436][I][quietcool:664]:       "B":    "Idle",
[10:32:07.436][I][quietcool:664]:       "C":    "TWO",
[10:32:07.436][I][quietcool:664]:       "D":    255,
[10:32:07.436][I][quietcool:664]:       "F":    80,
[10:32:07.436][I][quietcool:664]:       "G":    85,
[10:32:07.436][I][quietcool:664]:       "H":    60,
[10:32:07.436][I][quietcool:664]:       "I":    "LOW",
[10:32:07.436][I][quietcool:664]:       "J":    1,
[10:32:07.436][I][quietcool:664]:       "K":    0,
[10:32:07.436][I][quietcool:664]:       "L":    "LOW",
[10:32:07.436][I][quietcool:664]:       "M":    "No"
[10:32:07.436]}
[10:32:08.552][I][quietcool:125]: [east] Sending: {"A":1}
[10:32:08.693][I][quietcool:664]: [east] Received JSON data: QQ{
[10:32:08.693][I][quietcool:664]:       "A":    1,
[10:32:08.693][I][quietcool:664]:       "M":    "Idle",
[10:32:08.693][I][quietcool:664]:       "R":    "CLOSE",
[10:32:08.693][I][quietcool:664]:       "S":    "OK",
[10:32:08.693][I][quietcool:664]:       "T":    502,
[10:32:08.693][I][quietcool:664]:       "H":    79,
[10:32:08.693][I][quietcool:664]:       "C":    0
[10:32:08.693]}
[10:32:11.365][I][quietcool:140]: [west] Sending: {"A":17}
[10:32:11.428][I][quietcool:754]: [west] Received JSON data: QQ{
[10:32:11.428]}
[10:32:11.433][I][quietcool:125]: [east] Sending: {"A":13,"P":"redacted"}
[10:32:11.629][I][quietcool:664]: [east] Received JSON data: QQ{
[10:32:11.629][I][quietcool:664]:       "A":    13,
[10:32:11.629][I][quietcool:664]:       "R":    "Success",
[10:32:11.629][I][quietcool:664]:       "P":    "No"
[10:32:11.629]}
[10:32:13.279][I][quietcool:125]: [east] Sending: {"A":17}
[10:32:13.531][I][quietcool:664]: [east] Received JSON data: QQ{
[10:32:13.531][I][quietcool:664]:       "A":    17,
[10:32:13.531][I][quietcool:664]:       "N":    "East Fan ",
[10:32:13.531][I][quietcool:664]:       "M":    "1",
[10:32:13.531][I][quietcool:664]:       "S":    "",
[10:32:13.531][I][quietcool:664]:       "G":    "No"
[10:32:13.531]}
[10:32:14.611][I][quietcool:140]: [west] Sending: {"A":1}
[10:32:14.709][I][quietcool:754]: [west] Received JSON data: QQ{
[10:32:14.709]}
[10:32:14.711][I][quietcool:125]: [east] Sending: {"A":13,"P":"redacted"}
[10:32:14.875][I][quietcool:664]: [east] Received JSON data: QQ{
[10:32:14.875][I][quietcool:664]:       "A":    13,
[10:32:14.875][I][quietcool:664]:       "R":    "Success",
[10:32:14.875][I][quietcool:664]:       "P":    "No"
[10:32:14.875]}
[10:32:18.558][I][quietcool:125]: [east] Sending: {"A":1}
[10:32:18.778][I][quietcool:664]: [east] Received JSON data: QQ{
[10:32:18.778][I][quietcool:664]:       "A":    1,
[10:32:18.778][I][quietcool:664]:       "M":    "Idle",
[10:32:18.778][I][quietcool:664]:       "R":    "CLOSE",
[10:32:18.778][I][quietcool:664]:       "S":    "OK",
[10:32:18.778][I][quietcool:664]:       "T":    502,
[10:32:18.778][I][quietcool:664]:       "H":    79,
[10:32:18.778][I][quietcool:664]:       "C":    0
[10:32:18.778]}
[10:32:24.691][I][quietcool:140]: [west] Sending: {"A":1}
[10:32:24.743][W][component:297]: ble_client.text_sensor set Warning flag: unspecified
[10:32:24.768][I][esp32_ble_client:111]: [1] [redacted] 0x00 Connecting
[10:32:24.949][I][esp32_ble_client:321]: [1] [redacted] Connection open
[10:32:24.951][I][ble_text_sensor:041]: [BLE Data West] Connected successfully!
[10:32:25.686][I][esp32_ble_client:425]: [1] [redacted] Service discovery complete
[10:32:27.693][I][quietcool:140]: [west] Sending: {"A":13,"P":"redacted"}
[10:32:27.825][I][quietcool:754]: [west] Received JSON data: QQ{
[10:32:27.825][I][quietcool:754]:       "A":    13,
[10:32:27.825][I][quietcool:754]:       "R":    "Success",
[10:32:27.825][I][quietcool:754]:       "P":    "No"
[10:32:27.825]}
[10:32:28.560][I][quietcool:125]: [east] Sending: {"A":1}
[10:32:28.772][I][quietcool:664]: [east] Received JSON data: QQ{
[10:32:28.772][I][quietcool:664]:       "A":    1,
[10:32:28.772][I][quietcool:664]:       "M":    "Idle",
[10:32:28.772][I][quietcool:664]:       "R":    "CLOSE",
[10:32:28.772][I][quietcool:664]:       "S":    "OK",
[10:32:28.772][I][quietcool:664]:       "T":    502,
[10:32:28.772][I][quietcool:664]:       "H":    79,
[10:32:28.772][I][quietcool:664]:       "C":    0
[10:32:28.772]}

Sorry for the long output, I thought it was illustrative as you can see at the beginning east was working but west was returning QQ{} (empty json) because the connection it had at the time was no good. It eventually reconnected and everything worked again.

To see A:13 R:Fail P:No implies the login (api 13) failed, so the pairing does not seem to have succeeded. That said, you have logs that it succeeded from an earlier comment:

[01:51:33.401][D][quietcool:340]: Received JSON data: {
[01:51:33.401][D][quietcool:340]: 	"Api":	"Pair",
[01:51:33.401][D][quietcool:340]: 	"Result":	"Success"
[01:51:33.401]}

Suspecting yet another API change, I took a look at the latest app. At a cursory glance it doesn't seem like its different at least in how it communicates. You can see this yourself if you can sniff the BLE comms your phone makes to the fan (easy-ish on an Android device, iOS requires a mac with xcode). I have an iOS device but no mac, so I ended up discovering the API change via static analysis of the android app.

An elementary question and a bit of a reach: did you change the Pair ID property? The one whose default is a1b2c1d2a2b1c2d1? I changed it for my production use to a random value (same number of characters. Maybe it's worth giving a try? You will need to pair the fan with the esphome device again (and if you choose to try, it's worth not bothering to reflash to the other esphome version for now). This is what is sent when you pair or login, and I wonder if somehow that pair id is not recognized by the fan. Also, if you ever change that id, you'll need to pair the esphome with the fan again.

If that doesn't work, I'm unfortunately out of ideas. My guess is still that somehow your esphome is not properly paired.

@xsorifc28
Copy link

xsorifc28 commented Dec 24, 2025

Alright, I seem to have figured out the issue, but not a pretty solution.

I decompiled the latest app and with some LLM help, reversed it to understand API changes, and also did not see anything significant. After some back and forth, I thought why not pair a new phone via the app - I tried with the iOS app, and after the app tying to pair for some time, it returned "device memory full". This probably happened while I sent a bunch of pairing requests with unique pair IDs (not recognizing that I needed to use your branch to have it work right, doing it on the "original" "non-QQ" api).

I then recalled I had a JS script from long ago that I paired with, which I used a 10 character pairing ID..so I figured I'll use that as my pairing id with this branch that sets the min length to 10, and I've got data coming through.

So all is working now.

The problem? I can't access my fan (closed up in attic) to do a physical "pair request" so looks like I'm stuck with the pairings in there.

  1. Interesting bug in the firmware though - it sends a "paired" success but does not commit it to flash/memory - hence the "login" api returns "fail".
  2. Would be nice if the device could be put in to pairing mode after factory reset...too risky for me to try.

@kentonr
Copy link
Author

kentonr commented Dec 24, 2025

Im glad you got it to work in the end. It’s surprising that it could run out of memory, there must be quite a lot of pair ids in there.

I agree with you about (2), I’m in a similar boat where my fans are inaccessible and I can’t risk factory resetting them and needing physical access to do the initial pair either.

Happy holidays!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants