From c97395be06d16c4d68f0087d6cac95bfd980c28a Mon Sep 17 00:00:00 2001 From: ollo69 Date: Wed, 1 Feb 2023 00:04:19 +0100 Subject: [PATCH] Fixes running application identification (issue #145 and #224) --- .devcontainer/configuration.yaml | 21 ------ .../samsungtv_smart/api/samsungws.py | 68 +++++++++++-------- .../samsungtv_smart/manifest.json | 2 +- 3 files changed, 41 insertions(+), 50 deletions(-) diff --git a/.devcontainer/configuration.yaml b/.devcontainer/configuration.yaml index ba19737..045bf49 100644 --- a/.devcontainer/configuration.yaml +++ b/.devcontainer/configuration.yaml @@ -13,24 +13,3 @@ logger: default: info #logs: # custom_components.samsungtv_smart: debug - -samsungtv_smart: - # TV Samnsung - - host: 192.168.10.11 - source_list: - { "TV": "ST_TV", "Sky Italia": "ST_HDMI1", "BluRay Player": "ST_HDMI2" } - app_list: - { - "Netflix": "3201907018807/org.tizen.netflix-app", - "Disney+": "3201901017640/MCmYXNxgcu.DisneyPlus", - "Paramount+": "3202110025305/rJyOSqC6Up.PPlusIntl", - "PrimeVideo": "3201512006785/org.tizen.ignition", - "Infinity": "3201609010702/g3zzYA7yO8.InfinityNew", - "RaiPlay": "111399002034/5im3FztIhW.RaiTVtizen", - "CHILI": "3201505002690/vYlbEeT4gk.ChiliTizen", - "DAZN": "3201806016390/yu1NM3vHsU.DAZN", - "YouTube": "111299001912/9Ur5IzDKqV.TizenYouTube", - "Internet": "org.tizen.browser", - } - channel_list: '{"Rai1": "1@TV", "Rai2": "2@TV", "SkyTG24": "100@Sky Italia", "FoxHD": "112@Sky Italia"}' - #timeout: 5 diff --git a/custom_components/samsungtv_smart/api/samsungws.py b/custom_components/samsungtv_smart/api/samsungws.py index c2239c1..53f0188 100644 --- a/custom_components/samsungtv_smart/api/samsungws.py +++ b/custom_components/samsungtv_smart/api/samsungws.py @@ -44,7 +44,8 @@ from .shortcuts import SamsungTVShortcuts DEFAULT_POWER_ON_DELAY = 120 -MIN_APP_SCAN_INTERVAL = 10 +MIN_APP_SCAN_INTERVAL = 8 +MAX_APP_VALIDITY_SEC = 60 MAX_WS_PING_INTERVAL = 10 PING_TIMEOUT = 3 TYPE_DEEP_LINK = "DEEP_LINK" @@ -55,6 +56,7 @@ _WS_ENDPOINT_ART = "/api/v2/channels/com.samsung.art-app" _WS_LOG_NAME = "websocket" +_LOG_PING_PONG = False _LOGGING = logging.getLogger(__name__) @@ -99,6 +101,13 @@ def _process_api_response(response, *, raise_error=True): return response +def _log_ping_pong(msg, *args): + """Log ping pong message if enabled.""" + if not _LOG_PING_PONG: + return + _LOGGING.debug(msg=msg, args=args) + + class Ping: """Class for handling Ping to a specific host.""" @@ -495,7 +504,7 @@ def _client_remote_thread(self): def _on_ping_remote(self, _, payload): """Manage ping message received by remote WS connection.""" - _LOGGING.debug("Received WS remote ping %s, sending pong", payload) + _log_ping_pong("Received WS remote ping %s, sending pong", payload) self._last_ping = datetime.utcnow() if self._ws_remote.sock: try: @@ -532,7 +541,7 @@ def _on_message_remote(self, _, message): self._handle_installed_app(response) elif event == "ed.edenTV.update": _LOGGING.debug("Message remote: received edenTV") - self.get_running_app(force_scan=True) + self._get_running_app(force_scan=True) def _request_apps_list(self): """Request to the TV the list of installed apps.""" @@ -584,7 +593,7 @@ def _client_control_thread(self): def _on_ping_control(self, _, payload): """Manage ping message received by control WS channel.""" - _LOGGING.debug("Received WS control ping %s, sending pong", payload) + _log_ping_pong("Received WS control ping %s, sending pong", payload) self._last_control_ping = datetime.utcnow() if self._ws_control.sock: try: @@ -612,7 +621,7 @@ def _on_message_control(self, _, message): if not self._check_conn_id(conn_data): return _LOGGING.debug("Message control: received connect") - self.get_running_app() + self._get_running_app() elif event == "ed.installedApp.get": _LOGGING.debug("Message control: received installedApp") self._handle_installed_app(response) @@ -623,14 +632,16 @@ def _set_running_app(self, response): return if (result := response.get("result")) is None: return - if not isinstance(result, dict): - self._running_app = None - return - if (is_running := result.get("visible")) is None: + if isinstance(result, bool): + is_running = result + elif (is_running := result.get("visible")) is None: return - self._last_running_scan = datetime.utcnow() - self._running_apps[app_id] = self._last_running_scan + call_time = datetime.utcnow() + with self._sync_lock: + self._last_app_scan = call_time + self._last_running_scan = call_time + self._running_apps[app_id] = call_time if self._running_app: if is_running and app_id != self._running_app: _LOGGING.debug("app running: %s", app_id) @@ -717,7 +728,7 @@ def _client_art_thread(self): def _on_ping_art(self, _, payload): """Manage ping message received by art WS channel.""" - _LOGGING.debug("Received WS art ping %s, sending pong", payload) + _log_ping_pong("Received WS art ping %s, sending pong", payload) self._last_art_ping = datetime.utcnow() if self._ws_art.sock: try: @@ -833,7 +844,8 @@ def is_app_running(self, app_id: str) -> bool | None: return True if (last_seen := self._running_apps.get(app_id)) is None: return None - if (self._last_running_scan - last_seen).total_seconds() >= 60: + app_age = (self._last_running_scan - last_seen).total_seconds() + if app_age >= MAX_APP_VALIDITY_SEC: self._running_apps.pop(app_id) return None return False @@ -863,6 +875,7 @@ def _check_remote(self): self._artmode_status = ArtModeStatus.Unavailable else: self._check_art_mode() + self._get_running_app() self._notify_app_change() if self._power_on_requested: @@ -894,28 +907,16 @@ def _notify_app_change(self): self._running_app_changed = False self._status_callback() - def set_power_on_request(self, set_art_mode=False, power_on_delay=0): - """Set a power on request status and save the time of the rquest.""" - self._power_on_requested = True - self._power_on_requested_time = datetime.utcnow() - self._power_on_artmode = set_art_mode - self._power_on_delay = max(power_on_delay, 0) or DEFAULT_POWER_ON_DELAY - - def set_power_off_request(self): - """Remove a previous power on request.""" - self._power_on_requested = False - - def get_running_app(self, *, force_scan=False): + def _get_running_app(self, *, force_scan=False): """Query current running app using control channel.""" if not self._ws_control: return + scan_interval = 1 if force_scan else MIN_APP_SCAN_INTERVAL with self._sync_lock: call_time = datetime.utcnow() difference = (call_time - self._last_app_scan).total_seconds() - if ( - difference < MIN_APP_SCAN_INTERVAL and not force_scan - ) or difference < 1: + if difference <= scan_interval: return self._last_app_scan = call_time @@ -937,6 +938,17 @@ def get_running_app(self, *, force_scan=False): for app in app_to_check.values(): self._get_app_status(app.app_id, app.app_type) + def set_power_on_request(self, set_art_mode=False, power_on_delay=0): + """Set a power on request status and save the time of the rquest.""" + self._power_on_requested = True + self._power_on_requested_time = datetime.utcnow() + self._power_on_artmode = set_art_mode + self._power_on_delay = max(power_on_delay, 0) or DEFAULT_POWER_ON_DELAY + + def set_power_off_request(self): + """Remove a previous power on request.""" + self._power_on_requested = False + def start_poll(self): """Start polling the TV for status.""" if self._ping_thread is None or not self._ping_thread.is_alive(): diff --git a/custom_components/samsungtv_smart/manifest.json b/custom_components/samsungtv_smart/manifest.json index 77dbe67..09705be 100644 --- a/custom_components/samsungtv_smart/manifest.json +++ b/custom_components/samsungtv_smart/manifest.json @@ -14,5 +14,5 @@ "codeowners": ["@jaruba", "@ollo69", "@screwdgeh"], "config_flow": true, "iot_class": "cloud_polling", - "version": "0.11.4b1" + "version": "0.11.4" }