Integrate QN-Scale with Bluetooth (BLE) Help Wanted

Hi all,
I’m trying to integrate my QN-Scale (From Kamtron CS20M, similar models are FitIndex ES-26M / Elektra Scale / RENPHO ES-CS20M / RENPHO ES-30M / Kamtron CS20M) via Bluetooth.

I’ve made some code changes (similar to this: Add Meater BBQ sensor by 1technophile · Pull Request #1000 · 1technophile/OpenMQTTGateway · GitHub)

The Scale is supported by the openScale App (Driver here: openScale/BluetoothQNScale.java at master · oliexdev/openScale · GitHub).

The OMG does find the Scale, I’m reading the Monitor. It does

N: BLE Connect begin
T: Model to connect found: A4:C1:38:F8:E3:94
N: Failed to find service UUID: 0000ffe0-0000-1000-8000-00805f9b34fb
N: Failed registering notification

When the Scale Sleeps:

N: BLE Connect begin
T: Model to connect found: A4:C1:38:F8:E3:94
E NimBLEClient: "Connection failed; status=13 "
E: Connect to: a4:c1:38:f8:e3:94 failed
N: Failed registering notification

ZGatewayBLEconnect.h:

class QNSCALE_connect : public zBLEConnect {
  std::vector<uint8_t> m_data;
  void notifyCB(NimBLERemoteCharacteristic* pChar, uint8_t* pData, size_t length, bool isNotify);

public:
  QNSCALE_connect(NimBLEAddress& addr) : zBLEConnect(addr) {}
  void publishData() override;
};

/*-----------------------QN-Scale EXPERIMENTAL-----------------------*/
/* This is experimental and should be used for testing only.
 * At this time no testing has been done so this is only an example of extendability.
 */
void QNSCALE_connect::notifyCB(NimBLERemoteCharacteristic* pChar, uint8_t* pData, size_t length, bool isNotify) {
  if (!ProcessLock) {
    Log.trace(F("Callback from %s characteristic" CR), pChar->getUUID().toString().c_str());
    if (pData[0] == 1) {
      Log.trace(F("Device identified creating BLE buffer" CR));
      JsonObject& BLEdata = getBTJsonObject();
      String mac_adress = m_pClient->getPeerAddress().toString().c_str();
      mac_adress.toUpperCase();

      BLEdata.set("model", "QN-Scale");
      BLEdata.set("id", (char*)mac_adress.c_str());
      Log.trace(F("Device identified in CB: %s" CR), (char*)mac_adress.c_str());
      BLEdata.set("batt", (int)pData[1]);
      BLEdata.set("version", (float)pData[2] / 10.0);
      BLEdata.set("num_timers", (int)pData[8]);
      BLEdata.set("mode", (pData[9] & 0x08) ? "switch" : "press");
      BLEdata.set("inverted", (bool)(pData[9] & 0x01));
      BLEdata.set("hold_secs", (int)pData[10]);

      pubBT(BLEdata);
    } else {
      Log.notice(F("Device not identified" CR));
    }
  } else {
    Log.trace(F("Callback process canceled by processLock" CR));
  }
  xTaskNotifyGive(m_taskHandle);
}

void QNSCALE_connect::publishData() {
  NimBLEUUID serviceUUID("0000ffe0-0000-1000-8000-00805f9b34fb");
  NimBLEUUID charUUID("0000ffe1-0000-1000-8000-00805f9b34fb");
  NimBLERemoteCharacteristic* pChar = getCharacteristic(serviceUUID, charUUID);

  if (pChar && pChar->canNotify()) {
    Log.trace(F("Registering notification" CR));
    pChar->subscribe(true, std::bind(&QNSCALE_connect::notifyCB, this,
                                     std::placeholders::_1, std::placeholders::_2,
                                     std::placeholders::_3, std::placeholders::_4));
    m_taskHandle = xTaskGetCurrentTaskHandle();
    ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(BLE_CNCT_TIMEOUT));
  } else {
    Log.notice(F("Failed registering notification" CR));
  }
}

Here are the screenshots from nRF Connect:

Any hints how to be more succsessful?

One more screenshot:

You may use a callback instead of a direct read so as to leverage the NOTIFY function of the characteristic. The LYWSD03MMC use a callback you can use it as an example.

I’ve made some progress and was able to connect and read a characteristic. Now I’m struggling a bit to set the code up as in the driver from the openScale App to send the right info and receive the weight. I’ll report back when I find time to figure it out!

1 Like

@ChiefGlider I can help you with some of this.

Instead of using the 128 bit uuid try using the 16 bit value
i.e. change these:

  NimBLEUUID serviceUUID("0000ffe0-0000-1000-8000-00805f9b34fb");
  NimBLEUUID charUUID("0000ffe1-0000-1000-8000-00805f9b34fb");

to:

  NimBLEUUID serviceUUID("ffe0");
  NimBLEUUID charUUID("ffe");

There may be a comparison flaw with the NimBLE library that does not resolve the 128 bit version correctly, I’d appreciate feedback on this so I may correct it.

E NimBLEClient: "Connection failed; status=13 "

This is a connection timeout, the device may no longer be listening for connections when the attempt is made. try shortening the scan time.

if (pChar && pChar->canNotify()) {
    Log.trace(F("Registering notification" CR));
    pChar->subscribe(true, std::bind(&QNSCALE_connect::notifyCB, this,
                                     std::placeholders::_1, std::placeholders::_2,
                                     std::placeholders::_3, std::placeholders::_4));
    m_taskHandle = xTaskGetCurrentTaskHandle();
    ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(BLE_CNCT_TIMEOUT));

This may result in the gateway hanging on the ulTaskNotifyTake. I suggest changing this to:

  if (pChar) && pChar->canNotify())  {
      Log.trace(F("Registering notification" CR));
      if (pChar->subscribe(true, std::bind(&QNSCALE_connect::notifyCB, this,
                                     std::placeholders::_1, std::placeholders::_2,
                                     std::placeholders::_3, std::placeholders::_4));
        m_taskHandle = xTaskGetCurrentTaskHandle();
        ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(BLE_CNCT_TIMEOUT)); 
      }

Please post back with your progress.

Hello! Do you have any progress on the integration of the qn-scale? I can see it with theengs on a esp32, but I do not get any MQTT messages.

Welcome @lowChecker

From the above it looks as if the properties of the QN-Scale can only be retrieved by connection, while the current BLE decoding for most other sensors’ properties is being decoded from the BLE advertising data.

This means an integrated decoder is not possible at this stage, but you can retrieve the data from the scale by using the READ functionality of OpenMQTTGateway, as described above.

Thank you!

Do I understand your answer/link right. I have to setup some program to listen for the Mac address of the scale and than send the specific packages via MQTT to subscribe to the notification?

I have to admit that I don’t know if the current development version of OpenMQTTGateway allows for notifications, but yes, the above mentioned service/characteristic combo ffe0/ffe1 seems to hold the relevant weighing properties.

After a lot testing and failing, I found a solution to send the weight data to my MQTT broker. In the first place I didn’t want to compile my own firmware, but that didn’t worked out. I also gave openmqttgateway on my ESP32 a chance, but I failed.
My solution is to use ESPhome with the following custom YAML derived from the OpenScale JAVA code.

# MQTT Configuration

mqtt:

broker: mqtt.domain.com

port: 1883

username: username

password: password

topic_prefix: “qnscale”

########################

# QN-Scale Bridge Configuration

########################

# Enable the BLE stack

esp32_ble_tracker:

# Time component is now required for writing the timestamp

time:

  • platform: homeassistant

id: esptime

ble_client:

  • mac_address: “AA:BB:CC:DD:FF”

id: qn_scale_client

on_connect:

then:

    - wait_until:

api.connected:

    - delay: 2s

# - logger.log: “Writing commands to scale…”

    - ble_client.ble_write: # <-- CORRECTED ACTION NAME

id: qn_scale_client

service_uuid: ‘ffe0’

characteristic_uuid: ‘ffe3’

# Magic bytes to request measurement in KG (checksum 0x42)

value: [0x13, 0x09, 0x15, 0x01, 0x10, 0x00, 0x00, 0x00, 0x42]

    - delay: 500ms

    - ble_client.ble_write: # <-- CORRECTED ACTION NAME

id: qn_scale_client

service_uuid: ‘ffe0’

characteristic_uuid: ‘ffe4’

# Write the current time to the scale

value: !lambda |-

long timestamp = id(esptime).now().timestamp - 946702800;

return {0x02,

                  (uint8_t)(timestamp & 0xFF),

                  (uint8_t)((timestamp >> 8) & 0xFF),

                  (uint8_t)((timestamp >> 16) & 0xFF),

                  (uint8_t)((timestamp >> 24) & 0xFF)};

sensor:

  • platform: ble_client

type: characteristic # ← THE FINAL REQUIRED FIX

ble_client_id: qn_scale_client

name: “QN Scale Weight”

id: qn_scale_weight

service_uuid: ‘ffe0’

characteristic_uuid: ‘ffe1’

notify: true

unit_of_measurement: ‘kg’

accuracy_decimals: 2

icon: ‘mdi:scale-bathroom’

lambda: |-

if (x.size() >= 6 && x[0] == 0x10 && x[5] == 0x01) {

float weight = ((x[3] & 0xFF) << 8 | (x[4] & 0xFF)) / 100.0;

    // ESP_LOGD("qn_scale", "Received final weight data: %.2f kg", weight);

return weight;

  } else if (x.size() >= 6 && x\[0\] == 0x10 && x\[5\] == 0x00) {

float unsteady_weight = ((x[3] & 0xFF) << 8 | (x[4] & 0xFF)) / 100.0;

    // ESP_LOGD("qn_scale", "Received unsteady weight: %.2f kg", unsteady_weight);

return {};

  } else {

std::string raw_hex;

for (int i = 0; i < x.size(); i++) {

char hex[4];

sprintf(hex, "%02X ", x[i]);

      raw_hex += hex;

    }

    // ESP_LOGD("qn_scale", "Received unhandled BLE data: %s", raw_hex.c_str());

return {};

  }

on_value:

then:

    - mqtt.publish:

topic: ${mqtt.topic_prefix}/weight

payload: !lambda ‘return to_string(x);’

retain: true