Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Support for Plex media server monitoring."""
2 
3 from __future__ import annotations
4 
5 import logging
6 
7 from plexapi.exceptions import NotFound
8 import requests.exceptions
9 
10 from homeassistant.components.sensor import SensorEntity
11 from homeassistant.config_entries import ConfigEntry
12 from homeassistant.core import HomeAssistant
13 from homeassistant.helpers.debounce import Debouncer
14 from homeassistant.helpers.device_registry import DeviceInfo
15 from homeassistant.helpers.dispatcher import async_dispatcher_connect
16 from homeassistant.helpers.entity_platform import AddEntitiesCallback
17 
18 from .const import (
19  CONF_SERVER_IDENTIFIER,
20  DOMAIN,
21  PLEX_UPDATE_LIBRARY_SIGNAL,
22  PLEX_UPDATE_SENSOR_SIGNAL,
23 )
24 from .helpers import get_plex_server, pretty_title
25 
26 LIBRARY_ATTRIBUTE_TYPES = {
27  "artist": ["artist", "album"],
28  "photo": ["photoalbum"],
29  "show": ["show", "season"],
30 }
31 
32 LIBRARY_PRIMARY_LIBTYPE = {
33  "show": "episode",
34  "artist": "track",
35 }
36 
37 LIBRARY_RECENT_LIBTYPE = {
38  "show": "episode",
39  "artist": "album",
40 }
41 
42 LIBRARY_ICON_LOOKUP = {
43  "artist": "mdi:music",
44  "movie": "mdi:movie",
45  "photo": "mdi:image",
46  "show": "mdi:television",
47 }
48 
49 _LOGGER = logging.getLogger(__name__)
50 
51 
53  hass: HomeAssistant,
54  config_entry: ConfigEntry,
55  async_add_entities: AddEntitiesCallback,
56 ) -> None:
57  """Set up Plex sensor from a config entry."""
58  server_id = config_entry.data[CONF_SERVER_IDENTIFIER]
59  plexserver = get_plex_server(hass, server_id)
60  sensors: list[SensorEntity] = [PlexSensor(hass, plexserver)]
61 
62  def create_library_sensors():
63  """Create Plex library sensors with sync calls."""
64  sensors.extend(
65  PlexLibrarySectionSensor(hass, plexserver, library)
66  for library in plexserver.library.sections()
67  )
68 
69  await hass.async_add_executor_job(create_library_sensors)
70  async_add_entities(sensors)
71 
72 
74  """Representation of a Plex now playing sensor."""
75 
76  _attr_has_entity_name = True
77  _attr_name = None
78  _attr_translation_key = "plex"
79  _attr_should_poll = False
80  _attr_native_unit_of_measurement = "watching"
81 
82  def __init__(self, hass, plex_server):
83  """Initialize the sensor."""
84  self._attr_unique_id_attr_unique_id = f"sensor-{plex_server.machine_identifier}"
85 
86  self._server_server = plex_server
87  self.async_refresh_sensorasync_refresh_sensor = Debouncer(
88  hass,
89  _LOGGER,
90  cooldown=3,
91  immediate=False,
92  function=self._async_refresh_sensor_async_refresh_sensor,
93  ).async_call
94 
95  async def async_added_to_hass(self) -> None:
96  """Run when about to be added to hass."""
97  server_id = self._server_server.machine_identifier
98  self.async_on_removeasync_on_remove(
100  self.hasshass,
101  PLEX_UPDATE_SENSOR_SIGNAL.format(server_id),
102  self.async_refresh_sensorasync_refresh_sensor,
103  )
104  )
105 
106  async def _async_refresh_sensor(self) -> None:
107  """Set instance object and trigger an entity state update."""
108  _LOGGER.debug("Refreshing sensor [%s]", self.unique_idunique_id)
109  self._attr_native_value_attr_native_value = len(self._server_server.sensor_attributes)
110  self.async_write_ha_stateasync_write_ha_state()
111 
112  @property
114  """Return the state attributes."""
115  return self._server_server.sensor_attributes
116 
117  @property
118  def device_info(self) -> DeviceInfo | None:
119  """Return a device description for device registry."""
120  return DeviceInfo(
121  identifiers={(DOMAIN, self._server_server.machine_identifier)},
122  manufacturer="Plex",
123  model="Plex Media Server",
124  name=self._server_server.friendly_name,
125  sw_version=self._server_server.version,
126  configuration_url=f"{self._server.url_in_use}/web",
127  )
128 
129 
131  """Representation of a Plex library section sensor."""
132 
133  _attr_available = True
134  _attr_entity_registry_enabled_default = False
135  _attr_should_poll = False
136  _attr_native_unit_of_measurement = "Items"
137 
138  def __init__(self, hass, plex_server, plex_library_section):
139  """Initialize the sensor."""
140  self._server_server = plex_server
141  self.server_nameserver_name = plex_server.friendly_name
142  self.server_idserver_id = plex_server.machine_identifier
143  self.library_sectionlibrary_section = plex_library_section
144  self.library_typelibrary_type = plex_library_section.type
145 
146  self._attr_extra_state_attributes_attr_extra_state_attributes = {}
147  self._attr_icon_attr_icon = LIBRARY_ICON_LOOKUP.get(self.library_typelibrary_type, "mdi:plex")
148  self._attr_name_attr_name = f"{self.server_name} Library - {plex_library_section.title}"
149  self._attr_unique_id_attr_unique_id = f"library-{self.server_id}-{plex_library_section.uuid}"
150 
151  async def async_added_to_hass(self) -> None:
152  """Run when about to be added to hass."""
153  self.async_on_removeasync_on_remove(
155  self.hasshass,
156  PLEX_UPDATE_LIBRARY_SIGNAL.format(self.server_idserver_id),
157  self.async_refresh_sensorasync_refresh_sensor,
158  )
159  )
160  await self.async_refresh_sensorasync_refresh_sensor()
161 
162  async def async_refresh_sensor(self) -> None:
163  """Update state and attributes for the library sensor."""
164  _LOGGER.debug("Refreshing library sensor for '%s'", self.namename)
165  try:
166  await self.hasshass.async_add_executor_job(self._update_state_and_attrs_update_state_and_attrs)
167  self._attr_available_attr_available_attr_available = True
168  except NotFound:
169  self._attr_available_attr_available_attr_available = False
170  except requests.exceptions.RequestException as err:
171  _LOGGER.error(
172  "Could not update library sensor for '%s': %s",
173  self.library_sectionlibrary_section.title,
174  err,
175  )
176  self._attr_available_attr_available_attr_available = False
177  self.async_write_ha_stateasync_write_ha_state()
178 
180  """Update library sensor state with sync calls."""
181  primary_libtype = LIBRARY_PRIMARY_LIBTYPE.get(
182  self.library_typelibrary_type, self.library_typelibrary_type
183  )
184 
185  self._attr_native_value_attr_native_value = self.library_sectionlibrary_section.totalViewSize(
186  libtype=primary_libtype, includeCollections=False
187  )
188  for libtype in LIBRARY_ATTRIBUTE_TYPES.get(self.library_typelibrary_type, []):
189  self._attr_extra_state_attributes_attr_extra_state_attributes[f"{libtype}s"] = (
190  self.library_sectionlibrary_section.totalViewSize(
191  libtype=libtype, includeCollections=False
192  )
193  )
194 
195  recent_libtype = LIBRARY_RECENT_LIBTYPE.get(
196  self.library_typelibrary_type, self.library_typelibrary_type
197  )
198  recently_added = self.library_sectionlibrary_section.recentlyAdded(
199  maxresults=1, libtype=recent_libtype
200  )
201  if recently_added:
202  media = recently_added[0]
203  self._attr_extra_state_attributes_attr_extra_state_attributes["last_added_item"] = pretty_title(media)
204  self._attr_extra_state_attributes_attr_extra_state_attributes["last_added_timestamp"] = media.addedAt
205 
206  @property
207  def device_info(self) -> DeviceInfo | None:
208  """Return a device description for device registry."""
209  if self.unique_idunique_id is None:
210  return None
211 
212  return DeviceInfo(
213  identifiers={(DOMAIN, self.server_idserver_id)},
214  manufacturer="Plex",
215  model="Plex Media Server",
216  name=self.server_nameserver_name,
217  sw_version=self._server_server.version,
218  configuration_url=f"{self._server.url_in_use}/web",
219  )
def __init__(self, hass, plex_server, plex_library_section)
Definition: sensor.py:138
def __init__(self, hass, plex_server)
Definition: sensor.py:82
None async_on_remove(self, CALLBACK_TYPE func)
Definition: entity.py:1331
str|UndefinedType|None name(self)
Definition: entity.py:738
def pretty_title(media, short_name=False)
Definition: helpers.py:39
PlexServer get_plex_server(HomeAssistant hass, str server_id)
Definition: helpers.py:34
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: sensor.py:56
Callable[[], None] async_dispatcher_connect(HomeAssistant hass, str signal, Callable[..., Any] target)
Definition: dispatcher.py:103