Initial Home Assistant commit
This commit is contained in:
+472
@@ -0,0 +1,472 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.media_player import MediaPlayerEntityFeature
|
||||
from homeassistant.const import (
|
||||
STATE_IDLE,
|
||||
STATE_OFF,
|
||||
STATE_ON,
|
||||
STATE_PAUSED,
|
||||
STATE_PLAYING,
|
||||
STATE_UNKNOWN,
|
||||
)
|
||||
from homeassistant.core import State
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.intent import (
|
||||
Intent,
|
||||
IntentResponse,
|
||||
IntentResponseErrorCode,
|
||||
)
|
||||
|
||||
from smartinspectpython.siauto import SILevel, SIColors
|
||||
|
||||
from ..appmessages import STAppMessages
|
||||
from ..intent_loader import IntentLoader
|
||||
from ..const import (
|
||||
CONF_VALUE,
|
||||
DOMAIN,
|
||||
INTENT_PLAYER_DECK_CONTROL,
|
||||
PLATFORM_SPOTIFYPLUS,
|
||||
RESPONSE_ERROR_PLAYER_DECK_CONTROL_INVALID,
|
||||
RESPONSE_PLAYER_ALREADY_PLAYING_MEDIA,
|
||||
RESPONSE_PLAYER_DECK_CONTROL_PAUSE,
|
||||
RESPONSE_PLAYER_DECK_CONTROL_RESUME,
|
||||
RESPONSE_PLAYER_DECK_CONTROL_SEEK_START,
|
||||
RESPONSE_PLAYER_DECK_CONTROL_SKIP_NEXT,
|
||||
RESPONSE_PLAYER_DECK_CONTROL_SKIP_PREVIOUS,
|
||||
RESPONSE_PLAYER_NOT_PLAYING_MEDIA,
|
||||
SERVICE_SPOTIFY_PLAYER_MEDIA_PAUSE,
|
||||
SERVICE_SPOTIFY_PLAYER_MEDIA_RESUME,
|
||||
SERVICE_SPOTIFY_PLAYER_MEDIA_SEEK,
|
||||
SERVICE_SPOTIFY_PLAYER_MEDIA_SKIP_NEXT,
|
||||
SERVICE_SPOTIFY_PLAYER_MEDIA_SKIP_PREVIOUS,
|
||||
SLOT_AREA,
|
||||
SLOT_DELAY,
|
||||
SLOT_FLOOR,
|
||||
SLOT_NAME,
|
||||
SLOT_PREFERRED_AREA_ID,
|
||||
SLOT_PREFERRED_FLOOR_ID,
|
||||
SLOT_SPOTIFYPLUS_PLAYER_DECK_CONTROL,
|
||||
)
|
||||
|
||||
from .spotifyplusintenthandler import SpotifyPlusIntentHandler
|
||||
|
||||
|
||||
class SpotifyPlusPlayerDeckControl_Handler(SpotifyPlusIntentHandler):
|
||||
"""
|
||||
Handles intents for SpotifyPlusPlayerDeckControl.
|
||||
"""
|
||||
def __init__(self, intentLoader:IntentLoader) -> None:
|
||||
"""
|
||||
Initializes a new instance of the IntentHandler class.
|
||||
"""
|
||||
# invoke base class method.
|
||||
super().__init__(intentLoader)
|
||||
|
||||
# set intent handler basics.
|
||||
self.description = "Controls media player deck functions (pause, resume, next track, previous track, restart track, etc)."
|
||||
self.intent_type = INTENT_PLAYER_DECK_CONTROL
|
||||
self.platforms = {PLATFORM_SPOTIFYPLUS}
|
||||
|
||||
|
||||
@property
|
||||
def slot_schema(self) -> dict | None:
|
||||
"""
|
||||
Returns the slot schema for this intent.
|
||||
"""
|
||||
return {
|
||||
|
||||
# slots that determine which media player entity will be used.
|
||||
vol.Optional(SLOT_NAME): cv.string,
|
||||
vol.Optional(SLOT_AREA): cv.string,
|
||||
vol.Optional(SLOT_FLOOR): cv.string,
|
||||
vol.Optional(SLOT_PREFERRED_AREA_ID): cv.string,
|
||||
vol.Optional(SLOT_PREFERRED_FLOOR_ID): cv.string,
|
||||
|
||||
# slots for other service arguments.
|
||||
vol.Optional(SLOT_SPOTIFYPLUS_PLAYER_DECK_CONTROL): cv.string,
|
||||
}
|
||||
|
||||
|
||||
async def async_HandleIntent(
|
||||
self,
|
||||
intentObj: Intent,
|
||||
intentResponse: IntentResponse
|
||||
) -> IntentResponse:
|
||||
"""
|
||||
Handles the intent.
|
||||
|
||||
Args:
|
||||
intentObj (Intent):
|
||||
Intent object.
|
||||
intentResponse (IntentResponse)
|
||||
Intent response object.
|
||||
|
||||
Returns:
|
||||
An IntentResponse object.
|
||||
"""
|
||||
# get optional arguments (if provided).
|
||||
player_deck_control:str = intentObj.slots.get(SLOT_SPOTIFYPLUS_PLAYER_DECK_CONTROL, {}).get(CONF_VALUE, "").lower()
|
||||
|
||||
# process based on media type.
|
||||
if (player_deck_control == "pause"):
|
||||
return await self.async_ProcessPause(intentObj, intentResponse)
|
||||
|
||||
elif (player_deck_control == "resume"):
|
||||
return await self.async_ProcessResume(intentObj, intentResponse)
|
||||
|
||||
elif (player_deck_control == "seek_start"):
|
||||
return await self.async_ProcessSeekStart(intentObj, intentResponse)
|
||||
|
||||
elif (player_deck_control == "skip_previous"):
|
||||
return await self.async_ProcessSkipPrevious(intentObj, intentResponse)
|
||||
|
||||
elif (player_deck_control == "skip_next"):
|
||||
return await self.async_ProcessSkipNext(intentObj, intentResponse)
|
||||
|
||||
else:
|
||||
return await self.ReturnResponseByKey(intentObj, intentResponse, RESPONSE_ERROR_PLAYER_DECK_CONTROL_INVALID, IntentResponseErrorCode.FAILED_TO_HANDLE)
|
||||
|
||||
|
||||
async def async_ProcessPause(
|
||||
self,
|
||||
intentObj: Intent,
|
||||
intentResponse: IntentResponse,
|
||||
) -> IntentResponse:
|
||||
"""
|
||||
Handles the intent.
|
||||
|
||||
Args:
|
||||
intentObj (Intent):
|
||||
Intent object.
|
||||
intentResponse (IntentResponse)
|
||||
Intent response object.
|
||||
|
||||
Returns:
|
||||
An IntentResponse object.
|
||||
"""
|
||||
try:
|
||||
|
||||
# trace.
|
||||
self.logsi.EnterMethod(SILevel.Debug, colorValue=SIColors.Khaki)
|
||||
|
||||
# invoke base class method to resolve the player entity and its state.
|
||||
playerEntityState:State = await super().async_GetMatchingPlayerState(
|
||||
intentObj,
|
||||
intentResponse,
|
||||
desiredFeatures=MediaPlayerEntityFeature.PAUSE | MediaPlayerEntityFeature.PLAY_MEDIA,
|
||||
desiredStates=[STATE_PLAYING],
|
||||
desiredStateResponseKey=RESPONSE_PLAYER_NOT_PLAYING_MEDIA,
|
||||
requiresSpotifyPremium=True,
|
||||
)
|
||||
|
||||
# if media player was not resolved, then we are done;
|
||||
# note that the base class method above already called `async_set_speech` with a response.
|
||||
if playerEntityState is None:
|
||||
return intentResponse
|
||||
|
||||
# get optional arguments (if provided).
|
||||
delay = intentObj.slots.get(SLOT_DELAY, {}).get(CONF_VALUE, None)
|
||||
|
||||
# set service name and build parameters.
|
||||
svcName:str = SERVICE_SPOTIFY_PLAYER_MEDIA_PAUSE
|
||||
svcData:dict = \
|
||||
{
|
||||
"entity_id": playerEntityState.entity_id,
|
||||
"device_id": "", # always use current device for this service call.
|
||||
"delay": delay
|
||||
}
|
||||
|
||||
# call integration service for this intent.
|
||||
self.logsi.LogVerbose(STAppMessages.MSG_SERVICE_EXECUTE % (svcName, playerEntityState.entity_id), colorValue=SIColors.Khaki)
|
||||
await intentObj.hass.services.async_call(
|
||||
DOMAIN,
|
||||
svcName,
|
||||
svcData,
|
||||
blocking=True,
|
||||
context=intentObj.context,
|
||||
)
|
||||
|
||||
# return intent response.
|
||||
return await self.ReturnResponseByKey(intentObj, intentResponse, RESPONSE_PLAYER_DECK_CONTROL_PAUSE)
|
||||
|
||||
finally:
|
||||
|
||||
# trace.
|
||||
self.logsi.LeaveMethod(SILevel.Debug, colorValue=SIColors.Khaki)
|
||||
|
||||
|
||||
async def async_ProcessResume(
|
||||
self,
|
||||
intentObj: Intent,
|
||||
intentResponse: IntentResponse,
|
||||
) -> IntentResponse:
|
||||
"""
|
||||
Handles the intent.
|
||||
|
||||
Args:
|
||||
intentObj (Intent):
|
||||
Intent object.
|
||||
intentResponse (IntentResponse)
|
||||
Intent response object.
|
||||
|
||||
Returns:
|
||||
An IntentResponse object.
|
||||
"""
|
||||
try:
|
||||
|
||||
# trace.
|
||||
self.logsi.EnterMethod(SILevel.Debug, colorValue=SIColors.Khaki)
|
||||
|
||||
# invoke base class method to resolve the player entity and its state.
|
||||
playerEntityState:State = await super().async_GetMatchingPlayerState(
|
||||
intentObj,
|
||||
intentResponse,
|
||||
desiredFeatures=MediaPlayerEntityFeature.PAUSE | MediaPlayerEntityFeature.PLAY_MEDIA,
|
||||
desiredStates=None,
|
||||
desiredStateResponseKey=None,
|
||||
requiresSpotifyPremium=True,
|
||||
)
|
||||
|
||||
# if media player was not resolved, then we are done;
|
||||
# note that the base class method above already called `async_set_speech` with a response.
|
||||
if playerEntityState is None:
|
||||
return intentResponse
|
||||
|
||||
# if already playing, then there is nothing to do.
|
||||
if (playerEntityState.state == STATE_PLAYING):
|
||||
return await self.ReturnResponseByKey(intentObj, intentResponse, RESPONSE_PLAYER_ALREADY_PLAYING_MEDIA)
|
||||
|
||||
# get optional arguments (if provided).
|
||||
delay = intentObj.slots.get(SLOT_DELAY, {}).get(CONF_VALUE, None)
|
||||
|
||||
# set service name and build parameters.
|
||||
svcName:str = SERVICE_SPOTIFY_PLAYER_MEDIA_RESUME
|
||||
svcData:dict = \
|
||||
{
|
||||
"entity_id": playerEntityState.entity_id,
|
||||
"device_id": "", # always use current device for this service call.
|
||||
"delay": delay
|
||||
}
|
||||
|
||||
# call integration service for this intent.
|
||||
self.logsi.LogVerbose(STAppMessages.MSG_SERVICE_EXECUTE % (svcName, playerEntityState.entity_id), colorValue=SIColors.Khaki)
|
||||
await intentObj.hass.services.async_call(
|
||||
DOMAIN,
|
||||
svcName,
|
||||
svcData,
|
||||
blocking=True,
|
||||
context=intentObj.context,
|
||||
)
|
||||
|
||||
# return intent response.
|
||||
return await self.ReturnResponseByKey(intentObj, intentResponse, RESPONSE_PLAYER_DECK_CONTROL_RESUME)
|
||||
|
||||
finally:
|
||||
|
||||
# trace.
|
||||
self.logsi.LeaveMethod(SILevel.Debug, colorValue=SIColors.Khaki)
|
||||
|
||||
|
||||
async def async_ProcessSeekStart(
|
||||
self,
|
||||
intentObj: Intent,
|
||||
intentResponse: IntentResponse,
|
||||
) -> IntentResponse:
|
||||
"""
|
||||
Handles the intent.
|
||||
|
||||
Args:
|
||||
intentObj (Intent):
|
||||
Intent object.
|
||||
intentResponse (IntentResponse)
|
||||
Intent response object.
|
||||
|
||||
Returns:
|
||||
An IntentResponse object.
|
||||
"""
|
||||
try:
|
||||
|
||||
# trace.
|
||||
self.logsi.EnterMethod(SILevel.Debug, colorValue=SIColors.Khaki)
|
||||
|
||||
# invoke base class method to resolve the player entity and its state.
|
||||
playerEntityState:State = await super().async_GetMatchingPlayerState(
|
||||
intentObj,
|
||||
intentResponse,
|
||||
desiredFeatures=MediaPlayerEntityFeature.SEEK | MediaPlayerEntityFeature.PLAY_MEDIA,
|
||||
desiredStates=[STATE_PLAYING, STATE_PAUSED],
|
||||
desiredStateResponseKey=RESPONSE_PLAYER_NOT_PLAYING_MEDIA,
|
||||
requiresSpotifyPremium=True,
|
||||
)
|
||||
|
||||
# if media player was not resolved, then we are done;
|
||||
# note that the base class method above already called `async_set_speech` with a response.
|
||||
if playerEntityState is None:
|
||||
return intentResponse
|
||||
|
||||
# get optional arguments (if provided).
|
||||
delay = intentObj.slots.get(SLOT_DELAY, {}).get(CONF_VALUE, None)
|
||||
|
||||
# set service name and build parameters.
|
||||
svcName:str = SERVICE_SPOTIFY_PLAYER_MEDIA_SEEK
|
||||
svcData:dict = \
|
||||
{
|
||||
"entity_id": playerEntityState.entity_id,
|
||||
"device_id": "", # always use current device for this service call.
|
||||
"position_ms": 0, # restart track
|
||||
"delay": delay
|
||||
}
|
||||
|
||||
# call integration service for this intent.
|
||||
self.logsi.LogVerbose(STAppMessages.MSG_SERVICE_EXECUTE % (svcName, playerEntityState.entity_id), colorValue=SIColors.Khaki)
|
||||
await intentObj.hass.services.async_call(
|
||||
DOMAIN,
|
||||
svcName,
|
||||
svcData,
|
||||
blocking=True,
|
||||
context=intentObj.context,
|
||||
)
|
||||
|
||||
# return intent response.
|
||||
return await self.ReturnResponseByKey(intentObj, intentResponse, RESPONSE_PLAYER_DECK_CONTROL_SEEK_START)
|
||||
|
||||
finally:
|
||||
|
||||
# trace.
|
||||
self.logsi.LeaveMethod(SILevel.Debug, colorValue=SIColors.Khaki)
|
||||
|
||||
|
||||
async def async_ProcessSkipNext(
|
||||
self,
|
||||
intentObj: Intent,
|
||||
intentResponse: IntentResponse,
|
||||
) -> IntentResponse:
|
||||
"""
|
||||
Handles the intent.
|
||||
|
||||
Args:
|
||||
intentObj (Intent):
|
||||
Intent object.
|
||||
intentResponse (IntentResponse)
|
||||
Intent response object.
|
||||
|
||||
Returns:
|
||||
An IntentResponse object.
|
||||
"""
|
||||
try:
|
||||
|
||||
# trace.
|
||||
self.logsi.EnterMethod(SILevel.Debug, colorValue=SIColors.Khaki)
|
||||
|
||||
# invoke base class method to resolve the player entity and its state.
|
||||
playerEntityState:State = await super().async_GetMatchingPlayerState(
|
||||
intentObj,
|
||||
intentResponse,
|
||||
desiredFeatures=MediaPlayerEntityFeature.NEXT_TRACK | MediaPlayerEntityFeature.PLAY_MEDIA,
|
||||
desiredStates=[STATE_PLAYING, STATE_PAUSED],
|
||||
desiredStateResponseKey=RESPONSE_PLAYER_NOT_PLAYING_MEDIA,
|
||||
requiresSpotifyPremium=True,
|
||||
)
|
||||
|
||||
# if media player was not resolved, then we are done;
|
||||
# note that the base class method above already called `async_set_speech` with a response.
|
||||
if playerEntityState is None:
|
||||
return intentResponse
|
||||
|
||||
# get optional arguments (if provided).
|
||||
delay = intentObj.slots.get(SLOT_DELAY, {}).get(CONF_VALUE, None)
|
||||
|
||||
# set service name and build parameters.
|
||||
svcName:str = SERVICE_SPOTIFY_PLAYER_MEDIA_SKIP_NEXT
|
||||
svcData:dict = \
|
||||
{
|
||||
"entity_id": playerEntityState.entity_id,
|
||||
"device_id": "", # always use current device for this service call.
|
||||
"delay": delay
|
||||
}
|
||||
|
||||
# call integration service for this intent.
|
||||
self.logsi.LogVerbose(STAppMessages.MSG_SERVICE_EXECUTE % (svcName, playerEntityState.entity_id), colorValue=SIColors.Khaki)
|
||||
await intentObj.hass.services.async_call(
|
||||
DOMAIN,
|
||||
svcName,
|
||||
svcData,
|
||||
blocking=True,
|
||||
context=intentObj.context,
|
||||
)
|
||||
|
||||
# return intent response.
|
||||
return await self.ReturnResponseByKey(intentObj, intentResponse, RESPONSE_PLAYER_DECK_CONTROL_SKIP_NEXT)
|
||||
|
||||
finally:
|
||||
|
||||
# trace.
|
||||
self.logsi.LeaveMethod(SILevel.Debug, colorValue=SIColors.Khaki)
|
||||
|
||||
|
||||
async def async_ProcessSkipPrevious(
|
||||
self,
|
||||
intentObj: Intent,
|
||||
intentResponse: IntentResponse,
|
||||
) -> IntentResponse:
|
||||
"""
|
||||
Handles the intent.
|
||||
|
||||
Args:
|
||||
intentObj (Intent):
|
||||
Intent object.
|
||||
intentResponse (IntentResponse)
|
||||
Intent response object.
|
||||
|
||||
Returns:
|
||||
An IntentResponse object.
|
||||
"""
|
||||
try:
|
||||
|
||||
# trace.
|
||||
self.logsi.EnterMethod(SILevel.Debug, colorValue=SIColors.Khaki)
|
||||
|
||||
# invoke base class method to resolve the player entity and its state.
|
||||
playerEntityState:State = await super().async_GetMatchingPlayerState(
|
||||
intentObj,
|
||||
intentResponse,
|
||||
desiredFeatures=MediaPlayerEntityFeature.PREVIOUS_TRACK | MediaPlayerEntityFeature.PLAY_MEDIA,
|
||||
desiredStates=[STATE_PLAYING, STATE_PAUSED],
|
||||
desiredStateResponseKey=RESPONSE_PLAYER_NOT_PLAYING_MEDIA,
|
||||
requiresSpotifyPremium=True,
|
||||
)
|
||||
|
||||
# if media player was not resolved, then we are done;
|
||||
# note that the base class method above already called `async_set_speech` with a response.
|
||||
if playerEntityState is None:
|
||||
return intentResponse
|
||||
|
||||
# get optional arguments (if provided).
|
||||
delay = intentObj.slots.get(SLOT_DELAY, {}).get(CONF_VALUE, None)
|
||||
|
||||
# set service name and build parameters.
|
||||
svcName:str = SERVICE_SPOTIFY_PLAYER_MEDIA_SKIP_PREVIOUS
|
||||
svcData:dict = \
|
||||
{
|
||||
"entity_id": playerEntityState.entity_id,
|
||||
"device_id": "", # always use current device for this service call.
|
||||
"delay": delay
|
||||
}
|
||||
|
||||
# call integration service for this intent.
|
||||
self.logsi.LogVerbose(STAppMessages.MSG_SERVICE_EXECUTE % (svcName, playerEntityState.entity_id), colorValue=SIColors.Khaki)
|
||||
await intentObj.hass.services.async_call(
|
||||
DOMAIN,
|
||||
svcName,
|
||||
svcData,
|
||||
blocking=True,
|
||||
context=intentObj.context,
|
||||
)
|
||||
|
||||
# return intent response.
|
||||
return await self.ReturnResponseByKey(intentObj, intentResponse, RESPONSE_PLAYER_DECK_CONTROL_SKIP_PREVIOUS)
|
||||
|
||||
finally:
|
||||
|
||||
# trace.
|
||||
self.logsi.LeaveMethod(SILevel.Debug, colorValue=SIColors.Khaki)
|
||||
Reference in New Issue
Block a user