mirror of
https://github.com/home-assistant/core.git
synced 2026-03-16 16:02:06 +01:00
Compare commits
148 Commits
config-yam
...
2026.3.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c47e83342 | ||
|
|
e3c6a2184d | ||
|
|
0ba0829350 | ||
|
|
678048e681 | ||
|
|
743eeeae53 | ||
|
|
46555c6d9a | ||
|
|
dbaca0a723 | ||
|
|
9bb2959029 | ||
|
|
0304781fa9 | ||
|
|
e081d28aa4 | ||
|
|
34aa28c72f | ||
|
|
cfa2946db8 | ||
|
|
1b0779347c | ||
|
|
93a281e7af | ||
|
|
6b32e27fd3 | ||
|
|
79928a8c7c | ||
|
|
9146518e13 | ||
|
|
e9c5172f43 | ||
|
|
cce21ad4b9 | ||
|
|
10ec02ca3c | ||
|
|
bdf54491e5 | ||
|
|
0b05d34238 | ||
|
|
4c69a1c5f7 | ||
|
|
6f1f56dcaa | ||
|
|
d0b9991232 | ||
|
|
aacf39be8a | ||
|
|
bf055da82c | ||
|
|
0fb118bcd9 | ||
|
|
954ef7d1f5 | ||
|
|
b091299320 | ||
|
|
52483e18b2 | ||
|
|
57e8683ed7 | ||
|
|
67faace978 | ||
|
|
e4be64fcb1 | ||
|
|
f552b8221f | ||
|
|
55dc5392f9 | ||
|
|
5b93aeae38 | ||
|
|
33610bb1a1 | ||
|
|
6c3cebe413 | ||
|
|
5346895d9b | ||
|
|
05c3f08c6c | ||
|
|
1ce025733d | ||
|
|
1537ea86b8 | ||
|
|
ec137870fa | ||
|
|
816ee7f53e | ||
|
|
6e7eeec827 | ||
|
|
d100477a22 | ||
|
|
98ac6dd2c1 | ||
|
|
6b30969f60 | ||
|
|
e9a6b5d662 | ||
|
|
f95f3f9982 | ||
|
|
3f884a8cd1 | ||
|
|
10f284932e | ||
|
|
e1c4e6dc42 | ||
|
|
0976e7de4e | ||
|
|
ae1012b2f0 | ||
|
|
bb7c4faca5 | ||
|
|
0b1be61336 | ||
|
|
3ec44024a2 | ||
|
|
1200cc5779 | ||
|
|
d632931f74 | ||
|
|
2f9faa53a1 | ||
|
|
718607a758 | ||
|
|
3789156559 | ||
|
|
042ce6f2de | ||
|
|
0a5908002f | ||
|
|
3a5f71e10a | ||
|
|
04e4b05ab0 | ||
|
|
c2c5232899 | ||
|
|
593610094e | ||
|
|
47cb7870ea | ||
|
|
045b626e24 | ||
|
|
bea5468dee | ||
|
|
04fc12cc26 | ||
|
|
fec33ad42b | ||
|
|
07e323f1e9 | ||
|
|
ebe2612713 | ||
|
|
88ca668562 | ||
|
|
1d46ac0b64 | ||
|
|
13a5e6e85f | ||
|
|
d2665f03ff | ||
|
|
80412e4973 | ||
|
|
818d9f774e | ||
|
|
012e78d625 | ||
|
|
74abedbcd2 | ||
|
|
e16fb6b5a5 | ||
|
|
8906e5dcb5 | ||
|
|
10067c208a | ||
|
|
d4143205e9 | ||
|
|
a4da363ff2 | ||
|
|
bc9ae3dad6 | ||
|
|
9e5daaa784 | ||
|
|
ff0a6757cd | ||
|
|
62ffeeccb0 | ||
|
|
1afe00670e | ||
|
|
500ffe8153 | ||
|
|
2cebb28a1b | ||
|
|
80bfba0981 | ||
|
|
882e499375 | ||
|
|
e89aafc8e2 | ||
|
|
66ae5ab543 | ||
|
|
75d39c0b02 | ||
|
|
989133cb16 | ||
|
|
f559f8e014 | ||
|
|
a95207f2ef | ||
|
|
2c28a93ea0 | ||
|
|
3ff97a0820 | ||
|
|
f7a56447ae | ||
|
|
dfd086f253 | ||
|
|
b6a166ce48 | ||
|
|
e93b724ce4 | ||
|
|
d0b25ccc01 | ||
|
|
0a3ef64f28 | ||
|
|
e9ce3ffff9 | ||
|
|
55415b1559 | ||
|
|
0160dbf3a6 | ||
|
|
7dd83b1e8f | ||
|
|
e502f5f249 | ||
|
|
6e93ebc912 | ||
|
|
9a4fdf7f80 | ||
|
|
76d69a5f53 | ||
|
|
ae40c0cf4b | ||
|
|
078647d128 | ||
|
|
8a637c4e5b | ||
|
|
9e9daff26d | ||
|
|
41aeedaa82 | ||
|
|
a8297ae65d | ||
|
|
b7f1171c08 | ||
|
|
226f606cb9 | ||
|
|
9472be39f2 | ||
|
|
67a9e42b19 | ||
|
|
ba1837859f | ||
|
|
4a301eceac | ||
|
|
d138a99e62 | ||
|
|
a431f84dc9 | ||
|
|
aa9534600e | ||
|
|
54fa49e754 | ||
|
|
459b6152f4 | ||
|
|
60c8d997ca | ||
|
|
a598368895 | ||
|
|
2ff1499c48 | ||
|
|
348ddbe124 | ||
|
|
71ed43faf2 | ||
|
|
dc69a90296 | ||
|
|
f5db8e6ba4 | ||
|
|
b82a26ef68 | ||
|
|
0eaaeedf11 | ||
|
|
62e26e53ac |
@@ -34,7 +34,6 @@ base_platforms: &base_platforms
|
||||
- homeassistant/components/humidifier/**
|
||||
- homeassistant/components/image/**
|
||||
- homeassistant/components/image_processing/**
|
||||
- homeassistant/components/infrared/**
|
||||
- homeassistant/components/lawn_mower/**
|
||||
- homeassistant/components/light/**
|
||||
- homeassistant/components/lock/**
|
||||
|
||||
4
.github/workflows/ci.yaml
vendored
4
.github/workflows/ci.yaml
vendored
@@ -40,7 +40,7 @@ env:
|
||||
CACHE_VERSION: 3
|
||||
UV_CACHE_VERSION: 1
|
||||
MYPY_CACHE_VERSION: 1
|
||||
HA_SHORT_VERSION: "2026.4"
|
||||
HA_SHORT_VERSION: "2026.3"
|
||||
DEFAULT_PYTHON: "3.14.2"
|
||||
ALL_PYTHON_VERSIONS: "['3.14.2']"
|
||||
# 10.3 is the oldest supported version
|
||||
@@ -605,7 +605,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Dependency review
|
||||
uses: actions/dependency-review-action@05fe4576374b728f0c523d6a13d64c25081e0803 # v4.8.3
|
||||
uses: actions/dependency-review-action@3c4e3dcb1aa7874d2c16be7d79418e9b7efd6261 # v4.8.2
|
||||
with:
|
||||
license-check: false # We use our own license audit checks
|
||||
|
||||
|
||||
2
.github/workflows/wheels.yml
vendored
2
.github/workflows/wheels.yml
vendored
@@ -209,4 +209,4 @@ jobs:
|
||||
skip-binary: aiohttp;charset-normalizer;grpcio;multidict;SQLAlchemy;propcache;protobuf;pymicro-vad;yarl
|
||||
constraints: "homeassistant/package_constraints.txt"
|
||||
requirements-diff: "requirements_diff.txt"
|
||||
requirements: "requirements_all.txt"
|
||||
requirements: "requirements_all_wheels_${{ matrix.arch }}.txt"
|
||||
|
||||
@@ -289,7 +289,6 @@ homeassistant.components.imgw_pib.*
|
||||
homeassistant.components.immich.*
|
||||
homeassistant.components.incomfort.*
|
||||
homeassistant.components.inels.*
|
||||
homeassistant.components.infrared.*
|
||||
homeassistant.components.input_button.*
|
||||
homeassistant.components.input_select.*
|
||||
homeassistant.components.input_text.*
|
||||
@@ -545,7 +544,6 @@ homeassistant.components.tcp.*
|
||||
homeassistant.components.technove.*
|
||||
homeassistant.components.tedee.*
|
||||
homeassistant.components.telegram_bot.*
|
||||
homeassistant.components.teslemetry.*
|
||||
homeassistant.components.text.*
|
||||
homeassistant.components.thethingsnetwork.*
|
||||
homeassistant.components.threshold.*
|
||||
|
||||
8
CODEOWNERS
generated
8
CODEOWNERS
generated
@@ -401,6 +401,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/dsmr_reader/ @sorted-bits @glodenox @erwindouna
|
||||
/homeassistant/components/duckdns/ @tr4nt0r
|
||||
/tests/components/duckdns/ @tr4nt0r
|
||||
/homeassistant/components/duke_energy/ @hunterjm
|
||||
/tests/components/duke_energy/ @hunterjm
|
||||
/homeassistant/components/duotecno/ @cereal2nd
|
||||
/tests/components/duotecno/ @cereal2nd
|
||||
/homeassistant/components/dwd_weather_warnings/ @runningman84 @stephan192
|
||||
@@ -792,8 +794,6 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/inels/ @epdevlab
|
||||
/homeassistant/components/influxdb/ @mdegat01 @Robbie1221
|
||||
/tests/components/influxdb/ @mdegat01 @Robbie1221
|
||||
/homeassistant/components/infrared/ @home-assistant/core
|
||||
/tests/components/infrared/ @home-assistant/core
|
||||
/homeassistant/components/inkbird/ @bdraco
|
||||
/tests/components/inkbird/ @bdraco
|
||||
/homeassistant/components/input_boolean/ @home-assistant/core
|
||||
@@ -1899,8 +1899,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/withings/ @joostlek
|
||||
/homeassistant/components/wiz/ @sbidy @arturpragacz
|
||||
/tests/components/wiz/ @sbidy @arturpragacz
|
||||
/homeassistant/components/wled/ @frenck @mik-laj
|
||||
/tests/components/wled/ @frenck @mik-laj
|
||||
/homeassistant/components/wled/ @frenck
|
||||
/tests/components/wled/ @frenck
|
||||
/homeassistant/components/wmspro/ @mback2k
|
||||
/tests/components/wmspro/ @mback2k
|
||||
/homeassistant/components/wolflink/ @adamkrol93 @mtielen
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
# Create `config.yaml` For Config Flows
|
||||
|
||||
## Goal
|
||||
Document the persisted config entry and subentry payloads in each integration's `config.yaml` under `config_entry`, using selector-based field metadata that is consistent with Home Assistant selectors.
|
||||
|
||||
The output must describe what is **actually stored** in config entries (`data`, `options`, and `subentries`), not just what is shown in forms.
|
||||
|
||||
## Required Files Per Integration
|
||||
For each integration with `"config_flow": true` in `manifest.json`, inspect:
|
||||
|
||||
1. `config_flow.py`
|
||||
2. `__init__.py` (for migration and runtime usage confirmation)
|
||||
3. `const.py` (for `CONF_*`, version constants, and aliases)
|
||||
4. `strings.json` / translations only as fallback for field names not inferable from code
|
||||
5. Existing `config.yaml` (target file)
|
||||
|
||||
## Version Rules
|
||||
1. Default version is `major: 1`, `minor: 1` when no explicit version is defined.
|
||||
2. Read `VERSION` and `MINOR_VERSION` from the config flow class.
|
||||
3. If the class uses constants (for example `CONFIG_FLOW_VERSION`), resolve them from `const.py`.
|
||||
4. Document all known config-entry versions when code clearly supports multiple versions:
|
||||
- Current version from config flow class.
|
||||
- Historical versions from explicit migration branches (for example `async_migrate_entry` checks in `__init__.py`).
|
||||
5. Apply the same version logic to subentries (default `1.1` when unspecified).
|
||||
|
||||
## Storage Target Rules (Critical)
|
||||
Always determine where values are persisted:
|
||||
|
||||
1. `ConfigFlow.async_create_entry(data=...)` -> persisted in config entry `data`.
|
||||
2. `ConfigFlow.async_create_entry(..., options=...)` -> persisted in config entry `options`.
|
||||
3. `OptionsFlow.async_create_entry(data=...)` -> persisted in config entry `options`.
|
||||
4. `SchemaConfigFlowHandler` (default implementation):
|
||||
- Config flow values are stored in `options`.
|
||||
- Config entry `data` is empty.
|
||||
- Exception: class overrides `async_create_entry` (then follow override).
|
||||
5. `async_update_reload_and_abort(..., data=..., options=...)` updates existing entry payloads and must align with documented fields.
|
||||
|
||||
## Form-To-Storage Mapping Rules
|
||||
When `user_input` is stored directly, form schema must be mirrored in `config.yaml`.
|
||||
|
||||
### Config Flow
|
||||
If step logic returns `async_create_entry(data=user_input)`:
|
||||
1. Find the matching `async_show_form(..., data_schema=...)` for that step.
|
||||
2. Extract all schema keys.
|
||||
3. Add those keys to `config_entry.versions[*].data.fields`.
|
||||
|
||||
### Options Flow
|
||||
If options step returns `async_create_entry(data=user_input)`:
|
||||
1. Extract step schema keys.
|
||||
2. Add those keys to `config_entry.versions[*].options.fields`.
|
||||
|
||||
### Dict Payloads
|
||||
If `async_create_entry(data={...})` (or via a local dict variable/function that clearly returns a dict):
|
||||
1. Extract literal keys.
|
||||
2. Add keys to the relevant persisted section (`data` or `options`).
|
||||
|
||||
## Helper Flow Rules
|
||||
### `register_discovery_flow(...)`
|
||||
Creates entry with `data={}` by default. Keep data empty unless integration overrides flow behavior elsewhere.
|
||||
|
||||
### `register_webhook_flow(...)`
|
||||
Creates entry with:
|
||||
1. `webhook_id`
|
||||
2. `cloudhook`
|
||||
|
||||
These must be documented in `config_entry.versions[*].data.fields`.
|
||||
|
||||
### `AbstractOAuth2FlowHandler`
|
||||
Default OAuth payload includes:
|
||||
1. `auth_implementation`
|
||||
2. `token`
|
||||
|
||||
If integration overrides `async_oauth_create_entry` and adds additional stored keys, include those too.
|
||||
|
||||
## Subentry Rules
|
||||
1. Find `async_get_supported_subentry_types(...)` mapping and subentry flow classes (`ConfigSubentryFlow`).
|
||||
2. For each `subentry_type`, document under:
|
||||
- `config_entry.subentries.<subentry_type>.versions`
|
||||
3. Extract persisted subentry payload keys from:
|
||||
- `async_create_entry(data=...)` in subentry flow
|
||||
- direct subentry update calls with explicit data payloads
|
||||
4. Apply required/default/selector extraction exactly as for main config/option flows.
|
||||
|
||||
## Field Metadata Rules
|
||||
Each field entry should include:
|
||||
1. `required` (true/false)
|
||||
2. `selector` (valid HA selector structure)
|
||||
3. Optional `default` and `example` when directly known from code
|
||||
|
||||
### Required Flag
|
||||
1. `vol.Required(...)` -> `required: true`
|
||||
2. `vol.Optional(...)` -> `required: false`
|
||||
3. Literal dict payloads without schema context -> `required: true` unless clearly optional in code path
|
||||
|
||||
### Selector Mapping
|
||||
Use explicit selector calls when present (for example `TextSelector`, `NumberSelector`, `BooleanSelector`, `LocationSelector`, `SelectSelector`, etc).
|
||||
|
||||
If schema uses plain validators:
|
||||
1. `bool` / `cv.boolean` -> `selector: { boolean: {} }`
|
||||
2. numeric validators -> `selector: { number: {} }`
|
||||
3. `vol.In(...)` / constrained choices -> `selector: { select: {} }`
|
||||
4. unknown / string-like -> `selector: { text: {} }`
|
||||
5. structured blobs (for example OAuth `token`) -> `selector: { object: {} }`
|
||||
|
||||
## Validation Checklist (Per Integration)
|
||||
1. `config.yaml` exists when `manifest.json` has `config_flow: true`.
|
||||
2. `config_entry.versions` contains correct version entries.
|
||||
3. Documented fields exactly match persisted payloads (`data` vs `options`).
|
||||
4. `required` and selector format are valid.
|
||||
5. `subentries` are documented when supported.
|
||||
6. No placeholder empty blocks where code stores actual fields.
|
||||
|
||||
## Final QA Commands
|
||||
Run after updates:
|
||||
|
||||
```bash
|
||||
python -m script.hassfest -p config_entry --action validate
|
||||
ruff check script/hassfest/config_entry.py
|
||||
```
|
||||
|
||||
## High-Risk Pitfalls
|
||||
1. Assuming fields in forms are always stored in `data` (wrong for `SchemaConfigFlowHandler`).
|
||||
2. Missing fields when `data=user_input` is used with a non-empty schema.
|
||||
3. Skipping helper flows (`register_webhook_flow`, OAuth2 base handler behavior).
|
||||
4. Ignoring options/subentry flows that store separate payloads.
|
||||
5. Using placeholders instead of integration-specific field definitions.
|
||||
@@ -10,7 +10,6 @@ coverage:
|
||||
target: auto
|
||||
threshold: 1
|
||||
paths:
|
||||
- homeassistant/components/*/backup.py
|
||||
- homeassistant/components/*/config_flow.py
|
||||
- homeassistant/components/*/device_action.py
|
||||
- homeassistant/components/*/device_condition.py
|
||||
@@ -29,7 +28,6 @@ coverage:
|
||||
target: 100
|
||||
threshold: 0
|
||||
paths:
|
||||
- homeassistant/components/*/backup.py
|
||||
- homeassistant/components/*/config_flow.py
|
||||
- homeassistant/components/*/device_action.py
|
||||
- homeassistant/components/*/device_condition.py
|
||||
|
||||
@@ -70,7 +70,7 @@ from .const import (
|
||||
SIGNAL_BOOTSTRAP_INTEGRATIONS,
|
||||
)
|
||||
from .core_config import async_process_ha_core_config
|
||||
from .exceptions import HomeAssistantError
|
||||
from .exceptions import HomeAssistantError, UnsupportedStorageVersionError
|
||||
from .helpers import (
|
||||
area_registry,
|
||||
category_registry,
|
||||
@@ -239,6 +239,8 @@ DEFAULT_INTEGRATIONS = {
|
||||
}
|
||||
DEFAULT_INTEGRATIONS_RECOVERY_MODE = {
|
||||
# These integrations are set up if recovery mode is activated.
|
||||
"backup",
|
||||
"cloud",
|
||||
"frontend",
|
||||
}
|
||||
DEFAULT_INTEGRATIONS_SUPERVISOR = {
|
||||
@@ -433,32 +435,56 @@ def _init_blocking_io_modules_in_executor() -> None:
|
||||
is_docker_env()
|
||||
|
||||
|
||||
async def async_load_base_functionality(hass: core.HomeAssistant) -> None:
|
||||
"""Load the registries and modules that will do blocking I/O."""
|
||||
async def async_load_base_functionality(hass: core.HomeAssistant) -> bool:
|
||||
"""Load the registries and modules that will do blocking I/O.
|
||||
|
||||
Return whether loading succeeded.
|
||||
"""
|
||||
if DATA_REGISTRIES_LOADED in hass.data:
|
||||
return
|
||||
return True
|
||||
|
||||
hass.data[DATA_REGISTRIES_LOADED] = None
|
||||
entity.async_setup(hass)
|
||||
frame.async_setup(hass)
|
||||
template.async_setup(hass)
|
||||
translation.async_setup(hass)
|
||||
await asyncio.gather(
|
||||
create_eager_task(get_internal_store_manager(hass).async_initialize()),
|
||||
create_eager_task(area_registry.async_load(hass)),
|
||||
create_eager_task(category_registry.async_load(hass)),
|
||||
create_eager_task(device_registry.async_load(hass)),
|
||||
create_eager_task(entity_registry.async_load(hass)),
|
||||
create_eager_task(floor_registry.async_load(hass)),
|
||||
create_eager_task(issue_registry.async_load(hass)),
|
||||
create_eager_task(label_registry.async_load(hass)),
|
||||
hass.async_add_executor_job(_init_blocking_io_modules_in_executor),
|
||||
create_eager_task(template.async_load_custom_templates(hass)),
|
||||
create_eager_task(restore_state.async_load(hass)),
|
||||
create_eager_task(hass.config_entries.async_initialize()),
|
||||
create_eager_task(async_get_system_info(hass)),
|
||||
create_eager_task(condition.async_setup(hass)),
|
||||
create_eager_task(trigger.async_setup(hass)),
|
||||
)
|
||||
|
||||
recovery = hass.config.recovery_mode
|
||||
try:
|
||||
await asyncio.gather(
|
||||
create_eager_task(get_internal_store_manager(hass).async_initialize()),
|
||||
create_eager_task(area_registry.async_load(hass, load_empty=recovery)),
|
||||
create_eager_task(category_registry.async_load(hass, load_empty=recovery)),
|
||||
create_eager_task(device_registry.async_load(hass, load_empty=recovery)),
|
||||
create_eager_task(entity_registry.async_load(hass, load_empty=recovery)),
|
||||
create_eager_task(floor_registry.async_load(hass, load_empty=recovery)),
|
||||
create_eager_task(issue_registry.async_load(hass, load_empty=recovery)),
|
||||
create_eager_task(label_registry.async_load(hass, load_empty=recovery)),
|
||||
hass.async_add_executor_job(_init_blocking_io_modules_in_executor),
|
||||
create_eager_task(template.async_load_custom_templates(hass)),
|
||||
create_eager_task(restore_state.async_load(hass, load_empty=recovery)),
|
||||
create_eager_task(hass.config_entries.async_initialize()),
|
||||
create_eager_task(async_get_system_info(hass)),
|
||||
create_eager_task(condition.async_setup(hass)),
|
||||
create_eager_task(trigger.async_setup(hass)),
|
||||
)
|
||||
except UnsupportedStorageVersionError as err:
|
||||
# If we're already in recovery mode, we don't want to handle the exception
|
||||
# and activate recovery mode again, as that would lead to an infinite loop.
|
||||
if recovery:
|
||||
raise
|
||||
|
||||
_LOGGER.error(
|
||||
"Storage file %s was created by a newer version of Home Assistant"
|
||||
" (storage version %s > %s); activating recovery mode; on-disk data"
|
||||
" is preserved; upgrade Home Assistant or restore from a backup",
|
||||
err.storage_key,
|
||||
err.found_version,
|
||||
err.max_supported_version,
|
||||
)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_from_config_dict(
|
||||
@@ -475,7 +501,9 @@ async def async_from_config_dict(
|
||||
# Prime custom component cache early so we know if registry entries are tied
|
||||
# to a custom integration
|
||||
await loader.async_get_custom_components(hass)
|
||||
await async_load_base_functionality(hass)
|
||||
|
||||
if not await async_load_base_functionality(hass):
|
||||
return None
|
||||
|
||||
# Set up core.
|
||||
_LOGGER.debug("Setting up %s", CORE_INTEGRATIONS)
|
||||
|
||||
5
homeassistant/brands/ubisys.json
Normal file
5
homeassistant/brands/ubisys.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"domain": "ubisys",
|
||||
"name": "Ubisys",
|
||||
"iot_standards": ["zigbee"]
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 1
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
polling:
|
||||
required: true
|
||||
selector:
|
||||
text: {}
|
||||
options:
|
||||
fields: {}
|
||||
subentries: {}
|
||||
@@ -12,6 +12,10 @@ from homeassistant.helpers.dispatcher import dispatcher_send
|
||||
|
||||
from .const import DOMAIN, DOMAIN_DATA, LOGGER
|
||||
|
||||
SERVICE_SETTINGS = "change_setting"
|
||||
SERVICE_CAPTURE_IMAGE = "capture_image"
|
||||
SERVICE_TRIGGER_AUTOMATION = "trigger_automation"
|
||||
|
||||
ATTR_SETTING = "setting"
|
||||
ATTR_VALUE = "value"
|
||||
|
||||
@@ -71,13 +75,16 @@ def async_setup_services(hass: HomeAssistant) -> None:
|
||||
"""Home Assistant services."""
|
||||
|
||||
hass.services.async_register(
|
||||
DOMAIN, "change_setting", _change_setting, schema=CHANGE_SETTING_SCHEMA
|
||||
DOMAIN, SERVICE_SETTINGS, _change_setting, schema=CHANGE_SETTING_SCHEMA
|
||||
)
|
||||
|
||||
hass.services.async_register(
|
||||
DOMAIN, "capture_image", _capture_image, schema=CAPTURE_IMAGE_SCHEMA
|
||||
DOMAIN, SERVICE_CAPTURE_IMAGE, _capture_image, schema=CAPTURE_IMAGE_SCHEMA
|
||||
)
|
||||
|
||||
hass.services.async_register(
|
||||
DOMAIN, "trigger_automation", _trigger_automation, schema=AUTOMATION_SCHEMA
|
||||
DOMAIN,
|
||||
SERVICE_TRIGGER_AUTOMATION,
|
||||
_trigger_automation,
|
||||
schema=AUTOMATION_SCHEMA,
|
||||
)
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 1
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
is_new_style_scale:
|
||||
required: true
|
||||
selector:
|
||||
text: {}
|
||||
options:
|
||||
fields: {}
|
||||
subentries: {}
|
||||
@@ -1,26 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 1
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
api_key:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
latitude:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
longitude:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
name:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
options:
|
||||
fields: {}
|
||||
subentries: {}
|
||||
@@ -191,7 +191,7 @@ class AccuWeatherEntity(
|
||||
{
|
||||
ATTR_FORECAST_TIME: utc_from_timestamp(item["EpochDate"]).isoformat(),
|
||||
ATTR_FORECAST_CLOUD_COVERAGE: item["CloudCoverDay"],
|
||||
ATTR_FORECAST_HUMIDITY: item["RelativeHumidityDay"]["Average"],
|
||||
ATTR_FORECAST_HUMIDITY: item["RelativeHumidityDay"].get("Average"),
|
||||
ATTR_FORECAST_NATIVE_TEMP: item["TemperatureMax"][ATTR_VALUE],
|
||||
ATTR_FORECAST_NATIVE_TEMP_LOW: item["TemperatureMin"][ATTR_VALUE],
|
||||
ATTR_FORECAST_NATIVE_APPARENT_TEMP: item["RealFeelTemperatureMax"][
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 1
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
id:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
options:
|
||||
fields: {}
|
||||
subentries: {}
|
||||
@@ -1,14 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 1
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
api_token:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
options:
|
||||
fields: {}
|
||||
subentries: {}
|
||||
@@ -1,18 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 2
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
connection_type:
|
||||
required: true
|
||||
selector:
|
||||
select:
|
||||
options:
|
||||
- Cloud
|
||||
- Local
|
||||
default: Cloud
|
||||
options:
|
||||
fields: {}
|
||||
subentries: {}
|
||||
@@ -1,34 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 1
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
host:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
password:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
port:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
ssl:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
username:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
verify_ssl:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
options:
|
||||
fields: {}
|
||||
subentries: {}
|
||||
@@ -1,18 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 1
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
ip_address:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
port:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
options:
|
||||
fields: {}
|
||||
subentries: {}
|
||||
@@ -10,6 +10,8 @@ from homeassistant.helpers import config_validation as cv, service
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
ADVANTAGE_AIR_SERVICE_SET_TIME_TO = "set_time_to"
|
||||
|
||||
|
||||
@callback
|
||||
def async_setup_services(hass: HomeAssistant) -> None:
|
||||
@@ -18,7 +20,7 @@ def async_setup_services(hass: HomeAssistant) -> None:
|
||||
service.async_register_platform_entity_service(
|
||||
hass,
|
||||
DOMAIN,
|
||||
"set_time_to",
|
||||
ADVANTAGE_AIR_SERVICE_SET_TIME_TO,
|
||||
entity_domain=SENSOR_DOMAIN,
|
||||
schema={vol.Required("minutes"): cv.positive_int},
|
||||
func="set_time_to",
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 1
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
api_key:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
latitude:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
longitude:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
name:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
options:
|
||||
fields:
|
||||
radar_updates:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
station_updates:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
subentries: {}
|
||||
@@ -1,14 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 1
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
api_key:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
options:
|
||||
fields: {}
|
||||
subentries: {}
|
||||
@@ -1,14 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 1
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
server_url:
|
||||
required: true
|
||||
selector:
|
||||
text: {}
|
||||
options:
|
||||
fields: {}
|
||||
subentries: {}
|
||||
@@ -8,12 +8,18 @@ from homeassistant.helpers import service
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
_DEV_EN_ALT = "enable_alerts"
|
||||
_DEV_DS_ALT = "disable_alerts"
|
||||
_DEV_EN_REC = "start_recording"
|
||||
_DEV_DS_REC = "stop_recording"
|
||||
_DEV_SNAP = "snapshot"
|
||||
|
||||
CAMERA_SERVICES = {
|
||||
"enable_alerts": "async_enable_alerts",
|
||||
"disable_alerts": "async_disable_alerts",
|
||||
"start_recording": "async_start_recording",
|
||||
"stop_recording": "async_stop_recording",
|
||||
"snapshot": "async_snapshot",
|
||||
_DEV_EN_ALT: "async_enable_alerts",
|
||||
_DEV_DS_ALT: "async_disable_alerts",
|
||||
_DEV_EN_REC: "async_start_recording",
|
||||
_DEV_DS_REC: "async_stop_recording",
|
||||
_DEV_SNAP: "async_snapshot",
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 1
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
host:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
options:
|
||||
fields: {}
|
||||
subentries: {}
|
||||
@@ -1,26 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 1
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
api_key:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
latitude:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
longitude:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
name:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
options:
|
||||
fields: {}
|
||||
subentries: {}
|
||||
@@ -1,30 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 2
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
api_key:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
latitude:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
longitude:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
radius:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
options:
|
||||
fields:
|
||||
radius:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
subentries: {}
|
||||
@@ -1,22 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 1
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
host:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
password:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
username:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
options:
|
||||
fields: {}
|
||||
subentries: {}
|
||||
@@ -1,19 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 2
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
advanced_settings:
|
||||
required: true
|
||||
selector:
|
||||
text: {}
|
||||
mac_address:
|
||||
required: true
|
||||
selector:
|
||||
select:
|
||||
options: []
|
||||
options:
|
||||
fields: {}
|
||||
subentries: {}
|
||||
@@ -1,18 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 1
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
email:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
password:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
options:
|
||||
fields: {}
|
||||
subentries: {}
|
||||
@@ -1,26 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 1
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
ip_address:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
password:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
options:
|
||||
fields:
|
||||
clip_negatives:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
return_average:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
subentries: {}
|
||||
@@ -1,18 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 1
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
secret:
|
||||
required: true
|
||||
selector:
|
||||
text: {}
|
||||
id:
|
||||
required: true
|
||||
selector:
|
||||
text: {}
|
||||
options:
|
||||
fields: {}
|
||||
subentries: {}
|
||||
@@ -1,14 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 1
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
device_model:
|
||||
required: true
|
||||
selector:
|
||||
text: {}
|
||||
options:
|
||||
fields: {}
|
||||
subentries: {}
|
||||
@@ -1,14 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 1
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
host:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
options:
|
||||
fields: {}
|
||||
subentries: {}
|
||||
@@ -1,14 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 1
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
host:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
options:
|
||||
fields: {}
|
||||
subentries: {}
|
||||
@@ -1,18 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 3
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
integration_type:
|
||||
required: true
|
||||
selector:
|
||||
text: {}
|
||||
options:
|
||||
fields:
|
||||
show_on_map:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
subentries: {}
|
||||
@@ -1,18 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 1
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
ip_address:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
password:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
options:
|
||||
fields: {}
|
||||
subentries: {}
|
||||
@@ -1,22 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 1
|
||||
minor: 2
|
||||
data:
|
||||
fields:
|
||||
host:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
id:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
port:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
options:
|
||||
fields: {}
|
||||
subentries: {}
|
||||
@@ -1,22 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 1
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
id:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
password:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
username:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
options:
|
||||
fields: {}
|
||||
subentries: {}
|
||||
@@ -1,18 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 2
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
auth_implementation:
|
||||
required: true
|
||||
selector:
|
||||
text: {}
|
||||
token:
|
||||
required: true
|
||||
selector:
|
||||
object: {}
|
||||
options:
|
||||
fields: {}
|
||||
subentries: {}
|
||||
@@ -1,32 +0,0 @@
|
||||
"""Diagnostics support for Aladdin Connect."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.diagnostics import async_redact_data
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .coordinator import AladdinConnectConfigEntry
|
||||
|
||||
TO_REDACT = {"access_token", "refresh_token"}
|
||||
|
||||
|
||||
async def async_get_config_entry_diagnostics(
|
||||
hass: HomeAssistant, config_entry: AladdinConnectConfigEntry
|
||||
) -> dict[str, Any]:
|
||||
"""Return diagnostics for a config entry."""
|
||||
return {
|
||||
"config_entry": async_redact_data(config_entry.as_dict(), TO_REDACT),
|
||||
"doors": {
|
||||
uid: {
|
||||
"device_id": coordinator.data.device_id,
|
||||
"door_number": coordinator.data.door_number,
|
||||
"name": coordinator.data.name,
|
||||
"status": coordinator.data.status,
|
||||
"link_status": coordinator.data.link_status,
|
||||
"battery_level": coordinator.data.battery_level,
|
||||
}
|
||||
for uid, coordinator in config_entry.runtime_data.items()
|
||||
},
|
||||
}
|
||||
@@ -45,7 +45,7 @@ rules:
|
||||
|
||||
# Gold
|
||||
devices: done
|
||||
diagnostics: done
|
||||
diagnostics: todo
|
||||
discovery: done
|
||||
discovery-update-info:
|
||||
status: exempt
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 1
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
device_baudrate:
|
||||
required: true
|
||||
selector:
|
||||
number:
|
||||
mode: box
|
||||
default: 115200
|
||||
device_path:
|
||||
required: true
|
||||
selector:
|
||||
text: {}
|
||||
default: /dev/ttyUSB0
|
||||
options:
|
||||
fields:
|
||||
arm_options:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
zone_options:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
subentries: {}
|
||||
@@ -13,6 +13,9 @@ from homeassistant.helpers import config_validation as cv, service
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
SERVICE_ALARM_TOGGLE_CHIME = "alarm_toggle_chime"
|
||||
|
||||
SERVICE_ALARM_KEYPRESS = "alarm_keypress"
|
||||
ATTR_KEYPRESS = "keypress"
|
||||
|
||||
|
||||
@@ -23,7 +26,7 @@ def async_setup_services(hass: HomeAssistant) -> None:
|
||||
service.async_register_platform_entity_service(
|
||||
hass,
|
||||
DOMAIN,
|
||||
"alarm_toggle_chime",
|
||||
SERVICE_ALARM_TOGGLE_CHIME,
|
||||
entity_domain=ALARM_CONTROL_PANEL_DOMAIN,
|
||||
schema={
|
||||
vol.Required(ATTR_CODE): cv.string,
|
||||
@@ -34,7 +37,7 @@ def async_setup_services(hass: HomeAssistant) -> None:
|
||||
service.async_register_platform_entity_service(
|
||||
hass,
|
||||
DOMAIN,
|
||||
"alarm_keypress",
|
||||
SERVICE_ALARM_KEYPRESS,
|
||||
entity_domain=ALARM_CONTROL_PANEL_DOMAIN,
|
||||
schema={
|
||||
vol.Required(ATTR_KEYPRESS): cv.string,
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 1
|
||||
minor: 3
|
||||
data:
|
||||
fields:
|
||||
login_data:
|
||||
required: true
|
||||
selector:
|
||||
text: {}
|
||||
options:
|
||||
fields: {}
|
||||
subentries: {}
|
||||
@@ -1,6 +1,5 @@
|
||||
"""Defines a base Alexa Devices entity."""
|
||||
|
||||
from aioamazondevices.const.devices import SPEAKER_GROUP_MODEL
|
||||
from aioamazondevices.structures import AmazonDevice
|
||||
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
@@ -25,19 +24,15 @@ class AmazonEntity(CoordinatorEntity[AmazonDevicesCoordinator]):
|
||||
"""Initialize the entity."""
|
||||
super().__init__(coordinator)
|
||||
self._serial_num = serial_num
|
||||
model_details = coordinator.api.get_model_details(self.device) or {}
|
||||
model = model_details.get("model")
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, serial_num)},
|
||||
name=self.device.account_name,
|
||||
model=model,
|
||||
model=self.device.model,
|
||||
model_id=self.device.device_type,
|
||||
manufacturer=model_details.get("manufacturer", "Amazon"),
|
||||
hw_version=model_details.get("hw_version"),
|
||||
sw_version=(
|
||||
self.device.software_version if model != SPEAKER_GROUP_MODEL else None
|
||||
),
|
||||
serial_number=serial_num if model != SPEAKER_GROUP_MODEL else None,
|
||||
manufacturer=self.device.manufacturer or "Amazon",
|
||||
hw_version=self.device.hardware_version,
|
||||
sw_version=self.device.software_version,
|
||||
serial_number=serial_num,
|
||||
)
|
||||
self.entity_description = description
|
||||
self._attr_unique_id = f"{serial_num}-{description.key}"
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["aioamazondevices"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["aioamazondevices==12.0.0"]
|
||||
"requirements": ["aioamazondevices==13.0.1"]
|
||||
}
|
||||
|
||||
@@ -16,6 +16,9 @@ from .coordinator import AmazonConfigEntry
|
||||
ATTR_TEXT_COMMAND = "text_command"
|
||||
ATTR_SOUND = "sound"
|
||||
ATTR_INFO_SKILL = "info_skill"
|
||||
SERVICE_TEXT_COMMAND = "send_text_command"
|
||||
SERVICE_SOUND_NOTIFICATION = "send_sound"
|
||||
SERVICE_INFO_SKILL = "send_info_skill"
|
||||
|
||||
SCHEMA_SOUND_SERVICE = vol.Schema(
|
||||
{
|
||||
@@ -125,17 +128,17 @@ def async_setup_services(hass: HomeAssistant) -> None:
|
||||
"""Set up the services for the Amazon Devices integration."""
|
||||
for service_name, method, schema in (
|
||||
(
|
||||
"send_sound",
|
||||
SERVICE_SOUND_NOTIFICATION,
|
||||
async_send_sound_notification,
|
||||
SCHEMA_SOUND_SERVICE,
|
||||
),
|
||||
(
|
||||
"send_text_command",
|
||||
SERVICE_TEXT_COMMAND,
|
||||
async_send_text_command,
|
||||
SCHEMA_CUSTOM_COMMAND,
|
||||
),
|
||||
(
|
||||
"send_info_skill",
|
||||
SERVICE_INFO_SKILL,
|
||||
async_send_info_skill,
|
||||
SCHEMA_INFO_SKILL,
|
||||
),
|
||||
|
||||
@@ -101,7 +101,10 @@ class AmazonSwitchEntity(AmazonEntity, SwitchEntity):
|
||||
assert method is not None
|
||||
|
||||
await method(self.device, state)
|
||||
await self.coordinator.async_request_refresh()
|
||||
self.coordinator.data[self.device.serial_number].sensors[
|
||||
self.entity_description.key
|
||||
].value = state
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn the switch on."""
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 1
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
host:
|
||||
required: true
|
||||
selector:
|
||||
text: {}
|
||||
options:
|
||||
fields: {}
|
||||
subentries: {}
|
||||
@@ -1,16 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 1
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
site_id:
|
||||
required: true
|
||||
selector:
|
||||
select:
|
||||
mode: dropdown
|
||||
options: []
|
||||
options:
|
||||
fields: {}
|
||||
subentries: {}
|
||||
@@ -16,6 +16,8 @@ ATTRIBUTION = "Data provided by Amber Electric"
|
||||
LOGGER = logging.getLogger(__package__)
|
||||
PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR]
|
||||
|
||||
SERVICE_GET_FORECASTS = "get_forecasts"
|
||||
|
||||
GENERAL_CHANNEL = "general"
|
||||
CONTROLLED_LOAD_CHANNEL = "controlled_load"
|
||||
FEED_IN_CHANNEL = "feed_in"
|
||||
|
||||
@@ -22,6 +22,7 @@ from .const import (
|
||||
DOMAIN,
|
||||
FEED_IN_CHANNEL,
|
||||
GENERAL_CHANNEL,
|
||||
SERVICE_GET_FORECASTS,
|
||||
)
|
||||
from .coordinator import AmberConfigEntry
|
||||
from .helpers import format_cents_to_dollars, normalize_descriptor
|
||||
@@ -100,7 +101,7 @@ def async_setup_services(hass: HomeAssistant) -> None:
|
||||
|
||||
hass.services.async_register(
|
||||
DOMAIN,
|
||||
"get_forecasts",
|
||||
SERVICE_GET_FORECASTS,
|
||||
handle_get_forecasts,
|
||||
GET_FORECASTS_SCHEMA,
|
||||
supports_response=SupportsResponse.ONLY,
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 1
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
station:
|
||||
required: true
|
||||
selector:
|
||||
select:
|
||||
multiple: false
|
||||
sort: true
|
||||
options: []
|
||||
options:
|
||||
fields: {}
|
||||
subentries: {}
|
||||
@@ -1,18 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 2
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
api_key:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
app_key:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
options:
|
||||
fields: {}
|
||||
subentries: {}
|
||||
@@ -49,6 +49,18 @@ SCAN_INTERVAL = timedelta(seconds=15)
|
||||
|
||||
STREAM_SOURCE_LIST = ["snapshot", "mjpeg", "rtsp"]
|
||||
|
||||
_SRV_EN_REC = "enable_recording"
|
||||
_SRV_DS_REC = "disable_recording"
|
||||
_SRV_EN_AUD = "enable_audio"
|
||||
_SRV_DS_AUD = "disable_audio"
|
||||
_SRV_EN_MOT_REC = "enable_motion_recording"
|
||||
_SRV_DS_MOT_REC = "disable_motion_recording"
|
||||
_SRV_GOTO = "goto_preset"
|
||||
_SRV_CBW = "set_color_bw"
|
||||
_SRV_TOUR_ON = "start_tour"
|
||||
_SRV_TOUR_OFF = "stop_tour"
|
||||
|
||||
_SRV_PTZ_CTRL = "ptz_control"
|
||||
_ATTR_PTZ_TT = "travel_time"
|
||||
_ATTR_PTZ_MOV = "movement"
|
||||
_MOV = [
|
||||
@@ -91,17 +103,17 @@ _SRV_PTZ_SCHEMA = _SRV_SCHEMA.extend(
|
||||
)
|
||||
|
||||
CAMERA_SERVICES = {
|
||||
"enable_recording": (_SRV_SCHEMA, "async_enable_recording", ()),
|
||||
"disable_recording": (_SRV_SCHEMA, "async_disable_recording", ()),
|
||||
"enable_audio": (_SRV_SCHEMA, "async_enable_audio", ()),
|
||||
"disable_audio": (_SRV_SCHEMA, "async_disable_audio", ()),
|
||||
"enable_motion_recording": (_SRV_SCHEMA, "async_enable_motion_recording", ()),
|
||||
"disable_motion_recording": (_SRV_SCHEMA, "async_disable_motion_recording", ()),
|
||||
"goto_preset": (_SRV_GOTO_SCHEMA, "async_goto_preset", (_ATTR_PRESET,)),
|
||||
"set_color_bw": (_SRV_CBW_SCHEMA, "async_set_color_bw", (_ATTR_COLOR_BW,)),
|
||||
"start_tour": (_SRV_SCHEMA, "async_start_tour", ()),
|
||||
"stop_tour": (_SRV_SCHEMA, "async_stop_tour", ()),
|
||||
"ptz_control": (
|
||||
_SRV_EN_REC: (_SRV_SCHEMA, "async_enable_recording", ()),
|
||||
_SRV_DS_REC: (_SRV_SCHEMA, "async_disable_recording", ()),
|
||||
_SRV_EN_AUD: (_SRV_SCHEMA, "async_enable_audio", ()),
|
||||
_SRV_DS_AUD: (_SRV_SCHEMA, "async_disable_audio", ()),
|
||||
_SRV_EN_MOT_REC: (_SRV_SCHEMA, "async_enable_motion_recording", ()),
|
||||
_SRV_DS_MOT_REC: (_SRV_SCHEMA, "async_disable_motion_recording", ()),
|
||||
_SRV_GOTO: (_SRV_GOTO_SCHEMA, "async_goto_preset", (_ATTR_PRESET,)),
|
||||
_SRV_CBW: (_SRV_CBW_SCHEMA, "async_set_color_bw", (_ATTR_COLOR_BW,)),
|
||||
_SRV_TOUR_ON: (_SRV_SCHEMA, "async_start_tour", ()),
|
||||
_SRV_TOUR_OFF: (_SRV_SCHEMA, "async_stop_tour", ()),
|
||||
_SRV_PTZ_CTRL: (
|
||||
_SRV_PTZ_SCHEMA,
|
||||
"async_ptz_control",
|
||||
(_ATTR_PTZ_MOV, _ATTR_PTZ_TT),
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 2
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
tracked_apps:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
tracked_custom_integrations:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
tracked_integrations:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
options:
|
||||
fields:
|
||||
tracked_apps:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
tracked_custom_integrations:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
tracked_integrations:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
subentries: {}
|
||||
@@ -1,26 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 1
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
host:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
password:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
port:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
username:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
options:
|
||||
fields: {}
|
||||
subentries: {}
|
||||
@@ -1,77 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 1
|
||||
minor: 2
|
||||
data:
|
||||
fields:
|
||||
adb_server_ip:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
adb_server_port:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
adbkey:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
device_class:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
host:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
port:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
options:
|
||||
fields:
|
||||
app_delete:
|
||||
required: false
|
||||
selector:
|
||||
boolean: {}
|
||||
default: false
|
||||
apps:
|
||||
required: false
|
||||
selector:
|
||||
select:
|
||||
mode: dropdown
|
||||
options: []
|
||||
exclude_unnamed_apps:
|
||||
required: false
|
||||
selector:
|
||||
boolean: {}
|
||||
get_sources:
|
||||
required: false
|
||||
selector:
|
||||
boolean: {}
|
||||
rule_delete:
|
||||
required: false
|
||||
selector:
|
||||
boolean: {}
|
||||
default: false
|
||||
screencap_interval:
|
||||
required: true
|
||||
selector:
|
||||
number:
|
||||
mode: box
|
||||
state_detection_rules:
|
||||
required: false
|
||||
selector:
|
||||
select:
|
||||
mode: dropdown
|
||||
options: []
|
||||
turn_off_command:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
turn_on_command:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
subentries: {}
|
||||
@@ -36,7 +36,7 @@ from .const import (
|
||||
SIGNAL_CONFIG_ENTITY,
|
||||
)
|
||||
from .entity import AndroidTVEntity, adb_decorator
|
||||
from .services import ATTR_ADB_RESPONSE, ATTR_HDMI_INPUT
|
||||
from .services import ATTR_ADB_RESPONSE, ATTR_HDMI_INPUT, SERVICE_LEARN_SENDEVENT
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -271,7 +271,7 @@ class ADBDevice(AndroidTVEntity, MediaPlayerEntity):
|
||||
self.async_write_ha_state()
|
||||
|
||||
msg = (
|
||||
f"Output from service 'learn_sendevent' from"
|
||||
f"Output from service '{SERVICE_LEARN_SENDEVENT}' from"
|
||||
f" {self.entity_id}: '{output}'"
|
||||
)
|
||||
persistent_notification.async_create(
|
||||
|
||||
@@ -16,6 +16,11 @@ ATTR_DEVICE_PATH = "device_path"
|
||||
ATTR_HDMI_INPUT = "hdmi_input"
|
||||
ATTR_LOCAL_PATH = "local_path"
|
||||
|
||||
SERVICE_ADB_COMMAND = "adb_command"
|
||||
SERVICE_DOWNLOAD = "download"
|
||||
SERVICE_LEARN_SENDEVENT = "learn_sendevent"
|
||||
SERVICE_UPLOAD = "upload"
|
||||
|
||||
|
||||
@callback
|
||||
def async_setup_services(hass: HomeAssistant) -> None:
|
||||
@@ -24,7 +29,7 @@ def async_setup_services(hass: HomeAssistant) -> None:
|
||||
service.async_register_platform_entity_service(
|
||||
hass,
|
||||
DOMAIN,
|
||||
"adb_command",
|
||||
SERVICE_ADB_COMMAND,
|
||||
entity_domain=MEDIA_PLAYER_DOMAIN,
|
||||
schema={vol.Required(ATTR_COMMAND): cv.string},
|
||||
func="adb_command",
|
||||
@@ -32,7 +37,7 @@ def async_setup_services(hass: HomeAssistant) -> None:
|
||||
service.async_register_platform_entity_service(
|
||||
hass,
|
||||
DOMAIN,
|
||||
"learn_sendevent",
|
||||
SERVICE_LEARN_SENDEVENT,
|
||||
entity_domain=MEDIA_PLAYER_DOMAIN,
|
||||
schema=None,
|
||||
func="learn_sendevent",
|
||||
@@ -40,7 +45,7 @@ def async_setup_services(hass: HomeAssistant) -> None:
|
||||
service.async_register_platform_entity_service(
|
||||
hass,
|
||||
DOMAIN,
|
||||
"download",
|
||||
SERVICE_DOWNLOAD,
|
||||
entity_domain=MEDIA_PLAYER_DOMAIN,
|
||||
schema={
|
||||
vol.Required(ATTR_DEVICE_PATH): cv.string,
|
||||
@@ -51,7 +56,7 @@ def async_setup_services(hass: HomeAssistant) -> None:
|
||||
service.async_register_platform_entity_service(
|
||||
hass,
|
||||
DOMAIN,
|
||||
"upload",
|
||||
SERVICE_UPLOAD,
|
||||
entity_domain=MEDIA_PLAYER_DOMAIN,
|
||||
schema={
|
||||
vol.Required(ATTR_DEVICE_PATH): cv.string,
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 1
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
pin:
|
||||
required: true
|
||||
selector:
|
||||
text: {}
|
||||
options:
|
||||
fields:
|
||||
app_delete:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
app_icon:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
app_id:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
app_name:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
apps:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
enable_ime:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
subentries: {}
|
||||
@@ -1,16 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 1
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
account_number:
|
||||
required: true
|
||||
selector:
|
||||
select:
|
||||
multiple: false
|
||||
options: []
|
||||
options:
|
||||
fields: {}
|
||||
subentries: {}
|
||||
@@ -9,5 +9,5 @@
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["pyanglianwater"],
|
||||
"quality_scale": "bronze",
|
||||
"requirements": ["pyanglianwater==3.1.0"]
|
||||
"requirements": ["pyanglianwater==3.1.1"]
|
||||
}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 1
|
||||
minor: 2
|
||||
data:
|
||||
fields:
|
||||
password:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
username:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
options:
|
||||
fields: {}
|
||||
subentries: {}
|
||||
@@ -1,18 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 1
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
host:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
port:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
options:
|
||||
fields: {}
|
||||
subentries: {}
|
||||
@@ -1,68 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 2
|
||||
minor: 3
|
||||
data:
|
||||
fields:
|
||||
api_key:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
options:
|
||||
fields: {}
|
||||
subentries:
|
||||
ai_task_data:
|
||||
versions:
|
||||
- version:
|
||||
major: 1
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
chat_model:
|
||||
required: false
|
||||
selector:
|
||||
select:
|
||||
custom_value: true
|
||||
options: []
|
||||
max_tokens:
|
||||
required: false
|
||||
selector:
|
||||
number:
|
||||
mode: box
|
||||
temperature:
|
||||
required: false
|
||||
selector:
|
||||
number:
|
||||
min: 0
|
||||
max: 1
|
||||
step: 0.05
|
||||
options:
|
||||
fields: {}
|
||||
conversation:
|
||||
versions:
|
||||
- version:
|
||||
major: 1
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
chat_model:
|
||||
required: false
|
||||
selector:
|
||||
select:
|
||||
custom_value: true
|
||||
options: []
|
||||
max_tokens:
|
||||
required: false
|
||||
selector:
|
||||
number:
|
||||
mode: box
|
||||
temperature:
|
||||
required: false
|
||||
selector:
|
||||
number:
|
||||
min: 0
|
||||
max: 1
|
||||
step: 0.05
|
||||
options:
|
||||
fields: {}
|
||||
@@ -400,8 +400,8 @@ def _convert_content(
|
||||
# If there is only one text block, simplify the content to a string
|
||||
messages[-1]["content"] = messages[-1]["content"][0]["text"]
|
||||
else:
|
||||
# Note: We don't pass SystemContent here as it's passed to the API as the prompt
|
||||
raise HomeAssistantError("Unexpected content type in chat log")
|
||||
# Note: We don't pass SystemContent here as its passed to the API as the prompt
|
||||
raise TypeError(f"Unexpected content type: {type(content)}")
|
||||
|
||||
return messages, container_id
|
||||
|
||||
@@ -442,8 +442,8 @@ async def _transform_stream( # noqa: C901 - This is complex, but better to have
|
||||
|
||||
Each message could contain multiple blocks of the same type.
|
||||
"""
|
||||
if stream is None or not hasattr(stream, "__aiter__"):
|
||||
raise HomeAssistantError("Expected a stream of messages")
|
||||
if stream is None:
|
||||
raise TypeError("Expected a stream of messages")
|
||||
|
||||
current_tool_block: ToolUseBlockParam | ServerToolUseBlockParam | None = None
|
||||
current_tool_args: str
|
||||
@@ -456,6 +456,8 @@ async def _transform_stream( # noqa: C901 - This is complex, but better to have
|
||||
LOGGER.debug("Received response: %s", response)
|
||||
|
||||
if isinstance(response, RawMessageStartEvent):
|
||||
if response.message.role != "assistant":
|
||||
raise ValueError("Unexpected message role")
|
||||
input_usage = response.message.usage
|
||||
first_block = True
|
||||
elif isinstance(response, RawContentBlockStartEvent):
|
||||
@@ -664,7 +666,7 @@ class AnthropicBaseLLMEntity(Entity):
|
||||
|
||||
system = chat_log.content[0]
|
||||
if not isinstance(system, conversation.SystemContent):
|
||||
raise HomeAssistantError("First message must be a system message")
|
||||
raise TypeError("First message must be a system message")
|
||||
|
||||
# System prompt with caching enabled
|
||||
system_prompt: list[TextBlockParam] = [
|
||||
|
||||
@@ -31,7 +31,10 @@ rules:
|
||||
test-before-setup: done
|
||||
unique-config-entry: done
|
||||
# Silver
|
||||
action-exceptions: done
|
||||
action-exceptions:
|
||||
status: todo
|
||||
comment: |
|
||||
Reevaluate exceptions for entity services.
|
||||
config-entry-unloading: done
|
||||
docs-configuration-parameters: done
|
||||
docs-installation-parameters: done
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 1
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
email:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
password:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
options:
|
||||
fields: {}
|
||||
subentries: {}
|
||||
@@ -1,18 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 1
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
host:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
port:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
options:
|
||||
fields: {}
|
||||
subentries: {}
|
||||
@@ -1,18 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 1
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
device_input:
|
||||
required: true
|
||||
selector:
|
||||
text: {}
|
||||
options:
|
||||
fields:
|
||||
start_off:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
subentries: {}
|
||||
@@ -1,18 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 1
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
host:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
port:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
options:
|
||||
fields: {}
|
||||
subentries: {}
|
||||
@@ -1,18 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 1
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
ip_address:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
port:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
options:
|
||||
fields: {}
|
||||
subentries: {}
|
||||
@@ -1,23 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 1
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
brand:
|
||||
required: true
|
||||
selector:
|
||||
select:
|
||||
options: []
|
||||
refresh_token:
|
||||
required: true
|
||||
selector:
|
||||
text: {}
|
||||
refresh_token_creation_time:
|
||||
required: true
|
||||
selector:
|
||||
text: {}
|
||||
options:
|
||||
fields: {}
|
||||
subentries: {}
|
||||
@@ -117,7 +117,6 @@ class SharpAquosTVDevice(MediaPlayerEntity):
|
||||
| MediaPlayerEntityFeature.VOLUME_SET
|
||||
| MediaPlayerEntityFeature.PLAY
|
||||
)
|
||||
_attr_volume_step = 2 / 60
|
||||
|
||||
def __init__(
|
||||
self, name: str, remote: sharp_aquos_rc.TV, power_on_enabled: bool = False
|
||||
@@ -162,6 +161,22 @@ class SharpAquosTVDevice(MediaPlayerEntity):
|
||||
"""Turn off tvplayer."""
|
||||
self._remote.power(0)
|
||||
|
||||
@_retry
|
||||
def volume_up(self) -> None:
|
||||
"""Volume up the media player."""
|
||||
if self.volume_level is None:
|
||||
_LOGGER.debug("Unknown volume in volume_up")
|
||||
return
|
||||
self._remote.volume(int(self.volume_level * 60) + 2)
|
||||
|
||||
@_retry
|
||||
def volume_down(self) -> None:
|
||||
"""Volume down media player."""
|
||||
if self.volume_level is None:
|
||||
_LOGGER.debug("Unknown volume in volume_down")
|
||||
return
|
||||
self._remote.volume(int(self.volume_level * 60) - 2)
|
||||
|
||||
@_retry
|
||||
def set_volume_level(self, volume: float) -> None:
|
||||
"""Set Volume media player."""
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 1
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
address:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
options:
|
||||
fields: {}
|
||||
subentries: {}
|
||||
@@ -1,18 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 1
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
host:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
port:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
options:
|
||||
fields: {}
|
||||
subentries: {}
|
||||
@@ -1,18 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 1
|
||||
minor: 2
|
||||
data:
|
||||
fields:
|
||||
access_token:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
client_secret:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
options:
|
||||
fields: {}
|
||||
subentries: {}
|
||||
@@ -1,18 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 2
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
email:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
password:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
options:
|
||||
fields: {}
|
||||
subentries: {}
|
||||
@@ -1,58 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 1
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
host:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
mode:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
password:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
port:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
protocol:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
ssh_key:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
username:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
options:
|
||||
fields:
|
||||
consider_home:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
dnsmasq:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
interface:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
require_ip:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
track_unknown:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
subentries: {}
|
||||
@@ -1,18 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 1
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
host:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
port:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
options:
|
||||
fields: {}
|
||||
subentries: {}
|
||||
@@ -5,7 +5,7 @@ from __future__ import annotations
|
||||
from pathlib import Path
|
||||
from typing import cast
|
||||
|
||||
from aiohttp import ClientResponseError
|
||||
from aiohttp import ClientError
|
||||
from yalexs.exceptions import AugustApiAIOHTTPError
|
||||
from yalexs.manager.exceptions import CannotConnect, InvalidAuth, RequireValidation
|
||||
from yalexs.manager.gateway import Config as YaleXSConfig
|
||||
@@ -13,7 +13,12 @@ from yalexs.manager.gateway import Config as YaleXSConfig
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||
from homeassistant.exceptions import (
|
||||
ConfigEntryAuthFailed,
|
||||
ConfigEntryNotReady,
|
||||
OAuth2TokenRequestError,
|
||||
OAuth2TokenRequestReauthError,
|
||||
)
|
||||
from homeassistant.helpers import device_registry as dr, issue_registry as ir
|
||||
from homeassistant.helpers.config_entry_oauth2_flow import (
|
||||
ImplementationUnavailableError,
|
||||
@@ -45,11 +50,18 @@ async def async_setup_entry(hass: HomeAssistant, entry: AugustConfigEntry) -> bo
|
||||
august_gateway = AugustGateway(Path(hass.config.config_dir), session, oauth_session)
|
||||
try:
|
||||
await async_setup_august(hass, entry, august_gateway)
|
||||
except OAuth2TokenRequestReauthError as err:
|
||||
raise ConfigEntryAuthFailed from err
|
||||
except (RequireValidation, InvalidAuth) as err:
|
||||
raise ConfigEntryAuthFailed from err
|
||||
except TimeoutError as err:
|
||||
raise ConfigEntryNotReady("Timed out connecting to august api") from err
|
||||
except (AugustApiAIOHTTPError, ClientResponseError, CannotConnect) as err:
|
||||
except (
|
||||
AugustApiAIOHTTPError,
|
||||
OAuth2TokenRequestError,
|
||||
ClientError,
|
||||
CannotConnect,
|
||||
) as err:
|
||||
raise ConfigEntryNotReady from err
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
return True
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 1
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
implementation:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
options:
|
||||
fields: {}
|
||||
subentries: {}
|
||||
@@ -30,5 +30,5 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["pubnub", "yalexs"],
|
||||
"requirements": ["yalexs==9.2.0", "yalexs-ble==3.2.4"]
|
||||
"requirements": ["yalexs==9.2.0", "yalexs-ble==3.2.8"]
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 1
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
latitude:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
longitude:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
name:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
options:
|
||||
fields:
|
||||
forecast_threshold:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
subentries: {}
|
||||
@@ -1,18 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 1
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
address:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
port:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
options:
|
||||
fields: {}
|
||||
subentries: {}
|
||||
@@ -1,26 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 1
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
password:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
services:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
username:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
options:
|
||||
fields:
|
||||
services:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
subentries: {}
|
||||
@@ -1,18 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 1
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
email:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
password:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
options:
|
||||
fields: {}
|
||||
subentries: {}
|
||||
@@ -1,26 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 1
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
access_token:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
device:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
email:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
host:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
options:
|
||||
fields: {}
|
||||
subentries: {}
|
||||
@@ -19,7 +19,7 @@ from homeassistant.components.backup import (
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
|
||||
from . import S3ConfigEntry
|
||||
from .const import CONF_BUCKET, CONF_PREFIX, DATA_BACKUP_AGENT_LISTENERS, DOMAIN
|
||||
from .const import CONF_BUCKET, DATA_BACKUP_AGENT_LISTENERS, DOMAIN
|
||||
from .helpers import async_list_backups_from_s3
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -100,13 +100,6 @@ class S3BackupAgent(BackupAgent):
|
||||
self.unique_id = entry.entry_id
|
||||
self._backup_cache: dict[str, AgentBackup] = {}
|
||||
self._cache_expiration = time()
|
||||
self._prefix: str = entry.data.get(CONF_PREFIX, "")
|
||||
|
||||
def _with_prefix(self, key: str) -> str:
|
||||
"""Add prefix to a key if configured."""
|
||||
if not self._prefix:
|
||||
return key
|
||||
return f"{self._prefix}/{key}"
|
||||
|
||||
@handle_boto_errors
|
||||
async def async_download_backup(
|
||||
@@ -122,9 +115,7 @@ class S3BackupAgent(BackupAgent):
|
||||
backup = await self._find_backup_by_id(backup_id)
|
||||
tar_filename, _ = suggested_filenames(backup)
|
||||
|
||||
response = await self._client.get_object(
|
||||
Bucket=self._bucket, Key=self._with_prefix(tar_filename)
|
||||
)
|
||||
response = await self._client.get_object(Bucket=self._bucket, Key=tar_filename)
|
||||
return response["Body"].iter_chunks()
|
||||
|
||||
async def async_upload_backup(
|
||||
@@ -151,7 +142,7 @@ class S3BackupAgent(BackupAgent):
|
||||
metadata_content = json.dumps(backup.as_dict())
|
||||
await self._client.put_object(
|
||||
Bucket=self._bucket,
|
||||
Key=self._with_prefix(metadata_filename),
|
||||
Key=metadata_filename,
|
||||
Body=metadata_content,
|
||||
)
|
||||
except BotoCoreError as err:
|
||||
@@ -178,7 +169,7 @@ class S3BackupAgent(BackupAgent):
|
||||
|
||||
await self._client.put_object(
|
||||
Bucket=self._bucket,
|
||||
Key=self._with_prefix(tar_filename),
|
||||
Key=tar_filename,
|
||||
Body=bytes(file_data),
|
||||
)
|
||||
|
||||
@@ -195,7 +186,7 @@ class S3BackupAgent(BackupAgent):
|
||||
_LOGGER.debug("Starting multipart upload for %s", tar_filename)
|
||||
multipart_upload = await self._client.create_multipart_upload(
|
||||
Bucket=self._bucket,
|
||||
Key=self._with_prefix(tar_filename),
|
||||
Key=tar_filename,
|
||||
)
|
||||
upload_id = multipart_upload["UploadId"]
|
||||
try:
|
||||
@@ -225,7 +216,7 @@ class S3BackupAgent(BackupAgent):
|
||||
)
|
||||
part = await cast(Any, self._client).upload_part(
|
||||
Bucket=self._bucket,
|
||||
Key=self._with_prefix(tar_filename),
|
||||
Key=tar_filename,
|
||||
PartNumber=part_number,
|
||||
UploadId=upload_id,
|
||||
Body=part_data.tobytes(),
|
||||
@@ -253,7 +244,7 @@ class S3BackupAgent(BackupAgent):
|
||||
)
|
||||
part = await cast(Any, self._client).upload_part(
|
||||
Bucket=self._bucket,
|
||||
Key=self._with_prefix(tar_filename),
|
||||
Key=tar_filename,
|
||||
PartNumber=part_number,
|
||||
UploadId=upload_id,
|
||||
Body=remaining_data.tobytes(),
|
||||
@@ -262,7 +253,7 @@ class S3BackupAgent(BackupAgent):
|
||||
|
||||
await cast(Any, self._client).complete_multipart_upload(
|
||||
Bucket=self._bucket,
|
||||
Key=self._with_prefix(tar_filename),
|
||||
Key=tar_filename,
|
||||
UploadId=upload_id,
|
||||
MultipartUpload={"Parts": parts},
|
||||
)
|
||||
@@ -271,7 +262,7 @@ class S3BackupAgent(BackupAgent):
|
||||
try:
|
||||
await self._client.abort_multipart_upload(
|
||||
Bucket=self._bucket,
|
||||
Key=self._with_prefix(tar_filename),
|
||||
Key=tar_filename,
|
||||
UploadId=upload_id,
|
||||
)
|
||||
except BotoCoreError:
|
||||
@@ -292,12 +283,8 @@ class S3BackupAgent(BackupAgent):
|
||||
tar_filename, metadata_filename = suggested_filenames(backup)
|
||||
|
||||
# Delete both the backup file and its metadata file
|
||||
await self._client.delete_object(
|
||||
Bucket=self._bucket, Key=self._with_prefix(tar_filename)
|
||||
)
|
||||
await self._client.delete_object(
|
||||
Bucket=self._bucket, Key=self._with_prefix(metadata_filename)
|
||||
)
|
||||
await self._client.delete_object(Bucket=self._bucket, Key=tar_filename)
|
||||
await self._client.delete_object(Bucket=self._bucket, Key=metadata_filename)
|
||||
|
||||
# Reset cache after successful deletion
|
||||
self._cache_expiration = time()
|
||||
@@ -330,9 +317,7 @@ class S3BackupAgent(BackupAgent):
|
||||
if time() <= self._cache_expiration:
|
||||
return self._backup_cache
|
||||
|
||||
backups_list = await async_list_backups_from_s3(
|
||||
self._client, self._bucket, self._prefix
|
||||
)
|
||||
backups_list = await async_list_backups_from_s3(self._client, self._bucket)
|
||||
self._backup_cache = {b.backup_id: b for b in backups_list}
|
||||
self._cache_expiration = time() + CACHE_TTL
|
||||
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 1
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
access_key_id:
|
||||
required: true
|
||||
selector:
|
||||
text: {}
|
||||
bucket:
|
||||
required: true
|
||||
selector:
|
||||
text: {}
|
||||
endpoint_url:
|
||||
required: true
|
||||
selector:
|
||||
text: {}
|
||||
prefix:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
default: ""
|
||||
secret_access_key:
|
||||
required: true
|
||||
selector:
|
||||
text: {}
|
||||
options:
|
||||
fields: {}
|
||||
subentries: {}
|
||||
@@ -22,7 +22,6 @@ from .const import (
|
||||
CONF_ACCESS_KEY_ID,
|
||||
CONF_BUCKET,
|
||||
CONF_ENDPOINT_URL,
|
||||
CONF_PREFIX,
|
||||
CONF_SECRET_ACCESS_KEY,
|
||||
DEFAULT_ENDPOINT_URL,
|
||||
DESCRIPTION_AWS_S3_DOCS_URL,
|
||||
@@ -40,7 +39,6 @@ STEP_USER_DATA_SCHEMA = vol.Schema(
|
||||
vol.Required(CONF_ENDPOINT_URL, default=DEFAULT_ENDPOINT_URL): TextSelector(
|
||||
config=TextSelectorConfig(type=TextSelectorType.URL)
|
||||
),
|
||||
vol.Optional(CONF_PREFIX, default=""): cv.string,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -55,17 +53,12 @@ class S3ConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
errors: dict[str, str] = {}
|
||||
|
||||
if user_input is not None:
|
||||
normalized_prefix = user_input.get(CONF_PREFIX, "").strip("/")
|
||||
# Check for existing entries, treating missing prefix as empty
|
||||
for entry in self._async_current_entries(include_ignore=False):
|
||||
entry_prefix = (entry.data.get(CONF_PREFIX) or "").strip("/")
|
||||
if (
|
||||
entry.data.get(CONF_BUCKET) == user_input[CONF_BUCKET]
|
||||
and entry.data.get(CONF_ENDPOINT_URL)
|
||||
== user_input[CONF_ENDPOINT_URL]
|
||||
and entry_prefix == normalized_prefix
|
||||
):
|
||||
return self.async_abort(reason="already_configured")
|
||||
self._async_abort_entries_match(
|
||||
{
|
||||
CONF_BUCKET: user_input[CONF_BUCKET],
|
||||
CONF_ENDPOINT_URL: user_input[CONF_ENDPOINT_URL],
|
||||
}
|
||||
)
|
||||
|
||||
hostname = urlparse(user_input[CONF_ENDPOINT_URL]).hostname
|
||||
if not hostname or not hostname.endswith(AWS_DOMAIN):
|
||||
@@ -90,18 +83,9 @@ class S3ConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
except ConnectionError:
|
||||
errors[CONF_ENDPOINT_URL] = "cannot_connect"
|
||||
else:
|
||||
data = dict(user_input)
|
||||
if not normalized_prefix:
|
||||
# Do not persist empty optional values
|
||||
data.pop(CONF_PREFIX, None)
|
||||
else:
|
||||
data[CONF_PREFIX] = normalized_prefix
|
||||
|
||||
title = user_input[CONF_BUCKET]
|
||||
if normalized_prefix:
|
||||
title = f"{title} - {normalized_prefix}"
|
||||
|
||||
return self.async_create_entry(title=title, data=data)
|
||||
return self.async_create_entry(
|
||||
title=user_input[CONF_BUCKET], data=user_input
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
|
||||
@@ -11,7 +11,6 @@ CONF_ACCESS_KEY_ID = "access_key_id"
|
||||
CONF_SECRET_ACCESS_KEY = "secret_access_key"
|
||||
CONF_ENDPOINT_URL = "endpoint_url"
|
||||
CONF_BUCKET = "bucket"
|
||||
CONF_PREFIX = "prefix"
|
||||
|
||||
AWS_DOMAIN = "amazonaws.com"
|
||||
DEFAULT_ENDPOINT_URL = f"https://s3.eu-central-1.{AWS_DOMAIN}/"
|
||||
|
||||
@@ -13,7 +13,7 @@ from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import CONF_BUCKET, CONF_PREFIX, DOMAIN
|
||||
from .const import CONF_BUCKET, DOMAIN
|
||||
from .helpers import async_list_backups_from_s3
|
||||
|
||||
SCAN_INTERVAL = timedelta(hours=6)
|
||||
@@ -53,14 +53,11 @@ class S3DataUpdateCoordinator(DataUpdateCoordinator[SensorData]):
|
||||
)
|
||||
self.client = client
|
||||
self._bucket: str = entry.data[CONF_BUCKET]
|
||||
self._prefix: str = entry.data.get(CONF_PREFIX, "")
|
||||
|
||||
async def _async_update_data(self) -> SensorData:
|
||||
"""Fetch data from AWS S3."""
|
||||
try:
|
||||
backups = await async_list_backups_from_s3(
|
||||
self.client, self._bucket, self._prefix
|
||||
)
|
||||
backups = await async_list_backups_from_s3(self.client, self._bucket)
|
||||
except BotoCoreError as error:
|
||||
raise UpdateFailed(
|
||||
translation_domain=DOMAIN,
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
"""Diagnostics support for AWS S3."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.backup import (
|
||||
DATA_MANAGER as BACKUP_DATA_MANAGER,
|
||||
BackupManager,
|
||||
)
|
||||
from homeassistant.components.diagnostics import async_redact_data
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .const import (
|
||||
CONF_ACCESS_KEY_ID,
|
||||
CONF_BUCKET,
|
||||
CONF_PREFIX,
|
||||
CONF_SECRET_ACCESS_KEY,
|
||||
DOMAIN,
|
||||
)
|
||||
from .coordinator import S3ConfigEntry
|
||||
from .helpers import async_list_backups_from_s3
|
||||
|
||||
TO_REDACT = (CONF_ACCESS_KEY_ID, CONF_SECRET_ACCESS_KEY)
|
||||
|
||||
|
||||
async def async_get_config_entry_diagnostics(
|
||||
hass: HomeAssistant,
|
||||
entry: S3ConfigEntry,
|
||||
) -> dict[str, Any]:
|
||||
"""Return diagnostics for a config entry."""
|
||||
coordinator = entry.runtime_data
|
||||
backup_manager: BackupManager = hass.data[BACKUP_DATA_MANAGER]
|
||||
backups = await async_list_backups_from_s3(
|
||||
coordinator.client,
|
||||
bucket=entry.data[CONF_BUCKET],
|
||||
prefix=entry.data.get(CONF_PREFIX, ""),
|
||||
)
|
||||
|
||||
data = {
|
||||
"coordinator_data": dataclasses.asdict(coordinator.data),
|
||||
"config": {
|
||||
**entry.data,
|
||||
**entry.options,
|
||||
},
|
||||
"backup_agents": [
|
||||
{"name": agent.name}
|
||||
for agent in backup_manager.backup_agents.values()
|
||||
if agent.domain == DOMAIN
|
||||
],
|
||||
"backup": [backup.as_dict() for backup in backups],
|
||||
}
|
||||
|
||||
return async_redact_data(data, TO_REDACT)
|
||||
@@ -17,17 +17,11 @@ _LOGGER = logging.getLogger(__name__)
|
||||
async def async_list_backups_from_s3(
|
||||
client: S3Client,
|
||||
bucket: str,
|
||||
prefix: str,
|
||||
) -> list[AgentBackup]:
|
||||
"""List backups from an S3 bucket by reading metadata files."""
|
||||
paginator = client.get_paginator("list_objects_v2")
|
||||
metadata_files: list[dict[str, Any]] = []
|
||||
|
||||
list_kwargs: dict[str, Any] = {"Bucket": bucket}
|
||||
if prefix:
|
||||
list_kwargs["Prefix"] = prefix + "/"
|
||||
|
||||
async for page in paginator.paginate(**list_kwargs):
|
||||
async for page in paginator.paginate(Bucket=bucket):
|
||||
metadata_files.extend(
|
||||
obj
|
||||
for obj in page.get("Contents", [])
|
||||
|
||||
@@ -23,9 +23,7 @@ rules:
|
||||
runtime-data: done
|
||||
test-before-configure: done
|
||||
test-before-setup: done
|
||||
unique-config-entry:
|
||||
status: exempt
|
||||
comment: Hassfest does not recognize the duplicate prevention logic. Duplicate entries are prevented by checking bucket, endpoint URL, and prefix in the config flow.
|
||||
unique-config-entry: done
|
||||
|
||||
# Silver
|
||||
action-exceptions:
|
||||
@@ -38,14 +36,14 @@ rules:
|
||||
docs-installation-parameters: done
|
||||
entity-unavailable: done
|
||||
integration-owner: done
|
||||
log-when-unavailable: done
|
||||
log-when-unavailable: todo
|
||||
parallel-updates: done
|
||||
reauthentication-flow: todo
|
||||
test-coverage: done
|
||||
|
||||
# Gold
|
||||
devices: done
|
||||
diagnostics: done
|
||||
diagnostics: todo
|
||||
discovery-update-info:
|
||||
status: exempt
|
||||
comment: S3 is a cloud service that is not discovered on the network.
|
||||
|
||||
@@ -15,14 +15,12 @@
|
||||
"access_key_id": "Access key ID",
|
||||
"bucket": "Bucket name",
|
||||
"endpoint_url": "Endpoint URL",
|
||||
"prefix": "Prefix",
|
||||
"secret_access_key": "Secret access key"
|
||||
},
|
||||
"data_description": {
|
||||
"access_key_id": "Access key ID to connect to AWS S3 API",
|
||||
"bucket": "Bucket must already exist and be writable by the provided credentials.",
|
||||
"endpoint_url": "Endpoint URL provided to [Boto3 Session]({boto3_docs_url}). Region-specific [AWS S3 endpoints]({aws_s3_docs_url}) are available in their docs.",
|
||||
"prefix": "Folder or prefix to store backups in, for example `backups`",
|
||||
"secret_access_key": "Secret access key to connect to AWS S3 API"
|
||||
},
|
||||
"title": "Add AWS S3 bucket"
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
config_entry:
|
||||
versions:
|
||||
- version:
|
||||
major: 3
|
||||
minor: 1
|
||||
data:
|
||||
fields:
|
||||
host:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
password:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
port:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
protocol:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
username:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
options:
|
||||
fields:
|
||||
stream_profile:
|
||||
required: false
|
||||
selector:
|
||||
text: {}
|
||||
subentries: {}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user