Skip to content

Commit 53ed2d2

Browse files
committed
feat(scales): add the Silvergear Smart Scale 108 broadcast adapter
The unit is not connectable. Two iOS PacketLogger captures of complete weigh-ins contain LE advertising reports and no ATT traffic at all, which matches the reporter's nRF Connect finding, so a GATT connect was never going to work: BlueZ discards the Device1 object for a non-connectable peer the moment discovery stops, which is where the original "Connect doesn't exist" error came from. Everything the scale reports is in its manufacturer data, under an invented company id 0xA0AC. Twelve bytes: the device's own MAC reversed, then a six-byte payload. XOR every payload byte with 0xA0 except the last two, which are in clear, and it reads: d[0] status, bit 7 set = settled d[1..3] weight, 24-bit big-endian grams, biased by 0x8C0000 p[4] frame type, 0x0D weight, 0x06 post-weigh-in p[5] checksum, 0xA0 + ((p0+p1+p2+p3+p4) & 0x1F) Verified against both captures: the checksum closes on 127 of 127 frames, the settled frames read 108.480 kg and 5.610 kg against displays of 108.5 and 5.6, and the idle frame that appears byte for byte in BOTH captures reads exactly zero, which is what fixes the bias rather than fitting it to one session. Only settled frames are published. The settling stream swings from 39.60 through 107.03 kg on the way up in the 108.5 kg capture, so publishing any of it would publish a weight the scale never showed. The type 0x06 frame is logged and not published. Its field reads 529 for the 108.5 kg adult and 0 for the 5.6 kg object, which is the right shape and magnitude for a whole-body impedance, but one sample is not a decode. The node-ble passive path only ever read ServiceData, so a manufacturer-data broadcast scale would have worked on Noble and been dead on a Raspberry Pi and in the add-on. It now reads ManufacturerData through parseBroadcast as well, and both passive gates accept either parser instead of requiring parseServiceData. The pre-connect match also stopped throwing away the advertisement snapshot it had already read. Matching on the local name alone could not reach this device at all, since it calls itself "108", while its manufacturer data identifies it exactly. The snapshot result feeds the match only for the passive branch, so a device that now matches a connect-based adapter falls through as before and is re-resolved after discovery. Closes #297
1 parent 74bd6cd commit 53ed2d2

11 files changed

Lines changed: 505 additions & 98 deletions

File tree

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ Unit tests use [Vitest](https://vitest.dev/) and cover:
4949
- **Config writing** — atomic file write, write lock serialization, YAML comment preservation, debounced weight updates
5050
- **User matching** — 4-tier weight matching, all strategies (nearest/log/ignore), overlapping ranges, drift detection
5151
- **Environment validation**`validate-env.ts` (all validation rules and edge cases)
52-
- **Scale adapters**`parseNotification()`, `matches()`, `isComplete()`, `computeMetrics()`, and `onConnected()` for all 30 adapters
52+
- **Scale adapters**`parseNotification()`, `matches()`, `isComplete()`, `computeMetrics()`, and `onConnected()` for all 31 adapters
5353
- **Exporters** — config parsing, MQTT publish/HA discovery, MQTT multi-user topic routing + per-user HA discovery, Garmin subprocess, Webhook/InfluxDB/Ntfy delivery, ExportContext, ntfy drift warning
5454
- **Multi-user flow** — matching → profile resolution → exporter resolution → ExportContext construction, strategy fallback, tiebreak with last_known_weight
5555
- **Orchestrator** — healthcheck runner, export dispatch, parallel execution, partial/total failure handling

docs/alternatives.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ head:
2525
| **Push notifications** | Ntfy | No | No | App only |
2626
| **Local file export** | CSV and JSONL | SQLite | No | No |
2727
| **Multi-user** | Automatic weight matching | Manual selection | Per-user sync | Per-account |
28-
| **Supported scales** | 30 protocol adapters | 20+ brands | Via openScale | 1 (own brand) |
28+
| **Supported scales** | 31 protocol adapters | 20+ brands | Via openScale | 1 (own brand) |
2929
| **Body composition** | 10 metrics (BIA) | Varies | 4 metrics | Varies |
3030
| **Docker** | Multi-arch images | No | No | No |
3131
| **Open source** | GPL-3.0 | GPL-3.0 | GPL-3.0 | No |

docs/faq.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ head:
99
- - script
1010
- type: application/ld+json
1111
- |-
12-
{"@context":"https://schema.org","@type":"FAQPage","mainEntity":[{"@type":"Question","name":"Do I need a smartphone?","acceptedAnswer":{"@type":"Answer","text":"No. BLE Scale Sync is designed to replace the vendor phone app. Reads happen entirely on your server (Raspberry Pi, NAS, PC, Home Assistant host, or Docker host). Once the service runs, stepping on the scale is the only user action."}},{"@type":"Question","name":"Do I need Home Assistant?","acceptedAnswer":{"@type":"Answer","text":"No. The Home Assistant Add-on is one deployment target out of several. You can run the app on plain Docker, bare Node.js, or as the HA add-on. All three speak MQTT auto-discovery so the data can still appear in HA either way."}},{"@type":"Question","name":"Does it work offline without WiFi?","acceptedAnswer":{"@type":"Answer","text":"BLE read and body composition calculation are fully offline. Local exporters (File CSV/JSONL, a local MQTT broker, a local InfluxDB) work without internet. Cloud exporters (Garmin Connect, Strava, public Ntfy) need internet at the moment of export. There is no retry queue, so a reading taken while cloud is unreachable is lost for that specific exporter."}},{"@type":"Question","name":"Is my scale supported?","acceptedAnswer":{"@type":"Answer","text":"There are 31 scale adapters covering Xiaomi, Renpho, QN Scale, Eufy, Yunmai, Beurer, Sanitas, Medisana, Soehnle, and many unbranded Asian scales that reuse Renpho or QN protocols. Check the supported scales page for the full list. Auto-detect covers most cases."}},{"@type":"Question","name":"What does the update check send?","acceptedAnswer":{"@type":"Answer","text":"One GET request per 24 hours to api.blescalesync.dev/version after a successful measurement. Only the app version, OS, and CPU architecture are sent via the User-Agent header. No readings, no MAC address, no user data. The check is disabled automatically when CI=true, and can be turned off with update_check: false in config.yaml."}},{"@type":"Question","name":"Why are my body fat numbers different from the manufacturer app?","acceptedAnswer":{"@type":"Answer","text":"Scale vendors keep their BIA formulas closed and tune them for their own hardware. This project uses openScale-derived formulas, plus the Deurenberg fallback when impedance is not available. Absolute numbers will differ by a few percent from the vendor app, which is normal for any consumer BIA device. Use the trend, not the absolute value, for tracking."}},{"@type":"Question","name":"Does Garmin Connect 2FA / MFA work?","acceptedAnswer":{"@type":"Answer","text":"Yes. The setup wizard prompts for the 2FA code during the first login. Tokens are cached and reused for roughly twelve months, so the code is only needed once per cycle. Headless servers authenticate interactively during setup-garmin."}},{"@type":"Question","name":"Can I use multiple scales at once?","acceptedAnswer":{"@type":"Answer","text":"Not yet in a single instance. A workaround is to run two independent services with separate config files. Full multi-scale aggregation is tracked in GitHub issue 124."}}]}
12+
{"@context":"https://schema.org","@type":"FAQPage","mainEntity":[{"@type":"Question","name":"Do I need a smartphone?","acceptedAnswer":{"@type":"Answer","text":"No. BLE Scale Sync is designed to replace the vendor phone app. Reads happen entirely on your server (Raspberry Pi, NAS, PC, Home Assistant host, or Docker host). Once the service runs, stepping on the scale is the only user action."}},{"@type":"Question","name":"Do I need Home Assistant?","acceptedAnswer":{"@type":"Answer","text":"No. The Home Assistant Add-on is one deployment target out of several. You can run the app on plain Docker, bare Node.js, or as the HA add-on. All three speak MQTT auto-discovery so the data can still appear in HA either way."}},{"@type":"Question","name":"Does it work offline without WiFi?","acceptedAnswer":{"@type":"Answer","text":"BLE read and body composition calculation are fully offline. Local exporters (File CSV/JSONL, a local MQTT broker, a local InfluxDB) work without internet. Cloud exporters (Garmin Connect, Strava, public Ntfy) need internet at the moment of export. There is no retry queue, so a reading taken while cloud is unreachable is lost for that specific exporter."}},{"@type":"Question","name":"Is my scale supported?","acceptedAnswer":{"@type":"Answer","text":"There are 32 scale adapters covering Xiaomi, Renpho, QN Scale, Eufy, Yunmai, Beurer, Sanitas, Medisana, Soehnle, and many unbranded Asian scales that reuse Renpho or QN protocols. Check the supported scales page for the full list. Auto-detect covers most cases."}},{"@type":"Question","name":"What does the update check send?","acceptedAnswer":{"@type":"Answer","text":"One GET request per 24 hours to api.blescalesync.dev/version after a successful measurement. Only the app version, OS, and CPU architecture are sent via the User-Agent header. No readings, no MAC address, no user data. The check is disabled automatically when CI=true, and can be turned off with update_check: false in config.yaml."}},{"@type":"Question","name":"Why are my body fat numbers different from the manufacturer app?","acceptedAnswer":{"@type":"Answer","text":"Scale vendors keep their BIA formulas closed and tune them for their own hardware. This project uses openScale-derived formulas, plus the Deurenberg fallback when impedance is not available. Absolute numbers will differ by a few percent from the vendor app, which is normal for any consumer BIA device. Use the trend, not the absolute value, for tracking."}},{"@type":"Question","name":"Does Garmin Connect 2FA / MFA work?","acceptedAnswer":{"@type":"Answer","text":"Yes. The setup wizard prompts for the 2FA code during the first login. Tokens are cached and reused for roughly twelve months, so the code is only needed once per cycle. Headless servers authenticate interactively during setup-garmin."}},{"@type":"Question","name":"Can I use multiple scales at once?","acceptedAnswer":{"@type":"Answer","text":"Not yet in a single instance. A workaround is to run two independent services with separate config files. Full multi-scale aggregation is tracked in GitHub issue 124."}}]}
1313
---
1414

1515
# Frequently Asked Questions
@@ -62,7 +62,7 @@ Yes. Set `update_check: false` in `config.yaml`, or run with `CI=true` in the en
6262

6363
### Is my scale supported?
6464

65-
Check the [supported scales](/guide/supported-scales) page for the full list of 30 adapters. Coverage includes Xiaomi, Renpho (Elis 1, FITINDEX, Sencor, QN Scale), Eufy, Yunmai, Beurer (incl. BF720 / BF105 / BF500 / BF788 / BF950), Sanitas, Medisana, Soehnle, and many unbranded Asian scales that reuse the Renpho or QN protocol. Auto-detect matches most devices without any MAC pinning.
65+
Check the [supported scales](/guide/supported-scales) page for the full list of 31 adapters. Coverage includes Xiaomi, Renpho (Elis 1, FITINDEX, Sencor, QN Scale), Eufy, Yunmai, Beurer (incl. BF720 / BF105 / BF500 / BF788 / BF950), Sanitas, Medisana, Soehnle, and many unbranded Asian scales that reuse the Renpho or QN protocol. Auto-detect matches most devices without any MAC pinning.
6666

6767
### My scale is not listed. Can I add it?
6868

0 commit comments

Comments
 (0)