Skip to content

Commit

Permalink
3.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
antoinebou12 authored Mar 6, 2024
1 parent d781559 commit fb8cdb7
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 132 deletions.
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
# Renpho Weight Home Assistant Component

# NOT Working for now

![Version](https://img.shields.io/badge/version-v2.0.2.1-blue)
![License](https://img.shields.io/badge/license-MIT-green)
![Build Status](https://img.shields.io/badge/build-passing-brightgreen)
Expand Down
116 changes: 77 additions & 39 deletions api/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,43 @@ def get(self, key, default=None):
return getattr(self, key, default)


import asyncio
import datetime
import logging
import time
from base64 import b64encode
from typing import Callable, Dict, Final, List, Optional, Union

import aiohttp
from Crypto.Cipher import PKCS1_v1_5
from Crypto.PublicKey import RSA

from .const import CONF_PUBLIC_KEY

METRIC_TYPE_WEIGHT: Final = "weight"
METRIC_TYPE_GROWTH_RECORD: Final = "growth_record"
METRIC_TYPE_GIRTH: Final = "girth"
METRIC_TYPE_GIRTH_GOAL: Final = "girth_goals"

from .api_object import UserResponse, DeviceBind, MeasurementDetail, Users, GirthGoal, GirthGoalsResponse, Girth, GirthResponse, MeasurementResponse

# Initialize logging
_LOGGER = logging.getLogger(__name__)

# API Endpoints
API_AUTH_URL = "https://renpho.qnclouds.com/api/v3/users/sign_in.json?app_id=Renpho" # Authentication Post
API_SCALE_USERS_URL = "https://renpho.qnclouds.com/api/v3/scale_users/list_scale_user" # Scale users
API_MEASUREMENTS_URL = "https://renpho.qnclouds.com/api/v2/measurements/list.json" # Measurements
DEVICE_INFO_URL = "https://renpho.qnclouds.com/api/v2/device_binds/get_device.json" # Device info
LATEST_MODEL_URL = "https://renpho.qnclouds.com/api/v3/devices/list_lastest_model.json" # Latest model
GIRTH_URL = "https://renpho.qnclouds.com/api/v3/girths/list_girth.json" # Girth
GIRTH_GOAL_URL = "https://renpho.qnclouds.com/api/v3/girth_goals/list_girth_goal.json" # Girth goal
GROWTH_RECORD_URL = "https://renpho.qnclouds.com/api/v3/growth_records/list_growth_record.json" # Growth record
MESSAGE_LIST_URL = "https://renpho.qnclouds.com/api/v2/messages/list.json" # message to support
USER_REQUEST_URL = "https://renpho.qnclouds.com/api/v2/users/request_user.json" # error
USERS_REACH_GOAL = "https://renpho.qnclouds.com/api/v3/users/reach_goal.json" # error 404


class RenphoWeight:
"""
A class to interact with Renpho's weight scale API.
Expand Down Expand Up @@ -412,7 +449,6 @@ async def open_session(self):
)



async def _request(self, method: str, url: str, retries: int = 3, skip_auth=False, **kwargs):
"""
Perform an API request and return the parsed JSON response.
Expand All @@ -427,42 +463,43 @@ async def _request(self, method: str, url: str, retries: int = 3, skip_auth=Fals
Returns:
Union[Dict, List]: The parsed JSON response from the API request.
"""
token = self.token
while retries > 0:
session = aiohttp.ClientSession(
headers={"Content-Type": "application/json", "Accept": "application/json",
"User-Agent": "Renpho/2.1.0 (iPhone; iOS 14.4; Scale/2.1.0; en-US)"
}
)

if retries < 1:
_LOGGER.error("Max retries exceeded for API request.")
raise APIError("Max retries exceeded for API request.")

session = aiohttp.ClientSession(
headers={"Content-Type": "application/json", "Accept": "application/json",
"User-Agent": "Renpho/2.1.0 (iPhone; iOS 14.4; Scale/2.1.0; en-US)"
}
)

if not self.token and not url.endswith("sign_in.json"):
auth_success = await self.auth()
if not auth_success:
raise AuthenticationError("Authentication failed. Unable to proceed with the request.")

kwargs = self.prepare_data(kwargs)

try:
async with session.request(method, url, **kwargs) as response:
response.raise_for_status()
parsed_response = await response.json()

if parsed_response.get("status_code") == "40302":
pass
if parsed_response.get("status_code") == "50000":
raise APIError(f"Internal server error: {parsed_response.get('status_message')}")
if parsed_response.get("status_code") == "20000" and parsed_response.get("status_message") == "ok":
return parsed_response
else:
raise APIError(f"API request failed {method} {url}: {parsed_response.get('status_message')}")
except (aiohttp.ClientResponseError, aiohttp.ClientConnectionError) as e:
_LOGGER.error(f"Client error: {e}")
raise APIError(f"API request failed {method} {url}") from e
finally:
await session.close()
if not token and not url.endswith("sign_in.json") and not skip_auth:
auth_success = await self.auth()
token = self.token
if not auth_success:
raise AuthenticationError("Authentication failed. Unable to proceed with the request.")

kwargs = self.prepare_data(kwargs)

try:
async with session.request(method, url, **kwargs) as response:
response.raise_for_status()
parsed_response = await response.json()

if parsed_response.get("status_code") == "40302":
token = None
skip_auth = False
retries -= 1
continue # Retry the request
if parsed_response.get("status_code") == "50000":
raise APIError(f"Internal server error: {parsed_response.get('status_message')}")
if parsed_response.get("status_code") == "20000" and parsed_response.get("status_message") == "ok":
return parsed_response
else:
raise APIError(f"API request failed {method} {url}: {parsed_response.get('status_message')}")
except (aiohttp.ClientResponseError, aiohttp.ClientConnectionError) as e:
_LOGGER.error(f"Client error: {e}")
raise APIError(f"API request failed {method} {url}") from e
finally:
await session.close()

@staticmethod
def encrypt_password(public_key_str, password):
Expand Down Expand Up @@ -609,7 +646,7 @@ async def get_measurements(self):
if measurements := parsed["last_ary"]:
self.weight_history = [MeasurementDetail(**measurement) for measurement in measurements]
self.weight_info = self.weight_history[0] if self.weight_history else None
self.weight = self.weight_history[0].weight if self.weight_info else None
self.weight = self.weight_info.weight if self.weight_info else None
self.time_stamp = self.weight_info.time_stamp if self.weight_info else None
self._last_updated_weight = time.time()
return self.weight_info
Expand Down Expand Up @@ -652,8 +689,9 @@ async def get_device_info(self):

# Check for successful response code
if parsed.get("status_code") == "20000" and "device_binds_ary" in parsed:
self.device_info = parsed["device_binds_ary"]
return self.device_info
device_info = [DeviceBind(**device) for device in parsed["device_binds_ary"]]
self.device_info = device_info
return device_info
else:
# Handling different error scenarios
if "status_code" not in parsed:
Expand Down
94 changes: 44 additions & 50 deletions custom_components/renpho/api_renpho.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,6 @@ async def open_session(self):
)



async def _request(self, method: str, url: str, retries: int = 3, skip_auth=False, **kwargs):
"""
Perform an API request and return the parsed JSON response.
Expand All @@ -123,42 +122,43 @@ async def _request(self, method: str, url: str, retries: int = 3, skip_auth=Fals
Returns:
Union[Dict, List]: The parsed JSON response from the API request.
"""
token = self.token
while retries > 0:
session = aiohttp.ClientSession(
headers={"Content-Type": "application/json", "Accept": "application/json",
"User-Agent": "Renpho/2.1.0 (iPhone; iOS 14.4; Scale/2.1.0; en-US)"
}
)

if retries < 1:
_LOGGER.error("Max retries exceeded for API request.")
raise APIError("Max retries exceeded for API request.")

session = aiohttp.ClientSession(
headers={"Content-Type": "application/json", "Accept": "application/json",
"User-Agent": "Renpho/2.1.0 (iPhone; iOS 14.4; Scale/2.1.0; en-US)"
}
)

if not self.token and not url.endswith("sign_in.json"):
auth_success = await self.auth()
if not auth_success:
raise AuthenticationError("Authentication failed. Unable to proceed with the request.")

kwargs = self.prepare_data(kwargs)

try:
async with session.request(method, url, **kwargs) as response:
response.raise_for_status()
parsed_response = await response.json()

if parsed_response.get("status_code") == "40302":
pass
if parsed_response.get("status_code") == "50000":
raise APIError(f"Internal server error: {parsed_response.get('status_message')}")
if parsed_response.get("status_code") == "20000" and parsed_response.get("status_message") == "ok":
return parsed_response
else:
raise APIError(f"API request failed {method} {url}: {parsed_response.get('status_message')}")
except (aiohttp.ClientResponseError, aiohttp.ClientConnectionError) as e:
_LOGGER.error(f"Client error: {e}")
raise APIError(f"API request failed {method} {url}") from e
finally:
await session.close()
if not token and not url.endswith("sign_in.json") and not skip_auth:
auth_success = await self.auth()
token = self.token
if not auth_success:
raise AuthenticationError("Authentication failed. Unable to proceed with the request.")

kwargs = self.prepare_data(kwargs)

try:
async with session.request(method, url, **kwargs) as response:
response.raise_for_status()
parsed_response = await response.json()

if parsed_response.get("status_code") == "40302":
token = None
skip_auth = False
retries -= 1
continue # Retry the request
if parsed_response.get("status_code") == "50000":
raise APIError(f"Internal server error: {parsed_response.get('status_message')}")
if parsed_response.get("status_code") == "20000" and parsed_response.get("status_message") == "ok":
return parsed_response
else:
raise APIError(f"API request failed {method} {url}: {parsed_response.get('status_message')}")
except (aiohttp.ClientResponseError, aiohttp.ClientConnectionError) as e:
_LOGGER.error(f"Client error: {e}")
raise APIError(f"API request failed {method} {url}") from e
finally:
await session.close()

@staticmethod
def encrypt_password(public_key_str, password):
Expand Down Expand Up @@ -537,22 +537,16 @@ async def get_specific_metric(self, metric_type: str, metric: str, user_id: Opti
return self.weight_info.get(metric, None) if self.weight_info else None
elif metric_type == METRIC_TYPE_GIRTH:
if self._last_updated_girth is None or time.time() - self._last_updated_girth > self.refresh:
last_measurement = (
self.girth_info[0]
if self.girth_info
else None
)
return last_measurement.get(metric, None) if last_measurement else None
return self.girth_info[0].get(metric, None) if self.girth_info else None
await self.list_girth()
for girth_entry in self.girth_info:
if hasattr(girth_entry, f"{metric}_value"):
return getattr(girth_entry, f"{metric}_value", None)
elif metric_type == METRIC_TYPE_GIRTH_GOAL:
last_goal = next(
(goal for goal in self.girth_goal if goal.girth_type == metric),
None
)
if self._last_updated_girth_goal is None or time.time() - self._last_updated_girth_goal > self.refresh:
return last_goal.get('goal_value', None)
else:
return last_goal.get('goal_value', None)
await self.list_girth_goal()
for goal in self.girth_goal:
if goal.girth_type == metric:
return goal.goal_value
elif metric_type == METRIC_TYPE_GROWTH_RECORD:
if self._last_updated_growth_record is None or time.time() - self._last_updated_growth_record > self.refresh:
last_measurement = (
Expand Down
3 changes: 0 additions & 3 deletions custom_components/renpho/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,3 @@ async def _async_update_data(self):
@property
def last_updated(self):
return self._last_updated

async def get_specific_metric(self, metric_type: str, metric: str):
return await self.api.get_specific_metric(metric_type = metric_type, metric = metric, user_id = self._user_id)
1 change: 0 additions & 1 deletion custom_components/renpho/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,6 @@ def state(self):
async def async_update(self):
"""Request an immediate update of the coordinator data."""
try:
await coordinator.async_refresh()
metric_value = await self.coordinator.api.get_specific_metric(
metric_type=self._metric,
metric=self._id,
Expand Down
Loading

0 comments on commit fb8cdb7

Please sign in to comment.