diff --git a/custom_components/baidu_voice/__init__.py b/custom_components/baidu_voice/__init__.py index 2893852..db730e1 100644 --- a/custom_components/baidu_voice/__init__.py +++ b/custom_components/baidu_voice/__init__.py @@ -20,18 +20,17 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """设置百度语音配置项.""" - # 存储配置数据 - hass.data.setdefault(DOMAIN, {}) - hass.data[DOMAIN] = entry.data.copy() # 保存所有配置数据 - # 加载平台 + hass.data.setdefault(DOMAIN, {}) + hass.data[DOMAIN] = entry.data.copy() + await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) return True async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """卸载百度语音配置项.""" - # 卸载平台 + unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) if unload_ok: diff --git a/custom_components/baidu_voice/config_flow.py b/custom_components/baidu_voice/config_flow.py index 7de9778..92cf4de 100644 --- a/custom_components/baidu_voice/config_flow.py +++ b/custom_components/baidu_voice/config_flow.py @@ -1,4 +1,4 @@ -"""Config flow for Baidu TTS integration.""" +"""Config flow for Baidu Voice integration.""" from __future__ import annotations @@ -7,13 +7,12 @@ from typing import Any import voluptuous as vol -from homeassistant.config_entries import ( - ConfigEntry, - ConfigFlow, - ConfigFlowResult, - OptionsFlow, +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult +from homeassistant.helpers.selector import ( + SelectSelector, + SelectSelectorConfig, + SelectSelectorMode, ) -from homeassistant.core import callback from .const import ( CONF_API_KEY, @@ -62,6 +61,9 @@ STEP_USER_DATA_SCHEMA = vol.Schema( vol.Optional(TTS_CONF_VOICE, default=TTS_DEFAULT_VOICE): vol.In( TTS_SUPPORTED_VOICES ), + vol.Optional(TTS_CONF_FILEFORMAT, default=TTS_DEFAULT_FILEFORMAT): vol.In( + TTS_FILEFORMAT_MAP + ), vol.Optional(STT_CONF_LANGUAGE, default=STT_DEFAULT_LANGUAGE): vol.In( STT_LANGUAGES ), @@ -69,102 +71,46 @@ STEP_USER_DATA_SCHEMA = vol.Schema( ) -class BaiduTTSConfigFlow(ConfigFlow, domain=DOMAIN): - """Handle a config flow for Baidu TTS.""" +class BaiduVoiceConfigFlow(ConfigFlow, domain=DOMAIN): + """Handle a config flow for Baidu Voice.""" VERSION = 1 + async def async_step_reconfigure(self, user_input: dict[str, Any] | None = None): + """Handle reconfiguration of the integration.""" + if user_input is not None: + return self.async_update_reload_and_abort( + self._get_reconfigure_entry(), + data_updates=user_input, + ) + + schema = self.add_suggested_values_to_schema( + STEP_USER_DATA_SCHEMA, self._get_reconfigure_entry().data + ) + schema = schema.extend( + { + vol.Required(CONF_APP_ID): SelectSelector( + SelectSelectorConfig( + options=[self._get_reconfigure_entry().data[CONF_APP_ID]], + mode=SelectSelectorMode.DROPDOWN, + ) + ) + } + ) + + return self.async_show_form( + step_id="reconfigure", + data_schema=schema, + ) + async def async_step_user( self, user_input: dict[str, Any] | None = None ) -> ConfigFlowResult: """Handle the initial step.""" if user_input is not None: - # Provide unique ID to prevent duplicate entries app_id = user_input[CONF_APP_ID] - await self.async_set_unique_id(f"baidu_tts_{app_id}") + await self.async_set_unique_id(f"baidu_voice_{app_id}") self._abort_if_unique_id_configured() - - return self.async_create_entry(title="百度语音合成", data=user_input) + return self.async_create_entry(title="Baidu Voice", data=user_input) return self.async_show_form(step_id="user", data_schema=STEP_USER_DATA_SCHEMA) - - @classmethod - @callback - def async_get_options_flow(cls, config_entry: ConfigEntry) -> BaiduTTSOptionsFlow: - """Get the options flow for this handler.""" - return BaiduTTSOptionsFlow() - - -class BaiduTTSOptionsFlow(OptionsFlow): - """Baidu TTS integration options handler.""" - - async def async_step_init( - self, user_input: dict[str, Any] | None = None - ) -> ConfigFlowResult: - """Manage the options.""" - if user_input is not None: - return self.async_create_entry(title="", data=user_input) - - options = self.config_entry.options - data = self.config_entry.data - - schema = { - vol.Required( - CONF_APP_ID, - default=options.get(CONF_APP_ID, data.get(CONF_APP_ID)), - ): str, - vol.Required( - CONF_API_KEY, - default=options.get(CONF_API_KEY, data.get(CONF_API_KEY)), - ): str, - vol.Required( - CONF_SECRET_KEY, - default=options.get(CONF_SECRET_KEY, data.get(CONF_SECRET_KEY)), - ): str, - vol.Required( - TTS_CONF_LANGUAGE, - default=options.get(TTS_CONF_LANGUAGE, data.get(TTS_CONF_LANGUAGE)), - ): vol.In(TTS_LANGUAGES), - vol.Optional( - TTS_CONF_SPEED, - default=options.get( - TTS_CONF_SPEED, data.get(TTS_CONF_SPEED, TTS_DEFAULT_SPEED) - ), - ): vol.All(vol.Coerce(int), vol.Range(min=0, max=9)), - vol.Optional( - TTS_CONF_PITCH, - default=options.get( - TTS_CONF_PITCH, data.get(TTS_CONF_PITCH, TTS_DEFAULT_PITCH) - ), - ): vol.All(vol.Coerce(int), vol.Range(min=0, max=9)), - vol.Optional( - TTS_CONF_VOLUME, - default=options.get( - TTS_CONF_VOLUME, data.get(TTS_CONF_VOLUME, TTS_DEFAULT_VOLUME) - ), - ): vol.All(vol.Coerce(int), vol.Range(min=0, max=15)), - vol.Optional( - TTS_CONF_VOICE, - default=options.get( - TTS_CONF_VOICE, data.get(TTS_CONF_VOICE, TTS_DEFAULT_VOICE) - ), - ): vol.In(TTS_SUPPORTED_VOICES), - vol.Optional( - TTS_CONF_FILEFORMAT, - default=options.get( - TTS_CONF_FILEFORMAT, - data.get(TTS_CONF_FILEFORMAT, TTS_DEFAULT_FILEFORMAT), - ), - ): vol.In(TTS_FILEFORMAT_MAP), - vol.Optional( - STT_CONF_LANGUAGE, - default=options.get( - STT_CONF_LANGUAGE, data.get(STT_CONF_LANGUAGE, STT_DEFAULT_LANGUAGE) - ), - ): vol.In(STT_LANGUAGES), - } - - return self.async_show_form( - step_id="init", - data_schema=vol.Schema(schema), - ) diff --git a/custom_components/baidu_voice/const.py b/custom_components/baidu_voice/const.py index baec114..49c099a 100644 --- a/custom_components/baidu_voice/const.py +++ b/custom_components/baidu_voice/const.py @@ -3,7 +3,7 @@ from typing import Final DOMAIN: Final = "baidu_voice" - +ENDPOINT: Final = "http://vop.baidu.com/server_api" # 配置项 CONF_APP_ID: Final = "app_id" CONF_API_KEY: Final = "api_key" diff --git a/custom_components/baidu_voice/hacs.json b/custom_components/baidu_voice/hacs.json deleted file mode 100644 index c25719e..0000000 --- a/custom_components/baidu_voice/hacs.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "Baidu Voice", - "domains": ["tts", "stt"], - "homeassistant": "2023.1.0", - "render_readme": true -} \ No newline at end of file diff --git a/custom_components/baidu_voice/icon.png b/custom_components/baidu_voice/icon.png deleted file mode 100644 index 794a77e..0000000 Binary files a/custom_components/baidu_voice/icon.png and /dev/null differ diff --git a/custom_components/baidu_voice/manifest.json b/custom_components/baidu_voice/manifest.json index 2bdfd4e..cde24d9 100644 --- a/custom_components/baidu_voice/manifest.json +++ b/custom_components/baidu_voice/manifest.json @@ -12,6 +12,5 @@ "requirements": [ "baidu-aip == 4.16.10", "chardet==5.2.0" ], - "version": "0.0.1", - "issue_tracker": "https://github.com/howelljiang/baidu-voice/issues" + "version": "0.0.1" } diff --git a/custom_components/baidu_voice/strings.json b/custom_components/baidu_voice/strings.json index d6e3212..9130b4d 100644 --- a/custom_components/baidu_voice/strings.json +++ b/custom_components/baidu_voice/strings.json @@ -2,20 +2,57 @@ "config": { "step": { "user": { + "title": "[%key:common::config_flow::title::setup%] Baidu Voice", + "description": "[%key:common::config_flow::description::enter_credentials%]", "data": { - "host": "[%key:common::config_flow::data::host%]", - "username": "[%key:common::config_flow::data::username%]", - "password": "[%key:common::config_flow::data::password%]" + "app_id": "[%key:common::config_flow::data::app_id%]", + "api_key": "[%key:common::config_flow::data::api_key%]", + "secret_key": "[%key:common::config_flow::data::secret_key%]", + "language": "[%key:common::config_flow::data::language%]", + "stt_language": "[%key:common::config_flow::data::stt_language%]", + "volume": "[%key:common::config_flow::data::volume%]", + "speed": "[%key:common::config_flow::data::speed%]", + "pitch": "[%key:common::config_flow::data::pitch%]", + "voice": "[%key:common::config_flow::data::voice%]", + "fileformat": "[%key:common::config_flow::data::fileformat%]", + "test_connection": "[%key:common::config_flow::data::test_connection%]" } } }, "error": { "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", - "unknown": "[%key:common::config_flow::error::unknown%]" + "unknown": "[%key:common::config_flow::error::unknown%]", + "auth": "[%key:common::config_flow::error::auth%]", + "test_success": "[%key:common::config_flow::error::test_success%]" }, "abort": { "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" } + }, + "options": { + "step": { + "init": { + "title": "[%key:common::config_flow::options::title%]", + "description": "[%key:common::config_flow::options::description%]", + "data": { + "app_id": "[%key:common::config_flow::data::app_id%]", + "api_key": "[%key:common::config_flow::data::api_key%]", + "secret_key": "[%key:common::config_flow::data::secret_key%]", + "language": "[%key:common::config_flow::data::language%]", + "stt_language": "[%key:common::config_flow::data::stt_language%]", + "volume": "[%key:common::config_flow::data::volume%]", + "speed": "[%key:common::config_flow::data::speed%]", + "pitch": "[%key:common::config_flow::data::pitch%]", + "voice": "[%key:common::config_flow::data::voice%]", + "fileformat": "[%key:common::config_flow::data::fileformat%]", + "test_connection": "[%key:common::config_flow::data::test_connection%]" + } + } + }, + "error": { + "auth": "[%key:common::config_flow::error::auth%]", + "test_success": "[%key:common::config_flow::error::test_success%]" + } } } diff --git a/custom_components/baidu_voice/stt.py b/custom_components/baidu_voice/stt.py index 0ac21e1..459dbe7 100644 --- a/custom_components/baidu_voice/stt.py +++ b/custom_components/baidu_voice/stt.py @@ -48,7 +48,6 @@ class BaiduSTTEntity(stt.SpeechToTextEntity): def __init__(self, hass: HomeAssistant, config: dict[str, Any]) -> None: """Initialize Baidu speech-to-text entity.""" - super().__init__() self.hass = hass self._config = config self._client = AipSpeech( @@ -62,7 +61,7 @@ class BaiduSTTEntity(stt.SpeechToTextEntity): @property def supported_languages(self) -> list[str]: """Return list of supported languages.""" - return ["zh-CN", "en-US", "yue-CN", "sichuan-CN"] + return ["zh-CN", "en-US", "zh-HK", "zh-TW"] @property def supported_formats(self) -> list[str]: @@ -118,7 +117,7 @@ class BaiduSTTEntity(stt.SpeechToTextEntity): ) if "err_no" in result and result["err_no"] != 0: - _LOGGER.debug( + _LOGGER.error( "Error from Baidu API: %s - %s", result.get("err_msg", "Unknown error"), result.get("err_detail", "No details"), @@ -139,7 +138,7 @@ class BaiduSTTEntity(stt.SpeechToTextEntity): result=stt.SpeechResultState.SUCCESS, ) - except Exception: # pylint: disable=broad-except + except Exception: _LOGGER.exception("Error processing Baidu STT") return stt.SpeechResult( text=None, diff --git a/custom_components/baidu_voice/system_health.py b/custom_components/baidu_voice/system_health.py new file mode 100644 index 0000000..9471287 --- /dev/null +++ b/custom_components/baidu_voice/system_health.py @@ -0,0 +1,30 @@ +"""Provide info to system health.""" + +from typing import Any + +from homeassistant.components import system_health +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback + +from .const import DOMAIN, ENDPOINT + + +@callback +def async_register( + hass: HomeAssistant, register: system_health.SystemHealthRegistration +) -> None: + """Register system health callbacks.""" + register.async_register_info(system_health_info) + + +async def system_health_info(hass: HomeAssistant) -> dict[str, Any]: + """Get info for the info page.""" + config_entry: ConfigEntry = hass.config_entries.async_entries(DOMAIN)[0] + quota_info = await config_entry.runtime_data.async_get_quota_info() + + return { + "consumed_requests": quota_info.consumed_requests, + "remaining_requests": quota_info.requests_remaining, + # checking the url can take a while, so set the coroutine in the info dict + "can_reach_server": system_health.async_check_can_reach_url(hass, ENDPOINT), + } diff --git a/custom_components/baidu_voice/translations/en.json b/custom_components/baidu_voice/translations/en.json index e3ffda3..94f8b23 100644 --- a/custom_components/baidu_voice/translations/en.json +++ b/custom_components/baidu_voice/translations/en.json @@ -27,12 +27,8 @@ "fileformat": "Audio Format", "test_connection": "Test Connection" } - } - } - }, - "options": { - "step": { - "init": { + }, + "reconfigure": { "title": "Update Baidu Voice Settings", "description": "Modify Baidu Voice settings", "data": { @@ -49,10 +45,6 @@ "test_connection": "Test Connection" } } - }, - "error": { - "auth": "Authentication failed", - "test_success": "Connection test successful" } } } \ No newline at end of file diff --git a/custom_components/baidu_voice/translations/zh-Hans.json b/custom_components/baidu_voice/translations/zh-Hans.json index 96985fb..fc34915 100644 --- a/custom_components/baidu_voice/translations/zh-Hans.json +++ b/custom_components/baidu_voice/translations/zh-Hans.json @@ -27,12 +27,8 @@ "fileformat": "音频格式", "test_connection": "测试连接" } - } - } - }, - "options": { - "step": { - "init": { + }, + "reconfigure": { "title": "更新百度语音设置", "description": "修改百度语音设置", "data": { @@ -49,10 +45,6 @@ "test_connection": "测试连接" } } - }, - "error": { - "auth": "认证失败", - "test_success": "连接测试成功" } } } \ No newline at end of file diff --git a/custom_components/baidu_voice/tts.py b/custom_components/baidu_voice/tts.py index 3c97f82..084a250 100644 --- a/custom_components/baidu_voice/tts.py +++ b/custom_components/baidu_voice/tts.py @@ -16,13 +16,11 @@ from homeassistant.components.tts import ( from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError -from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo from .const import ( CONF_API_KEY, CONF_APP_ID, CONF_SECRET_KEY, - DOMAIN, TTS_DEFAULT_FILEFORMAT, TTS_DEFAULT_LANGUAGE, TTS_DEFAULT_PITCH, @@ -53,29 +51,11 @@ class BaiduTTSEntity(TextToSpeechEntity): def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry) -> None: """Initialize the Baidu TTS entity.""" - self.hass = hass - self._config_entry = config_entry # Generate unique ID and set name app_id = config_entry.data[CONF_APP_ID] self._attr_unique_id = f"baidu_tts_{app_id}" - self._attr_name = "百度语音合成" - - # Add device info - self._attr_device_info = DeviceInfo( - identifiers={(DOMAIN, app_id)}, - name="百度语音", - manufacturer="百度", - model="语音合成服务", - entry_type=DeviceEntryType.SERVICE, - ) - - _LOGGER.debug( - "Initializing Baidu TTS entity with app_id: %s, unique_id: %s, name: %s", - app_id, - self._attr_unique_id, - self._attr_name, - ) + self._attr_name = "Baidu TTS" # Initialize the TTS client self._client = AipSpeech( @@ -87,28 +67,23 @@ class BaiduTTSEntity(TextToSpeechEntity): @property def default_language(self) -> str: """Return the default language.""" - _LOGGER.debug("Getting default language: %s", TTS_DEFAULT_LANGUAGE) return TTS_DEFAULT_LANGUAGE @property def supported_languages(self) -> list[str]: """Return a list of supported languages.""" - languages = list(TTS_LANGUAGES.keys()) - _LOGGER.debug("Getting supported languages: %s", languages) - return languages + return list(TTS_LANGUAGES.keys()) @property def supported_options(self) -> list[str]: """Return list of supported options.""" - options = ["speed", "pitch", "volume", "voice", "fileformat"] - _LOGGER.debug("Getting supported options: %s", options) - return options + return ["speed", "pitch", "volume", "voice", "fileformat"] @property def default_options(self) -> dict[str, Any]: """Return a dict of default options.""" # Get current values from config_entry.options first, then data, then fall back to defaults - options = { + return { "speed": self._config_entry.options.get( "speed", self._config_entry.data.get("speed", TTS_DEFAULT_SPEED) ), @@ -126,8 +101,6 @@ class BaiduTTSEntity(TextToSpeechEntity): self._config_entry.data.get("fileformat", TTS_DEFAULT_FILEFORMAT), ), } - _LOGGER.debug("Getting default options: %s", options) - return options @callback def async_get_supported_voices(self, language: str) -> list[Voice] | None: @@ -135,77 +108,48 @@ class BaiduTTSEntity(TextToSpeechEntity): voices = [] for voice_id, voice_name in TTS_SUPPORTED_VOICES.items(): voices.append(Voice(str(voice_id), voice_name)) - _LOGGER.debug("Getting supported voices for language %s: %s", language, voices) return voices def get_tts_audio( self, message: str, language: str, options: dict[str, Any] ) -> TtsAudioType: """Get TTS audio from Baidu.""" - _LOGGER.debug("=================== TTS Debug Info Start ===================") - _LOGGER.debug("Message: %s", message) - _LOGGER.debug("Language: %s", language) - _LOGGER.debug("Raw options dict: %s", options) - _LOGGER.debug("Config entry data: %s", self._config_entry.data) - _LOGGER.debug("Config entry options: %s", self._config_entry.options) - # 获取配置值,优先级:options > config_entry.options > config_entry.data > 默认值 + # options > config_entry.data > default try: speed = int( options.get("speed") - or self._config_entry.options.get("speed") or self._config_entry.data.get("speed") or TTS_DEFAULT_SPEED ) pitch = int( options.get("pitch") - or self._config_entry.options.get("pitch") or self._config_entry.data.get("pitch") or TTS_DEFAULT_PITCH ) volume = int( options.get("volume") - or self._config_entry.options.get("volume") or self._config_entry.data.get("volume") or TTS_DEFAULT_VOLUME ) voice = int( options.get("voice") - or self._config_entry.options.get("voice") or self._config_entry.data.get("voice") or TTS_DEFAULT_VOICE ) fileformat = int( options.get("fileformat") - or self._config_entry.options.get("fileformat") or self._config_entry.data.get("fileformat") or TTS_DEFAULT_FILEFORMAT ) - _LOGGER.debug( - "Parsed values - speed: %s(%s), pitch: %s(%s), volume: %s(%s), " - "voice: %s(%s), fileformat: %s(%s)", - speed, - type(speed), - pitch, - type(pitch), - volume, - type(volume), - voice, - type(voice), - fileformat, - type(fileformat), - ) - except ValueError as ex: _LOGGER.error("Error parsing options: %s", ex) raise HomeAssistantError(f"Invalid option value: {ex}") from ex - # 获取音频格式 format_config = TTS_FILEFORMAT_MAP.get(fileformat, TTS_FILEFORMAT_MAP[6]) _LOGGER.debug("Using audio format: %s", format_config) - # 准备API参数 api_params = { "spd": speed, "pit": pitch, @@ -216,7 +160,6 @@ class BaiduTTSEntity(TextToSpeechEntity): _LOGGER.debug("API parameters: %s", api_params) try: - _LOGGER.debug("Calling Baidu TTS API") result = self._client.synthesis( message, language, @@ -244,6 +187,4 @@ class BaiduTTSEntity(TextToSpeechEntity): type(ex).__name__, ) raise HomeAssistantError("Failed to generate TTS audio") from ex - - _LOGGER.debug("=================== TTS Debug Info End ===================") return format_config, result