@@ -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 } "
0 commit comments