Coded PHY (S8) Scanning Not Working in update-nimble Branch (for Xiaomi Thermometer with ATC firmware)

I’m encountering an issue with the update-nimble branch of OpenMQTTGateway where Long Range BLE scanning (Coded PHY, S8) does not detect my Xiaomi thermometer flashed with ATC 1441 firmware set in long range.

A test sketch using NimBLE-Arduino 2.3.1 and framework-arduinoespressif32 @ 3.2.0 successfully detects the thermometer in long range, confirmed via nRF Connect to use Coded PHY.

However, OMG fails to detect the device, likely due to missing Coded PHY configuration in gatewayBT.cpp.

If I change the thermometer in normal mode (1M) it is detected and report to Mqtt and home Assistant but has shorter range.

EnvironmentHardware: ESP32-S3-DevKitC-1 Version: update-nimble branch ([commit reference, e.g., latest as of June 27, 2025])

PlatformIO Configuration:

Platform: espressif32

Framework: arduino, framework-arduinoespressif32 @ GitHub - espressif/arduino-esp32: Arduino core for the ESP32 NimBLE-Arduino @ 2.3.1 (h2zero/NimBLE-Arduino)

Board: esp32-s3-devkitc-1

Environment: esp32s3-dev-c1-ble

Thermometer: Xiaomi LYWSD03MMC with custom ATC firmware (pvvx/ATC_MiThermometer), in “ATC” advertising mode, supporting Long Range (Coded PHY, S8).

Operating System: WindowsIDE: Visual Studio Code with PlatformIO

Interesting test, @h2zero may have an idea

Coded PHY, which requires extended advertising to be enabled in the Nimble stack, is not available currently in OMG as it does not enable it in the stack. This is an enhancement that could be added later but would require different build flags.

I have solved this by using the following flags for NIMBLE in platformio :
build_flags =
${env.build_flags}
‘-DCONFIG_BT_NIMBLE_ROLE_PERIPHERAL_DISABLED’
‘-DCONFIG_BT_NIMBLE_ROLE_BROADCASTER_DISABLED’
‘-DCONFIG_BT_NIMBLE_EXT_ADV=1’

And modifying gatewayBT.cpp adding this line under void Blescan

pBLEScan->setPhy(NimBLEScan::SCAN_CODED); // Coded PHY (S8)

It is now reading the Xiaomi thermometer with ATC firwware set in Long range correctly.

1 Like

Great, I’m curious on what range you are getting with the LYWSD03MMC ?

Here are the signal on a thermometer in long-range coded/ S8) and one in legacy (S1/1m) booth set at +3dB screenshots at 175 meters outside in a city street (probably noisy in 2.4ghz).
That is done with my tablet and nrf connect. I did not made the test with ESP32-s3 yet as my change limit the scan on ble coded only so not great to compare.
Tablet used with NRF connect is Xiaomi pad6 pro (bluetooth5.3).
No significant difference in dB but better range as the signal decoding is more consistent in low levels.
NRF connect stop reporting under - 100dB.
It makes sense, my wife who is hearing impaired always says no need to speak very load, speak slowly and clearly and if I don’t understand repeat using other words.
It is the same idea in S8 protocol slowly 125 kbps and using more bits to keep a better BER. :blush:.

1 Like

Even better if you don’t want to loose 1M (legacy) reception you can do alternative scanning

void BLEscan() {
static bool useCodedPhy = false; // Start with 1M

while (uxQueueMessagesWaiting(BLEQueue) || queueLength != 0) {
delay(1);
}
Log.notice(F(“Scan begin” CR));
BLEScan* pBLEScan = BLEDevice::getScan();

if (useCodedPhy) {
pBLEScan->setPhy(NimBLEScan::SCAN_CODED);
Log.notice(F(“Scanning with Coded PHY (S8)” CR));
} else {
pBLEScan->setPhy(NimBLEScan::SCAN_1M);
Log.notice(F(“Scanning with 1M PHY” CR));
}
useCodedPhy = !useCodedPhy; // Alternate for next scan

pBLEScan->setScanCallbacks(&scanCallbacks);
if ((millis() > (timeBetweenActive + BTConfig.intervalActiveScan) || BTConfig.intervalActiveScan == BTConfig.BLEinterval) && !BTConfig.forcePassiveScan) {
pBLEScan->setActiveScan(true);
timeBetweenActive = millis();
} else {
pBLEScan->setActiveScan(false);
}
pBLEScan->setInterval(BLEScanInterval);
pBLEScan->setWindow(BLEScanWindow);
NimBLEScanResults foundDevices = pBLEScan->getResults(BTConfig.scanDuration, false);
if (foundDevices.getCount())
scanCount++;
Log.notice(F(“Found %d devices, scan number %d end” CR), foundDevices.getCount(), scanCount);
Log.trace(F(“Process BLE stack free: %u” CR), uxTaskGetStackHighWaterMark(xProcBLETaskHandle));
}