浏览代码

move from sensors to media player + change branding to fork repo

Noah Vogt 2 月之前
父节点
当前提交
b28d04bb22
共有 3 个文件被更改,包括 76 次插入46 次删除
  1. 2 2
      README.md
  2. 2 2
      custom_components/pjlink2/manifest.json
  3. 72 42
      custom_components/pjlink2/media_player.py

+ 2 - 2
README.md

@@ -15,7 +15,7 @@ The component has been developed and tested with an Epson LS12000 projector, but
 
 1. In HACS, select `Integrations`
 2. Select `Custom Repositories` from the three dots in top right corner
-3. Enter `https://github.com/TheRealKillaruna/ha-pjlink2` as repository and select `Integration` as category
+3. Enter `https://github.com/noahvogt/ha-pjlink2` as repository and select `Integration` as category
 4. Click `Add`
 5. Open `Custom Repositories` again and you should see `PJLink2` at the top of the list, click it
 6. Click `Download` in lower right corner
@@ -25,7 +25,7 @@ The component has been developed and tested with an Epson LS12000 projector, but
 
 **Manually**
 
-1. Copy `pjlink2` folder from [latest release](https://github.com/TheRealKillaruna/pjlink2/releases/latest) to `custom_components` folder in your config folder.
+1. Copy `pjlink2` folder from [latest release](https://github.com/noahvogt/pjlink2/releases/latest) to `custom_components` folder in your config folder.
 2. Configure your settings as described below
 3. Restart Home Assistant
 

+ 2 - 2
custom_components/pjlink2/manifest.json

@@ -1,8 +1,8 @@
 {
-  "codeowners": ["@TheRealKillaruna"],
+  "codeowners": ["@noahvogt"],
   "config_flow": false,
   "dependencies": [],
-  "documentation": "https://github.com/TheRealKillaruna/ha-pjlink2",
+  "documentation": "https://github.com/noahvogt/ha-pjlink2",
   "domain": "pjlink2",
   "iot_class": "calculated",
   "name": "PJLink2",

+ 72 - 42
custom_components/pjlink2/sensor.py → custom_components/pjlink2/media_player.py

@@ -1,4 +1,4 @@
-"""GitHub sensor platform."""
+"""PJLink2 media_player platform."""
 from __future__ import annotations
 
 from collections.abc import Callable
@@ -9,19 +9,22 @@ from typing import Any
 from aiopjlink import PJLink, PJLinkException, PJLinkProjectorError, Power, Sources, Lamp, Information 
 
 from homeassistant import config_entries, core
-from homeassistant.components.sensor import PLATFORM_SCHEMA
+from homeassistant.components.media_player import (
+    MediaPlayerEntity,
+    MediaPlayerEntityFeature,
+    MediaPlayerState,
+    PLATFORM_SCHEMA
+)
 from homeassistant.const import CONF_HOST, CONF_PORT, CONF_NAME, CONF_PASSWORD, CONF_TIMEOUT
 from homeassistant.core import HomeAssistant as HomeAssistantType
 
 import homeassistant.helpers.config_validation as cv
-from homeassistant.helpers.entity import Entity
 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
 
 import voluptuous as vol
 
 from .const import DOMAIN, CONF_ENCODING, DEFAULT_ENCODING, DEFAULT_PORT, DEFAULT_TIMEOUT, ATTR_PRODUCT_NAME, ATTR_MANUFACTURER_NAME, ATTR_PROJECTOR_NAME, ATTR_RESOLUTION_X, ATTR_RESOLUTION_Y, ATTR_LAMP_HOURS, ProjectorState
 
-
 _LOGGER = logging.getLogger(__name__)
 # Time between updating data from projector
 SCAN_INTERVAL = timedelta(seconds=3)
@@ -37,35 +40,45 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
     }
 )
 
-
 async def async_setup_platform(
     hass: HomeAssistantType,
     config: ConfigType,
     async_add_entities: Callable,
     discovery_info: DiscoveryInfoType | None = None,
 ) -> None:
-    """Set up the sensor platform."""
+    """Set up the media_player platform."""
     host = config.get(CONF_HOST)
     port =  config.get(CONF_PORT)
     password = config.get(CONF_PASSWORD)
     timeout = config.get(CONF_TIMEOUT)
     name = config.get(CONF_NAME)
     pjl = PJLink(host, port, password, timeout)
-    sensors = [PJLink2Sensor(pjl, name)]
-    async_add_entities(sensors, update_before_add=False)
+    devices = [PJLink2MediaPlayer(pjl, name)]
+    async_add_entities(devices, update_before_add=False)
+
 
+class PJLink2MediaPlayer(MediaPlayerEntity):
+    """Representation of a PJLink2 media player."""
 
-class PJLink2Sensor(Entity):
-    """Representation of a PJLink2 sensor."""
+    _attr_supported_features = (
+        MediaPlayerEntityFeature.TURN_ON
+        | MediaPlayerEntityFeature.TURN_OFF
+        | MediaPlayerEntityFeature.SELECT_SOURCE
+    )
 
     def __init__(self, pjl, name):
         super().__init__()
         self._projector = pjl
         self.attrs: dict[str, Any] = {}
         self._name = name
-        self._state = None
+        self._state = MediaPlayerState.OFF
         self._available = False
         self._connectionErrorLogged = False
+        self._current_source = None
+        
+        # PJLink standard inputs. You can modify these to friendly names later.
+        # Format is typically "31" for HDMI1, "32" for HDMI2, etc.
+        self._source_list = ["11", "12", "21", "22", "31", "32", "33"] 
 
     async def async_will_remove_from_hass(self) -> None:
         """Close connection."""
@@ -74,38 +87,58 @@ class PJLink2Sensor(Entity):
             try:
                 await self._projector.__aexit__(0,0,0)
             except (PJLinkException, OSError) as err:
-                _LOGGER.error("PJLink2 ERROR when closing connection to %s: %s", self._name, repr(err))
-            else:
-                _LOGGER.info("PJLink2 INFO for %s: Connection closed.", self._name)
+                _LOGGER.error("PJLink2 ERROR when closing connection: %s", repr(err))
 
     @property
     def name(self) -> str:
-        """Return the name of the entity."""
         return self._name
 
     @property
     def unique_id(self) -> str:
-        """Return the unique ID of the sensor."""
         return self._projector._address
 
     @property
     def available(self) -> bool:
-        """Return True if entity is available."""
         return self._available
 
     @property
-    def state(self) -> str | None:
+    def state(self) -> MediaPlayerState:
         return self._state
 
+    @property
+    def source(self) -> str | None:
+        """Name of the current input source."""
+        return self._current_source
+
+    @property
+    def source_list(self) -> list[str]:
+        """List of available input sources."""
+        return self._source_list
+
     @property
     def extra_state_attributes(self) -> dict[str, Any]:
+        """Return the custom PJLink2 attributes."""
         return self.attrs
 
+    async def async_turn_on(self) -> None:
+        """Turn the projector on."""
+        await Power(self._projector).set(Power.ON)
+        self._state = MediaPlayerState.ON
+
+    async def async_turn_off(self) -> None:
+        """Turn the projector off."""
+        await Power(self._projector).set(Power.OFF)
+        self._state = MediaPlayerState.OFF
+
+    async def async_select_source(self, source: str) -> None:
+        """Select input source."""
+        await Sources(self._projector).set(source)
+        self._current_source = source
+
     async def async_update(self) -> None:
-        """Update all sensors."""
+        """Update data from projector."""
         try:
             if not self._available:
-                # connect and init static information
                 await self._projector.__aenter__()
                 self._available = True
                 info = await Information(self._projector).table()
@@ -113,41 +146,38 @@ class PJLink2Sensor(Entity):
                 self.attrs[ATTR_MANUFACTURER_NAME] = info["manufacturer_name"]
                 self.attrs[ATTR_PROJECTOR_NAME] = info["projector_name"]
                 if self._name == None: self._name = info["projector_name"]
-                _LOGGER.info("PJLink2 INFO for %s: Connection opened.", self._name)
                 
             pwr = await Power(self._projector).get()
-            if pwr == Power.State.OFF: self._state = ProjectorState.OFF
-            elif pwr == Power.State.ON: self._state = ProjectorState.ON
-            elif pwr == Power.State.COOLING: self._state = ProjectorState.COOLING
-            elif pwr == Power.State.WARMING: self._state = ProjectorState.WARMING
+            if pwr == Power.State.OFF: self._state = MediaPlayerState.OFF
+            elif pwr == Power.State.ON: self._state = MediaPlayerState.ON
+            elif pwr in (Power.State.COOLING, Power.State.WARMING): self._state = MediaPlayerState.ON # Keeps UI active during transition
             
-            if pwr==Power.ON:
+            if pwr == Power.ON:
                 res = await Sources(self._projector).resolution()
                 self.attrs[ATTR_RESOLUTION_X] = res[0]
                 self.attrs[ATTR_RESOLUTION_Y] = res[1]
-                lmpHrs = await Lamp(self._projector).hours()
-                self.attrs[ATTR_LAMP_HOURS] = lmpHrs
+                self.attrs[ATTR_LAMP_HOURS] = await Lamp(self._projector).hours()
+                
+                # Fetch current source
+                self._current_source = await Sources(self._projector).get()
             else:
-                if ATTR_RESOLUTION_X in self.attrs: del self.attrs[ATTR_RESOLUTION_X]
-                if ATTR_RESOLUTION_Y in self.attrs: del self.attrs[ATTR_RESOLUTION_Y]
+                self.attrs.pop(ATTR_RESOLUTION_X, None)
+                self.attrs.pop(ATTR_RESOLUTION_Y, None)
+                self._current_source = None
                 
-            self._connectionErrorLogged = False # after successful update, enable error logging for next connection issue
+            self._connectionErrorLogged = False 
         
         except PJLinkProjectorError:
-            # resolution cannot be queried due to no input
-            if ATTR_RESOLUTION_X in self.attrs: del self.attrs[ATTR_RESOLUTION_X]
-            if ATTR_RESOLUTION_Y in self.attrs: del self.attrs[ATTR_RESOLUTION_Y]
-            _LOGGER.info("PJLink2 INFO for %s: Cannot get resolution", self._name)
+            self.attrs.pop(ATTR_RESOLUTION_X, None)
+            self.attrs.pop(ATTR_RESOLUTION_Y, None)
         except (PJLinkException, OSError) as err:
             if not self._connectionErrorLogged: 
                 _LOGGER.error("PJLink2 ERROR for %s: %s", self._name, repr(err))
-                self._connectionErrorLogged = True # do not spam logfile with same error message
-            self._state = None
+                self._connectionErrorLogged = True
+            self._state = MediaPlayerState.OFF
             if self._available:
-                self._available = False # only call exit function once after disconnect
+                self._available = False
                 try:
                     await self._projector.__aexit__(0,0,0)
-                except (PJLinkException, OSError) as err:
-                    _LOGGER.error("PJLink2 ERROR when closing connection to %s: %s", self._name, repr(err))
-                else:
-                    _LOGGER.info("PJLink2 INFO for %s: Connection closed.", self._name)
+                except Exception:
+                    pass