219 lines
7.0 KiB
Python
219 lines
7.0 KiB
Python
|
"""Media Player platform for BenQ Smart Board integration."""
|
||
|
import logging
|
||
|
from typing import Optional
|
||
|
|
||
|
from homeassistant.components.media_player import (
|
||
|
MediaPlayerEntity,
|
||
|
MediaPlayerEntityFeature,
|
||
|
)
|
||
|
from homeassistant.components.media_player.const import (
|
||
|
MEDIA_TYPE_TVSHOW,
|
||
|
SUPPORT_VOLUME_MUTE,
|
||
|
SUPPORT_VOLUME_SET,
|
||
|
SUPPORT_TURN_ON,
|
||
|
SUPPORT_TURN_OFF,
|
||
|
)
|
||
|
from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE
|
||
|
from homeassistant.helpers.entity import DeviceInfo
|
||
|
|
||
|
from homeassistant.config_entries import ConfigEntry
|
||
|
from homeassistant.core import HomeAssistant
|
||
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||
|
|
||
|
from . import DOMAIN
|
||
|
from .benq_smartboard_lib import (
|
||
|
BenQSmartBoard,
|
||
|
BenQSmartBoardError,
|
||
|
ConnectionError,
|
||
|
PowerState,
|
||
|
)
|
||
|
|
||
|
_LOGGER = logging.getLogger(__name__)
|
||
|
|
||
|
SUPPORT_BENQ_SMARTBOARD = (
|
||
|
SUPPORT_VOLUME_SET
|
||
|
| SUPPORT_VOLUME_MUTE
|
||
|
| SUPPORT_TURN_OFF
|
||
|
| SUPPORT_TURN_ON
|
||
|
)
|
||
|
|
||
|
|
||
|
async def async_setup_entry(
|
||
|
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||
|
):
|
||
|
"""Set up the BenQ Smart Board media player from a config entry."""
|
||
|
data = hass.data[DOMAIN][entry.entry_id]
|
||
|
host = data["host"]
|
||
|
port = data["port"]
|
||
|
|
||
|
name = f"BenQ Smart Board ({host})"
|
||
|
|
||
|
entity = BenQSmartBoardMediaPlayer(
|
||
|
name=name,
|
||
|
host=host,
|
||
|
port=port,
|
||
|
unique_id=f"benq_smartboard_{host}_{port}",
|
||
|
)
|
||
|
async_add_entities([entity], update_before_add=True)
|
||
|
|
||
|
|
||
|
class BenQSmartBoardMediaPlayer(MediaPlayerEntity):
|
||
|
"""Representation of the BenQ Smart Board as a Media Player."""
|
||
|
|
||
|
_attr_should_poll = True
|
||
|
_attr_supported_features = SUPPORT_BENQ_SMARTBOARD
|
||
|
_attr_media_content_type = MEDIA_TYPE_TVSHOW
|
||
|
|
||
|
def __init__(self, name: str, host: str, port: int, unique_id: str):
|
||
|
"""Initialize."""
|
||
|
self._attr_name = name
|
||
|
self._attr_unique_id = unique_id
|
||
|
self._host = host
|
||
|
self._port = port
|
||
|
|
||
|
# Internal states
|
||
|
self._state = STATE_UNAVAILABLE
|
||
|
self._volume_level = 0.0
|
||
|
self._is_muted = False
|
||
|
|
||
|
self._board: Optional[BenQSmartBoard] = None
|
||
|
|
||
|
# Setup device info so it appears as a device in HA
|
||
|
self._attr_device_info = DeviceInfo(
|
||
|
identifiers={(DOMAIN, unique_id)},
|
||
|
name=name,
|
||
|
manufacturer="BenQ",
|
||
|
model="Smart Board",
|
||
|
configuration_url=f"http://{host}", # or other relevant link
|
||
|
)
|
||
|
|
||
|
@property
|
||
|
def state(self):
|
||
|
"""Return the current power state: ON, OFF, or if unreachable -> UNAVAILABLE."""
|
||
|
return self._state
|
||
|
|
||
|
@property
|
||
|
def volume_level(self):
|
||
|
"""Volume level (0..1)."""
|
||
|
return self._volume_level
|
||
|
|
||
|
@property
|
||
|
def is_volume_muted(self):
|
||
|
"""Return True if muted."""
|
||
|
return self._is_muted
|
||
|
|
||
|
def _ensure_board(self):
|
||
|
"""Ensure we have a connected BenQSmartBoard instance."""
|
||
|
if self._board is None:
|
||
|
self._board = BenQSmartBoard(self._host, self._port, timeout=3.0)
|
||
|
return self._board
|
||
|
|
||
|
def _refresh_from_device(self):
|
||
|
"""
|
||
|
Attempt to query the current state from the device (power, volume, mute).
|
||
|
Called in update().
|
||
|
"""
|
||
|
board = self._ensure_board()
|
||
|
try:
|
||
|
# Connect if not connected
|
||
|
board.connect()
|
||
|
|
||
|
# Get power
|
||
|
pwr = board.get_power() # string e.g. "001" -> ON
|
||
|
if pwr == PowerState.ON.value:
|
||
|
self._state = STATE_ON
|
||
|
elif pwr == PowerState.STANDBY.value:
|
||
|
# Some prefer modeling STANDBY as OFF or custom
|
||
|
self._state = STATE_OFF
|
||
|
elif pwr == PowerState.OFF.value:
|
||
|
self._state = STATE_OFF
|
||
|
else:
|
||
|
# If unknown power code, consider it OFF or UNAVAILABLE
|
||
|
self._state = STATE_OFF
|
||
|
|
||
|
# Volume: e.g. "030"
|
||
|
vol_str = board.get_volume()
|
||
|
volume_int = int(vol_str)
|
||
|
self._volume_level = volume_int / 100.0
|
||
|
|
||
|
# Mute: "000" => Unmuted, "001" => Muted
|
||
|
mute_str = board.get_mute()
|
||
|
self._is_muted = (mute_str == "001")
|
||
|
|
||
|
except BenQSmartBoardError as err:
|
||
|
_LOGGER.warning("Error talking to BenQ Smart Board: %s", err)
|
||
|
self._state = STATE_UNAVAILABLE
|
||
|
except ConnectionError as err:
|
||
|
_LOGGER.warning("Connection to BenQ Smart Board lost: %s", err)
|
||
|
self._state = STATE_UNAVAILABLE
|
||
|
|
||
|
async def async_update(self):
|
||
|
"""Update the entity state by polling the device."""
|
||
|
await self.hass.async_add_executor_job(self._refresh_from_device)
|
||
|
|
||
|
async def async_turn_on(self):
|
||
|
"""Turn on the Smart Board (if off or in standby)."""
|
||
|
board = self._ensure_board()
|
||
|
try:
|
||
|
board.connect()
|
||
|
success = board.set_power(PowerState.ON)
|
||
|
if success:
|
||
|
self._state = STATE_ON
|
||
|
else:
|
||
|
_LOGGER.warning("Smart Board refused 'power on' command.")
|
||
|
except BenQSmartBoardError as e:
|
||
|
_LOGGER.error("Failed to turn on: %s", e)
|
||
|
self._state = STATE_UNAVAILABLE
|
||
|
self.async_write_ha_state()
|
||
|
|
||
|
async def async_turn_off(self):
|
||
|
"""Turn off the Smart Board."""
|
||
|
board = self._ensure_board()
|
||
|
try:
|
||
|
board.connect()
|
||
|
success = board.set_power(PowerState.OFF)
|
||
|
if success:
|
||
|
self._state = STATE_OFF
|
||
|
else:
|
||
|
_LOGGER.warning("Smart Board refused 'power off' command.")
|
||
|
except BenQSmartBoardError as e:
|
||
|
_LOGGER.error("Failed to turn off: %s", e)
|
||
|
self._state = STATE_UNAVAILABLE
|
||
|
self.async_write_ha_state()
|
||
|
|
||
|
async def async_set_volume_level(self, volume: float):
|
||
|
"""Set volume level (0..1)."""
|
||
|
board = self._ensure_board()
|
||
|
vol_int = int(volume * 100)
|
||
|
try:
|
||
|
board.connect()
|
||
|
success = board.set_volume(vol_int)
|
||
|
if success:
|
||
|
self._volume_level = volume
|
||
|
else:
|
||
|
_LOGGER.warning("Smart Board refused 'set volume' command.")
|
||
|
except BenQSmartBoardError as e:
|
||
|
_LOGGER.error("Failed to set volume: %s", e)
|
||
|
self._state = STATE_UNAVAILABLE
|
||
|
self.async_write_ha_state()
|
||
|
|
||
|
async def async_mute_volume(self, mute: bool):
|
||
|
"""Mute or unmute the volume."""
|
||
|
board = self._ensure_board()
|
||
|
try:
|
||
|
board.connect()
|
||
|
success = board.set_mute(mute)
|
||
|
if success:
|
||
|
self._is_muted = mute
|
||
|
else:
|
||
|
_LOGGER.warning("Smart Board refused 'mute' command.")
|
||
|
except BenQSmartBoardError as e:
|
||
|
_LOGGER.error("Failed to mute: %s", e)
|
||
|
self._state = STATE_UNAVAILABLE
|
||
|
self.async_write_ha_state()
|
||
|
|
||
|
async def async_will_remove_from_hass(self):
|
||
|
"""Disconnect from device on removal."""
|
||
|
if self._board:
|
||
|
await self.hass.async_add_executor_job(self._board.disconnect)
|