Skip to content

chore: meege upstream branch - #2

Open
AlphaKR93 wants to merge 47 commits into
MadeByAlpha:mainfrom
oliexdev:master
Open

chore: meege upstream branch#2
AlphaKR93 wants to merge 47 commits into
MadeByAlpha:mainfrom
oliexdev:master

Conversation

@AlphaKR93

Copy link
Copy Markdown
Member

No description provided.

oliexdev and others added 30 commits July 18, 2026 08:49
* Translated using Weblate (Japanese)

Currently translated at 97.2% (779 of 801 strings)

Translation: openScale/Strings
Translate-URL: https://hosted.weblate.org/projects/openscale/strings/ja/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (801 of 801 strings)

Translation: openScale/Strings
Translate-URL: https://hosted.weblate.org/projects/openscale/strings/es/

* Translated using Weblate (Russian)

Currently translated at 92.1% (738 of 801 strings)

Translation: openScale/Strings
Translate-URL: https://hosted.weblate.org/projects/openscale/strings/ru/

* Translated using Weblate (German)

Currently translated at 100.0% (801 of 801 strings)

Translation: openScale/Strings
Translate-URL: https://hosted.weblate.org/projects/openscale/strings/de/

* Added translation using Weblate (Catalan)

* Translated using Weblate (Swedish)

Currently translated at 88.7% (711 of 801 strings)

Translation: openScale/Strings
Translate-URL: https://hosted.weblate.org/projects/openscale/strings/sv/

* Translated using Weblate (Catalan)

Currently translated at 8.8% (71 of 801 strings)

Translation: openScale/Strings
Translate-URL: https://hosted.weblate.org/projects/openscale/strings/ca/

* Translated using Weblate (Swedish)

Currently translated at 89.1% (714 of 801 strings)

Translation: openScale/Strings
Translate-URL: https://hosted.weblate.org/projects/openscale/strings/sv/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (801 of 801 strings)

Translation: openScale/Strings
Translate-URL: https://hosted.weblate.org/projects/openscale/strings/pt_BR/

* Translated using Weblate (Bulgarian)

Currently translated at 100.0% (801 of 801 strings)

Translation: openScale/Strings
Translate-URL: https://hosted.weblate.org/projects/openscale/strings/bg/

* Translated using Weblate (French)

Currently translated at 87.6% (702 of 801 strings)

Translation: openScale/Strings
Translate-URL: https://hosted.weblate.org/projects/openscale/strings/fr/

* Translated using Weblate (Bulgarian)

Currently translated at 100.0% (801 of 801 strings)

Translation: openScale/Strings
Translate-URL: https://hosted.weblate.org/projects/openscale/strings/bg/

* Translated using Weblate (Swedish)

Currently translated at 93.0% (745 of 801 strings)

Translation: openScale/Strings
Translate-URL: https://hosted.weblate.org/projects/openscale/strings/sv/

* Translated using Weblate (Swedish)

Currently translated at 96.0% (769 of 801 strings)

Translation: openScale/Strings
Translate-URL: https://hosted.weblate.org/projects/openscale/strings/sv/

---------

Co-authored-by: apemay <apemay.dev@gmail.com>
Co-authored-by: Isaac Clark <pkv.ko@yandex.ru>
Co-authored-by: Holger Eisold <github@eisold.family>
Co-authored-by: Eduard Ereza Martínez <eduard@ereza.cat>
Co-authored-by: Joel A <joeax910@student.liu.se>
Co-authored-by: Luan M. D. Lima <weblate.capitol237@slmail.me>
Co-authored-by: Viktoria PETROVA <victoriaapetrova@gmail.com>
Co-authored-by: François Kabiry <francoiskabiry@yahoo.fr>
Added values-hi/strings.xml to provide full Hindi localization support.
* Add support for Yunmai X (YMBS-M268) broadcast-only scale

The Yunmai X (YMBS-M268) advertises measurements as manufacturer-specific
data without a local name and needs no GATT connection (the vendor app
calls this device family "YUNMAI-ADV"). Frames echo the device MAC
byte-reversed, carry a 0B 74 17 signature, a state byte (live/stable/
final), weight and impedance as u16 BE, and an XOR checksum.

Identification uses the frame structure + MAC echo during scans and the
advertised 16-bit service UUID 0x1320 for saved-device snapshots (the
scale advertises no name to match on). Body composition is derived from
weight + impedance via YunmaiLib, like the Yunmai SE/Mini handler.

Protocol reverse-engineered from an advertisement capture and confirmed
end-to-end against a live measurement driven through this handler (weight
and full body composition derived and stored). Unit-test fixtures are
synthetic frames built to the observed layout.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Show handler name for saved scales that advertise no name

Broadcast-only scales such as the Yunmai X advertise no local name, so the
saved-scale panel rendered a blank line above the MAC. Fall back to the
resolved handler display name when the advertised name is blank.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* Added scale support for EEBBL scale.

* Remove "DEBUG" from supported device names in EEBBLHandler

---------

Co-authored-by: oliexdev <olie.xdev@googlemail.com>
The Vitafit "Body Fat" scale (VT701, Vitafit/Fitdays app) speaks a custom
GATT protocol on service 0xFFF0 that no existing handler covers. The protocol
was reverse-engineered from an HCI snoop of a Vitafit-app weigh-in and confirmed
against the scale's displayed readings (85.00 kg / 393 Ohm, 84.95 kg / 389 Ohm).

Frames are [hdr][len][0x26][type][data][chk][0xAA] with an XOR checksum. The
handler streams live weight (type 0x10, stable at flag 0x02, u16 BE in 0.01 kg),
acks the stable weight to trigger the impedance measurement, reads the whole-body
impedance (type 0x11, u16 BE Ohm), and derives body composition via the shared
StandardImpedanceLib. A 6-second fallback publishes weight-only if no impedance
frame arrives.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: OliE <olie.xdev@googlemail.com>
* Add minimal Huawei Hagrid WSP support

* moved HuaweiHagridSecretProvider into HuaweiHagridWspLib.kt

---------

Co-authored-by: shisoratsu <277485761+shisoratsu@users.noreply.github.com>
Co-authored-by: oliexdev <olie.xdev@googlemail.com>
- Allow external apps to enrich existing measurement entries with any metric (including custom types) via the `values_json` payload.
- Skip types already handled by explicit columns to prevent redundant updates.
- Add `GenericValueJsonTest` to verify round-trip parsing of numeric values across predefined and custom types.
Commit cd4d5e1 ("Fix: Use UTC for MiScale time synchronization")
switched both the write of the BLE Current Time characteristic and the
parsing of time read back from the scale from the device's default
(local) timezone to hardcoded UTC. The scale itself has no timezone
concept: it only ever receives and echoes back plain wall-clock digits
(year/month/day/hour/minute/second), with no UTC/local distinction. It
expects those digits to represent local wall-clock time.

Sending it UTC digits instead - while still interpreting whatever it
reports back as UTC - produces a timestamp permanently offset from the
true measurement time by the device's local UTC offset (e.g. +2h during
CEST, +1h during CET), since the round-trip no longer matches the
convention the firmware itself assumes.

This was found by cross-referencing years of MIBFS (Xiaomi Mi Body
Composition Scale) measurements recorded by openScale against an
independent BLE reader of the same physical scale: every measurement
from before cd4d5e1 matches ground truth exactly; every measurement
since is offset by exactly the local UTC delta at the time of
measurement. No linked issue or repro was found for cd4d5e1's stated
rationale ("previously the device's local timezone was used, which
could lead to incorrect timestamps"), and it is contradicted by this
data.

This reverts the two timezone-related lines to their pre-cd4d5e16
behavior (local timezone for both write and parse), while keeping the
unrelated day-of-week/adjust-reason payload byte correction from the
same commit, which is a separate, valid protocol fix.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
eLink/Icomon "MGB"-family scale on service 0xFFB0. Reads weight plus body fat, water, lean mass, bone, protein and visceral fat from the scale's data dump, with time/user/unit sync. Registered ahead of MGBHandler so its FitTrack name match wins the shared 0xFFB0 service. Includes unit tests for the frame decode.
)

* Add selectable Scientific body-composition algorithm to Mi Scale

Mi Scale v1/v2 previously derived body composition only from the
reverse-engineered Mi Fit algorithm (MiScaleLib). This adds a per-scale
setting letting users pick between that "Xiaomi (original app)" path and
a new "Scientific" estimator.

- BodyMiScaleLib: mono-frequency estimator ported from the bodymiscale
  Home Assistant integration (GPL-3.0). SCIENCE mode chains a
  hardware-calibrated LBM with peer-reviewed formulas (Siri, Pace, Wang,
  Schofield) and additionally reports protein and BMR. Locked to
  byte-for-byte parity with the reference project via a regression test.
- MiScaleHandler: radio picker to choose the algorithm; new measurements
  use the selected path.
- ScaleDeviceHandler: extract a reusable SettingRadioGroup composable
  (settings-backed radio group) for device configuration UIs.
- CREDITS: add bodymiscale attribution.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Daniel Kukula <daniel.kuku@gmail.com>

* Simplify BodyMiScaleLib to a scientific-only estimator

The Mode enum threaded through every method was a vestigial artifact of
the upstream bodymiscale port. openScale already selects the algorithm at
the handler (MiScaleLib vs BodyMiScaleLib), so BodyMiScaleLib.Mode.XIAOMI
was never used in production — only by a test comparing it to MiScaleLib.

Drop Mode and its XIAOMI branches so the class is purely the scientific
path. Method signatures shrink accordingly (getProtein no longer needs
muscle/water, which existed only for the Xiaomi formula). Output is
unchanged; the regression fixture still matches byte-for-byte.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Update CREDITS

---------

Signed-off-by: Daniel Kukula <daniel.kuku@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: OliE <olie.xdev@googlemail.com>
* Add Runstar R6 scale support

Adds RunstarR6Handler for the Runstar R6, whose FFB0 protocol is
distinct from R5 despite the similar name. Publishes live weight,
heart rate, impedance, and syncs offline-stored history entries.

RunstarR5Handler now excludes "RUNSTAR-R6" explicitly so the two
can't collide; R6 is registered ahead of R5 in ScaleFactory. Also
corrects two stale comments in RobiS9Handler now that the shared
FFB0 checksum and impedance/heart-rate offsets are known.

* Tighten Runstar R5 name matching instead of excluding R6

R5 matched any "RUNSTAR-" prefix, which also caught R6 devices.
Restrict it to the known "RUNSTAR-R5"/"RUNSTAR-RX" names instead of
adding an R6-specific exclusion — removes the need for R6 to be
registered ahead of R5, and the collision no longer needs explaining
in RunstarR6Handler's doc.

Also trims two RobiS9Handler comments down to the new finding
(checksum formula, impedance/heart-rate offsets) without pointing at
RunstarR6Handler for the backstory.

---------

Co-authored-by: Leon Dönch <leon.doench@nelsen.de>
* Add Keep S3 scale support

* Revert addition of extended measurement metrics and database version 16

This change removes support for several recently added measurement types and reverts the database schema to version 15.

Key changes include:
*   **Measurement Types:** Removed `PHASE_ANGLE`, `SKELETAL_MUSCLE`, `LEAN_SOFT_TISSUE`, `SUBCUTANEOUS_FAT`, `BODY_AGE`, `BMI_22_REFERENCE_WEIGHT`, and `DEVICE_IMPEDANCE` from `MeasurementTypeKey` and `ScaleMeasurement`.
*   **Database:** Reverted `AppDatabase` version to 15, deleted `MIGRATION_15_16`, and removed the version 16 JSON schema.
*   **Units & Strings:** Removed `UnitType.DEGREE` and associated string resources and localization logic.
*   **Logic Cleanup:**
    *   Removed generalized percentage-to-mass unit conversion logic in `ConverterUtils` and `MeasurementTypeCrudUseCases`.
    *   Updated `KeepS3Handler` to stop publishing extended metrics.
    *   Removed `awaitPendingOperations` from the BLE transport interface.

* Add muscle percentage mapping in KeepS3Handler

* Fix Keep S3 runtime behavior

---------

Co-authored-by: oliexdev <olie.xdev@googlemail.com>
Shares service 0xFFB0 and the 20-byte frame family with RobiS9Handler and
RunstarR6Handler, but differs in two ways.

Frames fragment: byte 2 is a fragment index, not a constant zero, and the
0xA3 result spans two frames. Every fragment contributes bytes[3..18], so
the byte after the fragment index is payload rather than a header byte to
skip. Both existing handlers in this family reject such frames outright.

The 0xBA profile is generated rather than replayed. RobiS9Handler replays
a captured handshake because its timestamp and token could not be
regenerated; this one is understood field by field, so it is built from
the openScale user. flags1 carries sex and age and is the only place they
are sent -- getting it wrong is silent, the scale simply computes for the
wrong person. The generated frame is byte-identical to a capture the scale
accepts.

Body composition comes from Wla25BodyComposition, ported from
ICBodyFatAlgorithmWLA25::calc in the vendor library: fat mass is a 13-term
linear regression over height, weight, the rounded BMI and all ten
impedances, and the rest follows from fat-free mass. Verified against the
vendor library over 240 randomised inputs spanning both clamp boundaries,
both impedance branches and both sexes -- exact on all ten fields.

Three roundings are load-bearing and each is worth a tenth of a unit: the
weight is rounded before anything else, the BMI fed into the regression is
rounded, and fat mass is rounded before fat-free mass is derived from it.
The rounding is half-up and computed in float32; everything else runs in
double, matching the library. Doing it all in Float shifts BMR by 1 kcal
in roughly one case in eighty.

compute() takes no sex parameter: this algorithm's body composition does
not depend on it, only metabolic age does.

Known limitation: bone mass can read 0.1 kg below the scale's own display.
The vendor app agrees with this code, and no algorithm the vendor library
ships reproduces the scale's combination of values, so the scale's
firmware appears to compute bone differently.

Metabolic age is implemented but not published -- ScaleMeasurement has no
field for it. EtekcityLib and HesleyHandler hit the same wall, one
computing it and the other reading it off the wire, both discarding it.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Support 8-byte streaming frames in MGBHandler (Dr Trust Smart 505)

MGBHandler previously assumed every notification was a 20-byte
composite frame and dropped anything else, so scales like the Dr
Trust Smart 505 (same SWAN name / 0xFFB0 service, but 8-byte
streaming frames) connected and never produced a measurement.

onNotification now dispatches on frame size: the existing 20-byte
composite path is unchanged (only moved into onCompositeFrame), and
a new 8-byte streaming path parses live/final weight and impedance
frames via pure, unit-tested companion functions, latching both
values before publishing one measurement and deriving body
composition with StandardImpedanceLib (same approach as
VitafitVT701Handler).

* Inline onStreamingFrame into onNotification

It had a single call site; folding it into the 8-byte branch matches
VitafitVT701Handler's flatter onNotification style. No logic change.

---------

Co-authored-by: Claude <noreply@anthropic.com>
* Add Keep S3 scale support

* Revert addition of extended measurement metrics and database version 16

This change removes support for several recently added measurement types and reverts the database schema to version 15.

Key changes include:
*   **Measurement Types:** Removed `PHASE_ANGLE`, `SKELETAL_MUSCLE`, `LEAN_SOFT_TISSUE`, `SUBCUTANEOUS_FAT`, `BODY_AGE`, `BMI_22_REFERENCE_WEIGHT`, and `DEVICE_IMPEDANCE` from `MeasurementTypeKey` and `ScaleMeasurement`.
*   **Database:** Reverted `AppDatabase` version to 15, deleted `MIGRATION_15_16`, and removed the version 16 JSON schema.
*   **Units & Strings:** Removed `UnitType.DEGREE` and associated string resources and localization logic.
*   **Logic Cleanup:**
    *   Removed generalized percentage-to-mass unit conversion logic in `ConverterUtils` and `MeasurementTypeCrudUseCases`.
    *   Updated `KeepS3Handler` to stop publishing extended metrics.
    *   Removed `awaitPendingOperations` from the BLE transport interface.

* Add muscle percentage mapping in KeepS3Handler

* Fix Keep S3 runtime behavior

* Complete Traditional Chinese translation

---------

Co-authored-by: oliexdev <olie.xdev@googlemail.com>
- Switched to `ExistingPeriodicWorkPolicy.UPDATE` to prevent backup postponement on reboots or settings changes
- Added tracking and display for the next scheduled backup time in settings
- Added detailed error reporting and specific failure states (location inaccessible, file creation, or write failures)
- Removed flex interval from `PeriodicWorkRequest` to ensure more regular scheduling
- Improved folder picker behavior by launching at the currently configured location
- Added unit tests for backup error state persistence
* Added a "package" extra in `startSyncService()`, so all five triggers carry it and a future one cannot forget it.
  Scales only report what they can weigh, so waist, hips, caliper and
  custom values were absent on every Bluetooth sync. applyValueInheritance
  fills those gaps from the last measurement *before* the incoming
  timestamp, so historic entries read from a scale's memory inherit the
  state of their own point in time, never of a later one.

  Only numeric (FLOAT/INT) values are inherited; empty, derived, internal
  and disabled types are skipped. Values the scale delivered always win.
  The add-measurement form now pre-fills through the same method, so a
  manual entry starts out exactly where a sync would.
  Aggregated entries are synthetic and all carry Measurement.id == -1,
  so the LazyColumn key must not fall back to the id. It did whenever
  isAggregated was false while the list already held aggregated items —
  that flag comes from the settings flow, the items from the data flow,
  and the two disagree for a frame after an aggregation change or on the
  first composition, where the cached data flow already has the previous
  result. Every row then got the key -1 and Compose aborted the measure
  pass with "Key -1 was already used".

  Key by id + timestamp instead: unique for raw rows via the database id,
  unique for aggregated rows because their periods are disjoint.
* Complete the Turkish translation (154 -> 782 of 811 strings)

Turkish was the least complete of the maintained locales at 19%, while
German and Spanish sit at 95%. Four out of five strings a Turkish user
saw were still English, which makes large parts of the app - Bluetooth
setup, insights, backup and every error message - unreadable for users
who do not speak English.

This adds the 628 missing translatable strings. Existing translations
are untouched, and their terminology is followed for consistency.
Strings marked translatable=false are left out.

Verified: gradlew :app:processDebugResources completes successfully;
format specifiers match the source strings one for one.

* Reword three Turkish strings that read awkwardly in context

Found while walking the app on an emulator with the Turkish locale
active - these were literal renderings that a native speaker would not
say out loud.

- permission prompt: 'erişimine gereksinimimiz var' -> 'erişimi
  gerekiyor'
- generic confirm: the verb placeholder no longer has to fit a Turkish
  case ending, which it cannot do for an arbitrary verb
- unit-change dialog: 'sürdürmek' -> 'devam etmek'

* Shorten the plateau chip so it fits on screen in Turkish

The insight chips sit in a horizontally scrolling Row, and my
translation of the plateau chip was long enough that it was clipped at
the screen edge on a 1080px-wide device: it rendered as '180 gunluk
durag'. The English 'plateau %1$d days' fits, so this was the
translation's problem, not the layout's.

'%1$d gunluk duraganlik' -> '%1$d gun duragan', which is shorter than
the English source and reads just as naturally.

Verified on an Android 16 emulator with the app locale set to Turkish:
the chip now renders in full as '180 gun duragan'.
Every other file in core/bluetooth/libs has a matching test - 17 libs,
16 tests. This adds the one that was missing.

The class documents three rounding steps as load-bearing and pins the
rounding mode with two exact values; both are asserted here through
compute(), since round1 is private and rounds the incoming weight
before anything else. The validity gate's asymmetry - slots 0 and 5
checked against 1.0, the other eight against 100.0 - is covered per
slot, because that asymmetry is what fixes the ordering of the ten
impedances.

The remaining assertions are on invariants the source states outright
(the 3-60% fat clamp, visceral fat 1-20, fat-free mass as weight minus
fat, BMR from Katch-McArdle), so they hold independently of the
regression constants and cannot go stale if those are ever retuned.
ahmetabdullahgultekin and others added 17 commits August 11, 2026 17:47
Aggregation averaged the values but wrote intValue = null, while copying
the type unchanged - so the synthetic entry still reported
InputFieldType.INT. Every consumer switches on inputType and reads
intValue, so an INT-typed measurement read as absent once any
aggregation level other than NONE was selected:

- MeasurementChart: yValue resolves to null, the series disappears
- StatisticsScreen: min/max/avg/first/last/difference all null
- MeasurementSmoothingUseCases: the type is dropped from smoothing

Heart rate is InputFieldType.INT, as are INT custom types, so switching
the statistics screen to weekly aggregation made those cards render
Min/Max/Avg as - with an empty chart, while daily raw data rendered
fine.

The average is rounded rather than truncated, so 60 and 65 aggregate to
63 rather than 62. floatValue stays populated as before, so consumers
reading either field agree.

Verified: the two new INT tests fail on master and pass with this
change; 7/7 green afterwards.
…1458)

ScaleMeasurement.muscle is declared as 'must be in percentage', but this
handler assigned skeletalMuscleMassKg. The sibling assignments in the
same block get it right - fat uses totalFatPercentage and water uses
totalBodyWaterPercentage - and the three other handlers driving
StandardImpedanceLib all use skeletalMusclePercentage.

The log lines said muscle=...kg, so the unit was known at the call site;
the field it was written into is the percentage one. Users of this scale
saw a kilogram figure wherever a muscle percentage was expected, in
every screen and every export.

Also corrects those two log lines to %.
  The ContentProvider's values_json path only ever wrote floatValue. INT values
  landed in the wrong column and read as absent everywhere downstream, and TEXT,
  DATE and TIME were dropped entirely — parse() bailed out on a missing "value"
  key before it ever looked at "text".

  GenericValueJson.parse now returns the type together with a MeasurementValue
  that has the right column filled, so both provider call sites just store what
  they get. The wire format is unchanged; build() is untouched.

  Existing rows keep their misfiled values — repairing those needs a migration.
* Add support for the Weight Gurus 0376 scale

Greater Goods / DMD Brands 0376B, a Transtek A3 device. Body composition,
time sync and user sync over the vendor service 0d005750-c36b-11e3-9c1a-
0800200c9a66, plus the stored history the scale hands over on connect.

Pairing needs three things the scale is strict about. It streams all eight
user slots and the host must stay silent until the last one has arrived;
answering earlier drops the scale into E1 instead of completing. The 0x51
profile carries height as an IEEE-11073 SFLOAT in metres, not raw
centimetres, so 170 cm goes out as 0xD6A4. And 0x22 is followed by waiting
for the scale to close the link itself, because tearing it down from this
side interrupts the commit and is reported as E1 as well.

The scale computes body composition on-device from the profile stored in
whichever slot the reading is attributed to, and slot selection is a button
on the scale that the host cannot influence. A slot left holding a stale
height and age therefore produces wrong percentages for the right weight.
Registering into that slot corrects it, so the handler asks which of the
eight slots the user is, listing the names read off the scale, and writes
the openScale profile there. The choice is persisted and reused after that.

Deduplication matches on exact timestamp instead of tracking the newest
stored measurement. The scale keeps a separate queue per slot, drains only
the selected one, and delivers newest first, so readings legitimately arrive
older than ones already held; a high-water mark discards them, and since the
scale drops a reading once delivered, anything discarded is lost.

Decoded weight matches the scale's own display, and impedance is reported
for handlers downstream.

* Document the Weight Gurus 0376 protocol

Frame layouts, command encodings and the sync behaviour behind
WeightGurusA3Handler, so the parts that are easy to get wrong are written
down instead of rediscovered: the SFLOAT-in-metres height, the wait for all
eight slot frames, per-slot measurement queues that drain newest first, and
readings being dropped by the scale once delivered.

Records what is confirmed against hardware and what is not, including the
weight-frame flag 0x20 and the visceral fat and basal metabolism fields that
no test weighing has exercised.

* Delete docs/misc/weight_gurus_a3_protocol.md

---------

Co-authored-by: OliE <olie.xdev@googlemail.com>
* add rudimentary support for AE-BS06

* add support for BIA calculations

matching what scales report on their screen.
The support added only for reportted "algType = 0x07"

* switch to name-based matching for AE BS-06

* report heart rate for AE BS-06

* add accurate BMI calculations for AE BS-06

only alg=0x07 is supported for now
* Support Terraillon X-LINE scale

* Also support 1X-LINE*
* feat(bluetooth): add Omron WLC body composition scales (HBF-702T family)

Adds support for the Omron HBF-702T and its KRD-703T / Asia-Pacific trims,
plus the HBF-222T, HBF-227T, HBF-228T and HBF-230T, which share the same
"Wellness Link" (WLC) transfer protocol.

These scales do not stream live weights. They store up to 30 measurements per
on-device user slot in EEPROM, which the phone reads out afterwards over a
vendor GATT service that splits every command across four 16-byte write
channels and reassembles the answer from four notify channels.

- OmronWlpFrame: command framing, XOR checksum, response reassembly.
- OmronBodyCompositionLib: per-model record geometry and the bit-packed field
  decoder. Field positions differ only in record size and visceral fat
  granularity between the 48-byte HBF-702T and the 32-byte siblings.
- OmronWlcHandler: unlock/pair handshake, transfer session, and slot readout.
  The on-device user slot is bound to an openScale user through the existing
  CHOOSE_USER dialog, which previews each slot's newest measurement.

The driver is read-only apart from the unlock handshake: it never resets the
device's unread counters or writes its clock, and new measurements are the
records stamped later than the newest one openScale already holds. A protocol
mistake therefore cannot corrupt measurements stored on the scale.

The protocol was derived from the OMRON connect app's own device descriptors
and communication library, not from a capture of a physical device, so it is
unverified against hardware.

Co-Authored-By: Claude Mythos <noreply@anthropic.com>

* Fold the Omron WLP framing into a single OmronLib file.

---------

Co-authored-by: Claude Mythos <noreply@anthropic.com>
Co-authored-by: oliexdev <olie.xdev@googlemail.com>
…ler tests cannot see across handlers, so a misplaced matcher steals a device while they stay green. The handler list moves into ScaleFactory.createHandlers()

  (@VisibleForTesting) to be reachable without Hilt; no behaviour change.

  Also switches BackupRestoreUseCasesTest to Truth
* feat: Added support for Etekcity Fit 8S Scale.

* Etekcity Fit 8S: parse only our own manufacturer record, and cover the handler in ScaleFactoryTest

  onAdvertisement took the first manufacturer record of at least 20 bytes, so a second record in the same advertisement would have been decoded as a measurement — look up 0x06D0 directly instead. Drops the redundant SparseArray cast along the way.

  The registry test gains manufacturer-data fixtures (it could only build name/service advertisements before) and pins the one real overlap: ScaleupHandler matches every company id whose low byte is 0xD0, which 0x06D0 is, so the Fit 8S is only kept on its own driver by standing earlier in the list.

* Enforce exact payload size in EtekcityFit8SHandler

---------

Co-authored-by: oliexdev <olie.xdev@googlemail.com>
  The CSV importer accepts ISO 8601 only, so a spreadsheet round-trip ("8:30:00 AM")
  silently sent every timestamp to noon (#1475). Behaviour is unchanged, the fallbacks
  now name the line and expected format. The separate-columns test only checked the row
  counter, so it would have stayed green even if TIME had been ignored — it now asserts
  the imported time.

  Also sets FLAG_KEEP_SCREEN_ON while the measurement detail screen is open, cleared on
  dispose, since entering values means standing on the scale and not touching the phone.
- Adds a new GitHub Actions job to publish the "Supported scales in openScale" wiki page artifact
- Includes necessary logic to generate the Markdown content by running `ScaleCatalogTest`
- Commits and pushes changes to the specified wiki location if the content has changed
- Creates placeholder resources (`scale_catalog_remarks.txt`, `scale_catalog_gallery.md`) for hand-curated catalog data
- Adds `ScaleCatalog.kt` object containing logic to synthesize scale support from registered handlers
- Add support for many new branded Bluetooth scales to the supported catalog
- Update test fixture list in `ScaleCatalog` to include all newly supported devices
- Refactor testing structure in `ScaleFactoryTest` to use shared fixtures with `ScaleCatalog`
- Simplify Bluetooth device representation using helper functions
The PC-PW 3008 BT runs the same Chipsea "WeChat scale" firmware as the
Hoffen BBS-8107: it advertises the WeChat service 0xFEE7 and speaks the
0xFA framing on 0xFFB0/0xFFB2 that HoffenBbs8107Handler already
implements. Only the exact-name gate kept it from being recognised, so
that check becomes a small advertised-name -> display-name map and the
scale is reported under its own product name.

Confirmed against the vendor's Dr.Curve+ app (com.yilai.DrCurvePlus),
whose onWeixinFatScale routine decodes exactly the offsets
parseFinalMeasurement already uses: weight LE16 @3, fat @6, water @8,
muscle @10, bone @14, visceral fat @17.

Matched by advertised name rather than by service 0xFEE7, which is the
generic WeChat BLE service and would claim unrelated devices.

Verified on hardware: weight and all body composition values read
correctly against the vendor app.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Added Huawei Scale 3 support

* Restore unrelated changes

* git commit -m "Refine Huawei Scale 3 support"

* Update strings.xml

* Fix Huawei Scale 3 realtime and history handling

* Make Huawei realtime timestamp parsing tolerant

* Update bluetooth scale model mapping for better coverage

- Update test assertions to use Google Truth assertions instead of JUnit assertions
- Map additional scale model names to the correct handler class
- Add mappings for "HUAWEI Scale 3", "HUAWEI Scale 3 Pro", and "Hagrid-B29"

---------

Co-authored-by: OliE <olie.xdev@googlemail.com>
- Introduce a standalone developer-mode setting in `SettingsFacade` that routes all Bluetooth connections to the diagnostic handler without modifying the saved device snapshot
- Refactor `DebugGattHandler` to exclude itself from the handler registry, preventing name-based conflicts with actual device handlers
- Add a confirmation dialog and a persistent UI banner to prevent accidental activation and clearly indicate when measurements are disabled
- Add unit tests verifying that developer mode correctly overrides device routing and preserves the scale's original identity
- Use icon tint to signal developer mode instead of swapping the icon, keeping the connection state visible in the top bar
- Append a developer-mode note to the icon's content description so screen readers still convey the active state
- Warn in the developer-mode dialog when selecting a different scale will replace the currently saved device
- Replace the generic warning icon in Bluetooth settings with the bug report icon to match the top bar indicator
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.