Skip to content

Commit 2ad0540

Browse files
committed
update
1 parent 16db1c0 commit 2ad0540

File tree

2 files changed

+35
-218
lines changed

2 files changed

+35
-218
lines changed

custom_components/eufylife_api/sensor.py

Lines changed: 34 additions & 217 deletions
Original file line numberDiff line numberDiff line change
@@ -114,18 +114,9 @@ async def _async_update_data(self) -> dict[str, Any]:
114114
raise UpdateFailed(f"Error communicating with API: {err}") from err
115115

116116
async def _fetch_data(self) -> dict[str, Any]:
117-
"""Fetch data from EufyLife API."""
117+
"""Fetch data from EufyLife API using device data endpoint only."""
118118
data = self.entry.runtime_data
119119

120-
# Try device data endpoint first (more recent data)
121-
device_data = await self._fetch_device_data()
122-
processed_device_data = {}
123-
if device_data:
124-
_LOGGER.info("Device data endpoint returned %d records, processing...", len(device_data))
125-
processed_device_data = await self._process_device_data(device_data)
126-
127-
# Continue with existing target endpoint logic for fallback
128-
129120
# Log token status
130121
current_time = time.time()
131122
token_expires_at = data.expires_at
@@ -147,97 +138,17 @@ async def _fetch_data(self) -> dict[str, Any]:
147138
)
148139
return {}
149140

150-
_LOGGER.debug("Token is valid, proceeding with API calls")
151-
152-
headers = {
153-
"Accept": "*/*",
154-
"Accept-Language": "en-US,en;q=0.9",
155-
"User-Agent": f"EufyLife-iOS-{USER_AGENT_VERSION}",
156-
"Category": "Health",
157-
"Language": "en",
158-
"Timezone": "UTC",
159-
"Country": "US",
160-
"Token": data.access_token,
161-
"Uid": data.user_id,
162-
}
141+
_LOGGER.debug("Token is valid, proceeding with device data API call")
163142

164-
_LOGGER.debug("Making API request to: %s/v1/customer/all_target", API_BASE_URL)
165-
_LOGGER.debug("Request headers (token redacted): %s", {k: v if k != "Token" else "***REDACTED***" for k, v in headers.items()})
166-
167-
try:
168-
# Fetch current weight targets
169-
start_time = time.time()
170-
async with self.session.get(
171-
f"{API_BASE_URL}/v1/customer/all_target",
172-
headers=headers,
173-
timeout=aiohttp.ClientTimeout(total=30),
174-
) as response:
175-
request_duration = time.time() - start_time
176-
177-
_LOGGER.debug(
178-
"API request completed in %.2f seconds. Status: %d, Headers: %s",
179-
request_duration,
180-
response.status,
181-
dict(response.headers)
182-
)
183-
184-
if response.status == 200:
185-
target_data = await response.json()
186-
_LOGGER.debug("API response received: %s", target_data)
187-
188-
if target_data.get("res_code") == 1:
189-
_LOGGER.info("API response successful, processing target data...")
190-
target_processed_data = await self._process_target_data(target_data, headers)
191-
192-
# Merge device data with target data, prioritizing device data
193-
final_data = target_processed_data.copy()
194-
for customer_id, device_info in processed_device_data.items():
195-
if customer_id in final_data:
196-
# Update existing customer data with device data
197-
final_data[customer_id].update(device_info)
198-
_LOGGER.debug("Updated customer %s with device data", customer_id[:8])
199-
else:
200-
# Add new customer from device data
201-
final_data[customer_id] = device_info
202-
_LOGGER.debug("Added new customer %s from device data", customer_id[:8])
203-
204-
if processed_device_data:
205-
_LOGGER.info("Merged device data for %d customers with target data", len(processed_device_data))
206-
207-
return final_data
208-
else:
209-
_LOGGER.error(
210-
"API returned error response. res_code: %s, message: %s",
211-
target_data.get("res_code"),
212-
target_data.get("res_msg", "Unknown error")
213-
)
214-
# Return device data only if target data failed
215-
return processed_device_data
216-
else:
217-
response_text = await response.text()
218-
_LOGGER.error(
219-
"API request failed with status %d. Response: %s",
220-
response.status,
221-
response_text[:500] # Limit response text length
222-
)
223-
# Return device data if available, empty dict otherwise
224-
return processed_device_data
225-
226-
except asyncio.TimeoutError:
227-
_LOGGER.error(
228-
"Timeout fetching data from EufyLife API after 30 seconds. "
229-
"Check internet connection and API availability."
230-
)
231-
# Return device data if available, empty dict otherwise
232-
return processed_device_data
233-
except aiohttp.ClientError as err:
234-
_LOGGER.error("HTTP client error fetching data from EufyLife API: %s", err)
235-
# Return device data if available, empty dict otherwise
236-
return processed_device_data
237-
except Exception as err:
238-
_LOGGER.error("Unexpected error fetching data from EufyLife API: %s", err, exc_info=True)
239-
# Return device data if available, empty dict otherwise
143+
# Fetch device data only (most recent and reliable data)
144+
device_data = await self._fetch_device_data()
145+
if device_data:
146+
_LOGGER.info("Device data endpoint returned %d records, processing...", len(device_data))
147+
processed_device_data = await self._process_device_data(device_data)
240148
return processed_device_data
149+
else:
150+
_LOGGER.warning("No device data available from API")
151+
return {}
241152

242153
async def _fetch_device_data(self) -> dict[str, Any]:
243154
"""Fetch recent device data from EufyLife API."""
@@ -307,6 +218,7 @@ async def _process_device_data(self, device_data: list) -> dict[str, Any]:
307218
weight = None
308219
body_fat = None
309220
muscle_mass = None
221+
target_weight = None
310222
timestamp = None
311223

312224
# Try different possible field names for weight (in grams, need to convert to kg)
@@ -323,6 +235,20 @@ async def _process_device_data(self, device_data: list) -> dict[str, Any]:
323235
else:
324236
weight = float(weight_raw)
325237

238+
# Try different field names for target weight
239+
target_weight_raw = (record.get("target_weight") or
240+
record.get("targetWeight") or
241+
record.get("goal_weight") or
242+
record.get("goalWeight"))
243+
244+
if target_weight_raw:
245+
# Device data target weight might be in grams or already in kg with decimal
246+
if isinstance(target_weight_raw, (int, float)):
247+
if target_weight_raw > 1000: # Assume grams if > 1000
248+
target_weight = target_weight_raw / 1000.0
249+
else:
250+
target_weight = float(target_weight_raw)
251+
326252
# Try different field names for body fat percentage
327253
body_fat = (record.get("body_fat") or
328254
record.get("bodyfat") or
@@ -357,13 +283,17 @@ async def _process_device_data(self, device_data: list) -> dict[str, Any]:
357283
_LOGGER.debug("Could not parse timestamp %s: %s", timestamp_raw, ts_err)
358284

359285
# Only process if we have some meaningful data
360-
if weight or body_fat or muscle_mass:
286+
if weight or body_fat or muscle_mass or target_weight:
361287
customer_data = {}
362288

363289
if weight:
364290
customer_data["weight"] = round(weight, 1)
365291
customer_data["device_weight"] = True # Mark as from device data
366292

293+
if target_weight:
294+
customer_data["target_weight"] = round(target_weight, 1)
295+
customer_data["device_target_weight"] = True
296+
367297
if body_fat:
368298
customer_data["body_fat"] = round(float(body_fat), 1)
369299
customer_data["device_body_fat"] = True
@@ -383,8 +313,8 @@ async def _process_device_data(self, device_data: list) -> dict[str, Any]:
383313
processed_data[customer_id] = customer_data
384314

385315
_LOGGER.debug(
386-
"Processed device record #%d for customer %s: weight=%s, body_fat=%s, muscle_mass=%s",
387-
i, customer_id[:8] if customer_id else "unknown", weight, body_fat, muscle_mass
316+
"Processed device record #%d for customer %s: weight=%s, target_weight=%s, body_fat=%s, muscle_mass=%s",
317+
i, customer_id[:8] if customer_id else "unknown", weight, target_weight, body_fat, muscle_mass
388318
)
389319
else:
390320
_LOGGER.debug("Device record #%d for customer %s contains no usable measurement data",
@@ -397,112 +327,7 @@ async def _process_device_data(self, device_data: list) -> dict[str, Any]:
397327
_LOGGER.info("Successfully processed device data for %d customers", len(processed_data))
398328
return processed_data
399329

400-
async def _process_target_data(self, target_data: dict, headers: dict) -> dict[str, Any]:
401-
"""Process target data and fetch additional customer details."""
402-
processed_data = {}
403-
404-
target_list = target_data.get("target_list", [])
405-
_LOGGER.debug("Processing %d targets from API response", len(target_list))
406-
407-
for i, target in enumerate(target_list):
408-
customer_id = target.get("customer_id")
409-
if not customer_id:
410-
_LOGGER.warning("Target #%d missing customer_id, skipping", i)
411-
continue
412-
413-
_LOGGER.debug("Processing target #%d for customer %s", i, customer_id[:8])
414-
415-
# Get customer details for additional body composition data
416-
customer_details = await self._fetch_customer_details(customer_id, headers)
417-
418-
# Process the data with detailed logging
419-
raw_weight = target.get("current_weight", 0)
420-
raw_target_weight = target.get("target_weight", 0)
421-
raw_body_fat = target.get("current_bodyfat", 0)
422-
raw_muscle_mass = target.get("current_muscle_mass", 0)
423-
update_time = target.get("update_time", 0)
424-
425-
_LOGGER.debug(
426-
"Raw data for customer %s: weight=%s, target_weight=%s, body_fat=%s, muscle_mass=%s, update_time=%s",
427-
customer_id[:8], raw_weight, raw_target_weight, raw_body_fat, raw_muscle_mass, update_time
428-
)
429-
430-
customer_data = {
431-
"weight": raw_weight / 10.0 if raw_weight else 0, # Convert from tenths
432-
"target_weight": raw_target_weight / 10.0 if raw_target_weight else 0,
433-
"body_fat": raw_body_fat,
434-
"muscle_mass": raw_muscle_mass,
435-
"last_update": datetime.fromtimestamp(update_time) if update_time else None,
436-
}
437-
438-
# Calculate BMI if we have weight and height data
439-
if customer_data["weight"] > 0:
440-
# BMI calculation would need height data, for now we'll estimate
441-
customer_data["bmi"] = None # Would need height from user profile
442-
443-
# Add customer details if available
444-
if customer_details:
445-
_LOGGER.debug("Adding customer details: %s", customer_details)
446-
customer_data.update(customer_details)
447-
448-
_LOGGER.debug(
449-
"Processed data for customer %s: %s",
450-
customer_id[:8],
451-
{k: v for k, v in customer_data.items() if k != "last_update"}
452-
)
453-
454-
processed_data[customer_id] = customer_data
455-
456-
_LOGGER.info("Successfully processed data for %d customers", len(processed_data))
457-
return processed_data
458330

459-
async def _fetch_customer_details(self, customer_id: str, headers: dict) -> dict[str, Any]:
460-
"""Fetch detailed customer data."""
461-
_LOGGER.debug("Fetching detailed data for customer %s", customer_id[:8])
462-
463-
try:
464-
start_time = time.time()
465-
async with self.session.get(
466-
f"{API_BASE_URL}/v1/customer/target/{customer_id}",
467-
headers=headers,
468-
timeout=aiohttp.ClientTimeout(total=30),
469-
) as response:
470-
request_duration = time.time() - start_time
471-
472-
_LOGGER.debug(
473-
"Customer detail request for %s completed in %.2f seconds. Status: %d",
474-
customer_id[:8], request_duration, response.status
475-
)
476-
477-
if response.status == 200:
478-
data = await response.json()
479-
_LOGGER.debug("Customer detail response for %s: %s", customer_id[:8], data)
480-
481-
if data.get("res_code") == 1 and "target" in data:
482-
target = data["target"]
483-
details = {
484-
"detailed_weight": target.get("current_weight", 0) / 10.0,
485-
"detailed_body_fat": target.get("current_bodyfat", 0),
486-
"detailed_muscle_mass": target.get("current_muscle_mass", 0),
487-
"target_body_fat": target.get("target_bodyfat", 0),
488-
}
489-
_LOGGER.debug("Extracted customer details for %s: %s", customer_id[:8], details)
490-
return details
491-
else:
492-
_LOGGER.warning(
493-
"Customer detail API returned error for %s. res_code: %s",
494-
customer_id[:8], data.get("res_code")
495-
)
496-
else:
497-
_LOGGER.warning(
498-
"Customer detail request failed for %s with status %d",
499-
customer_id[:8], response.status
500-
)
501-
502-
except Exception as err:
503-
_LOGGER.warning("Error fetching customer details for %s: %s", customer_id[:8], err)
504-
505-
return {}
506331

507332
def update_interval_from_config(self) -> None:
508333
"""Update the coordinator's update interval from config entry."""
@@ -670,16 +495,8 @@ def extra_state_attributes(self) -> dict[str, Any] | None:
670495
if self.coordinator._last_successful_update:
671496
attrs["last_successful_update"] = self.coordinator._last_successful_update.isoformat()
672497

673-
# Add data source information
674-
data_sources = []
675-
if customer_data.get("device_weight") or customer_data.get("device_body_fat") or customer_data.get("device_muscle_mass"):
676-
data_sources.append("device_data")
677-
if any(key not in ["device_weight", "device_body_fat", "device_muscle_mass", "device_timestamp"]
678-
for key in customer_data.keys() if key.startswith(("weight", "body_fat", "muscle_mass", "target_"))):
679-
data_sources.append("target_data")
680-
681-
if data_sources:
682-
attrs["data_source"] = ", ".join(data_sources)
498+
# Add data source information (device data only)
499+
attrs["data_source"] = "device_data"
683500

684501
# Add specific device data indicators for this sensor type
685502
device_data_key = f"device_{self.sensor_type}"

version.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2.0.4
1+
2.0.5

0 commit comments

Comments
 (0)