Repository navigation
feat(bluetooth): support the iHealth HS2S family and the Wyze Scale (WHSCL1) - #1569
Open
HerbFargus wants to merge 1 commit into
Open
HerbFargus wants to merge 1 commit into
HerbFargus wants to merge 1 commit into
Conversation
…WHSCL1) The original Wyze Scale (WHSCL1) is an iHealth/Jiuan OEM build that speaks the iHealth HS2S protocol, so one handler covers the iHealth HS2S, the HS2S Pro and the Wyze Scale. - XXTEA challenge-response (iHealth IdentifyIns2). The HS2S and HS2S Pro derive their session key from the scale MAC and a model key shipped in iHealth's app. The Wyze Scale uses a per-scale key that Wyze serves from its cloud; the user fetches it once with wyze-scale-key and pastes it into the device settings. - Session as in the vendor apps: time sync, user update/select (retried after 0x24 while the scale is busy), paged offline records then delete, live result. The result is acked before disconnecting. - Body fat, water, muscle, bone and protein come from the scale; LBM, BMR and visceral fat follow the Wyze app's formulas (the HS2S reports its own visceral fat). Tested on a Wyze Scale (WHSCL1) with a Pixel 10 Pro. The HS2S and HS2S Pro are implemented from iHealth's SDK and are untested on hardware. Refs oliexdev#853 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 task done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Scale
Wyze Scale, model WHSCL1 (the original Wyze Scale, not the S/X/Ultra). Requested in #853.
It turned out to be an iHealth/Jiuan OEM build. Its GATT service is the ASCII string
com.jiuan.BFSV21(636f6d2e-6a69-7561-6e2e-424653563231), the model prefix isJA.SC, andit speaks iHealth's HS2S command set almost byte for byte. So the handler is written for the
family: iHealth HS2S (
com.jiuan.BFSV22), iHealth HS2S Pro (com.jiuan.HS2S02) and theWyze Scale (
com.jiuan.BFSV21). Notify and write characteristics are the same names withsed./rec.in place ofcom..Protocol
Frames are
B0|A0 len seqHi seqLo A9 cmd payload… sum(B0 host→scale, A0 scale→host,sum= sum of thelenbytes & 0xFF), split into 20-byte writes/notifications.Authentication is iHealth's
IdentifyIns2challenge-response:FA: host sends 16 random ASCII bytes R1.FB: scale answers R2 (16) · XXTEA(R1, K) (20) · seed (16), where seed = MAC + 10 zero bytes.FC: host checks R1 and replies XXTEA(R2, K). The scale acksFDor rejectsFE.XXTEA here is iHealth's variant: little-endian words, encrypt appends the plaintext length as
an extra word.
K = XXTEA(seed, Ka)with a per-model keyKa:Kaships in iHealth's MyVitals app, so K comes from the MAC alone.The handler reproduces a logged HS2S Pro handshake byte for byte (test below).
Ka. The Wyze app gets each scale's K ("device_key") fromwyze-scale-service.wyzecam.comand caches it. I checked every 16-byte constant in two Wyze appversions, iHealth's app and both native libraries; none is the master key. So the user fetches
their scale's key once with wyze-scale-key
(a small Python script using wyze-sdk and bleak) and pastes it into the device settings, as
with the S400 bind key. The key is fixed per scale: the one I fetched in 2026 authenticates my
2022 captures.
Session, in the order the vendor apps use: time sync (
10, reply carries the batterylevel) → create/update user (
21) → select user (23;01= busy, retry after the scale sends24) → offline records (31, paged with a read offset, then deleted with32) → live weight(
40), stable weight (41, acked) and result (42, acked, then disconnect).Records carry weight, impedances (4 on the WHSCL1, 6 on the HS2S), user number, gender, age,
height, a timestamp, and the scale's own body fat, muscle mass, bone mass, water and protein.
The WHSCL1 adds a body-type byte and ends with a 4-byte encrypted impedance that only feeds
metabolic age in a closed Holtek library, so it's ignored. LBM, BMR (
370 + 21.6 × LBM) andvisceral fat (
LBM × 0.142 − 11.218 + BMI × 0.433, clamped 1–59) follow the Wyze app's ownformulas, which reproduce its values on all four weighings I compared. The HS2S reports its own
visceral fat, which is used instead.
What works
Tested on a Wyze Scale WHSCL1 with a Pixel 10 Pro (GrapheneOS), debug build of this branch:
FD, every time.62.7 kg, bone 4.1 kg, protein 14.7 %, LBM 66.8 kg, BMR 1814, visceral fat 9. The Wyze cloud
record of an earlier weighing matched the decoded fields exactly.
42ack is delivered before disconnecting. (The first build disconnected straight away,the queued ack was cancelled, and the scale kept the result. Fixed by waiting 1 s, as
TaylorBIAHandlerdoes.)Not tested on hardware: iHealth HS2S and HS2S Pro (I don't have one). Both follow iHealth's
own
Hs2sInsSetparser and user-record builders; the HS2S Pro key derivation is verifiedagainst a real iHealth handshake. Their catalog remarks say "untested".
Changes
IHealthHs2sLib: framing, reassembly, XXTEA, key derivation and challenge answer, user recordand offline request builders, record parsers, the Wyze BMR/visceral-fat formulas. No Android
types, so it's unit tested directly.
IHealthHs2sHandler: detection by service UUID (name fallbackWHSCL1/HS2S/HS2S PRO),the session above, and a device-key field in
DeviceConfigurationUi(same pattern as the S400).ScaleFactory: registers the handler afterIHealthHS3Handler.strings.xml: device-key label, description, error and three warnings.ScaleCatalogfixtures andscale_catalog_remarks.txtlines for the three scales.IHealthHs2sLibTest(14 tests), using public data only:FCbyte for byte);FArebuilt,FBreassembled, theresult decoded to the values the Wyze app showed, the user record matching what the Wyze app
sent, and the visceral-fat/BMR formulas against the app.
./gradlew testDebugUnitTest compileDebugAndroidTestKotlin assembleDebug: everything passesexcept 4 tests in
BackupRestoreUseCasesTest("Main DB file 'openScale.db' is not a validopenScale database"), which fail the same way on unmodified
masteron my Windows machine.Closes #853
🤖 Generated with Claude Code