Explorar o código

decouple tcp socket state from HA availablity

Noah Vogt hai 2 meses
pai
achega
038aaaa0db
Modificáronse 1 ficheiros con 16 adicións e 25 borrados
  1. 16 25
      custom_components/pjlink2/media_player.py

+ 16 - 25
custom_components/pjlink2/media_player.py

@@ -74,7 +74,6 @@ async def async_setup_platform(
     async_add_entities: Callable,
     discovery_info: DiscoveryInfoType | None = None,
 ) -> None:
-    """Set up the media_player platform."""
     host = config.get(CONF_HOST)
     port = config.get(CONF_PORT)
     password = config.get(CONF_PASSWORD)
@@ -86,7 +85,6 @@ async def async_setup_platform(
 
 
 class PJLink2MediaPlayer(MediaPlayerEntity):
-    """Representation of a PJLink2 media player."""
 
     _attr_supported_features = (
         MediaPlayerEntityFeature.TURN_ON
@@ -100,12 +98,14 @@ class PJLink2MediaPlayer(MediaPlayerEntity):
         self.attrs: dict[str, Any] = {}
         self._name = name
         self._state = MediaPlayerState.OFF
-        self._available = False
+
+        # The Fix: Decoupling TCP socket state from HA's availability state
+        self._socket_open = False
+        self._is_available = False
+
         self._connectionErrorLogged = False
         self._current_source = None
 
-        # --- FRIENDLY NAMES MAPPING ---
-        # Change these values to whatever you want them to be called in Home Assistant
         self._source_mapping = {
             "31": "HDMI 1",
             "32": "HDMI 2",
@@ -116,9 +116,8 @@ class PJLink2MediaPlayer(MediaPlayerEntity):
         self._source_list = list(self._source_mapping.values())
 
     async def async_will_remove_from_hass(self) -> None:
-        """Close connection."""
         await super().async_will_remove_from_hass()
-        if self._available:
+        if self._socket_open:
             try:
                 await self._projector.__aexit__(0, 0, 0)
             except Exception:
@@ -134,7 +133,7 @@ class PJLink2MediaPlayer(MediaPlayerEntity):
 
     @property
     def available(self) -> bool:
-        return self._available
+        return self._is_available
 
     @property
     def state(self) -> MediaPlayerState:
@@ -161,8 +160,6 @@ class PJLink2MediaPlayer(MediaPlayerEntity):
         self._state = MediaPlayerState.OFF
 
     async def async_select_source(self, source: str) -> None:
-        """Select input source."""
-        # Convert friendly name (e.g. "HDMI 1") back to raw PJLink code (e.g. "31")
         raw_source = self._reverse_mapping.get(source, source)
         source_type = raw_source[0]
         source_index = raw_source[1]
@@ -171,11 +168,11 @@ class PJLink2MediaPlayer(MediaPlayerEntity):
         self._current_source = source
 
     async def async_update(self) -> None:
-        """Update data from projector."""
         try:
-            if not self._available:
+            if not self._socket_open:
                 await self._projector.__aenter__()
-                self._available = True
+                self._socket_open = True
+                self._is_available = True
                 info = await Information(self._projector).table()
                 self.attrs[ATTR_PRODUCT_NAME] = info.get("product_name")
                 self.attrs[ATTR_MANUFACTURER_NAME] = info.get(
@@ -194,26 +191,20 @@ class PJLink2MediaPlayer(MediaPlayerEntity):
                 self._state = MediaPlayerState.ON
 
             if pwr == Power.ON:
-                # 1. Fetch current source
                 try:
                     current = await Sources(self._projector).get()
                     if isinstance(current, (tuple, list)):
                         raw_source = "".join(map(str, current))
                     else:
                         raw_source = str(current)
-
-                    # Apply friendly name mapping
                     self._current_source = self._source_mapping.get(
                         raw_source, raw_source
                     )
-
                 except Exception as e:
-                    # THE FIX: If the projector is busy (ERR3), bubble the error up so we force a disconnect
                     if "ERR3" in repr(e) or "unavailable" in repr(e):
                         raise e
                     _LOGGER.debug("Ignored error getting source: %s", repr(e))
 
-                # 2. Fetch Lamp Hours
                 try:
                     self.attrs[ATTR_LAMP_HOURS] = await Lamp(
                         self._projector
@@ -221,7 +212,6 @@ class PJLink2MediaPlayer(MediaPlayerEntity):
                 except Exception:
                     pass
 
-                # 3. Fetch Resolution
                 try:
                     res = await Sources(self._projector).resolution()
                     self.attrs[ATTR_RESOLUTION_X] = res[0]
@@ -230,7 +220,7 @@ class PJLink2MediaPlayer(MediaPlayerEntity):
                     self.attrs.pop(ATTR_RESOLUTION_X, None)
                     self.attrs.pop(ATTR_RESOLUTION_Y, None)
 
-            else:
+            elif pwr == Power.State.OFF:
                 self.attrs.pop(ATTR_RESOLUTION_X, None)
                 self.attrs.pop(ATTR_RESOLUTION_Y, None)
                 self._current_source = None
@@ -240,10 +230,8 @@ class PJLink2MediaPlayer(MediaPlayerEntity):
         except Exception as err:
             err_str = repr(err)
 
-            # THE FIX: Forcefully drop the connection on any error.
-            # This prevents the projector from getting "stuck" on a stale socket.
-            if self._available:
-                self._available = False
+            if self._socket_open:
+                self._socket_open = False
                 try:
                     await self._projector.__aexit__(0, 0, 0)
                 except Exception:
@@ -253,10 +241,13 @@ class PJLink2MediaPlayer(MediaPlayerEntity):
                 _LOGGER.debug(
                     "Projector is busy switching inputs. Reconnecting next poll."
                 )
+                # Notice we do NOT set self._is_available = False here!
+                # This keeps the attributes stable in Home Assistant.
             else:
                 if not self._connectionErrorLogged:
                     _LOGGER.error(
                         "PJLink2 ERROR for %s: %s", self._name, err_str
                     )
                     self._connectionErrorLogged = True
+                self._is_available = False
                 self._state = MediaPlayerState.OFF