Skip to content

Commit

Permalink
Refactoring maintenance
Browse files Browse the repository at this point in the history
  • Loading branch information
kloemi committed Nov 25, 2024
1 parent 3bdd220 commit f28638a
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 81 deletions.
75 changes: 34 additions & 41 deletions custom_components/samsvolleyball/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
VERSION,
)

UPDATE_FULL_INTERVAL = 5 * 60 # 5 min.
_LOGGER = logging.getLogger(__name__)


Expand Down Expand Up @@ -80,7 +81,8 @@ def __init__(
self.get_url = get_url
self.ws = None
self.ws_task = None
self.last_receive_ts = dt_util.as_timestamp(dt_util.utcnow())
self.last_get_ts = dt_util.as_timestamp(dt_util.start_of_local_day())
self.last_ws_receive_ts = dt_util.as_timestamp(dt_util.utcnow())
self.connected = False
self.loop: asyncio.AbstractEventLoop = asyncio.get_event_loop()
self.receive_timout = TIMEOUT[NO_GAME]
Expand All @@ -93,13 +95,28 @@ def __init__(
"Accept-Encoding": "gzip, deflate, br, zstd",
"Accept-Language": "de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7",
}

_LOGGER.debug("Init coordinator for %s", self.name)
super().__init__(hass, _LOGGER, name=self.name)

async def _async_update_data(self):
if not self.ws or not self.connected:
_LOGGER.debug("Connect to %s", self.websocket_url)
await self.connect()
now = dt_util.utcnow()
ts = dt_util.as_timestamp(now)
data = None
if (ts - self.last_get_ts) > UPDATE_FULL_INTERVAL:
data = await self.get_full_data()
self.last_get_ts = ts

if await self.game_active():
if not self.ws or not self.connected:
_LOGGER.debug("Connect to %s", self.websocket_url)
await self.connect()
self.last_ws_receive_ts = ts
if ts - self.last_ws_receive_ts > self.receive_timout:
_LOGGER.debug("Timeout on ws %s - reconnect", self.name)
await self.disconnect()
await self.connect()
self.last_ws_receive_ts = ts
return data

async def _on_close(self):
_LOGGER.debug(f"Connection closed - {self.name}")
Expand All @@ -109,16 +126,13 @@ async def _on_open(self):
_LOGGER.info(f"Connection opened - {self.name}")
self.connected = True

async def update_data(self, data):
self.async_set_updated_data(data)
self.last_receive_ts = dt_util.as_timestamp(dt_util.utcnow())

async def _on_message(self, message: WSMessage):
if message.type == WSMsgType.TEXT:
data = json.loads(message.data)
_LOGGER.debug("Received data: %s ", str(message)[1:500])
if data:
await self.update_data(data)
self.async_set_updated_data(data)
self.last_ws_receive_ts = dt_util.as_timestamp(dt_util.utcnow())
if self.receive_timout == TIMEOUT[NO_GAME]:
_LOGGER.info(f"{self.name} - no game active - close socket.")
await self.disconnect()
Expand All @@ -127,6 +141,12 @@ async def _on_message(self, message: WSMessage):
"%s - received unexpected message: %s ", self.name, str(message)[1:500]
)

async def get_full_data(self) -> dict:
resp = await self.session.get(self.get_url, raise_for_status=True)
_LOGGER.info("%s received full ticker json", self.name)
data = await resp.json()
return data

async def _process_messages(self):
try:
async for msg in self.ws:
Expand All @@ -142,15 +162,8 @@ async def _process_messages(self):
"Error during processing new message: %s", exc.with_traceback()
)

async def get_initial_data(self):
resp = await self.session.get(self.get_url, raise_for_status=True)
data = await resp.json()
await self.update_data(data)
return data

async def connect(self):
try:
await self.get_initial_data()
self.ws = await self.session.ws_connect(
self.websocket_url,
autoclose=False,
Expand All @@ -172,29 +185,9 @@ async def disconnect(self):
await self.ws.close()
self.ws = None

async def check_timeout(self, now):
# check last received data time
await self.update_timeout()
ts = dt_util.as_timestamp(now)
diff = ts - self.last_receive_ts
if diff > self.receive_timout:
self.last_receive_ts = ts # prevent rush of reconnects
_LOGGER.info("%s Sams Websocket reset - receive data timeout", self.name)
await self.disconnect()
await self.connect()

async def update_timeout(self):
match_active = NO_GAME
async def game_active(self) -> bool:
for _, active_cb in list(self._listeners.values()):
# call function get_active_state
active = active_cb()
if active > match_active:
match_active = active
timeout = TIMEOUT[match_active]
if timeout < self.receive_timout:
await self.disconnect()
await self.connect()
self.receive_timout = timeout

def hasListener(self) -> bool:
return len(self._listeners) > 0
if active_cb() > 0:
return True
return False
2 changes: 1 addition & 1 deletion custom_components/samsvolleyball/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]):
coordinator = SamsDataCoordinator(hass, session, "ConfigValidate", url, get_url)

try:
data = await coordinator.get_initial_data()
data = await coordinator.get_full_data()
except Exception as exc:
raise CannotConnect from exc
if not data:
Expand Down
38 changes: 16 additions & 22 deletions custom_components/samsvolleyball/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
from homeassistant.const import ATTR_ATTRIBUTION
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.util import dt as dt_util
from homeassistant.util import slugify
Expand All @@ -32,12 +31,12 @@
NO_GAME,
STATES_IN,
STATES_NOT_FOUND,
TIMEOUT_PERIOD_CHECK,
VOLLEYBALL,
)
from .utils import SamsUtils

_LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(seconds=5)


async def async_setup_entry(
Expand All @@ -54,7 +53,7 @@ async def async_setup_entry(
]

# Add sensor entities.
async_add_entities(entities, True)
async_add_entities(entities, update_before_add=False)


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
Expand Down Expand Up @@ -92,19 +91,14 @@ def __init__(
async def async_added_to_hass(self) -> None:
"""Subscribe timer events."""
await super().async_added_to_hass()
self.async_on_remove(
async_track_time_interval(
self.hass,
self._coordinator.check_timeout,
timedelta(seconds=TIMEOUT_PERIOD_CHECK),
)
)

try:
self._lang = self.hass.config.language
except Exception: # pylint: disable=broad-except
lang, _ = locale.getlocale()
self._lang = lang or "en_US"
_LOGGER.debug(
f"Added entity {self.name} with coordinator {self._coordinator.name} listener {len(self._coordinator._listeners)}"
)

def _update_overview(self, data):
_LOGGER.debug("Update team data for sensor %s", self._name)
Expand Down Expand Up @@ -132,17 +126,13 @@ def _update_overview(self, data):

def get_active_state(self):
# check if we are nearby (2 hours before / 3 hours behind)
if not self._ticker_data:
return NEAR_GAME

if self._state != STATES_NOT_FOUND:
if self._state == STATES_IN:
return IN_GAME
if self._match and "date" in self._attr:
date = self._attr["date"]
duration = (dt_util.now() - date).total_seconds()
if (-2 * 60 * 60) < duration < (3 * 60 * 60):
return NEAR_GAME
if self._state == STATES_IN:
return IN_GAME
elif self._match and "date" in self._attr:
date = self._attr["date"]
duration = (dt_util.now() - date).total_seconds()
if (-2 * 60 * 60) < duration < (3 * 60 * 60):
return NEAR_GAME
return NO_GAME

@callback
Expand Down Expand Up @@ -211,3 +201,7 @@ def available(self) -> bool:
def icon(self) -> str:
"""Return the icon to use in the frontend, if any."""
return DEFAULT_ICON

@property
def should_poll(self) -> bool:
return True
38 changes: 21 additions & 17 deletions custom_components/samsvolleyball/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ def get_leaguelist(data: dict, gender=None) -> list[dict[str, str]]:
leagues = [
{NAME: series[NAME], ID: series_id}
for series_id, series in data[MATCHSERIES].items()
if gender and series[GENDER] == gender and series[CLASS] == CLASS_LEAGUE
if gender
and series[GENDER] == gender
and series[CLASS] == CLASS_LEAGUE
]
else:
leagues = [
Expand All @@ -67,26 +69,26 @@ def get_leaguelist(data: dict, gender=None) -> list[dict[str, str]]:
return leagues

@staticmethod
def get_league_by_id(data: dict, league_id:str)-> dict:
def get_league_by_id(data: dict, league_id: str) -> dict:
if SamsUtils.is_overview(data):
return data[MATCHSERIES][league_id]
return {}

@staticmethod
def get_teamlist(data:dict, league_id:str) -> dict[str, str]:
def get_teamlist(data: dict, league_id: str) -> dict[str, str]:
teams: dict[str, str] = {}
series = SamsUtils.get_league_by_id(data, league_id)
for team in series[TEAMS]:
teams[team[NAME]] = team[ID]
return teams

@staticmethod
def get_league_data(data:dict, league_id:str, field):
def get_league_data(data: dict, league_id: str, field):
series = SamsUtils.get_league_by_id(data, league_id)
return series[field]

@staticmethod
def get_uuids_by_name(data:dict, name:str, league:str):
def get_uuids_by_name(data: dict, name: str, league: str):
uuid = []
if SamsUtils.is_overview(data):
for series_id in data[MATCHSERIES]:
Expand All @@ -98,7 +100,7 @@ def get_uuids_by_name(data:dict, name:str, league:str):
return uuid

@staticmethod
def get_team_by_id(data:dict, team_id:str):
def get_team_by_id(data: dict, team_id: str):
if SamsUtils.is_overview(data):
for series_id in data[MATCHSERIES]:
series = data[MATCHSERIES][series_id]
Expand All @@ -112,24 +114,24 @@ def get_match_data(data):
return data[PAYLOAD]

@staticmethod
def get_matches(data:dict, team_id):
def get_matches(data: dict, team_id):
matches = []
if SamsUtils.is_overview(data):
matchdays = data[MATCHDAYS]
for matchday in matchdays:
for match in matchday[MATCHES]:
if team_id in (match[TEAM+"1"], match[TEAM+"2"]):
if team_id in (match[TEAM + "1"], match[TEAM + "2"]):
matches.append(match)
return matches

@staticmethod
def get_match_state(data:dict, match_id: str):
def get_match_state(data: dict, match_id: str):
if SamsUtils.is_overview(data):
if match_id in data[MATCHSTATES]:
return data[MATCHSTATES][match_id]

@staticmethod
def state_from_match_state(match_state:dict):
def state_from_match_state(match_state: dict):
state = STATES_NOT_FOUND
if match_state:
if match_state[FINISHED]:
Expand All @@ -144,7 +146,7 @@ def state_from_match_state(match_state:dict):
return state

@staticmethod
def state_from_match(data:dict, match:dict):
def state_from_match(data: dict, match: dict):
match_state = SamsUtils.get_match_state(data, match[ID])
return SamsUtils.state_from_match_state(match_state)

Expand All @@ -153,7 +155,7 @@ def date_from_match(match) -> datetime:
return dt_util.as_local(dt_util.utc_from_timestamp(float(match[DATE]) / 1000))

@staticmethod
def select_match(data:dict, matches: list):
def select_match(data: dict, matches: list):
# assumes matches are sorted by date
for match in matches:
state = SamsUtils.state_from_match(data, match)
Expand Down Expand Up @@ -202,7 +204,9 @@ def _get_set_string(match_state, team_num, opponent_num, offset):
return set_string

@staticmethod
def fill_match_attrs(attrs:dict, match_state:dict, state:str, team_num:str, opponent_num:str):
def fill_match_attrs(
attrs: dict, match_state: dict, state: str, team_num: str, opponent_num: str
):
attrs["team_winner"] = None
attrs["opponent_winner"] = None

Expand Down Expand Up @@ -247,13 +251,13 @@ def fill_match_attrs(attrs:dict, match_state:dict, state:str, team_num:str, oppo
return attrs

@staticmethod
def _get_ranking(league:dict, team_id:str):
def _get_ranking(league: dict, team_id: str):
for rank in league["rankings"]["fullRankings"]:
if rank[TEAM][ID] == team_id:
return rank

@staticmethod
def fill_team_attributes(attrs:dict, data:dict, team:dict, state:str):
def fill_team_attributes(attrs: dict, data: dict, team: dict, state: str):
try:
_, league = SamsUtils.get_team_by_id(data, team[ID])
rank_team = SamsUtils._get_ranking(league, team[ID])
Expand Down Expand Up @@ -284,7 +288,7 @@ def fill_team_attributes(attrs:dict, data:dict, team:dict, state:str):
return attrs

@staticmethod
def fill_match_attributes(attrs:dict, data:dict, match:dict, team:dict, lang):
def fill_match_attributes(attrs: dict, data: dict, match: dict, team: dict, lang):
try:
attrs["match_id"] = match[ID]
state = SamsUtils.state_from_match(data, match)
Expand Down Expand Up @@ -353,7 +357,7 @@ def fill_match_attributes(attrs:dict, data:dict, match:dict, team:dict, lang):
return attrs

@staticmethod
def update_match_attributes(attrs:dict, data:dict):
def update_match_attributes(attrs: dict, data: dict):
match_state = data
state = SamsUtils.state_from_match_state(match_state)

Expand Down

0 comments on commit f28638a

Please sign in to comment.