Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add commands and sensors for jvc projectors #131320

Draft
wants to merge 17 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions homeassistant/components/jvc_projector/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

from __future__ import annotations

from jvcprojector import JvcProjector, JvcProjectorAuthError, JvcProjectorConnectError
from jvcprojector.device import JvcProjectorAuthError
from jvcprojector.projector import JvcProjector, JvcProjectorConnectError

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
Expand All @@ -15,18 +16,20 @@
from homeassistant.core import Event, HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady

from .const import DOMAIN
from .coordinator import JvcProjectorDataUpdateCoordinator

type JVCConfigEntry = ConfigEntry[JvcProjectorDataUpdateCoordinator]

PLATFORMS = [Platform.BINARY_SENSOR, Platform.REMOTE, Platform.SELECT, Platform.SENSOR]


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: JVCConfigEntry) -> bool:
"""Set up integration from a config entry."""
device = JvcProjector(
host=entry.data[CONF_HOST],
port=entry.data[CONF_PORT],
password=entry.data[CONF_PASSWORD],
timeout=1,
)

try:
Expand All @@ -43,7 +46,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
coordinator = JvcProjectorDataUpdateCoordinator(hass, device)
await coordinator.async_config_entry_first_refresh()

hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
entry.runtime_data = coordinator

async def disconnect(event: Event) -> None:
await device.disconnect()
Expand All @@ -57,9 +60,8 @@ async def disconnect(event: Event) -> None:
return True


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: JVCConfigEntry) -> bool:
"""Unload config entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
await hass.data[DOMAIN][entry.entry_id].device.disconnect()
hass.data[DOMAIN].pop(entry.entry_id)
await entry.runtime_data.device.disconnect()
return unload_ok
85 changes: 69 additions & 16 deletions homeassistant/components/jvc_projector/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,43 +2,96 @@

from __future__ import annotations

from collections.abc import Callable
from dataclasses import dataclass

from jvcprojector import const

from homeassistant.components.binary_sensor import BinarySensorEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass,
BinarySensorEntity,
BinarySensorEntityDescription,
)
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from . import JvcProjectorDataUpdateCoordinator
from .const import DOMAIN
from . import JVCConfigEntry, JvcProjectorDataUpdateCoordinator
from .entity import JvcProjectorEntity

ON_STATUS = (const.ON, const.WARMING)

@dataclass(frozen=True, kw_only=True)
class JVCBinarySensorEntityDescription(BinarySensorEntityDescription):
"""Describe JVC binary sensor entity."""

value_fn: Callable[[str | None], bool | None] = lambda x: x == "on"
enabled_default: bool | Callable[[JvcProjectorEntity], bool] = True


JVC_BINARY_SENSORS = (
JVCBinarySensorEntityDescription(
key=const.KEY_POWER,
translation_key="jvc_power",
device_class=BinarySensorDeviceClass.POWER,
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda x: x in (const.ON, const.WARMING) if x is not None else None,
),
JVCBinarySensorEntityDescription(
key=const.KEY_LOW_LATENCY,
translation_key="jvc_low_latency_status",
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda x: x == const.ON if x is not None else None,
),
JVCBinarySensorEntityDescription(
key=const.KEY_ESHIFT,
translation_key="jvc_eshift",
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda x: x == const.ON if x is not None else None,
enabled_default=JvcProjectorEntity.has_eshift,
),
JVCBinarySensorEntityDescription(
key=const.KEY_SOURCE,
translation_key="jvc_source_status",
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda x: x == const.SIGNAL if x is not None else None,
),
)


async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
hass: HomeAssistant, entry: JVCConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up the JVC Projector platform from a config entry."""
coordinator: JvcProjectorDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]

async_add_entities([JvcBinarySensor(coordinator)])
"""Set up the JVC Projector binary sensor platform from a config entry."""
coordinator = entry.runtime_data
async_add_entities(
JvcBinarySensor(coordinator, description) for description in JVC_BINARY_SENSORS
)


class JvcBinarySensor(JvcProjectorEntity, BinarySensorEntity):
"""The entity class for JVC Projector Binary Sensor."""

_attr_translation_key = "jvc_power"
entity_description: JVCBinarySensorEntityDescription

def __init__(
self,
coordinator: JvcProjectorDataUpdateCoordinator,
description: JVCBinarySensorEntityDescription,
) -> None:
"""Initialize the JVC Projector sensor."""
"""Initialize the JVC Projector binary sensor."""
super().__init__(coordinator)
self._attr_unique_id = f"{coordinator.device.mac}_power"
self.entity_description = description
self._attr_unique_id = f"{coordinator.unique_id}_{description.key}"
self._attr_entity_registry_enabled_default = (
description.enabled_default(self)
if callable(description.enabled_default)
else description.enabled_default
)

@property
def is_on(self) -> bool:
"""Return true if the JVC is on."""
return self.coordinator.data["power"] in ON_STATUS
def is_on(self) -> bool | None:
"""Return true if the binary sensor is on."""
value = self.coordinator.data.get(self.entity_description.key)
if value is None:
return None
return self.entity_description.value_fn(value)
9 changes: 5 additions & 4 deletions homeassistant/components/jvc_projector/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@
from collections.abc import Mapping
from typing import Any

from jvcprojector import JvcProjector, JvcProjectorAuthError, JvcProjectorConnectError
from jvcprojector.projector import DEFAULT_PORT
from jvcprojector.device import JvcProjectorAuthError
from jvcprojector.projector import DEFAULT_PORT, JvcProjector, JvcProjectorConnectError
import voluptuous as vol

from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT
from homeassistant.helpers.device_registry import format_mac
from homeassistant.util.network import is_host_valid

from . import JVCConfigEntry
from .const import DOMAIN, NAME


Expand All @@ -22,7 +23,7 @@ class JvcProjectorConfigFlow(ConfigFlow, domain=DOMAIN):

VERSION = 1

_reauth_entry: ConfigEntry | None = None
_reauth_entry: JVCConfigEntry | None = None

async def async_step_user(
self, user_input: dict[str, Any] | None = None
Expand Down
35 changes: 35 additions & 0 deletions homeassistant/components/jvc_projector/const.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,40 @@
"""Constants for the jvc_projector integration."""

from jvcprojector import const

NAME = "JVC Projector"
DOMAIN = "jvc_projector"
MANUFACTURER = "JVC"

REMOTE_COMMANDS = {
"menu": const.REMOTE_MENU,
"up": const.REMOTE_UP,
"down": const.REMOTE_DOWN,
"left": const.REMOTE_LEFT,
"right": const.REMOTE_RIGHT,
"ok": const.REMOTE_OK,
"back": const.REMOTE_BACK,
"mpc": const.REMOTE_MPC,
"hide": const.REMOTE_HIDE,
"info": const.REMOTE_INFO,
"input": const.REMOTE_INPUT,
"cmd": const.REMOTE_CMD,
"advanced_menu": const.REMOTE_ADVANCED_MENU,
"picture_mode": const.REMOTE_PICTURE_MODE,
"color_profile": const.REMOTE_COLOR_PROFILE,
"lens_control": const.REMOTE_LENS_CONTROL,
"setting_memory": const.REMOTE_SETTING_MEMORY,
"gamma_settings": const.REMOTE_GAMMA_SETTINGS,
"hdmi_1": const.REMOTE_HDMI_1,
"hdmi_2": const.REMOTE_HDMI_2,
"mode_1": const.REMOTE_MODE_1,
"mode_2": const.REMOTE_MODE_2,
"mode_3": const.REMOTE_MODE_3,
"lens_ap": const.REMOTE_LENS_AP,
"gamma": const.REMOTE_GAMMA,
"color_temp": const.REMOTE_COLOR_TEMP,
"natural": const.REMOTE_NATURAL,
"cinema": const.REMOTE_CINEMA,
"anamo": const.REMOTE_ANAMO,
"3d_format": const.REMOTE_3D_FORMAT,
}
12 changes: 5 additions & 7 deletions homeassistant/components/jvc_projector/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,8 @@
from datetime import timedelta
import logging

from jvcprojector import (
JvcProjector,
JvcProjectorAuthError,
JvcProjectorConnectError,
const,
)
from jvcprojector.device import JvcProjectorAuthError
from jvcprojector.projector import JvcProjector, JvcProjectorConnectError, const

from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed
Expand Down Expand Up @@ -43,7 +39,9 @@ def __init__(self, hass: HomeAssistant, device: JvcProjector) -> None:
async def _async_update_data(self) -> dict[str, str]:
"""Get the latest state data."""
try:
state = await self.device.get_state()
state_mapping = await self.device.get_state()
# Only include non-None values in the final state dict
state = {k: v for k, v in state_mapping.items() if v is not None}
except JvcProjectorConnectError as err:
raise UpdateFailed(f"Unable to connect to {self.device.host}") from err
except JvcProjectorAuthError as err:
Expand Down
22 changes: 16 additions & 6 deletions homeassistant/components/jvc_projector/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,14 @@

from __future__ import annotations

import logging
from jvcprojector.projector import JvcProjector

from jvcprojector import JvcProjector

from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity

from .const import DOMAIN, MANUFACTURER, NAME
from .coordinator import JvcProjectorDataUpdateCoordinator

_LOGGER = logging.getLogger(__name__)


class JvcProjectorEntity(CoordinatorEntity[JvcProjectorDataUpdateCoordinator]):
"""Defines a base JVC Projector entity."""
Expand All @@ -30,9 +26,23 @@ def __init__(self, coordinator: JvcProjectorDataUpdateCoordinator) -> None:
name=NAME,
model=self.device.model,
manufacturer=MANUFACTURER,
sw_version=self.device.version,
connections={(CONNECTION_NETWORK_MAC, self.device.mac)},
)

@property
def device(self) -> JvcProjector:
"""Return the device representing the projector."""
return self.coordinator.device

@property
def has_eshift(self) -> bool:
"""Return if device has e-shift."""
return (
"NZ" in self.device.model or "NX9" in self.device.model
) # nx9 is the only lamp model with eshift

@property
def has_laser(self) -> bool:
"""Return if device has laser."""
return "NZ" in self.device.model
6 changes: 6 additions & 0 deletions homeassistant/components/jvc_projector/icons.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@
"select": {
"input": {
"default": "mdi:hdmi-port"
},
"anamorphic": {
"default": "mdi:stretch-to-page-outline"
},
"laser_power": {
"default": "mdi:brightness-5"
}
},
"sensor": {
Expand Down
Loading