From 99d26405afd0aae049b434181159ce8ec30b32de Mon Sep 17 00:00:00 2001 From: Todd Lucas Date: Fri, 21 Feb 2025 16:15:25 -0600 Subject: [PATCH] [ 1.0.98 ] * Added service `get_audio_product_tone_controls`. Gets the current audio product tone controls configuration of the device. * Added service `set_audio_product_tone_controls`. Sets the current audio product tone controls configuration of the device. * Updated underlying `spotifywebapiPython` package requirement to version 1.0.171. --- CHANGELOG.md | 6 + custom_components/soundtouchplus/__init__.py | 52 +++++++- .../soundtouchplus/manifest.json | 4 +- .../soundtouchplus/media_player.py | 117 +++++++++++++++++- .../soundtouchplus/services.yaml | 55 ++++++++ custom_components/soundtouchplus/strings.json | 32 +++++ .../soundtouchplus/translations/en.json | 32 +++++ requirements.txt | 2 +- 8 files changed, 289 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07591c4..c6a5628 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ Change are listed in reverse chronological order (newest to oldest). +###### [ 1.0.98 ] - 2025/02/21 + + * Added service `get_audio_product_tone_controls`. Gets the current audio product tone controls configuration of the device. + * Added service `set_audio_product_tone_controls`. Sets the current audio product tone controls configuration of the device. + * Updated underlying `spotifywebapiPython` package requirement to version 1.0.171. + ###### [ 1.0.97 ] - 2025/02/20 * Added service `get_balance`. Gets the current balance configuration of the device. diff --git a/custom_components/soundtouchplus/__init__.py b/custom_components/soundtouchplus/__init__.py index e0295f7..89844ee 100644 --- a/custom_components/soundtouchplus/__init__.py +++ b/custom_components/soundtouchplus/__init__.py @@ -74,6 +74,7 @@ # ----------------------------------------------------------------------------------- SERVICE_AUDIO_TONE_LEVELS = "audio_tone_levels" SERVICE_CLEAR_SOURCE_NOWPLAYINGSTATUS = "clear_source_nowplayingstatus" +SERVICE_GET_AUDIO_PRODUCT_TONE_CONTROLS = "get_audio_product_tone_controls" SERVICE_GET_BALANCE = "get_balance" SERVICE_GET_BASS_CAPABILITIES = "get_bass_capabilities" SERVICE_GET_SOURCE_LIST = "get_source_list" @@ -88,6 +89,7 @@ SERVICE_RECENT_LIST = "recent_list" SERVICE_RECENT_LIST_CACHE = "recent_list_cache" SERVICE_REMOTE_KEYPRESS = "remote_keypress" +SERVICE_SET_AUDIO_PRODUCT_TONE_CONTROLS = "set_audio_product_tone_controls" SERVICE_SET_BALANCE_LEVEL = "set_balance_level" SERVICE_SET_BASS_LEVEL = "set_bass_level" SERVICE_SNAPSHOT_RESTORE = "snapshot_restore" @@ -111,6 +113,13 @@ } ) +SERVICE_GET_AUDIO_PRODUCT_TONE_CONTROLS_SCHEMA = vol.Schema( + { + vol.Required("entity_id"): cv.entity_id, + vol.Optional("refresh", default=True): cv.boolean, + } +) + SERVICE_GET_BALANCE_SCHEMA = vol.Schema( { vol.Required("entity_id"): cv.entity_id, @@ -229,10 +238,18 @@ } ) +SERVICE_SET_AUDIO_PRODUCT_TONE_CONTROLS_SCHEMA = vol.Schema( + { + vol.Required("entity_id"): cv.entity_id, + vol.Required("bass_level", default=25): vol.All(vol.Range(min=-100,max=100)), + vol.Required("treble_level", default=50): vol.All(vol.Range(min=-100,max=100)) + } +) + SERVICE_SET_BALANCE_LEVEL_SCHEMA = vol.Schema( { vol.Required("entity_id"): cv.entity_id, - vol.Required("level", default=-0): vol.All(vol.Range(min=-7,max=7)) + vol.Required("level", default=0): vol.All(vol.Range(min=-7,max=7)) } ) @@ -418,6 +435,12 @@ async def service_handle_entity(service:ServiceCall) -> None: _logsi.LogVerbose(STAppMessages.MSG_SERVICE_EXECUTE % (service.service, entity.name)) await hass.async_add_executor_job(entity.service_remote_keypress, key_id, key_state) + elif service.service == SERVICE_SET_AUDIO_PRODUCT_TONE_CONTROLS: + bass_level = service.data.get("bass_level") + treble_level = service.data.get("treble_level") + _logsi.LogVerbose(STAppMessages.MSG_SERVICE_EXECUTE % (service.service, entity.name)) + await hass.async_add_executor_job(entity.service_set_audio_product_tone_controls, bass_level, treble_level) + elif service.service == SERVICE_SET_BALANCE_LEVEL: level = service.data.get("level") _logsi.LogVerbose(STAppMessages.MSG_SERVICE_EXECUTE % (service.service, entity.name)) @@ -598,7 +621,14 @@ async def service_handle_serviceresponse(service: ServiceCall) -> ServiceRespons response:dict = {} # process service request. - if service.service == SERVICE_GET_BALANCE: + if service.service == SERVICE_GET_AUDIO_PRODUCT_TONE_CONTROLS: + + # get audio product tone controls. + refresh = service.data.get("refresh") + _logsi.LogVerbose(STAppMessages.MSG_SERVICE_EXECUTE % (service.service, entity.name)) + response = await hass.async_add_executor_job(entity.service_get_audio_product_tone_controls, refresh) + + elif service.service == SERVICE_GET_BALANCE: # get balance. refresh = service.data.get("refresh") @@ -737,6 +767,15 @@ def _GetEntityFromServiceData(hass:HomeAssistant, service:ServiceCall, field_id: supports_response=SupportsResponse.NONE, ) + _logsi.LogObject(SILevel.Verbose, STAppMessages.MSG_SERVICE_REQUEST_REGISTER % SERVICE_GET_AUDIO_PRODUCT_TONE_CONTROLS, SERVICE_GET_AUDIO_PRODUCT_TONE_CONTROLS_SCHEMA) + hass.services.async_register( + DOMAIN, + SERVICE_GET_AUDIO_PRODUCT_TONE_CONTROLS, + service_handle_serviceresponse, + schema=SERVICE_GET_AUDIO_PRODUCT_TONE_CONTROLS_SCHEMA, + supports_response=SupportsResponse.ONLY, + ) + _logsi.LogObject(SILevel.Verbose, STAppMessages.MSG_SERVICE_REQUEST_REGISTER % SERVICE_GET_BALANCE, SERVICE_GET_BALANCE_SCHEMA) hass.services.async_register( DOMAIN, @@ -863,6 +902,15 @@ def _GetEntityFromServiceData(hass:HomeAssistant, service:ServiceCall, field_id: supports_response=SupportsResponse.NONE, ) + _logsi.LogObject(SILevel.Verbose, STAppMessages.MSG_SERVICE_REQUEST_REGISTER % SERVICE_SET_AUDIO_PRODUCT_TONE_CONTROLS, SERVICE_SET_AUDIO_PRODUCT_TONE_CONTROLS_SCHEMA) + hass.services.async_register( + DOMAIN, + SERVICE_SET_AUDIO_PRODUCT_TONE_CONTROLS, + service_handle_entity, + schema=SERVICE_SET_AUDIO_PRODUCT_TONE_CONTROLS_SCHEMA, + supports_response=SupportsResponse.NONE, + ) + _logsi.LogObject(SILevel.Verbose, STAppMessages.MSG_SERVICE_REQUEST_REGISTER % SERVICE_SET_BALANCE_LEVEL, SERVICE_SET_BALANCE_LEVEL_SCHEMA) hass.services.async_register( DOMAIN, diff --git a/custom_components/soundtouchplus/manifest.json b/custom_components/soundtouchplus/manifest.json index 6201add..a1d9cbe 100644 --- a/custom_components/soundtouchplus/manifest.json +++ b/custom_components/soundtouchplus/manifest.json @@ -15,10 +15,10 @@ "requests>=2.31.0", "requests_oauthlib>=1.3.1", "smartinspectPython>=3.0.34", - "spotifywebapiPython>=1.0.170", + "spotifywebapiPython>=1.0.171", "urllib3>=1.21.1,<1.27", "zeroconf>=0.132.2" ], - "version": "1.0.97", + "version": "1.0.98", "zeroconf": [ "_soundtouch._tcp.local." ] } diff --git a/custom_components/soundtouchplus/media_player.py b/custom_components/soundtouchplus/media_player.py index 8b0051d..c38f0f7 100644 --- a/custom_components/soundtouchplus/media_player.py +++ b/custom_components/soundtouchplus/media_player.py @@ -1584,18 +1584,21 @@ def service_audio_tone_levels( _logsi.LogWarning("'%s': MediaPlayer device does not support AudioProductToneControls; cannot change the tone levels" % self.name) return - # get current tone levels. + # get current configuration. config:AudioProductToneControls = self.data.client.GetAudioProductToneControls() - # set new tone control values. + # set audio product tone values. config.Bass.Value = bassLevel - config.Treble.Value = bassLevel + config.Treble.Value = trebleLevel self.data.client.SetAudioProductToneControls(config) # the following exceptions have already been logged, so we just need to # pass them back to HA for display in the log (or service UI). except SoundTouchError as ex: - raise HomeAssistantError(ex.Message) + raise ServiceValidationError(ex.Message) + except Exception as ex: + _logsi.LogException(None, ex) + raise IntegrationError(str(ex)) from ex finally: @@ -1646,6 +1649,54 @@ def service_clear_source_nowplayingstatus( _logsi.LeaveMethod(SILevel.Debug, apiMethodName) + def service_get_audio_product_tone_controls( + self, + refresh:bool=False, + ) -> dict: + """ + Gets the current audio product tone controls configuration of the device. + + Args: + refresh (bool): + True to query the device for realtime information and refresh the cache; + otherwise, False to just return the cached information. + + Returns: + A `AudioProductToneControls` object dictionary that contains audio product tone control + configuration of the device IF the device supports it (e.g. ST-300, etc); + otherwise, None if the device does not support it. + + Note that some SoundTouch devices do not support this functionality. For example, + the ST-300 will support this, but the ST-10 will not. + """ + apiMethodName:str = 'service_get_audio_product_tone_controls' + apiMethodParms:SIMethodParmListContext = None + result:AudioProductToneControls = None + + try: + + # trace. + apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName) + apiMethodParms.AppendKeyValue("refresh", refresh) + _logsi.LogMethodParmList(SILevel.Verbose, "SoundTouch Get Audio Product Tone Controls Service", apiMethodParms) + + # request information from SoundTouch Web API. + result = self.data.client.GetAudioProductToneControls(refresh) + + # return the result dictionary. + return result.ToDictionary() + + # the following exceptions have already been logged, so we just need to + # pass them back to HA for display in the log (or service UI). + except SoundTouchError as ex: + raise ServiceValidationError(ex.Message) + + finally: + + # trace. + _logsi.LeaveMethod(SILevel.Debug, apiMethodName) + + def service_get_balance( self, refresh:bool=False, @@ -2367,6 +2418,62 @@ def service_remote_keypress( _logsi.LeaveMethod(SILevel.Debug, apiMethodName) + def service_set_audio_product_tone_controls( + self, + bassLevel:int=0, + trebleLevel:int=0, + ) -> None: + """ + Sets the current audio product tone controls configuration of the device. + + Args: + bassLevel (int): + Bass level to set, usually in the range of -100 (low) to 100 (high). + trebleLevel (int): + Treble level to set, usually in the range of -100 (low) to 100 (high). + + Raises: + SoundTouchError: + If the device is not capable of supporting `audioproducttonecontrols` functions, + as determined by a query to the cached `supportedURLs` web-services api. + If the controls argument is None, or not of type `AudioProductToneControls`. + + Note that some SoundTouch devices do not support this functionality. For example, + the ST-300 will support this, but the ST-10 will not. + """ + apiMethodName:str = 'service_set_audio_product_tone_controls' + apiMethodParms:SIMethodParmListContext = None + + try: + + # trace. + apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName) + apiMethodParms.AppendKeyValue("bassLevel", bassLevel) + apiMethodParms.AppendKeyValue("trebleLevel", trebleLevel) + _logsi.LogMethodParmList(SILevel.Verbose, "SoundTouch Set Audio Product Tone Controls Service", apiMethodParms) + + # get current configuration. + config:AudioProductToneControls = self.data.client.GetAudioProductToneControls() + + # set audio product tone values. + config.Bass.Value = bassLevel + config.Treble.Value = trebleLevel + self.data.client.SetAudioProductToneControls(config) + + # the following exceptions have already been logged, so we just need to + # pass them back to HA for display in the log (or service UI). + except SoundTouchError as ex: + raise ServiceValidationError(ex.Message) + except Exception as ex: + _logsi.LogException(None, ex) + raise IntegrationError(str(ex)) from ex + + finally: + + # trace. + _logsi.LeaveMethod(SILevel.Debug, apiMethodName) + + def service_set_balance_level( self, level:int=0, @@ -2395,7 +2502,6 @@ def service_set_balance_level( _logsi.LogMethodParmList(SILevel.Verbose, "SoundTouch Set Balance Level Service", apiMethodParms) # set bass level. - _logsi.LogVerbose("Setting balance level") self.data.client.SetBalanceLevel(level) # the following exceptions have already been logged, so we just need to @@ -2437,7 +2543,6 @@ def service_set_bass_level( _logsi.LogMethodParmList(SILevel.Verbose, "SoundTouch Set Bass Level Service", apiMethodParms) # set bass level. - _logsi.LogVerbose("Setting bass level") self.data.client.SetBassLevel(level) # the following exceptions have already been logged, so we just need to diff --git a/custom_components/soundtouchplus/services.yaml b/custom_components/soundtouchplus/services.yaml index f005318..5390e10 100644 --- a/custom_components/soundtouchplus/services.yaml +++ b/custom_components/soundtouchplus/services.yaml @@ -55,6 +55,27 @@ clear_source_nowplayingstatus: selector: text: +get_audio_product_tone_controls: + name: Get Audio Product Tone Controls + description: Gets the current audio product tone controls configuration of the device. + fields: + entity_id: + name: Entity ID + description: Entity ID of the SoundTouchPlus device that will process the request. + example: "media_player.soundtouch_livingroom" + required: true + selector: + entity: + integration: soundtouchplus + domain: media_player + refresh: + name: Refresh? + description: True to query the device for realtime information and refresh the cache; otherwise, False to just return the cached information. + example: "false" + required: true + selector: + boolean: + get_balance: name: Get Balance description: Gets the current balance configuration of the device. @@ -549,6 +570,40 @@ remote_keypress: - press - release +set_audio_product_tone_controls: + name: Set Audio Product Tone Controls + description: Sets the current audio product tone controls configuration of the device. + fields: + entity_id: + name: Entity ID + description: Entity ID of the SoundTouchPlus device that will process the request. + example: "media_player.soundtouch_livingroom" + required: true + selector: + entity: + integration: soundtouchplus + domain: media_player + bass_level: + name: Bass Level + description: Bass level to set, usually in the range of -100 (low) to 100 (high). + example: "25" + required: true + selector: + number: + min: -100 + max: 100 + mode: box + treble_level: + name: Treble Level + description: Treble level to set, usually in the range of -100 (low) to 100 (high). + example: "50" + required: true + selector: + number: + min: -100 + max: 100 + mode: box + set_balance_level: name: Set Balance Level description: Sets the device balance level to the given level. diff --git a/custom_components/soundtouchplus/strings.json b/custom_components/soundtouchplus/strings.json index 0f52810..abd95c8 100644 --- a/custom_components/soundtouchplus/strings.json +++ b/custom_components/soundtouchplus/strings.json @@ -102,6 +102,20 @@ } } }, + "get_audio_product_tone_controls": { + "name": "Get Audio Product Tone Controls", + "description": "Gets the current audio product tone controls configuration of the device.", + "fields": { + "entity_id": { + "name": "Entity ID", + "description": "Entity ID of the SoundTouchPlus device that will process the request." + }, + "refresh": { + "name": "Refresh?", + "description": "True to query the device for realtime information and refresh the cache; otherwise, False to just return the cached information." + } + } + }, "get_balance": { "name": "Get Balance", "description": "Gets the current balance configuration of the device.", @@ -368,6 +382,24 @@ } } }, + "set_audio_product_tone_controls": { + "name": "Set Audio Product Tone Controls", + "description": "Sets the current audio product tone controls configuration of the device.", + "fields": { + "entity_id": { + "name": "Entity ID", + "description": "Entity ID of the SoundTouchPlus device that will process the request." + }, + "bass_level": { + "name": "Bass Level", + "description": "Bass level to set, usually in the range of -100 (low) to 100 (high)." + }, + "treble_level": { + "name": "Treble Level", + "description": "Treble level to set, usually in the range of -100 (low) to 100 (high)." + } + } + }, "set_balance_level": { "name": "Set Balance Level", "description": "Sets the device balance level to the given level.", diff --git a/custom_components/soundtouchplus/translations/en.json b/custom_components/soundtouchplus/translations/en.json index 6d88d19..12e6003 100644 --- a/custom_components/soundtouchplus/translations/en.json +++ b/custom_components/soundtouchplus/translations/en.json @@ -102,6 +102,20 @@ } } }, + "get_audio_product_tone_controls": { + "name": "Get Audio Product Tone Controls", + "description": "Gets the current audio product tone controls configuration of the device.", + "fields": { + "entity_id": { + "name": "Entity ID", + "description": "Entity ID of the SoundTouchPlus device that will process the request." + }, + "refresh": { + "name": "Refresh?", + "description": "True to query the device for realtime information and refresh the cache; otherwise, False to just return the cached information." + } + } + }, "get_balance": { "name": "Get Balance", "description": "Gets the current balance configuration of the device.", @@ -368,6 +382,24 @@ } } }, + "set_audio_product_tone_controls": { + "name": "Set Audio Product Tone Controls", + "description": "Sets the current audio product tone controls configuration of the device.", + "fields": { + "entity_id": { + "name": "Entity ID", + "description": "Entity ID of the SoundTouchPlus device that will process the request." + }, + "bass_level": { + "name": "Bass Level", + "description": "Bass level to set, usually in the range of -100 (low) to 100 (high)." + }, + "treble_level": { + "name": "Treble Level", + "description": "Treble level to set, usually in the range of -100 (low) to 100 (high)." + } + } + }, "set_balance_level": { "name": "Set Balance Level", "description": "Sets the device balance level to the given level.", diff --git a/requirements.txt b/requirements.txt index 38f89c2..cf73365 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,4 @@ homeassistant==2024.5.0 ruff==0.1.3 bosesoundtouchapi==1.0.70 smartinspectPython>=3.0.34 -spotifywebapiPython>=1.0.170 +spotifywebapiPython>=1.0.171