Skip to content

feat(bluetooth): support the Urikar Aero I in TaylorBIAHandler - #1567

Open
kashif-khan wants to merge 1 commit into
oliexdev:masterfrom
kashif-khan:feat/aero-i-in-taylorbia
Open

kashif-khan wants to merge 1 commit into
oliexdev:masterfrom
kashif-khan:feat/aero-i-in-taylorbia

Conversation

@kashif-khan

Copy link
Copy Markdown

Scale

Urikar Aero I Pro, sold with the Fitdays app (the label reads "Urikar APP"; Smart Scale, model Aero I Pro, FCC ID 2AS9D-AEROIPRO, 6-180 kg, d = 0.1 kg / 0.2 lb). It advertises as Aero I with service 0xFFB0, so MGBHandler claimed it by service alone. MGBHandler cannot parse the scale's 20-byte AC 27 frames, so the scale connected and never produced a measurement.

It speaks the same AC 27 protocol as the ALPHA Smart Scale PRO 3 (#1534), which TaylorBIAHandler already drives: the same 0xD5 live weight, 0xD6 result, 0xD8 stored record and 0xD0/0xDF phone frames. So it joins TaylorBIAHandler as a third model instead of getting a handler of its own. TaylorBIAHandler is already ahead of MGBHandler in ScaleFactory.

What works

Tested on a Samsung Galaxy S24 Ultra (SM-S928W, Android 16) with openScale built from this branch (plus #1566 for the reconnect, see below):

  • Weight and body composition. The locked weight waits for the result packet, which follows about 3 s later and carries the impedance. Two weigh-ins in one connection were both recorded: 80.8 kg with 545 Ω and 539 Ω, and StandardImpedanceLib derives fat, water, muscle, bone, LBM and BMR from them as it does for the ALPHA. The scale reported 0 Ω in some weigh-ins (presumably when the feet did not touch the electrodes) and 514-559 Ω in others; with 0 Ω only the weight is recorded.
  • Display unit. The 0xD0 frame carries the unit (kg 0, lb 1, st 2), so the scale's display follows the weight unit set in openScale. With an earlier standalone version of this handler, writing exactly the frames in TaylorBIAHandlerTest, the display switched kg → lb → st → kg. The reworked handler writes identical bytes (asserted by the tests) and was run on the scale with kg.
  • Stored records. The scale is only awake for a few seconds after it wakes, so a weigh-in made before the phone has connected is stored on the scale and arrives on the next connection. It is imported at its timestamp and acknowledged, as for the ALPHA.
  • Results are acknowledged (DF 04 D6). The scale repeats the result three times within about 0.3 s whether or not it is acknowledged; the handler publishes the first and ignores the rest.
Connected to Aero I
→ write to ffb1 … payload=[AC 27 6A D6 E7 1E F0 00 01 AE 1F 86 27 01 00 00 01 00 D0 …]   (profile, kg)
locked weight 80800 g, waiting for the result packet
final result 0 g, impedance=545 Ω, locked=false
← publish measurement to app            (weight, impedance, fat, water, muscle, bone, LBM, BMR)
→ write to ffb1 … payload=[AC 27 04 D6 00 … DF B9]                                        (ack)

Differences from the ALPHA

  • The result carries no weight: its weight field is 0. The weight comes from the locked 0xD5 packet that precedes it, held until the result arrives (8 s limit, then it is published alone).
  • The link is left open. The scale switches itself off after a weigh-in (the link then times out about 18 s later), so the handler does not disconnect. After a weigh-in the scale sends nothing instead of reporting an empty platform, so the next weigh-in is recognised by its first unlocked reading.
  • The profile frame carries the unit, the real time zone and the user's profile weight. The ALPHA keeps the bytes of its capture (see below).
  • No DF 01 session-start frame is sent (its purpose is unknown, and the scale does not need it).

The 0xD0 frame

buildProfile() now takes the time zone, unit, user index, weights and flags as parameters. Their defaults reproduce the ALPHA capture byte for byte, so the ALPHA and Taylor paths are unchanged (the default profile frame still reproduces the ALPHA capture). The fields are documented in the class comment; they come from the vendor SDK's encodeTimeAndUserInfo, and the ALPHA's captured frame has the same layout (its 08 is a time zone of +2 h, its 00 00 weights and 03 flags):

body field
0..3 Unix time, big-endian
4 UTC offset in 15-minute steps, signed
5 display unit: 0 kg, 1 lb, 2 st
6 user index
7 height, cm
8..9 the user's weight in hundredths of a kg
10 age
11 sex, 1 male / 2 female
12..13 target weight in hundredths of a kg
14 flags, bit 0 = measure impedance

The profile weight is ScaleUser.initialWeight, which openScale already converts to kg. lastMeasurementFor() is not unit-aware (it returns the stored number as if it were kg), so it is not used.

Changes

  • TaylorBIAHandler: new AERO_I model (matched by the Aero I name together with 0xFFB0); buildProfile() parameters; unitCode() and hundredths(); the Aero I's result/stored-record/re-arm handling; class comment
  • ScaleCatalog fixture and a scale_catalog_remarks.txt line
  • TaylorBIAHandlerTest: the frames above, taken from the device log, the three profile frames that switched the display, and the Aero I flows through the existing harness (including the weight-only fallback with a TestScope)

./gradlew :app:testDebugUnitTest passes (947 tests).

Not verified

  • The ALPHA and Taylor paths were not run on hardware; they are covered by the existing tests and by the test that the default profile frame is unchanged.
  • The unit was only written with kg from the reworked handler on the device; lb and st were checked with the earlier standalone version (identical bytes, see above).
  • Body composition uses the generic StandardImpedanceLib formulas, so the numbers differ from the Fitdays app's.
  • The impedance inside a stored record is not decoded (the weight and timestamp are).
  • The ALPHA's profile frame still sends the captured time zone and kg. The same fields could be filled in for it (its unit switching is untested), which I left alone.
  • Without feat(bluetooth): reconnect automatically when the scale link drops #1566 the link ends when the scale switches itself off and is not re-established until the user taps connect; with it, openScale reconnects when the scale wakes.

The Aero I / Aero I Pro (sold with the Fitdays app, advertises as "Aero I" on 0xFFB0) speaks the
same `AC 27` protocol as the ALPHA Smart Scale PRO 3, so it joins TaylorBIAHandler as a third
model instead of getting a handler of its own. MGBHandler used to claim it by service alone and
could not parse its frames, so it connected and never produced a measurement.

Aero I behaviour:
* The result packet (0xD6) carries the impedance but no weight, so the weight comes from the
  locked 0xD5 packet and waits for the result (8 s limit, then it is published alone). Body
  composition is derived with StandardImpedanceLib, as for the ALPHA.
* Results and stored records are acknowledged and stored records are imported, as for the ALPHA.
  The scale is only awake for a few seconds after it wakes, so a weigh-in made before the phone
  has connected is stored on the scale and arrives on the next connection.
* The link is left open: the scale switches itself off. After a weigh-in it sends nothing instead
  of reporting an empty platform, so the next weigh-in is recognised by its first unlocked reading.
* The 0xD0 profile frame carries the real time zone, the display unit and the profile weight.

The 0xD0 body is documented field by field in the class comment. buildProfile() takes the
time zone, unit, user index, weights and flags as parameters whose defaults reproduce the ALPHA
capture byte for byte, so the ALPHA and Taylor paths are unchanged. The unit codes (kg 0, lb 1,
st 2) and the field layout come from the vendor SDK; the three profile frames in the tests are
the ones written to a real Aero I Pro whose display switched kg -> lb -> st -> kg with them.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant