mirror of
https://github.com/home-assistant/core.git
synced 2026-05-21 00:05:16 +02:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5f5d74cfbd | |||
| c188fdcc8b | |||
| a3b43fc19b | |||
| 894a68acb6 | |||
| 30bc3fc412 | |||
| 3cc0cc38ab | |||
| 296caa90c1 | |||
| bb4c211fb6 | |||
| d4fa904386 | |||
| db98f0b434 | |||
| 7341ac91ee | |||
| b2fb5df0fb | |||
| 265485a7d0 | |||
| bf1b93fb66 | |||
| be9d4bedfd | |||
| e8ac982e83 | |||
| 6c8e5a8e98 | |||
| e40a3e18db | |||
| cba05caadd | |||
| ef3bc61e2b | |||
| 3eff36eb9d |
@@ -19,7 +19,6 @@ machine/* linguist-generated=true
|
||||
mypy.ini linguist-generated=true
|
||||
requirements.txt linguist-generated=true
|
||||
requirements_all.txt linguist-generated=true
|
||||
requirements_test_all.txt linguist-generated=true
|
||||
requirements_test_pre_commit.txt linguist-generated=true
|
||||
script/hassfest/docker/Dockerfile linguist-generated=true
|
||||
.github/workflows/*.lock.yml linguist-generated=true
|
||||
|
||||
+17
-17
@@ -1,4 +1,4 @@
|
||||
# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"f302a3180732b8ac09ba6f393867233445c8f22d14301456227a56c6ab8fe758","compiler_version":"v0.74.4","strict":true,"agent_id":"copilot"}
|
||||
# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"1ad29b8fb97f5df4466be54051779a3188f094d7efb041a8ed55211eab33c5f5","compiler_version":"v0.74.4","strict":true,"agent_id":"copilot"}
|
||||
# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"d3abfe96a194bce3a523ed2093ddedd5704cdf62","version":"v0.74.4"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.46"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.46"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.46"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.9","digest":"sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.9@sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388"},{"image":"ghcr.io/github/github-mcp-server:v1.0.4"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]}
|
||||
# ___ _ _
|
||||
# / _ \ | | (_)
|
||||
@@ -112,7 +112,7 @@ jobs:
|
||||
GH_AW_INFO_EXPERIMENTAL: "false"
|
||||
GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true"
|
||||
GH_AW_INFO_STAGED: "false"
|
||||
GH_AW_INFO_ALLOWED_DOMAINS: '["defaults"]'
|
||||
GH_AW_INFO_ALLOWED_DOMAINS: '["python"]'
|
||||
GH_AW_INFO_FIREWALL_ENABLED: "true"
|
||||
GH_AW_INFO_AWF_VERSION: "v0.25.46"
|
||||
GH_AW_INFO_AWMG_VERSION: ""
|
||||
@@ -189,20 +189,20 @@ jobs:
|
||||
run: |
|
||||
bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh"
|
||||
{
|
||||
cat << 'GH_AW_PROMPT_378af7db8428e64c_EOF'
|
||||
cat << 'GH_AW_PROMPT_bb296919e461941b_EOF'
|
||||
<system>
|
||||
GH_AW_PROMPT_378af7db8428e64c_EOF
|
||||
GH_AW_PROMPT_bb296919e461941b_EOF
|
||||
cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md"
|
||||
cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md"
|
||||
cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md"
|
||||
cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md"
|
||||
cat << 'GH_AW_PROMPT_378af7db8428e64c_EOF'
|
||||
cat << 'GH_AW_PROMPT_bb296919e461941b_EOF'
|
||||
<safe-output-tools>
|
||||
Tools: add_comment, missing_tool, missing_data, noop
|
||||
</safe-output-tools>
|
||||
GH_AW_PROMPT_378af7db8428e64c_EOF
|
||||
GH_AW_PROMPT_bb296919e461941b_EOF
|
||||
cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md"
|
||||
cat << 'GH_AW_PROMPT_378af7db8428e64c_EOF'
|
||||
cat << 'GH_AW_PROMPT_bb296919e461941b_EOF'
|
||||
<github-context>
|
||||
The following GitHub context information is available for this workflow:
|
||||
{{#if github.actor}}
|
||||
@@ -231,12 +231,12 @@ jobs:
|
||||
{{/if}}
|
||||
</github-context>
|
||||
|
||||
GH_AW_PROMPT_378af7db8428e64c_EOF
|
||||
GH_AW_PROMPT_bb296919e461941b_EOF
|
||||
cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md"
|
||||
cat << 'GH_AW_PROMPT_378af7db8428e64c_EOF'
|
||||
cat << 'GH_AW_PROMPT_bb296919e461941b_EOF'
|
||||
</system>
|
||||
{{#runtime-import .github/workflows/check-requirements.md}}
|
||||
GH_AW_PROMPT_378af7db8428e64c_EOF
|
||||
GH_AW_PROMPT_bb296919e461941b_EOF
|
||||
} > "$GH_AW_PROMPT"
|
||||
- name: Interpolate variables and render templates
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
@@ -454,9 +454,9 @@ jobs:
|
||||
mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs"
|
||||
mkdir -p /tmp/gh-aw/safeoutputs
|
||||
mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs
|
||||
cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_39549f77910cdeb7_EOF'
|
||||
cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_c09f5151c817ddfc_EOF'
|
||||
{"add_comment":{"max":1,"target":"${{ env.PR_NUMBER }}"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}}
|
||||
GH_AW_SAFE_OUTPUTS_CONFIG_39549f77910cdeb7_EOF
|
||||
GH_AW_SAFE_OUTPUTS_CONFIG_c09f5151c817ddfc_EOF
|
||||
- name: Generate Safe Outputs Tools
|
||||
env:
|
||||
GH_AW_TOOLS_META_JSON: |
|
||||
@@ -648,7 +648,7 @@ jobs:
|
||||
|
||||
mkdir -p /home/runner/.copilot
|
||||
GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node)
|
||||
cat << GH_AW_MCP_CONFIG_da05d5b6908ed578_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
|
||||
cat << GH_AW_MCP_CONFIG_d12799b4d7ffe5c2_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
|
||||
{
|
||||
"mcpServers": {
|
||||
"github": {
|
||||
@@ -692,7 +692,7 @@ jobs:
|
||||
"payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}"
|
||||
}
|
||||
}
|
||||
GH_AW_MCP_CONFIG_da05d5b6908ed578_EOF
|
||||
GH_AW_MCP_CONFIG_d12799b4d7ffe5c2_EOF
|
||||
- name: Mount MCP servers as CLIs
|
||||
id: mount-mcp-clis
|
||||
continue-on-error: true
|
||||
@@ -725,7 +725,7 @@ jobs:
|
||||
GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true)
|
||||
export GH_AW_NODE_BIN
|
||||
(umask 177 && touch /tmp/gh-aw/agent-stdio.log)
|
||||
printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.46/awf-config.schema.json","network":{"allowDomains":["api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","api.snapcraft.io","archive.ubuntu.com","azure.archive.ubuntu.com","crl.geotrust.com","crl.globalsign.com","crl.identrust.com","crl.sectigo.com","crl.thawte.com","crl.usertrust.com","crl.verisign.com","crl3.digicert.com","crl4.digicert.com","crls.ssl.com","github.com","host.docker.internal","json-schema.org","json.schemastore.org","keyserver.ubuntu.com","ocsp.digicert.com","ocsp.geotrust.com","ocsp.globalsign.com","ocsp.identrust.com","ocsp.sectigo.com","ocsp.ssl.com","ocsp.thawte.com","ocsp.usertrust.com","ocsp.verisign.com","packagecloud.io","packages.cloud.google.com","packages.microsoft.com","ppa.launchpad.net","raw.githubusercontent.com","registry.npmjs.org","s.symcb.com","s.symcd.com","security.ubuntu.com","telemetry.enterprise.githubcopilot.com","ts-crl.ws.symantec.com","ts-ocsp.ws.symantec.com","www.googleapis.com"]},"apiProxy":{"enabled":true,"enableTokenSteering":true,"maxRuns":500,"maxEffectiveTokens":25000000,"models":{"auto":["large"],"coding":["copilot/gpt-5*codex*","openai/gpt-5*codex*","gpt-5-codex"],"deep-research":["copilot/deep-research*","copilot/o3-deep-research*","copilot/o4-mini-deep-research*","google/deep-research*","gemini/deep-research*","openai/o3-deep-research*","openai/o4-mini-deep-research*"],"gemini-flash":["copilot/gemini-*flash*","google/gemini-*flash*","gemini/gemini-*flash*"],"gemini-flash-lite":["copilot/gemini-*flash*lite*","google/gemini-*flash*lite*","gemini/gemini-*flash*lite*"],"gemini-pro":["copilot/gemini-*pro*","google/gemini-*pro*","gemini/gemini-*pro*"],"gemma":["copilot/gemma*","google/gemma*","gemini/gemma*"],"gpt-4.1":["copilot/gpt-4.1*","openai/gpt-4.1*"],"gpt-5":["copilot/gpt-5*","openai/gpt-5*"],"gpt-5-codex":["copilot/gpt-5*codex*","openai/gpt-5*codex*"],"gpt-5-mini":["copilot/gpt-5*mini*","openai/gpt-5*mini*"],"gpt-5-nano":["copilot/gpt-5*nano*","openai/gpt-5*nano*"],"gpt-5-pro":["copilot/gpt-5*pro*","openai/gpt-5*pro*"],"haiku":["copilot/*haiku*","anthropic/*haiku*"],"large":["sonnet","gpt-5-pro","gpt-5","gemini-pro"],"mini":["haiku","gpt-5-mini","gpt-5-nano","gemini-flash-lite"],"opus":["copilot/*opus*","anthropic/*opus*"],"reasoning":["copilot/o1*","copilot/o3*","copilot/o4*","openai/o1*","openai/o3*","openai/o4*"],"small":["mini"],"sonnet":["copilot/*sonnet*","anthropic/*sonnet*"],"vision":["copilot/gemini-*image*","gemini/gemini-*image*","copilot/gemini-*flash*","gemini/gemini-*flash*"]}},"container":{"imageTag":"0.25.46"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" && cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json
|
||||
printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.46/awf-config.schema.json","network":{"allowDomains":["*.pythonhosted.org","anaconda.org","api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","binstar.org","bootstrap.pypa.io","conda.anaconda.org","conda.binstar.org","files.pythonhosted.org","github.com","host.docker.internal","pip.pypa.io","pypi.org","pypi.python.org","raw.githubusercontent.com","registry.npmjs.org","repo.anaconda.com","repo.continuum.io","telemetry.enterprise.githubcopilot.com"]},"apiProxy":{"enabled":true,"enableTokenSteering":true,"maxRuns":500,"maxEffectiveTokens":25000000,"models":{"auto":["large"],"coding":["copilot/gpt-5*codex*","openai/gpt-5*codex*","gpt-5-codex"],"deep-research":["copilot/deep-research*","copilot/o3-deep-research*","copilot/o4-mini-deep-research*","google/deep-research*","gemini/deep-research*","openai/o3-deep-research*","openai/o4-mini-deep-research*"],"gemini-flash":["copilot/gemini-*flash*","google/gemini-*flash*","gemini/gemini-*flash*"],"gemini-flash-lite":["copilot/gemini-*flash*lite*","google/gemini-*flash*lite*","gemini/gemini-*flash*lite*"],"gemini-pro":["copilot/gemini-*pro*","google/gemini-*pro*","gemini/gemini-*pro*"],"gemma":["copilot/gemma*","google/gemma*","gemini/gemma*"],"gpt-4.1":["copilot/gpt-4.1*","openai/gpt-4.1*"],"gpt-5":["copilot/gpt-5*","openai/gpt-5*"],"gpt-5-codex":["copilot/gpt-5*codex*","openai/gpt-5*codex*"],"gpt-5-mini":["copilot/gpt-5*mini*","openai/gpt-5*mini*"],"gpt-5-nano":["copilot/gpt-5*nano*","openai/gpt-5*nano*"],"gpt-5-pro":["copilot/gpt-5*pro*","openai/gpt-5*pro*"],"haiku":["copilot/*haiku*","anthropic/*haiku*"],"large":["sonnet","gpt-5-pro","gpt-5","gemini-pro"],"mini":["haiku","gpt-5-mini","gpt-5-nano","gemini-flash-lite"],"opus":["copilot/*opus*","anthropic/*opus*"],"reasoning":["copilot/o1*","copilot/o3*","copilot/o4*","openai/o1*","openai/o3*","openai/o4*"],"small":["mini"],"sonnet":["copilot/*sonnet*","anthropic/*sonnet*"],"vision":["copilot/gemini-*image*","gemini/gemini-*image*","copilot/gemini-*flash*","gemini/gemini-*flash*"]}},"container":{"imageTag":"0.25.46"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" && cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json
|
||||
GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS=""
|
||||
if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
|
||||
GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw"
|
||||
@@ -820,7 +820,7 @@ jobs:
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
|
||||
GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com"
|
||||
GH_AW_ALLOWED_DOMAINS: "*.pythonhosted.org,anaconda.org,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,binstar.org,bootstrap.pypa.io,conda.anaconda.org,conda.binstar.org,files.pythonhosted.org,github.com,host.docker.internal,pip.pypa.io,pypi.org,pypi.python.org,raw.githubusercontent.com,registry.npmjs.org,repo.anaconda.com,repo.continuum.io,telemetry.enterprise.githubcopilot.com"
|
||||
GITHUB_SERVER_URL: ${{ github.server_url }}
|
||||
GITHUB_API_URL: ${{ github.api_url }}
|
||||
with:
|
||||
@@ -1390,7 +1390,7 @@ jobs:
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
|
||||
GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com"
|
||||
GH_AW_ALLOWED_DOMAINS: "*.pythonhosted.org,anaconda.org,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,binstar.org,bootstrap.pypa.io,conda.anaconda.org,conda.binstar.org,files.pythonhosted.org,github.com,host.docker.internal,pip.pypa.io,pypi.org,pypi.python.org,raw.githubusercontent.com,registry.npmjs.org,repo.anaconda.com,repo.continuum.io,telemetry.enterprise.githubcopilot.com"
|
||||
GITHUB_SERVER_URL: ${{ github.server_url }}
|
||||
GITHUB_API_URL: ${{ github.api_url }}
|
||||
GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":1,\"target\":\"${{ env.PR_NUMBER }}\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}"
|
||||
|
||||
Vendored
+3
-3
@@ -132,7 +132,7 @@
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Install all Requirements",
|
||||
"label": "Install all production Requirements",
|
||||
"type": "shell",
|
||||
"command": "uv pip install -r requirements_all.txt",
|
||||
"group": {
|
||||
@@ -146,9 +146,9 @@
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Install all Test Requirements",
|
||||
"label": "Install all (test & production) Requirements",
|
||||
"type": "shell",
|
||||
"command": "uv pip install -r requirements.txt -r requirements_test_all.txt",
|
||||
"command": "uv pip install -r requirements_all.txt -r requirements_test.txt",
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
|
||||
@@ -81,8 +81,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: AirOSConfigEntry) -> boo
|
||||
) as err:
|
||||
raise ConfigEntryAuthFailed from err
|
||||
except AirOSKeyDataMissingError as err:
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise ConfigEntryError("key_data_missing") from err
|
||||
except Exception as err:
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise ConfigEntryError("unknown") from err
|
||||
|
||||
airos_class: type[AirOS8 | AirOS6] = (
|
||||
|
||||
@@ -91,6 +91,7 @@ class AmazonDevicesCoordinator(DataUpdateCoordinator[dict[str, AmazonDevice]]):
|
||||
translation_placeholders={"error": repr(err)},
|
||||
) from err
|
||||
except CannotAuthenticate as err:
|
||||
# pylint: disable-next=home-assistant-exception-translation-key-missing
|
||||
raise ConfigEntryAuthFailed(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="invalid_auth",
|
||||
|
||||
@@ -51,13 +51,11 @@
|
||||
"advanced": {
|
||||
"data": {
|
||||
"chat_model": "[%key:common::generic::model%]",
|
||||
"prompt_caching": "[%key:component::anthropic::config_subentries::conversation::step::advanced::data::prompt_caching%]",
|
||||
"temperature": "[%key:component::anthropic::config_subentries::conversation::step::advanced::data::temperature%]"
|
||||
"prompt_caching": "[%key:component::anthropic::config_subentries::conversation::step::advanced::data::prompt_caching%]"
|
||||
},
|
||||
"data_description": {
|
||||
"chat_model": "[%key:component::anthropic::config_subentries::conversation::step::advanced::data_description::chat_model%]",
|
||||
"prompt_caching": "[%key:component::anthropic::config_subentries::conversation::step::advanced::data_description::prompt_caching%]",
|
||||
"temperature": "[%key:component::anthropic::config_subentries::conversation::step::advanced::data_description::temperature%]"
|
||||
"prompt_caching": "[%key:component::anthropic::config_subentries::conversation::step::advanced::data_description::prompt_caching%]"
|
||||
},
|
||||
"title": "[%key:component::anthropic::config_subentries::conversation::step::advanced::title%]"
|
||||
},
|
||||
@@ -120,13 +118,11 @@
|
||||
"advanced": {
|
||||
"data": {
|
||||
"chat_model": "[%key:common::generic::model%]",
|
||||
"prompt_caching": "Caching strategy",
|
||||
"temperature": "Temperature"
|
||||
"prompt_caching": "Caching strategy"
|
||||
},
|
||||
"data_description": {
|
||||
"chat_model": "The model to serve the responses.",
|
||||
"prompt_caching": "Optimize your API cost and response times based on your usage.",
|
||||
"temperature": "Control the randomness of the response, trading off between creativity and coherence."
|
||||
"prompt_caching": "Optimize your API cost and response times based on your usage."
|
||||
},
|
||||
"title": "Advanced settings"
|
||||
},
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
"""Config flow to configure the Arcam FMJ component."""
|
||||
|
||||
import socket
|
||||
from typing import Any
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from arcam.fmj.client import Client, ConnectionFailed
|
||||
from arcam.fmj import ConnectionFailed
|
||||
from arcam.fmj.client import Client
|
||||
from arcam.fmj.utils import get_uniqueid_from_host, get_uniqueid_from_udn
|
||||
import voluptuous as vol
|
||||
|
||||
@@ -29,26 +31,19 @@ class ArcamFmjFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
await self.async_set_unique_id(uuid)
|
||||
self._abort_if_unique_id_configured({CONF_HOST: host, CONF_PORT: port})
|
||||
|
||||
async def _async_check_and_create(self, host: str, port: int) -> ConfigFlowResult:
|
||||
async def _async_try_connect(self, host: str, port: int) -> None:
|
||||
"""Verify the device is reachable."""
|
||||
client = Client(host, port)
|
||||
try:
|
||||
await client.start()
|
||||
except ConnectionFailed:
|
||||
return self.async_abort(reason="cannot_connect")
|
||||
finally:
|
||||
await client.stop()
|
||||
|
||||
return self.async_create_entry(
|
||||
title=f"{DEFAULT_NAME} ({host})",
|
||||
data={CONF_HOST: host, CONF_PORT: port},
|
||||
)
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle a discovered device."""
|
||||
errors: dict[str, str] = {}
|
||||
|
||||
if user_input is not None:
|
||||
uuid = await get_uniqueid_from_host(
|
||||
async_get_clientsession(self.hass), user_input[CONF_HOST]
|
||||
@@ -58,18 +53,36 @@ class ArcamFmjFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
user_input[CONF_HOST], user_input[CONF_PORT], uuid
|
||||
)
|
||||
|
||||
return await self._async_check_and_create(
|
||||
user_input[CONF_HOST], user_input[CONF_PORT]
|
||||
)
|
||||
try:
|
||||
await self._async_try_connect(
|
||||
user_input[CONF_HOST], user_input[CONF_PORT]
|
||||
)
|
||||
except socket.gaierror:
|
||||
errors["base"] = "invalid_host"
|
||||
except TimeoutError:
|
||||
errors["base"] = "timeout_connect"
|
||||
except ConnectionRefusedError:
|
||||
errors["base"] = "connection_refused"
|
||||
except ConnectionFailed, OSError:
|
||||
errors["base"] = "cannot_connect"
|
||||
else:
|
||||
return self.async_create_entry(
|
||||
title=f"{DEFAULT_NAME} ({user_input[CONF_HOST]})",
|
||||
data={
|
||||
CONF_HOST: user_input[CONF_HOST],
|
||||
CONF_PORT: user_input[CONF_PORT],
|
||||
},
|
||||
)
|
||||
|
||||
fields = {
|
||||
vol.Required(CONF_HOST): str,
|
||||
vol.Required(CONF_PORT, default=DEFAULT_PORT): int,
|
||||
}
|
||||
schema = vol.Schema(fields)
|
||||
if user_input is not None:
|
||||
schema = self.add_suggested_values_to_schema(schema, user_input)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user", data_schema=vol.Schema(fields), errors=errors
|
||||
)
|
||||
return self.async_show_form(step_id="user", data_schema=schema, errors=errors)
|
||||
|
||||
async def async_step_confirm(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
@@ -79,7 +92,10 @@ class ArcamFmjFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
self.context["title_placeholders"] = placeholders
|
||||
|
||||
if user_input is not None:
|
||||
return await self._async_check_and_create(self.host, self.port)
|
||||
return self.async_create_entry(
|
||||
title=f"{DEFAULT_NAME} ({self.host})",
|
||||
data={CONF_HOST: self.host, CONF_PORT: self.port},
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="confirm", description_placeholders=placeholders
|
||||
@@ -97,6 +113,11 @@ class ArcamFmjFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
|
||||
await self._async_set_unique_id_and_update(host, port, uuid)
|
||||
|
||||
try:
|
||||
await self._async_try_connect(host, port)
|
||||
except ConnectionFailed, OSError:
|
||||
return self.async_abort(reason="cannot_connect")
|
||||
|
||||
self.host = host
|
||||
self.port = DEFAULT_PORT
|
||||
self.port = port
|
||||
return await self.async_step_confirm()
|
||||
|
||||
@@ -5,6 +5,12 @@
|
||||
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"connection_refused": "Host refused connection",
|
||||
"invalid_host": "[%key:common::config_flow::error::invalid_host%]",
|
||||
"timeout_connect": "[%key:common::config_flow::error::timeout_connect%]"
|
||||
},
|
||||
"flow_title": "{host}",
|
||||
"step": {
|
||||
"confirm": {
|
||||
|
||||
@@ -82,7 +82,7 @@ rules:
|
||||
comment: |
|
||||
This integration does not have any entities that should disabled by default.
|
||||
entity-translations: done
|
||||
exception-translations: done
|
||||
exception-translations: todo
|
||||
icon-translations: done
|
||||
reconfiguration-flow: todo
|
||||
repair-issues:
|
||||
|
||||
@@ -67,7 +67,7 @@ rules:
|
||||
comment: |
|
||||
Only one entity type (device_tracker) is created, making this not applicable.
|
||||
entity-translations: done
|
||||
exception-translations: done
|
||||
exception-translations: todo
|
||||
icon-translations: done
|
||||
reconfiguration-flow:
|
||||
status: todo
|
||||
|
||||
@@ -51,6 +51,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: S3ConfigEntry) -> bool:
|
||||
translation_key="invalid_bucket_name",
|
||||
) from err
|
||||
except ValueError as err:
|
||||
# pylint: disable-next=home-assistant-exception-translation-key-missing
|
||||
raise ConfigEntryError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="invalid_endpoint_url",
|
||||
|
||||
@@ -72,7 +72,7 @@ rules:
|
||||
entity-device-class: done
|
||||
entity-disabled-by-default: done
|
||||
entity-translations: done
|
||||
exception-translations: done
|
||||
exception-translations: todo
|
||||
icon-translations:
|
||||
status: exempt
|
||||
comment: This integration does not use icons.
|
||||
|
||||
@@ -75,11 +75,13 @@ def handle_backup_errors[_R, **P](
|
||||
err.message,
|
||||
exc_info=True,
|
||||
)
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise BackupAgentError(
|
||||
f"Error during backup operation in {func.__name__}:"
|
||||
f" Status {err.status_code}, message: {err.message}"
|
||||
) from err
|
||||
except ServiceRequestError as err:
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise BackupAgentError(
|
||||
f"Timeout during backup operation in {func.__name__}"
|
||||
) from err
|
||||
@@ -90,6 +92,7 @@ def handle_backup_errors[_R, **P](
|
||||
err,
|
||||
exc_info=True,
|
||||
)
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise BackupAgentError(
|
||||
f"Error during backup operation in {func.__name__}: {err}"
|
||||
) from err
|
||||
@@ -118,6 +121,7 @@ class AzureStorageBackupAgent(BackupAgent):
|
||||
"""Download a backup file."""
|
||||
blob = await self._find_blob_by_backup_id(backup_id)
|
||||
if blob is None:
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise BackupNotFound(f"Backup {backup_id} not found")
|
||||
download_stream = await self._client.download_blob(blob.name)
|
||||
return download_stream.chunks()
|
||||
@@ -155,6 +159,7 @@ class AzureStorageBackupAgent(BackupAgent):
|
||||
"""Delete a backup file."""
|
||||
blob = await self._find_blob_by_backup_id(backup_id)
|
||||
if blob is None:
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise BackupNotFound(f"Backup {backup_id} not found")
|
||||
await self._client.delete_blob(blob.name)
|
||||
|
||||
@@ -181,6 +186,7 @@ class AzureStorageBackupAgent(BackupAgent):
|
||||
"""Return a backup."""
|
||||
blob = await self._find_blob_by_backup_id(backup_id)
|
||||
if blob is None:
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise BackupNotFound(f"Backup {backup_id} not found")
|
||||
|
||||
return AgentBackup.from_dict(json.loads(blob.metadata["backup_metadata"]))
|
||||
|
||||
@@ -89,6 +89,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: BackblazeConfigEntry) ->
|
||||
translation_key="cannot_connect",
|
||||
) from err
|
||||
except exception.MissingAccountData as err:
|
||||
# pylint: disable-next=home-assistant-exception-translation-key-missing
|
||||
raise ConfigEntryAuthFailed(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="invalid_auth",
|
||||
|
||||
@@ -96,7 +96,7 @@ rules:
|
||||
entity-translations:
|
||||
status: exempt
|
||||
comment: This integration does not have entities.
|
||||
exception-translations: done
|
||||
exception-translations: todo
|
||||
icon-translations:
|
||||
status: exempt
|
||||
comment: This integration does not use icons.
|
||||
|
||||
@@ -67,6 +67,9 @@
|
||||
"cannot_connect": {
|
||||
"message": "Cannot connect to endpoint"
|
||||
},
|
||||
"invalid_auth": {
|
||||
"message": "Authentication failed using the provided key ID and application key."
|
||||
},
|
||||
"invalid_bucket_name": {
|
||||
"message": "Bucket does not exist or is not writable by the provided credentials."
|
||||
},
|
||||
|
||||
@@ -169,6 +169,7 @@ class BlinkCamera(CoordinatorEntity[BlinkUpdateCoordinator], Camera):
|
||||
try:
|
||||
await self._camera.save_recent_clips(output_dir=file_path)
|
||||
except OSError as err:
|
||||
# pylint: disable-next=home-assistant-exception-message-with-translation
|
||||
raise ServiceValidationError(
|
||||
str(err),
|
||||
translation_domain=DOMAIN,
|
||||
@@ -190,6 +191,7 @@ class BlinkCamera(CoordinatorEntity[BlinkUpdateCoordinator], Camera):
|
||||
try:
|
||||
await self._camera.video_to_file(filename)
|
||||
except OSError as err:
|
||||
# pylint: disable-next=home-assistant-exception-message-with-translation
|
||||
raise ServiceValidationError(
|
||||
str(err),
|
||||
translation_domain=DOMAIN,
|
||||
|
||||
@@ -183,6 +183,7 @@ class BSBLANClimate(BSBLanCircuitEntity, ClimateEntity):
|
||||
try:
|
||||
await self.coordinator.client.thermostat(**data, circuit=self._circuit)
|
||||
except BSBLANError as err:
|
||||
# pylint: disable-next=home-assistant-exception-message-with-translation
|
||||
raise HomeAssistantError(
|
||||
"An error occurred while updating the BSBLAN device",
|
||||
translation_domain=DOMAIN,
|
||||
|
||||
@@ -94,7 +94,7 @@ rules:
|
||||
entity-translations:
|
||||
status: exempt
|
||||
comment: This integration does not have entities.
|
||||
exception-translations: done
|
||||
exception-translations: todo
|
||||
icon-translations:
|
||||
status: exempt
|
||||
comment: This integration does not use icons.
|
||||
|
||||
@@ -65,6 +65,7 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str,
|
||||
translation_placeholders={"error": repr(err)},
|
||||
) from err
|
||||
except aiocomelit_exceptions.CannotAuthenticate as err:
|
||||
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
|
||||
raise InvalidAuth(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="cannot_authenticate",
|
||||
|
||||
@@ -353,6 +353,7 @@ class EcovacsVacuum(
|
||||
if self._capability.clean.action.area is None:
|
||||
info = self._device.device_info
|
||||
name = info.get("nick", info["name"])
|
||||
# pylint: disable-next=home-assistant-exception-translation-key-missing
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="vacuum_send_command_area_not_supported",
|
||||
|
||||
@@ -88,7 +88,7 @@ rules:
|
||||
entity-translations:
|
||||
status: exempt
|
||||
comment: This integration does not create its own entities.
|
||||
exception-translations: done
|
||||
exception-translations: todo
|
||||
icon-translations:
|
||||
status: exempt
|
||||
comment: This integration does not create its own entities.
|
||||
|
||||
@@ -364,6 +364,7 @@ class ESPHomeManager:
|
||||
response_dict = {"response": response}
|
||||
|
||||
except TemplateError as ex:
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise HomeAssistantError(
|
||||
f"Error rendering response template: {ex}"
|
||||
) from ex
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
"mqtt": ["esphome/discover/#"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": [
|
||||
"aioesphomeapi==45.0.2",
|
||||
"aioesphomeapi==45.0.4",
|
||||
"esphome-dashboard-api==1.3.0",
|
||||
"bleak-esphome==3.7.3"
|
||||
],
|
||||
|
||||
@@ -63,7 +63,7 @@ rules:
|
||||
comment: |
|
||||
This integration does not have many entities. All of them are fundamental.
|
||||
entity-translations: done
|
||||
exception-translations: done
|
||||
exception-translations: todo
|
||||
icon-translations: done
|
||||
reconfiguration-flow: done
|
||||
repair-issues: todo
|
||||
|
||||
@@ -61,13 +61,20 @@ async def async_setup_entry(hass: HomeAssistant, entry: FritzConfigEntry) -> boo
|
||||
except FRITZ_AUTH_EXCEPTIONS as ex:
|
||||
raise ConfigEntryAuthFailed from ex
|
||||
except FRITZ_EXCEPTIONS as ex:
|
||||
raise ConfigEntryNotReady from ex
|
||||
raise ConfigEntryNotReady(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="error_connecting",
|
||||
translation_placeholders={"error": str(ex)},
|
||||
) from ex
|
||||
|
||||
if (
|
||||
"X_AVM-DE_UPnP1" in avm_wrapper.connection.services
|
||||
and not (await avm_wrapper.async_get_upnp_configuration())["NewEnable"]
|
||||
):
|
||||
raise ConfigEntryAuthFailed("Missing UPnP configuration")
|
||||
raise ConfigEntryNotReady(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="error_upnp_disabled",
|
||||
)
|
||||
|
||||
await avm_wrapper.async_config_entry_first_refresh()
|
||||
|
||||
|
||||
@@ -185,12 +185,18 @@
|
||||
"config_entry_not_found": {
|
||||
"message": "Failed to perform action \"{service}\". Config entry for target not found"
|
||||
},
|
||||
"error_connecting": {
|
||||
"message": "Error connecting to the FRITZ!Box: {error}"
|
||||
},
|
||||
"error_parse_device_info": {
|
||||
"message": "Error parsing device info. Please check the system event log of your FRITZ!Box for malformed data and clear the event list."
|
||||
},
|
||||
"error_refresh_hosts_info": {
|
||||
"message": "Error refreshing hosts info"
|
||||
},
|
||||
"error_upnp_disabled": {
|
||||
"message": "UPnP is disabled on the FRITZ!Box. Please enable UPnP to use this integration."
|
||||
},
|
||||
"service_dial_failed": {
|
||||
"message": "Failed to dial, check if the click to dial service of the FRITZ!Box is activated"
|
||||
},
|
||||
@@ -200,9 +206,6 @@
|
||||
"service_parameter_unknown": {
|
||||
"message": "Action or parameter unknown"
|
||||
},
|
||||
"unable_to_connect": {
|
||||
"message": "Unable to establish a connection"
|
||||
},
|
||||
"update_failed": {
|
||||
"message": "Error while updating the data: {error}"
|
||||
}
|
||||
|
||||
@@ -100,6 +100,7 @@ class GoogleDriveBackupAgent(BackupAgent):
|
||||
try:
|
||||
await self._client.async_upload_backup(wrapped_open_stream, backup)
|
||||
except (GoogleDriveApiError, HomeAssistantError, TimeoutError) as err:
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise BackupAgentError(f"Failed to upload backup: {err}") from err
|
||||
|
||||
async def async_list_backups(self, **kwargs: Any) -> list[AgentBackup]:
|
||||
@@ -107,6 +108,7 @@ class GoogleDriveBackupAgent(BackupAgent):
|
||||
try:
|
||||
return await self._client.async_list_backups()
|
||||
except (GoogleDriveApiError, HomeAssistantError, TimeoutError) as err:
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise BackupAgentError(f"Failed to list backups: {err}") from err
|
||||
|
||||
async def async_get_backup(
|
||||
@@ -119,6 +121,7 @@ class GoogleDriveBackupAgent(BackupAgent):
|
||||
for backup in backups:
|
||||
if backup.backup_id == backup_id:
|
||||
return backup
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise BackupNotFound(f"Backup {backup_id} not found")
|
||||
|
||||
async def async_download_backup(
|
||||
@@ -139,7 +142,9 @@ class GoogleDriveBackupAgent(BackupAgent):
|
||||
stream = await self._client.async_download(file_id)
|
||||
return ChunkAsyncStreamIterator(stream)
|
||||
except (GoogleDriveApiError, HomeAssistantError, TimeoutError) as err:
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise BackupAgentError(f"Failed to download backup: {err}") from err
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise BackupNotFound(f"Backup {backup_id} not found")
|
||||
|
||||
async def async_delete_backup(
|
||||
@@ -160,5 +165,7 @@ class GoogleDriveBackupAgent(BackupAgent):
|
||||
_LOGGER.debug("Deleted backup_id: %s", backup_id)
|
||||
return
|
||||
except (GoogleDriveApiError, HomeAssistantError, TimeoutError) as err:
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise BackupAgentError(f"Failed to delete backup: {err}") from err
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise BackupNotFound(f"Backup {backup_id} not found")
|
||||
|
||||
@@ -84,6 +84,7 @@ async def _async_handle_upload(call: ServiceCall) -> ServiceResponse:
|
||||
|
||||
scopes = config_entry.data["token"]["scope"].split(" ")
|
||||
if UPLOAD_SCOPE not in scopes:
|
||||
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="missing_upload_permission",
|
||||
|
||||
@@ -56,7 +56,7 @@ rules:
|
||||
entity-device-class: done
|
||||
entity-disabled-by-default: done
|
||||
entity-translations: done
|
||||
exception-translations: done
|
||||
exception-translations: todo
|
||||
icon-translations: done
|
||||
reconfiguration-flow: done
|
||||
repair-issues:
|
||||
|
||||
@@ -109,6 +109,7 @@ class HabiticaBaseNotifyEntity(HabiticaBase, NotifyEntity):
|
||||
try:
|
||||
await self._send_message(message)
|
||||
except NotAuthorizedError as e:
|
||||
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="send_message_forbidden",
|
||||
@@ -118,6 +119,7 @@ class HabiticaBaseNotifyEntity(HabiticaBase, NotifyEntity):
|
||||
},
|
||||
) from e
|
||||
except NotFoundError as e:
|
||||
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="send_message_not_found",
|
||||
|
||||
@@ -266,6 +266,7 @@ class HomeConnectAirConditioningEntity(HomeConnectEntity, ClimateEntity):
|
||||
value=BSH_POWER_ON,
|
||||
)
|
||||
except HomeConnectError as err:
|
||||
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="power_on",
|
||||
|
||||
@@ -52,9 +52,7 @@ reload_config_entry:
|
||||
target:
|
||||
fields:
|
||||
entry_id:
|
||||
advanced: true
|
||||
required: false
|
||||
example: 8955375327824e14ba89e4b29cc3ec9a
|
||||
selector:
|
||||
config_entry:
|
||||
|
||||
|
||||
@@ -223,10 +223,10 @@
|
||||
"name": "Reload all Home Assistant configuration"
|
||||
},
|
||||
"reload_config_entry": {
|
||||
"description": "Reloads the specified config entry.",
|
||||
"description": "Reloads any explicitly provided config entry ID and any config entries referenced by entities or devices in the target. If both are provided, the union of those config entries is reloaded.",
|
||||
"fields": {
|
||||
"entry_id": {
|
||||
"description": "The configuration entry ID of the entry to be reloaded.",
|
||||
"description": "Optional configuration entry ID to reload.",
|
||||
"name": "Config entry ID"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -59,7 +59,7 @@ rules:
|
||||
entity-device-class: done
|
||||
entity-disabled-by-default: done
|
||||
entity-translations: done
|
||||
exception-translations: done
|
||||
exception-translations: todo
|
||||
icon-translations: done
|
||||
reconfiguration-flow: done
|
||||
repair-issues: todo
|
||||
|
||||
@@ -50,12 +50,14 @@ def homevolt_exception_handler[_HomevoltEntityT: HomevoltEntity, **_P](
|
||||
translation_key="auth_failed",
|
||||
) from error
|
||||
except HomevoltConnectionError as error:
|
||||
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="communication_error",
|
||||
translation_placeholders={"error": str(error)},
|
||||
) from error
|
||||
except HomevoltError as error:
|
||||
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="unknown_error",
|
||||
|
||||
@@ -44,6 +44,7 @@ class HWEnergyDeviceUpdateCoordinator(DataUpdateCoordinator[DeviceResponseEntry]
|
||||
data = await self.api.combined()
|
||||
|
||||
except RequestError as ex:
|
||||
# pylint: disable-next=home-assistant-exception-message-with-translation
|
||||
raise UpdateFailed(
|
||||
ex, translation_domain=DOMAIN, translation_key="communication_error"
|
||||
) from ex
|
||||
@@ -60,6 +61,7 @@ class HWEnergyDeviceUpdateCoordinator(DataUpdateCoordinator[DeviceResponseEntry]
|
||||
self.config_entry.entry_id
|
||||
)
|
||||
|
||||
# pylint: disable-next=home-assistant-exception-message-with-translation
|
||||
raise UpdateFailed(
|
||||
ex, translation_domain=DOMAIN, translation_key="api_disabled"
|
||||
) from ex
|
||||
|
||||
@@ -62,7 +62,7 @@ rules:
|
||||
entity-device-class: done
|
||||
entity-disabled-by-default: done
|
||||
entity-translations: done
|
||||
exception-translations: done
|
||||
exception-translations: todo
|
||||
icon-translations: done
|
||||
reconfiguration-flow:
|
||||
status: exempt
|
||||
|
||||
@@ -94,7 +94,7 @@ rules:
|
||||
entity-translations:
|
||||
status: exempt
|
||||
comment: This integration does not have entities.
|
||||
exception-translations: done
|
||||
exception-translations: todo
|
||||
icon-translations:
|
||||
status: exempt
|
||||
comment: This integration does not use icons.
|
||||
|
||||
@@ -62,7 +62,7 @@ rules:
|
||||
comment: >
|
||||
The device class is a service. When removed, entities are removed as well.
|
||||
diagnostics: done
|
||||
exception-translations: done
|
||||
exception-translations: todo
|
||||
icon-translations: done
|
||||
reconfiguration-flow:
|
||||
status: todo
|
||||
|
||||
@@ -73,6 +73,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: InComfortConfigEntry) ->
|
||||
except InvalidHeaterList as exc:
|
||||
raise NoHeaters from exc
|
||||
except InvalidGateway as exc:
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise ConfigEntryAuthFailed("Incorrect credentials") from exc
|
||||
except ClientResponseError as exc:
|
||||
if exc.status == 404:
|
||||
|
||||
@@ -78,11 +78,15 @@ class InComfortDataCoordinator(DataUpdateCoordinator[InComfortData]):
|
||||
for heater in self.incomfort_data.heaters:
|
||||
await heater.update()
|
||||
except TimeoutError as exc:
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise UpdateFailed("Timeout error") from exc
|
||||
except ClientResponseError as exc:
|
||||
if exc.status == 401:
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise ConfigEntryError("Incorrect credentials") from exc
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise UpdateFailed(exc.message) from exc
|
||||
except InvalidHeaterList as exc:
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise UpdateFailed(exc.message) from exc
|
||||
return self.incomfort_data
|
||||
|
||||
@@ -10,6 +10,7 @@ import voluptuous as vol
|
||||
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
||||
from homeassistant.const import CONF_HOST, CONF_MODEL
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo
|
||||
|
||||
from .const import CONF_GENERATION, CONF_SERIAL_NUMBER, DEFAULT_PORT, DOMAIN
|
||||
|
||||
@@ -21,6 +22,12 @@ class IndevoltConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
|
||||
VERSION = 1
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize the config flow."""
|
||||
super().__init__()
|
||||
self._discovered_host: str | None = None
|
||||
self._discovered_device_data: dict[str, Any] | None = None
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
@@ -83,6 +90,55 @@ class IndevoltConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
async def async_step_dhcp(
|
||||
self, discovery_info: DhcpServiceInfo
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle DHCP discovery — probe the device to confirm it is an Indevolt device."""
|
||||
host = discovery_info.ip
|
||||
|
||||
try:
|
||||
device_data = await self._async_get_device_data(host)
|
||||
except OSError, ClientError, KeyError:
|
||||
return self.async_abort(reason="cannot_connect")
|
||||
|
||||
await self.async_set_unique_id(device_data[CONF_SERIAL_NUMBER])
|
||||
self._abort_if_unique_id_configured(
|
||||
updates={CONF_HOST: host}, reload_on_update=True
|
||||
)
|
||||
|
||||
self.context["title_placeholders"] = {"model": device_data[CONF_MODEL]}
|
||||
self._discovered_host = host
|
||||
self._discovered_device_data = device_data
|
||||
|
||||
return await self.async_step_discovery_confirm()
|
||||
|
||||
async def async_step_discovery_confirm(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Confirm DHCP discovery by user."""
|
||||
assert self._discovered_host is not None
|
||||
assert self._discovered_device_data is not None
|
||||
|
||||
# Attempt to setup from user input
|
||||
if user_input is not None:
|
||||
return self.async_create_entry(
|
||||
title=f"INDEVOLT {self._discovered_device_data[CONF_MODEL]}",
|
||||
data={
|
||||
CONF_HOST: self._discovered_host,
|
||||
**self._discovered_device_data,
|
||||
},
|
||||
)
|
||||
|
||||
# Retrieve user confirmation
|
||||
self._set_confirm_only()
|
||||
return self.async_show_form(
|
||||
step_id="discovery_confirm",
|
||||
description_placeholders={
|
||||
CONF_HOST: self._discovered_host,
|
||||
CONF_MODEL: self._discovered_device_data[CONF_MODEL],
|
||||
},
|
||||
)
|
||||
|
||||
async def _async_validate_input(
|
||||
self, user_input: dict[str, Any]
|
||||
) -> tuple[dict[str, str], dict[str, Any] | None]:
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
"name": "Indevolt",
|
||||
"codeowners": ["@xirt"],
|
||||
"config_flow": true,
|
||||
"dhcp": [
|
||||
{ "macaddress": "1C784B*" },
|
||||
{ "macaddress": "34EAE7*" },
|
||||
{ "macaddress": "7C3E82*" },
|
||||
{ "registered_devices": true }
|
||||
],
|
||||
"documentation": "https://www.home-assistant.io/integrations/indevolt",
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
|
||||
@@ -40,12 +40,8 @@ rules:
|
||||
# Gold
|
||||
devices: done
|
||||
diagnostics: done
|
||||
discovery-update-info:
|
||||
status: exempt
|
||||
comment: Integration does not support network discovery
|
||||
discovery:
|
||||
status: exempt
|
||||
comment: Integration does not support network discovery
|
||||
discovery-update-info: done
|
||||
discovery: done
|
||||
docs-data-update: done
|
||||
docs-examples: done
|
||||
docs-known-limitations: done
|
||||
@@ -60,7 +56,7 @@ rules:
|
||||
entity-device-class: done
|
||||
entity-disabled-by-default: done
|
||||
entity-translations: done
|
||||
exception-translations: done
|
||||
exception-translations: todo
|
||||
icon-translations: done
|
||||
reconfiguration-flow: done
|
||||
repair-issues:
|
||||
|
||||
@@ -12,6 +12,10 @@
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||
},
|
||||
"step": {
|
||||
"discovery_confirm": {
|
||||
"description": "Do you want to add {model} ({host}) to Home Assistant?",
|
||||
"title": "Discovered Indevolt {model}"
|
||||
},
|
||||
"reconfigure": {
|
||||
"data": {
|
||||
"host": "[%key:common::config_flow::data::host%]"
|
||||
|
||||
@@ -60,7 +60,7 @@ rules:
|
||||
entity-device-class: done
|
||||
entity-disabled-by-default: done
|
||||
entity-translations: done
|
||||
exception-translations: done
|
||||
exception-translations: todo
|
||||
icon-translations: done
|
||||
reconfiguration-flow: todo
|
||||
repair-issues: done
|
||||
|
||||
@@ -29,6 +29,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: IsraelRailConfigEntry) -
|
||||
try:
|
||||
await hass.async_add_executor_job(train_schedule.query, start, destination)
|
||||
except Exception as e:
|
||||
# pylint: disable-next=home-assistant-exception-translation-key-missing
|
||||
raise ConfigEntryNotReady(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="request_timeout",
|
||||
|
||||
@@ -7,6 +7,7 @@ import voluptuous as vol
|
||||
|
||||
from homeassistant.const import (
|
||||
CONF_ADDRESS,
|
||||
CONF_CODE,
|
||||
CONF_COMMAND,
|
||||
CONF_NAME,
|
||||
CONF_UNIT_OF_MEASUREMENT,
|
||||
@@ -38,8 +39,6 @@ SERVICE_DELETE_ZWAVE_LOCK_USER_CODE = "delete_zwave_lock_user_code"
|
||||
CONF_PARAMETER = "parameter"
|
||||
CONF_PARAMETERS = "parameters"
|
||||
CONF_USER_NUM = "user_num"
|
||||
# pylint: disable-next=home-assistant-duplicate-const
|
||||
CONF_CODE = "code"
|
||||
CONF_VALUE = "value"
|
||||
CONF_INIT = "init"
|
||||
CONF_ISY = "isy"
|
||||
|
||||
@@ -115,6 +115,7 @@ async def async_attach_trigger(
|
||||
try:
|
||||
trigger_config = TRIGGER_TRIGGER_SCHEMA(trigger_config)
|
||||
except vol.Invalid as err:
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise InvalidDeviceAutomationConfig(f"{err}") from err
|
||||
|
||||
return await trigger.async_attach_trigger(
|
||||
|
||||
@@ -66,7 +66,7 @@ rules:
|
||||
entity-device-class: done
|
||||
entity-disabled-by-default: done
|
||||
entity-translations: done
|
||||
exception-translations: done
|
||||
exception-translations: todo
|
||||
icon-translations: done
|
||||
reconfiguration-flow: todo
|
||||
repair-issues: todo
|
||||
|
||||
@@ -105,6 +105,7 @@ class ThinQEntity(CoordinatorEntity[DeviceDataUpdateCoordinator]):
|
||||
except ThinQAPIException as exc:
|
||||
if on_fail_method:
|
||||
on_fail_method()
|
||||
# pylint: disable-next=home-assistant-exception-message-with-translation
|
||||
raise ServiceValidationError(
|
||||
exc.message, translation_domain=DOMAIN, translation_key=exc.code
|
||||
) from exc
|
||||
|
||||
@@ -44,8 +44,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: LiebherrConfigEntry) ->
|
||||
try:
|
||||
devices = await client.get_devices()
|
||||
except LiebherrAuthenticationError as err:
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise ConfigEntryAuthFailed("Invalid API key") from err
|
||||
except LiebherrConnectionError as err:
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise ConfigEntryNotReady(f"Failed to connect to Liebherr API: {err}") from err
|
||||
|
||||
# Create a coordinator for each device (may be empty if no devices)
|
||||
|
||||
@@ -58,8 +58,10 @@ class LiebherrCoordinator(DataUpdateCoordinator[DeviceState]):
|
||||
try:
|
||||
await self.client.get_device(self.device_id)
|
||||
except LiebherrAuthenticationError as err:
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise ConfigEntryAuthFailed("Invalid API key") from err
|
||||
except LiebherrConnectionError as err:
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise ConfigEntryNotReady(
|
||||
f"Failed to connect to device {self.device_id}: {err}"
|
||||
) from err
|
||||
@@ -69,12 +71,15 @@ class LiebherrCoordinator(DataUpdateCoordinator[DeviceState]):
|
||||
try:
|
||||
return await self.client.get_device_state(self.device_id)
|
||||
except LiebherrAuthenticationError as err:
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise ConfigEntryAuthFailed("API key is no longer valid") from err
|
||||
except LiebherrTimeoutError as err:
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise UpdateFailed(
|
||||
f"Timeout communicating with device {self.device_id}"
|
||||
) from err
|
||||
except LiebherrConnectionError as err:
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise UpdateFailed(
|
||||
f"Error communicating with device {self.device_id}"
|
||||
) from err
|
||||
|
||||
@@ -15,6 +15,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up Local file from a config entry."""
|
||||
file_path: str = entry.options[CONF_FILE_PATH]
|
||||
if not await hass.async_add_executor_job(check_file_path_access, file_path):
|
||||
# pylint: disable-next=home-assistant-exception-translation-key-missing
|
||||
raise ConfigEntryError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="not_readable_path",
|
||||
|
||||
@@ -77,6 +77,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: LunatoneConfigEntry) ->
|
||||
await coordinator_info.async_config_entry_first_refresh()
|
||||
|
||||
if info_api.data is None or info_api.serial_number is None:
|
||||
# pylint: disable-next=home-assistant-exception-translation-key-missing
|
||||
raise ConfigEntryError(
|
||||
translation_domain=DOMAIN, translation_key="missing_device_info"
|
||||
)
|
||||
|
||||
@@ -422,6 +422,7 @@ class ManualAlarm(AlarmControlPanelEntity, RestoreEntity):
|
||||
},
|
||||
)
|
||||
|
||||
# pylint: disable-next=home-assistant-exception-message-with-translation
|
||||
raise ServiceValidationError(
|
||||
"Invalid alarm code provided",
|
||||
translation_domain=DOMAIN,
|
||||
|
||||
@@ -165,6 +165,7 @@ class MieleFan(MieleEntity, FanEntity):
|
||||
try:
|
||||
await self.api.send_action(self._device_id, {POWER_ON: True})
|
||||
except ClientResponseError as ex:
|
||||
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="set_state_error",
|
||||
@@ -182,6 +183,7 @@ class MieleFan(MieleEntity, FanEntity):
|
||||
try:
|
||||
await self.api.send_action(self._device_id, {POWER_OFF: True})
|
||||
except ClientResponseError as ex:
|
||||
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="set_state_error",
|
||||
|
||||
@@ -60,7 +60,7 @@ rules:
|
||||
entity-device-class: done
|
||||
entity-disabled-by-default: done
|
||||
entity-translations: done
|
||||
exception-translations: done
|
||||
exception-translations: todo
|
||||
icon-translations: done
|
||||
reconfiguration-flow: todo
|
||||
repair-issues:
|
||||
|
||||
@@ -291,12 +291,13 @@ async def async_check_config_schema(
|
||||
message = conf_util.format_schema_error(
|
||||
hass, exc, domain, config, integration.documentation
|
||||
)
|
||||
# pylint: disable-next=home-assistant-exception-message-with-translation
|
||||
raise ServiceValidationError(
|
||||
message,
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="invalid_platform_config",
|
||||
translation_key="invalid_platform_config_message",
|
||||
translation_placeholders={
|
||||
"domain": domain,
|
||||
"message": message,
|
||||
},
|
||||
) from exc
|
||||
|
||||
|
||||
@@ -156,8 +156,8 @@ async def async_publish(
|
||||
) -> None:
|
||||
"""Publish message to a MQTT topic."""
|
||||
if not mqtt_config_entry_enabled(hass):
|
||||
# pylint: disable-next=home-assistant-exception-message-with-translation
|
||||
raise HomeAssistantError(
|
||||
f"Cannot publish to topic '{topic}', MQTT is not enabled",
|
||||
translation_key="mqtt_not_setup_cannot_publish",
|
||||
translation_domain=DOMAIN,
|
||||
translation_placeholders={"topic": topic},
|
||||
@@ -281,17 +281,17 @@ def async_subscribe_internal(
|
||||
try:
|
||||
mqtt_data = hass.data[DATA_MQTT]
|
||||
except KeyError as exc:
|
||||
# pylint: disable-next=home-assistant-exception-message-with-translation
|
||||
raise HomeAssistantError(
|
||||
f"Cannot subscribe to topic '{topic}', make sure MQTT is set up correctly",
|
||||
translation_key="mqtt_not_setup_cannot_subscribe",
|
||||
translation_domain=DOMAIN,
|
||||
translation_placeholders={"topic": topic},
|
||||
) from exc
|
||||
client = mqtt_data.client
|
||||
if not mqtt_config_entry_enabled(hass):
|
||||
# pylint: disable-next=home-assistant-exception-message-with-translation
|
||||
raise HomeAssistantError(
|
||||
f"Cannot subscribe to topic '{topic}', MQTT is not enabled",
|
||||
translation_key="mqtt_not_setup_cannot_subscribe",
|
||||
translation_key="mqtt_not_enabled_cannot_subscribe",
|
||||
translation_domain=DOMAIN,
|
||||
translation_placeholders={"topic": topic},
|
||||
)
|
||||
|
||||
@@ -73,9 +73,8 @@ class SubscriptionID:
|
||||
|
||||
subscription_id = self._next_id
|
||||
if subscription_id > MAX_28BIT:
|
||||
# pylint: disable-next=home-assistant-exception-message-with-translation
|
||||
raise HomeAssistantError(
|
||||
"MQTT Subscription ID limit reached. "
|
||||
"Cannot generate more IDs to subscribe",
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="mqtt_max_subscription_id_reached",
|
||||
)
|
||||
|
||||
@@ -11,29 +11,27 @@ publish:
|
||||
example: "The temperature is {{ states('sensor.temperature') }}"
|
||||
selector:
|
||||
template:
|
||||
publish_options:
|
||||
collapsed: true
|
||||
fields:
|
||||
evaluate_payload:
|
||||
default: false
|
||||
selector:
|
||||
boolean:
|
||||
qos:
|
||||
default: "0"
|
||||
selector:
|
||||
select:
|
||||
options:
|
||||
- "0"
|
||||
- "1"
|
||||
- "2"
|
||||
retain:
|
||||
default: false
|
||||
selector:
|
||||
boolean:
|
||||
message_expiry_interval:
|
||||
selector:
|
||||
duration:
|
||||
enable_day: true
|
||||
evaluate_payload:
|
||||
default: false
|
||||
selector:
|
||||
boolean:
|
||||
qos:
|
||||
default: 0
|
||||
selector:
|
||||
select:
|
||||
options:
|
||||
- "0"
|
||||
- "1"
|
||||
- "2"
|
||||
retain:
|
||||
default: false
|
||||
selector:
|
||||
boolean:
|
||||
|
||||
message_expiry_interval:
|
||||
selector:
|
||||
duration:
|
||||
enable_day: true
|
||||
dump:
|
||||
fields:
|
||||
topic:
|
||||
|
||||
@@ -1090,8 +1090,8 @@
|
||||
"command_template_error": {
|
||||
"message": "Parsing template `{command_template}` for entity `{entity_id}` failed with error: {error}."
|
||||
},
|
||||
"invalid_platform_config": {
|
||||
"message": "Reloading YAML config for manually configured MQTT `{domain}` item failed. See logs for more details."
|
||||
"invalid_platform_config_message": {
|
||||
"message": "Reloading YAML config for manually configured MQTT `{domain}` item failed. Message: {message}"
|
||||
},
|
||||
"invalid_publish_topic": {
|
||||
"message": "Unable to publish: topic template `{topic_template}` produced an invalid topic `{topic}` after rendering ({error})"
|
||||
@@ -1105,6 +1105,9 @@
|
||||
"mqtt_message_expiry_interval_not_supported": {
|
||||
"message": "Publishing to topic {topic} with a Message Expiry Interval is not supported for protocol version {protocol}."
|
||||
},
|
||||
"mqtt_not_enabled_cannot_subscribe": {
|
||||
"message": "Cannot subscribe to topic \"{topic}\" because MQTT is not enabled, make sure MQTT is set up correctly."
|
||||
},
|
||||
"mqtt_not_setup_cannot_publish": {
|
||||
"message": "Cannot publish to topic \"{topic}\", make sure MQTT is set up correctly."
|
||||
},
|
||||
@@ -1575,12 +1578,7 @@
|
||||
"name": "Topic"
|
||||
}
|
||||
},
|
||||
"name": "Publish",
|
||||
"sections": {
|
||||
"publish_options": {
|
||||
"name": "Publish options"
|
||||
}
|
||||
}
|
||||
"name": "Publish"
|
||||
},
|
||||
"reload": {
|
||||
"description": "Reloads MQTT entities from the YAML-configuration.",
|
||||
|
||||
@@ -50,6 +50,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: NASwebConfigEntry) -> bo
|
||||
)
|
||||
if not await webio_api.refresh_device_info():
|
||||
_LOGGER.error("[%s] Refresh device info failed", entry.data[CONF_HOST])
|
||||
# pylint: disable-next=home-assistant-exception-translation-key-domain-mismatch
|
||||
raise ConfigEntryError(
|
||||
translation_key="config_entry_error_internal_error",
|
||||
translation_placeholders={"support_email": SUPPORT_EMAIL},
|
||||
@@ -57,6 +58,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: NASwebConfigEntry) -> bo
|
||||
webio_serial = webio_api.get_serial_number()
|
||||
if webio_serial is None:
|
||||
_LOGGER.error("[%s] Serial number not available", entry.data[CONF_HOST])
|
||||
# pylint: disable-next=home-assistant-exception-translation-key-domain-mismatch
|
||||
raise ConfigEntryError(
|
||||
translation_key="config_entry_error_internal_error",
|
||||
translation_placeholders={"support_email": SUPPORT_EMAIL},
|
||||
@@ -65,6 +67,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: NASwebConfigEntry) -> bo
|
||||
_LOGGER.error(
|
||||
"[%s] Serial number doesn't match config entry", entry.data[CONF_HOST]
|
||||
)
|
||||
# pylint: disable-next=home-assistant-exception-translation-key-domain-mismatch
|
||||
raise ConfigEntryError(translation_key="config_entry_error_serial_mismatch")
|
||||
|
||||
coordinator = NASwebCoordinator(
|
||||
@@ -76,12 +79,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: NASwebConfigEntry) -> bo
|
||||
webhook_url = nasweb_data.get_webhook_url(hass)
|
||||
if not await webio_api.status_subscription(webhook_url, True):
|
||||
_LOGGER.error("Failed to subscribe for status updates from webio")
|
||||
# pylint: disable-next=home-assistant-exception-translation-key-domain-mismatch
|
||||
raise ConfigEntryError(
|
||||
translation_key="config_entry_error_internal_error",
|
||||
translation_placeholders={"support_email": SUPPORT_EMAIL},
|
||||
)
|
||||
if not await nasweb_data.notify_coordinator.check_connection(webio_serial):
|
||||
_LOGGER.error("Did not receive status from device")
|
||||
# pylint: disable-next=home-assistant-exception-translation-key-domain-mismatch
|
||||
raise ConfigEntryError(
|
||||
translation_key="config_entry_error_no_status_update",
|
||||
translation_placeholders={"support_email": SUPPORT_EMAIL},
|
||||
@@ -91,10 +96,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: NASwebConfigEntry) -> bo
|
||||
f"[{entry.data[CONF_HOST]}] Check connection reached timeout"
|
||||
) from error
|
||||
except AuthError as error:
|
||||
# pylint: disable-next=home-assistant-exception-translation-key-domain-mismatch
|
||||
raise ConfigEntryError(
|
||||
translation_key="config_entry_error_invalid_authentication"
|
||||
) from error
|
||||
except NoURLAvailableError as error:
|
||||
# pylint: disable-next=home-assistant-exception-translation-key-domain-mismatch
|
||||
raise ConfigEntryError(
|
||||
translation_key="config_entry_error_missing_internal_url"
|
||||
) from error
|
||||
|
||||
@@ -66,7 +66,7 @@ rules:
|
||||
entity-translations:
|
||||
status: exempt
|
||||
comment: Entities use device name as entity name.
|
||||
exception-translations: done
|
||||
exception-translations: todo
|
||||
icon-translations:
|
||||
status: exempt
|
||||
comment: No entity icons are used.
|
||||
|
||||
@@ -160,8 +160,8 @@ class NFAndroidTVNotificationService(BaseNotificationService):
|
||||
auth=imagedata.get(ATTR_IMAGE_AUTH),
|
||||
)
|
||||
else:
|
||||
# pylint: disable-next=home-assistant-exception-message-with-translation
|
||||
raise ServiceValidationError(
|
||||
"Invalid image provided",
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="invalid_notification_image",
|
||||
translation_placeholders={"type": type(imagedata).__name__},
|
||||
@@ -182,8 +182,8 @@ class NFAndroidTVNotificationService(BaseNotificationService):
|
||||
auth=icondata.get(ATTR_ICON_AUTH),
|
||||
)
|
||||
else:
|
||||
# pylint: disable-next=home-assistant-exception-message-with-translation
|
||||
raise ServiceValidationError(
|
||||
"Invalid Icon provided",
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="invalid_notification_icon",
|
||||
translation_placeholders={"type": type(icondata).__name__},
|
||||
|
||||
@@ -52,6 +52,7 @@ class NintendoUpdateCoordinator(DataUpdateCoordinator[None]):
|
||||
try:
|
||||
return await self.api.update()
|
||||
except InvalidOAuthConfigurationException as err:
|
||||
# pylint: disable-next=home-assistant-exception-translation-key-missing
|
||||
raise ConfigEntryError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="invalid_auth",
|
||||
|
||||
@@ -57,7 +57,7 @@ rules:
|
||||
entity-device-class: done
|
||||
entity-disabled-by-default: done
|
||||
entity-translations: done
|
||||
exception-translations: done
|
||||
exception-translations: todo
|
||||
icon-translations:
|
||||
status: exempt
|
||||
comment: |
|
||||
|
||||
@@ -65,7 +65,7 @@ rules:
|
||||
PR #170135.
|
||||
entity-disabled-by-default: todo
|
||||
entity-translations: todo
|
||||
exception-translations: done
|
||||
exception-translations: todo
|
||||
icon-translations: todo
|
||||
reconfiguration-flow: todo
|
||||
repair-issues:
|
||||
|
||||
@@ -66,7 +66,7 @@ rules:
|
||||
entity-device-class: done
|
||||
entity-disabled-by-default: done
|
||||
entity-translations: done
|
||||
exception-translations: done
|
||||
exception-translations: todo
|
||||
icon-translations: done
|
||||
reconfiguration-flow: done
|
||||
repair-issues: todo
|
||||
|
||||
@@ -81,6 +81,7 @@ def handle_backup_errors[_R, **P](
|
||||
return await func(self, *args, **kwargs)
|
||||
except AuthenticationError as err:
|
||||
self._entry.async_start_reauth(self._hass)
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise BackupAgentError("Authentication error") from err
|
||||
except OneDriveException as err:
|
||||
_LOGGER.error(
|
||||
@@ -89,12 +90,14 @@ def handle_backup_errors[_R, **P](
|
||||
err,
|
||||
)
|
||||
_LOGGER.debug("Full error: %s", err, exc_info=True)
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise BackupAgentError("Backup operation failed") from err
|
||||
except TimeoutError as err:
|
||||
_LOGGER.error(
|
||||
"Error during backup in %s: Timeout",
|
||||
func.__name__,
|
||||
)
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise BackupAgentError("Backup operation timed out") from err
|
||||
|
||||
return wrapper
|
||||
@@ -183,6 +186,7 @@ class OneDriveBackupAgent(BackupAgent):
|
||||
),
|
||||
)
|
||||
except HashMismatchError as err:
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise BackupAgentError(
|
||||
"Hash validation failed, backup file might be corrupt"
|
||||
) from err
|
||||
@@ -292,4 +296,5 @@ class OneDriveBackupAgent(BackupAgent):
|
||||
if backup := metadata_files.get(backup_id):
|
||||
return backup
|
||||
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise BackupNotFound(f"Backup {backup_id} not found")
|
||||
|
||||
@@ -81,6 +81,7 @@ def handle_backup_errors[_R, **P](
|
||||
return await func(self, *args, **kwargs)
|
||||
except AuthenticationError as err:
|
||||
self._entry.async_start_reauth(self._hass)
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise BackupAgentError("Authentication error") from err
|
||||
except OneDriveException as err:
|
||||
_LOGGER.error(
|
||||
@@ -89,12 +90,14 @@ def handle_backup_errors[_R, **P](
|
||||
err,
|
||||
)
|
||||
_LOGGER.debug("Full error: %s", err, exc_info=True)
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise BackupAgentError("Backup operation failed") from err
|
||||
except TimeoutError as err:
|
||||
_LOGGER.error(
|
||||
"Error during backup in %s: Timeout",
|
||||
func.__name__,
|
||||
)
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise BackupAgentError("Backup operation timed out") from err
|
||||
|
||||
return wrapper
|
||||
@@ -177,6 +180,7 @@ class OneDriveBackupAgent(BackupAgent):
|
||||
),
|
||||
)
|
||||
except HashMismatchError as err:
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise BackupAgentError(
|
||||
"Hash validation failed, backup file might be corrupt"
|
||||
) from err
|
||||
@@ -280,4 +284,5 @@ class OneDriveBackupAgent(BackupAgent):
|
||||
if backup := metadata_files.get(backup_id):
|
||||
return backup
|
||||
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise BackupNotFound(f"Backup {backup_id} not found")
|
||||
|
||||
@@ -57,7 +57,7 @@ rules:
|
||||
entity-device-class: done
|
||||
entity-disabled-by-default: done
|
||||
entity-translations: done
|
||||
exception-translations: done
|
||||
exception-translations: todo
|
||||
icon-translations: done
|
||||
reconfiguration-flow:
|
||||
status: exempt
|
||||
|
||||
@@ -64,6 +64,7 @@ class OpenRGBCoordinator(DataUpdateCoordinator[dict[str, Device]]):
|
||||
DEFAULT_CLIENT_NAME,
|
||||
)
|
||||
except CONNECTION_ERRORS as err:
|
||||
# pylint: disable-next=home-assistant-exception-translation-key-missing
|
||||
raise UpdateFailed(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="cannot_connect",
|
||||
|
||||
@@ -111,6 +111,7 @@ class OpowerCoordinator(DataUpdateCoordinator[dict[str, OpowerData]]):
|
||||
raise ConfigEntryAuthFailed from err
|
||||
except CannotConnect as err:
|
||||
_LOGGER.error("Error during login: %s", err)
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise UpdateFailed(f"Error during login: {err}") from err
|
||||
|
||||
try:
|
||||
|
||||
@@ -65,7 +65,7 @@ rules:
|
||||
entity-device-class: done
|
||||
entity-disabled-by-default: todo
|
||||
entity-translations: done
|
||||
exception-translations: done
|
||||
exception-translations: todo
|
||||
icon-translations: done
|
||||
reconfiguration-flow: todo
|
||||
repair-issues:
|
||||
|
||||
@@ -48,10 +48,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: PeblarConfigEntry) -> bo
|
||||
system_information = await peblar.system_information()
|
||||
api = await peblar.rest_api(enable=True, access_mode=AccessMode.READ_WRITE)
|
||||
except PeblarConnectionError as err:
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise ConfigEntryNotReady("Could not connect to Peblar charger") from err
|
||||
except PeblarAuthenticationError as err:
|
||||
raise ConfigEntryAuthFailed from err
|
||||
except PeblarError as err:
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise ConfigEntryNotReady(
|
||||
"Unknown error occurred while connecting to Peblar charger"
|
||||
) from err
|
||||
|
||||
@@ -64,15 +64,18 @@ async def async_setup_entry(hass: HomeAssistant, entry: PooldoseConfigEntry) ->
|
||||
try:
|
||||
client_status = await client.connect()
|
||||
except TimeoutError as err:
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise ConfigEntryNotReady(
|
||||
f"Timeout connecting to PoolDose device: {err}"
|
||||
) from err
|
||||
except (ConnectionError, OSError) as err:
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise ConfigEntryNotReady(
|
||||
f"Failed to connect to PoolDose device: {err}"
|
||||
) from err
|
||||
|
||||
if client_status != RequestStatus.SUCCESS:
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise ConfigEntryNotReady(
|
||||
f"Failed to create PoolDose client while initialization: {client_status}"
|
||||
)
|
||||
|
||||
@@ -49,18 +49,22 @@ class PooldoseCoordinator(DataUpdateCoordinator[StructuredValuesDict]):
|
||||
try:
|
||||
status, instant_values = await self.client.instant_values_structured()
|
||||
except TimeoutError as err:
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise UpdateFailed(
|
||||
f"Timeout fetching data from PoolDose device: {err}"
|
||||
) from err
|
||||
except (ConnectionError, OSError) as err:
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise UpdateFailed(
|
||||
f"Failed to connect to PoolDose device while fetching data: {err}"
|
||||
) from err
|
||||
|
||||
if status != RequestStatus.SUCCESS:
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise UpdateFailed(f"API returned status: {status}")
|
||||
|
||||
if not instant_values:
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise UpdateFailed("No data received from API")
|
||||
|
||||
_LOGGER.debug("Instant values structured: %s", instant_values)
|
||||
|
||||
@@ -58,12 +58,14 @@ class PTDevicesCoordinator(DataUpdateCoordinator[PTDevicesResponseData]):
|
||||
try:
|
||||
data = await self.interface.get_data()
|
||||
except aioptdevices.PTDevicesRequestError as err:
|
||||
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
|
||||
raise UpdateFailed(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="cannot_connect",
|
||||
translation_placeholders={"error": repr(err)},
|
||||
) from err
|
||||
except aioptdevices.PTDevicesUnauthorizedError as err:
|
||||
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
|
||||
raise UpdateFailed(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="invalid_access_token",
|
||||
|
||||
@@ -52,7 +52,7 @@ rules:
|
||||
entity-device-class: done
|
||||
entity-disabled-by-default: done
|
||||
entity-translations: done
|
||||
exception-translations: done
|
||||
exception-translations: todo
|
||||
icon-translations: done
|
||||
reconfiguration-flow: done
|
||||
repair-issues: done
|
||||
|
||||
@@ -74,6 +74,7 @@ async def async_setup_entry(
|
||||
await host.async_init()
|
||||
except (UserNotAdmin, CredentialsInvalidError, PasswordIncompatible) as err:
|
||||
await host.stop()
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise ConfigEntryAuthFailed(err) from err
|
||||
except (
|
||||
ReolinkException,
|
||||
|
||||
@@ -88,12 +88,15 @@ class ReolinkDeviceCoordinator(ReolinkCoordinator):
|
||||
self._host.credential_errors += 1
|
||||
if self._host.credential_errors >= NUM_CRED_ERRORS:
|
||||
await self._host.stop()
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise ConfigEntryAuthFailed(err) from err
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise UpdateFailed(str(err)) from err
|
||||
except LoginPrivacyModeError:
|
||||
pass # HTTP API is shutdown when privacy mode is active
|
||||
except ReolinkError as err:
|
||||
self._host.credential_errors = 0
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise UpdateFailed(str(err)) from err
|
||||
|
||||
self._host.credential_errors = 0
|
||||
@@ -167,6 +170,7 @@ class ReolinkFirmwareCoordinator(ReolinkCoordinator):
|
||||
)
|
||||
return
|
||||
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise UpdateFailed(
|
||||
"Error checking Reolink firmware update"
|
||||
f" from {self._host.api.nvr_name}, "
|
||||
|
||||
@@ -171,6 +171,7 @@ class ReolinkHost:
|
||||
translation_placeholders={"name": self._config_entry.title},
|
||||
)
|
||||
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise PasswordIncompatible(
|
||||
"Reolink password contains incompatible special character or "
|
||||
"is too long, please change the password to only contain characters: "
|
||||
@@ -192,9 +193,11 @@ class ReolinkHost:
|
||||
await self._api.get_host_data()
|
||||
|
||||
if self._api.mac_address is None:
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise ReolinkSetupException("Could not get mac address")
|
||||
|
||||
if not self._api.is_admin:
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise UserNotAdmin(
|
||||
f"User '{self._api.username}' has authorization level "
|
||||
f"'{self._api.user_level}', only admin users can change camera settings"
|
||||
@@ -739,6 +742,7 @@ class ReolinkHost:
|
||||
self._base_url = get_url(self._hass, prefer_external=True)
|
||||
except NoURLAvailableError as err:
|
||||
self.unregister_webhook()
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise ReolinkWebhookException(
|
||||
f"Error registering URL for webhook {event_id}: "
|
||||
"HomeAssistant URL is not available"
|
||||
|
||||
@@ -63,6 +63,7 @@ class ReolinkVODMediaSource(MediaSource):
|
||||
if item.identifier is not None:
|
||||
identifier = item.identifier.split("|", 6)
|
||||
if identifier[0] != "FILE":
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise Unresolvable(f"Unknown media item '{item.identifier}'.")
|
||||
|
||||
_, config_entry_id, channel_str, stream_res, filename, start_time, end_time = (
|
||||
@@ -172,6 +173,7 @@ class ReolinkVODMediaSource(MediaSource):
|
||||
event,
|
||||
)
|
||||
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise Unresolvable(f"Unknown media item '{item.identifier}' during browsing.")
|
||||
|
||||
async def _async_generate_root(self) -> BrowseMediaSource:
|
||||
|
||||
@@ -62,6 +62,7 @@ def get_host(hass: HomeAssistant, config_entry_id: str) -> ReolinkHost:
|
||||
config_entry_id
|
||||
)
|
||||
if config_entry is None:
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise Unresolvable(
|
||||
f"Could not find Reolink config entry id '{config_entry_id}'."
|
||||
)
|
||||
|
||||
@@ -95,6 +95,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: RoborockConfigEntry) ->
|
||||
prefer_cache=False,
|
||||
)
|
||||
except RoborockInvalidCredentials as err:
|
||||
# pylint: disable-next=home-assistant-exception-message-with-translation
|
||||
raise ConfigEntryAuthFailed(
|
||||
"Invalid credentials",
|
||||
translation_domain=DOMAIN,
|
||||
@@ -117,6 +118,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: RoborockConfigEntry) ->
|
||||
) from err
|
||||
except RoborockException as err:
|
||||
_LOGGER.debug("Failed to get Roborock home data: %s", err)
|
||||
# pylint: disable-next=home-assistant-exception-message-with-translation
|
||||
raise ConfigEntryNotReady(
|
||||
"Failed to get Roborock home data",
|
||||
translation_domain=DOMAIN,
|
||||
@@ -176,6 +178,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: RoborockConfigEntry) ->
|
||||
len(v1_coords) + len(a01_coords) + len(b01_q7_coords) + len(b01_q10_coords) == 0
|
||||
and enabled_devices
|
||||
):
|
||||
# pylint: disable-next=home-assistant-exception-message-with-translation
|
||||
raise ConfigEntryNotReady(
|
||||
"No devices were able to successfully setup",
|
||||
translation_domain=DOMAIN,
|
||||
|
||||
@@ -161,6 +161,7 @@ class RoborockDataUpdateCoordinator(DataUpdateCoordinator[DeviceState | None]):
|
||||
_LOGGER.info("Home discovery skipped while device is busy/cleaning")
|
||||
except RoborockException as err:
|
||||
_LOGGER.debug("Failed to get maps: %s", err)
|
||||
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
|
||||
raise UpdateFailed(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="map_failure",
|
||||
|
||||
@@ -62,7 +62,7 @@ rules:
|
||||
status: exempt
|
||||
comment: There are no noisy entities.
|
||||
entity-translations: done
|
||||
exception-translations: done
|
||||
exception-translations: todo
|
||||
icon-translations: todo
|
||||
reconfiguration-flow: todo
|
||||
repair-issues: done
|
||||
|
||||
@@ -43,6 +43,7 @@ async def async_validate_trigger_config(
|
||||
device = async_get_device_entry_by_device_id(hass, device_id)
|
||||
async_get_client_by_device_entry(hass, device)
|
||||
except ValueError as err:
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise InvalidDeviceAutomationConfig(err) from err
|
||||
|
||||
return config
|
||||
|
||||
@@ -39,6 +39,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: LeilSaunaConfigEntry) ->
|
||||
try:
|
||||
client = await SaunumClient.create(host)
|
||||
except (SaunumConnectionError, SaunumTimeoutError) as exc:
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise ConfigEntryNotReady(f"Error connecting to {host}: {exc}") from exc
|
||||
|
||||
entry.async_on_unload(client.async_close)
|
||||
|
||||
@@ -111,7 +111,7 @@ rules:
|
||||
status: exempt
|
||||
comment: |
|
||||
This integration does not have entities.
|
||||
exception-translations: done
|
||||
exception-translations: todo
|
||||
icon-translations:
|
||||
status: exempt
|
||||
comment: |
|
||||
|
||||
@@ -401,6 +401,7 @@ class ShellyBlockCoordinator(ShellyCoordinatorBase[BlockDevice]):
|
||||
"""Fetch data."""
|
||||
if self.sleep_period:
|
||||
# Sleeping device, no point polling it, just mark it unavailable
|
||||
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
|
||||
raise UpdateFailed(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="update_error_sleeping_device",
|
||||
@@ -670,6 +671,7 @@ class ShellyRpcCoordinator(ShellyCoordinatorBase[RpcDevice]):
|
||||
|
||||
if self.sleep_period:
|
||||
# Sleeping device, no point polling it, just mark it unavailable
|
||||
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
|
||||
raise UpdateFailed(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="update_error_sleeping_device",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import base64
|
||||
import binascii
|
||||
import contextlib
|
||||
from dataclasses import dataclass
|
||||
import datetime
|
||||
import hashlib
|
||||
@@ -38,6 +39,15 @@ from .utils import get_device_entry_gen
|
||||
CONTENT_TYPE_AUDIO = "audio"
|
||||
CONTENT_TYPE_RADIO = "radio"
|
||||
|
||||
ALLOWED_IMAGE_MIME_TYPES: Final = frozenset(
|
||||
{
|
||||
"image/gif",
|
||||
"image/jpeg",
|
||||
"image/png",
|
||||
"image/webp",
|
||||
}
|
||||
)
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
@@ -102,6 +112,9 @@ class ShellyRpcMediaPlayer(ShellyRpcAttributeEntity, MediaPlayerEntity):
|
||||
_last_media_position: int | None = None
|
||||
_last_media_position_updated_at: datetime.datetime | None = None
|
||||
|
||||
_cached_thumb: str | None = None
|
||||
_cached_thumb_result: tuple[bytes, str] | None = None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: ShellyRpcCoordinator,
|
||||
@@ -215,9 +228,11 @@ class ShellyRpcMediaPlayer(ShellyRpcAttributeEntity, MediaPlayerEntity):
|
||||
@property
|
||||
def media_image_hash(self) -> str | None:
|
||||
"""Hash value for media image."""
|
||||
if (thumb := self._media_meta.get("thumb")) and thumb.startswith("data"):
|
||||
return hashlib.sha256(thumb.encode("utf-8")).hexdigest()[:16]
|
||||
return super().media_image_hash
|
||||
thumb = self._media_meta.get("thumb")
|
||||
if not thumb or self._decode_image_data(thumb) is None:
|
||||
return super().media_image_hash
|
||||
|
||||
return hashlib.sha256(thumb.encode("utf-8")).hexdigest()[:16]
|
||||
|
||||
def _get_updated_media_position(self) -> int | None:
|
||||
"""Return the current playback position and update its timestamp."""
|
||||
@@ -235,15 +250,11 @@ class ShellyRpcMediaPlayer(ShellyRpcAttributeEntity, MediaPlayerEntity):
|
||||
|
||||
async def async_get_media_image(self) -> tuple[bytes | None, str | None]:
|
||||
"""Fetch media image of current playing track."""
|
||||
thumb = self._media_meta["thumb"]
|
||||
try:
|
||||
prefix, image_data = thumb.split(",", 1)
|
||||
image = base64.b64decode(image_data, validate=True)
|
||||
mime = prefix.split(";", 1)[0].rsplit(":", 1)[-1]
|
||||
except binascii.Error, ValueError:
|
||||
thumb = self._media_meta.get("thumb")
|
||||
if not thumb or (result := self._decode_image_data(thumb)) is None:
|
||||
return await super().async_get_media_image()
|
||||
|
||||
return image, mime
|
||||
return result
|
||||
|
||||
@rpc_call
|
||||
async def async_media_play(self) -> None:
|
||||
@@ -434,3 +445,25 @@ class ShellyRpcMediaPlayer(ShellyRpcAttributeEntity, MediaPlayerEntity):
|
||||
translation_key="unsupported_media_type",
|
||||
translation_placeholders={"media_type": str(media_type)},
|
||||
)
|
||||
|
||||
def _decode_image_data(self, thumb: str) -> tuple[bytes, str] | None:
|
||||
"""Return image_bytes and mime_type for a valid image data or None."""
|
||||
if thumb == self._cached_thumb:
|
||||
return self._cached_thumb_result
|
||||
|
||||
result: tuple[bytes, str] | None = None
|
||||
if thumb.startswith("data"):
|
||||
try:
|
||||
prefix, image_data = thumb.split(",", 1)
|
||||
mime = prefix.split(";", 1)[0].rsplit(":", 1)[-1]
|
||||
except IndexError, ValueError:
|
||||
pass
|
||||
else:
|
||||
if mime in ALLOWED_IMAGE_MIME_TYPES:
|
||||
with contextlib.suppress(binascii.Error):
|
||||
result = base64.b64decode(image_data, validate=True), mime
|
||||
|
||||
self._cached_thumb = thumb
|
||||
self._cached_thumb_result = result
|
||||
|
||||
return result
|
||||
|
||||
@@ -344,6 +344,7 @@ class RpcUpdateEntity(ShellyRpcAttributeEntity, UpdateEntity):
|
||||
translation_placeholders={"device": self.coordinator.name},
|
||||
) from err
|
||||
except RpcCallError as err:
|
||||
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="ota_update_rpc_error",
|
||||
|
||||
@@ -69,12 +69,14 @@ class SMADataUpdateCoordinator(DataUpdateCoordinator[SMACoordinatorData]):
|
||||
SmaConnectionException,
|
||||
) as err:
|
||||
await self.async_close_sma_session()
|
||||
# pylint: disable-next=home-assistant-exception-translation-key-missing
|
||||
raise ConfigEntryNotReady(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="cannot_connect",
|
||||
translation_placeholders={"error": repr(err)},
|
||||
) from err
|
||||
except SmaAuthenticationException as err:
|
||||
# pylint: disable-next=home-assistant-exception-translation-key-missing
|
||||
raise ConfigEntryAuthFailed(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="invalid_auth",
|
||||
@@ -89,12 +91,14 @@ class SMADataUpdateCoordinator(DataUpdateCoordinator[SMACoordinatorData]):
|
||||
SmaReadException,
|
||||
SmaConnectionException,
|
||||
) as err:
|
||||
# pylint: disable-next=home-assistant-exception-translation-key-missing
|
||||
raise UpdateFailed(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="cannot_connect",
|
||||
translation_placeholders={"error": repr(err)},
|
||||
) from err
|
||||
except SmaAuthenticationException as err:
|
||||
# pylint: disable-next=home-assistant-exception-translation-key-missing
|
||||
raise ConfigEntryAuthFailed(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="invalid_auth",
|
||||
|
||||
@@ -67,6 +67,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
"cannot_connect": {
|
||||
"message": "Could not connect to SMA device - {error}"
|
||||
},
|
||||
"invalid_auth": {
|
||||
"message": "Invalid authentication for SMA device - {error}"
|
||||
}
|
||||
},
|
||||
"selector": {
|
||||
"group": {
|
||||
"options": {
|
||||
|
||||
@@ -48,7 +48,7 @@ rules:
|
||||
entity-device-class: done
|
||||
entity-disabled-by-default: done
|
||||
entity-translations: done
|
||||
exception-translations: done
|
||||
exception-translations: todo
|
||||
icon-translations: done
|
||||
reconfiguration-flow: todo
|
||||
repair-issues: todo
|
||||
|
||||
@@ -80,6 +80,7 @@ class SnooSwitch(SnooDescriptionEntity, SwitchEntity):
|
||||
True,
|
||||
)
|
||||
except SnooCommandException as err:
|
||||
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="switch_on_failed",
|
||||
@@ -96,6 +97,7 @@ class SnooSwitch(SnooDescriptionEntity, SwitchEntity):
|
||||
False,
|
||||
)
|
||||
except SnooCommandException as err:
|
||||
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="switch_off_failed",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user