Skip to content

vesync_scale_sniff.tc

vesync_scale_sniff.tc — does the Etekcity ESF37 broadcast weight in its advertisement?

Source on GitHub

// vesync_scale_sniff.tc — does the Etekcity ESF37 broadcast weight in its advertisement?
//
// PURPOSE (pre-check before the localKey/Tuya-handshake route):
//   The ESF37 is a Tuya-BLE device — a connect+read needs the per-device localKey.
//   BUT some scales also put the live weight straight into the BLE advert (manufacturer
//   data, company-id 0x06D0). If THIS unit does, we can read it passively with zero
//   secrets. This script finds out.
//
// HOW IT WORKS:
//   Continuous bleScan. For every advert whose manufacturer-id is 0x06D0 (Etekcity/VeSync),
//   it dumps ALL manufacturer bytes as hex — but only when they CHANGE (position-weighted
//   checksum), so the log isn't flooded by identical idle beacons. Step on the scale and
//   watch: if any byte tracks your weight, new lines will stream while the reading settles,
//   and the changing bytes are the weight field. If the bytes never change (just a static
//   beacon) → the advert carries no weight, and we must do the encrypted local read.
//
// DEPLOY: a USE_TINYC_BLE build (e.g. .39, env tinyc32s3-ble). bleScan auto-enables BLE.
// READ THE LOG via UDP syslog (preferred) or the web console /cs.
//
// CAVEAT: the BLE API exposes name + manufacturer-data (AD type 0xFF) only — NOT service-data
//   (AD type 0x16). If the scale hides weight in a service-data field, this sniff won't see it
//   (false negative); that still leaves the localKey route. Manufacturer data is the usual
//   place, and 0x06D0 is exactly what this unit advertises, so it's the right first look.

char nm[40];       // advert local name (ESF37 usually empty)
int  mac[8];       // 6 MAC bytes (display order)
int  mfg[40];      // manufacturer-specific data bytes (incl. 2-byte company id, little-endian)

int  started;
int  seen;         // have we logged the scale's identity banner yet?
int  lastsum;      // position-weighted checksum of last logged mfg payload
int  lastlen;      // length of last logged mfg payload
int  hb;           // heartbeat tick

int main() {
  started = 0; seen = 0; lastsum = -1; lastlen = -1; hb = 0;
  return 0;
}

void TaskLoop() {
  if (started == 0) {
    bleScan(0);
    started = 1;
    addLog("sniff: scanning — step on the scale to wake it");
  }

  int got = bleNext();
  while (got) {
    bleMac(mac);
    int r  = bleRssi();
    int nl = bleName(nm);
    int ml = bleMfg(mfg);

    // Is this our scale? mfr-id 0x06D0 (bytes little-endian: [0]=0xD0 [1]=0x06), or a name match.
    int isScale = 0;
    if (ml >= 2 && mfg[0] == 0xD0 && mfg[1] == 0x06) { isScale = 1; }
    if (strFind(nm, "Etekcity") >= 0 || strFind(nm, "Fitness") >= 0 || strFind(nm, "QN-Scale") >= 0) { isScale = 1; }

    if (isScale) {
      // One-time identity banner: MAC + address type + name.
      if (seen == 0) {
        seen = 1;
        char b[100];
        sprintf(b, "sniff: SCALE seen mac=%02x:%02x:%02x:%02x:%02x:%02x type=%d name=%s",
                mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], bleAddrType(), nm);
        addLog(b);
      }

      // Position-weighted checksum so a change in ANY byte (even a swap) is detected.
      int sum = 0;
      int i = 0;
      while (i < ml) { sum = sum + (mfg[i] & 0xFF) * (i + 3); i = i + 1; }

      if (sum != lastsum || ml != lastlen) {
        lastsum = sum; lastlen = ml;

        // Build the full payload as lowercase hex (skip nothing — company id included).
        char hex[84];
        int hi = 0;
        i = 0;
        while (i < ml) {
          int by = mfg[i] & 0xFF;
          int n1 = (by >> 4) & 0xF;
          int n2 = by & 0xF;
          if (n1 < 10) { hex[hi] = 48 + n1; } else { hex[hi] = 87 + n1; }
          hi = hi + 1;
          if (n2 < 10) { hex[hi] = 48 + n2; } else { hex[hi] = 87 + n2; }
          hi = hi + 1;
          i = i + 1;
        }
        hex[hi] = 0;

        char m[140];
        sprintf(m, "sniff: rssi=%d len=%d mfg=%s", r, ml, hex);
        addLog(m);
      }
    }
    got = bleNext();
  }

  // Heartbeat every ~6 s so we know the script is alive while the scale sleeps.
  hb = hb + 1;
  if (hb >= 20) { hb = 0; if (seen == 0) { addLog("sniff: ...waiting, step on the scale"); } }
  delay(300);
}