From fab92cf62185f606c53bbf0901857fffd677a060 Mon Sep 17 00:00:00 2001 From: Le-Vu Tran Date: Wed, 22 Jul 2026 23:37:33 +0700 Subject: [PATCH] fix: measureData returns no data due to missing deviceModel and lossy MAC-derived serial Two independent bugs in OmronConnect1.get_measurements() combined to make the Kii measureData call always return deviceModelList: null, even for a valid registered device and date range: 1. The request body omitted `deviceModel`, which the backend silently requires to route the query correctly (confirmed by comparing against a real traffic capture of the official app, which always sends it). 2. `device.serial` reconstructs the 8-byte Kii deviceSerialID from the 6-byte BLE MAC via ble_mac_to_serial(), but serial_to_mac() (used when storing the device from get_registered_devices) discards the serial's last two bytes converting to a MAC in the first place. The round trip is lossy, so the reconstructed serial rarely matches the real one. Fix: send deviceModel in the request (derived from OmronDevice.name, which is always ":"), and store the real deviceSerialID on OmronDevice when it's known (from get_registered_devices) so `serial` no longer needs to guess. Devices added before this fix won't have real_serial populated in their config entry and will still use the lossy fallback; re-run `add`/`init` device discovery to pick up the real serial. Verified against a live account: measureData now returns real measureList/bodyIndexList data instead of an empty stub, and `sync` successfully downloads and cross-matches real weigh-ins against Garmin. --- omronconnect.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/omronconnect.py b/omronconnect.py index e084cf1..b65cb3f 100644 --- a/omronconnect.py +++ b/omronconnect.py @@ -319,6 +319,11 @@ class OmronDevice: category: T.Optional[DeviceCategory] = None user: int = 1 enabled: bool = True + # Kii's deviceSerialID is 8 bytes; the BLE MAC we store/display is only 6, + # so macaddr<->serial is a lossy round-trip (see serial_to_mac/ble_mac_to_serial). + # When the server hands us the real serial directly (get_registered_devices), + # we keep it here so `serial` doesn't have to reconstruct a wrong value. + real_serial: T.Optional[str] = None def __post_init__(self) -> None: if not isinstance(self.category, DeviceCategory): @@ -335,7 +340,7 @@ def __post_init__(self) -> None: @property def serial(self) -> str: - return ble_mac_to_serial(self.macaddr) + return self.real_serial or ble_mac_to_serial(self.macaddr) def to_dict(self) -> dict: result = asdict(self) @@ -566,6 +571,7 @@ def unique_devices(sync_list): name=f"{device['deviceModel']}:{device['userNumberInDevice']}", macaddr=serial_to_mac(device["deviceSerialID"]), user=int(device["userNumberInDevice"]), + real_serial=device["deviceSerialID"], ) result.append(ocDev) @@ -579,11 +585,15 @@ def get_measurements( "containCorrectedDataFlag": 1, "containAllDataTypeFlag": 1, "deviceCategory": device.category, + # Required by the backend for it to actually return measureList data — + # omitting it yields a 200 OK with deviceModelList: null every time, + # even for a valid device/date range. `name` is always stored as + # ":" (see get_registered_devices). + "deviceModel": device.name.rsplit(":", 1)[0], "deviceSerialID": device.serial, "userNumberInDevice": int(device.user), "searchDateFrom": searchDateFrom if searchDateFrom >= 0 else 0, "searchDateTo": int(U.utcnow().timestamp() * 1000) if searchDateTo <= 0 else searchDateTo, - # "deviceModel": "OGSC", "HBF-xxx", ... } r = self._client.post(