hacs structure
This commit is contained in:
parent
ddec014571
commit
aabdabb9c5
6 changed files with 9 additions and 0 deletions
218
custom_components/benq_smartboard/media_player.py
Normal file
218
custom_components/benq_smartboard/media_player.py
Normal file
|
@ -0,0 +1,218 @@
|
|||
"""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)
|
Loading…
Add table
Add a link
Reference in a new issue