vesync_scale_sniff.tc¶
vesync_scale_sniff.tc — does the Etekcity ESF37 broadcast weight in its advertisement?
// 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);
}